From a1fbb398afaf0eff8e626e2cc1aa3cecad9159d5 Mon Sep 17 00:00:00 2001 From: lizhipeng Date: Sun, 31 May 2020 11:55:57 +0800 Subject: [PATCH 001/242] =?UTF-8?q?1=E3=80=81update=20nacos=20sdk=20versio?= =?UTF-8?q?n=202=E3=80=81fix=20some=20nacos=20config=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/constant/key.go | 3 ++ config_center/nacos/client.go | 87 ++++++++++++++------------------- config_center/nacos/listener.go | 5 +- go.mod | 20 +++----- go.sum | 8 +++ registry/nacos/registry.go | 3 ++ 6 files changed, 62 insertions(+), 64 deletions(-) diff --git a/common/constant/key.go b/common/constant/key.go index 07335bed59..664264ee48 100644 --- a/common/constant/key.go +++ b/common/constant/key.go @@ -145,6 +145,9 @@ const ( NACOS_CATEGORY_KEY = "category" NACOS_PROTOCOL_KEY = "protocol" NACOS_PATH_KEY = "path" + NACOS_PASSWORD = "password" + NACOS_USERNAME = "username" + NACOS_NAMESPACEID = "namespaceId" ) const ( diff --git a/config_center/nacos/client.go b/config_center/nacos/client.go index d3373e249b..b5764d3cce 100644 --- a/config_center/nacos/client.go +++ b/config_center/nacos/client.go @@ -32,6 +32,7 @@ import ( ) import ( + "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" ) @@ -87,17 +88,16 @@ func ValidateNacosClient(container nacosClientFacade, opts ...option) error { } url := container.GetUrl() - + timeout, err := time.ParseDuration(url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT)) + if err != nil { + logger.Errorf("timeout config %v is invalid ,err is %v", + url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT), err.Error()) + return perrors.WithMessagef(err, "newNacosClient(address:%+v)", url.Location) + } + nacosAddresses := strings.Split(url.Location, ",") if container.NacosClient() == nil { //in dubbo ,every registry only connect one node ,so this is []string{r.Address} - timeout, err := time.ParseDuration(url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT)) - if err != nil { - logger.Errorf("timeout config %v is invalid ,err is %v", - url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT), err.Error()) - return perrors.WithMessagef(err, "newNacosClient(address:%+v)", url.Location) - } - nacosAddresses := strings.Split(url.Location, ",") - newClient, err := newNacosClient(os.nacosName, nacosAddresses, timeout) + newClient, err := newNacosClient(os.nacosName, nacosAddresses, timeout, url) if err != nil { logger.Warnf("newNacosClient(name{%s}, nacos address{%v}, timeout{%d}) = error{%v}", os.nacosName, url.Location, timeout.String(), err) @@ -107,41 +107,19 @@ func ValidateNacosClient(container nacosClientFacade, opts ...option) error { } if container.NacosClient().Client() == nil { - svrConfList := []nacosconst.ServerConfig{} - for _, nacosAddr := range container.NacosClient().NacosAddrs { - split := strings.Split(nacosAddr, ":") - port, err := strconv.ParseUint(split[1], 10, 64) - if err != nil { - logger.Warnf("nacos addr port parse error ,error message is %v", err) - continue - } - svrconf := nacosconst.ServerConfig{ - IpAddr: split[0], - Port: port, - } - svrConfList = append(svrConfList, svrconf) - } - - client, err := clients.CreateConfigClient(map[string]interface{}{ - "serverConfigs": svrConfList, - "clientConfig": nacosconst.ClientConfig{ - TimeoutMs: uint64(int32(container.NacosClient().Timeout / time.Millisecond)), - ListenInterval: 10000, - NotLoadCacheAtStart: true, - LogDir: logDir, - }, - }) - - container.NacosClient().SetClient(&client) + configClient, err := initNacosConfigClient(nacosAddresses, timeout, url) if err != nil { logger.Errorf("nacos create config client error:%v", err) + return perrors.WithMessagef(err, "newNacosClient(address:%+v)", url.Location) } + container.NacosClient().SetClient(&configClient) + } return perrors.WithMessagef(nil, "newNacosClient(address:%+v)", url.PrimitiveURL) } -func newNacosClient(name string, nacosAddrs []string, timeout time.Duration) (*NacosClient, error) { +func newNacosClient(name string, nacosAddrs []string, timeout time.Duration, url common.URL) (*NacosClient, error) { var ( err error n *NacosClient @@ -157,12 +135,23 @@ func newNacosClient(name string, nacosAddrs []string, timeout time.Duration) (*N }, } - svrConfList := make([]nacosconst.ServerConfig, 0, len(n.NacosAddrs)) - for _, nacosAddr := range n.NacosAddrs { + configClient, err := initNacosConfigClient(nacosAddrs, timeout, url) + if err != nil { + logger.Errorf("nacos create config client error:%v", err) + return n, perrors.WithMessagef(err, "newNacosClient(address:%+v)", url.Location) + } + n.SetClient(&configClient) + + return n, nil +} + +func initNacosConfigClient(nacosAddrs []string, timeout time.Duration, url common.URL) (config_client.IConfigClient, error) { + svrConfList := []nacosconst.ServerConfig{} + for _, nacosAddr := range nacosAddrs { split := strings.Split(nacosAddr, ":") port, err := strconv.ParseUint(split[1], 10, 64) if err != nil { - logger.Warnf("convert port , source:%s , error:%v ", split[1], err) + logger.Warnf("nacos addr port parse error ,error message is %v", err) continue } svrconf := nacosconst.ServerConfig{ @@ -171,21 +160,21 @@ func newNacosClient(name string, nacosAddrs []string, timeout time.Duration) (*N } svrConfList = append(svrConfList, svrconf) } - client, err := clients.CreateConfigClient(map[string]interface{}{ + + return clients.CreateConfigClient(map[string]interface{}{ "serverConfigs": svrConfList, "clientConfig": nacosconst.ClientConfig{ - TimeoutMs: uint64(timeout / time.Millisecond), - ListenInterval: 20000, + TimeoutMs: uint64(int32(timeout / time.Millisecond)), + ListenInterval: uint64(int32(timeout / time.Millisecond)), NotLoadCacheAtStart: true, - LogDir: logDir, + LogDir: url.GetParam(constant.NACOS_LOG_DIR_KEY, ""), + CacheDir: url.GetParam(constant.NACOS_CACHE_DIR_KEY, ""), + Endpoint: url.GetParam(constant.NACOS_ENDPOINT, ""), + Username: url.GetParam(constant.NACOS_USERNAME, ""), + Password: url.GetParam(constant.NACOS_PASSWORD, ""), + NamespaceId: url.GetParam(constant.NACOS_NAMESPACEID, ""), }, }) - n.SetClient(&client) - if err != nil { - return nil, perrors.WithMessagef(err, "nacos clients.CreateConfigClient(nacosAddrs:%+v)", nacosAddrs) - } - - return n, nil } // Done Get nacos client exit signal diff --git a/config_center/nacos/listener.go b/config_center/nacos/listener.go index 25c586586c..5ac10aac22 100644 --- a/config_center/nacos/listener.go +++ b/config_center/nacos/listener.go @@ -46,7 +46,10 @@ func (l *nacosDynamicConfiguration) addListener(key string, listener config_cent go callback(listener, namespace, group, dataId, data) }, }) - logger.Errorf("nacos : listen config fail, error:%v ", err) + if err != nil { + logger.Errorf("nacos : listen config fail, error:%v ", err) + return + } newListener := make(map[config_center.ConfigurationListener]context.CancelFunc) newListener[listener] = cancel l.keyListeners.Store(key, newListener) diff --git a/go.mod b/go.mod index 83091cf8b9..4aa26cf2b4 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,9 @@ module github.com/apache/dubbo-go require ( github.com/Workiva/go-datastructures v1.0.50 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 - github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190802083043-4cd0c391755e // indirect + github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5 // indirect github.com/apache/dubbo-go-hessian2 v1.4.0 - github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 // indirect + github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect github.com/coreos/bbolt v1.3.3 // indirect github.com/coreos/etcd v3.3.13+incompatible github.com/coreos/go-semver v0.3.0 // indirect @@ -16,8 +16,6 @@ require ( github.com/dubbogo/go-zookeeper v1.0.0 github.com/dubbogo/gost v1.5.2 github.com/emicklei/go-restful/v3 v3.0.0 - github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect - github.com/go-errors/errors v1.0.1 // indirect github.com/go-resty/resty/v2 v2.1.0 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect github.com/golang/mock v1.3.1 @@ -29,32 +27,26 @@ require ( github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 github.com/hashicorp/consul v1.5.3 github.com/hashicorp/consul/api v1.1.0 - github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 // indirect github.com/jinzhu/copier v0.0.0-20190625015134-976e0346caa8 - github.com/jonboulle/clockwork v0.1.0 // indirect - github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570 // indirect - github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f // indirect - github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect github.com/magiconair/properties v1.8.1 github.com/mitchellh/mapstructure v1.1.2 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd - github.com/nacos-group/nacos-sdk-go v0.0.0-20190723125407-0242d42e3dbb + github.com/nacos-group/nacos-sdk-go v0.3.2 github.com/opentracing/opentracing-go v1.1.0 - github.com/pkg/errors v0.8.1 + github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.1.0 - github.com/satori/go.uuid v1.2.0 + github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 // indirect github.com/soheilhy/cmux v0.1.4 // indirect github.com/stretchr/testify v1.5.1 - github.com/tebeka/strftime v0.1.3 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect - github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8 go.etcd.io/bbolt v1.3.3 // indirect go.etcd.io/etcd v3.3.13+incompatible go.uber.org/atomic v1.4.0 go.uber.org/zap v1.10.0 + golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect google.golang.org/grpc v1.22.1 gopkg.in/yaml.v2 v2.2.2 k8s.io/api v0.0.0-20190325185214-7544f9db76f6 diff --git a/go.sum b/go.sum index 813496b6ee..1bf70600bc 100644 --- a/go.sum +++ b/go.sum @@ -37,6 +37,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190802083043-4cd0c391755e h1:MSuLXx/mveDbpDNhVrcWTMeV4lbYWKcyO4rH+jAxmX0= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190802083043-4cd0c391755e/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk= github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/apache/dubbo-go-hessian2 v1.4.0 h1:Cb9FQVTy3G93dnDr7P93U8DeKFYpDTJjQp44JG5TafA= github.com/apache/dubbo-go-hessian2 v1.4.0/go.mod h1:VwEnsOMidkM1usya2uPfGpSLO9XUF//WQcWn3y+jFz8= @@ -383,6 +385,8 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nacos-group/nacos-sdk-go v0.0.0-20190723125407-0242d42e3dbb h1:lbmvw8r9W55w+aQgWn35W1nuleRIECMoqUrmwAOAvoI= github.com/nacos-group/nacos-sdk-go v0.0.0-20190723125407-0242d42e3dbb/go.mod h1:CEkSvEpoveoYjA81m4HNeYQ0sge0LFGKSEqO3JKHllo= +github.com/nacos-group/nacos-sdk-go v0.3.2 h1:q+ukmIImL6u0zBtbceMZl2frgeAc45QT6cIrTZZz50c= +github.com/nacos-group/nacos-sdk-go v0.3.2/go.mod h1:4TdsN7eZnnVCDlOlBa61b0gsRnvNJI74m9+2+OKZkcw= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 h1:BQ1HW7hr4IVovMwWg0E0PYcyW8CzqDcVmaew9cujU4s= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk= github.com/oklog/run v0.0.0-20180308005104-6934b124db28 h1:Hbr3fbVPXea52oPQeP7KLSxP52g6SFaNY1IqAmUyEW0= @@ -416,6 +420,8 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -446,6 +452,8 @@ github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1T github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= +github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shirou/gopsutil v0.0.0-20181107111621-48177ef5f880 h1:1Ge4j/3uB2rxzPWD3TC+daeCw+w91z8UCUL/7WH5gn8= diff --git a/registry/nacos/registry.go b/registry/nacos/registry.go index 965e91e894..fe911cbfba 100644 --- a/registry/nacos/registry.go +++ b/registry/nacos/registry.go @@ -95,6 +95,9 @@ func getNacosConfig(url *common.URL) (map[string]interface{}, error) { clientConfig.CacheDir = url.GetParam(constant.NACOS_CACHE_DIR_KEY, "") clientConfig.LogDir = url.GetParam(constant.NACOS_LOG_DIR_KEY, "") clientConfig.Endpoint = url.GetParam(constant.NACOS_ENDPOINT, "") + clientConfig.Username = url.GetParam(constant.NACOS_USERNAME, "") + clientConfig.Password = url.GetParam(constant.NACOS_PASSWORD, "") + clientConfig.NamespaceId = url.GetParam(constant.NACOS_NAMESPACEID, "") clientConfig.NotLoadCacheAtStart = true configMap["clientConfig"] = clientConfig From bc184e6057b058c45416e3238f2ef5b3aba8ed6d Mon Sep 17 00:00:00 2001 From: lizhipeng Date: Sun, 31 May 2020 18:04:10 +0800 Subject: [PATCH 002/242] update uuid to v1.2.0 --- common/url.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/common/url.go b/common/url.go index ebb648db27..7e6c7199d1 100644 --- a/common/url.go +++ b/common/url.go @@ -175,7 +175,10 @@ func WithToken(token string) option { if len(token) > 0 { value := token if strings.ToLower(token) == "true" || strings.ToLower(token) == "default" { - value = uuid.NewV4().String() + UUID, err := uuid.NewV4() + if err == nil { + value = UUID.String() + } } url.SetParam(constant.TOKEN_KEY, value) } From 4de80e56a7d65d3a6daa82a3caf0e1c53efd458d Mon Sep 17 00:00:00 2001 From: lizhipeng Date: Thu, 4 Jun 2020 09:57:43 +0800 Subject: [PATCH 003/242] fix review comments --- config_center/nacos/client.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/config_center/nacos/client.go b/config_center/nacos/client.go index b5764d3cce..c4f987c4c6 100644 --- a/config_center/nacos/client.go +++ b/config_center/nacos/client.go @@ -90,8 +90,8 @@ func ValidateNacosClient(container nacosClientFacade, opts ...option) error { url := container.GetUrl() timeout, err := time.ParseDuration(url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT)) if err != nil { - logger.Errorf("timeout config %v is invalid ,err is %v", - url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT), err.Error()) + logger.Errorf("invalid timeout config %+v,got err %+v", + url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT), err) return perrors.WithMessagef(err, "newNacosClient(address:%+v)", url.Location) } nacosAddresses := strings.Split(url.Location, ",") @@ -109,7 +109,8 @@ func ValidateNacosClient(container nacosClientFacade, opts ...option) error { if container.NacosClient().Client() == nil { configClient, err := initNacosConfigClient(nacosAddresses, timeout, url) if err != nil { - logger.Errorf("nacos create config client error:%v", err) + logger.Errorf("initNacosConfigClient(addr:%+v,timeout:%v,url:%v) = err %+v", + nacosAddresses, timeout.String(), url, err) return perrors.WithMessagef(err, "newNacosClient(address:%+v)", url.Location) } container.NacosClient().SetClient(&configClient) @@ -137,7 +138,8 @@ func newNacosClient(name string, nacosAddrs []string, timeout time.Duration, url configClient, err := initNacosConfigClient(nacosAddrs, timeout, url) if err != nil { - logger.Errorf("nacos create config client error:%v", err) + logger.Errorf("initNacosConfigClient(addr:%+v,timeout:%v,url:%v) = err %+v", + nacosAddrs, timeout.String(), url, err) return n, perrors.WithMessagef(err, "newNacosClient(address:%+v)", url.Location) } n.SetClient(&configClient) @@ -151,7 +153,7 @@ func initNacosConfigClient(nacosAddrs []string, timeout time.Duration, url commo split := strings.Split(nacosAddr, ":") port, err := strconv.ParseUint(split[1], 10, 64) if err != nil { - logger.Warnf("nacos addr port parse error ,error message is %v", err) + logger.Warnf("strconv.ParseUint(nacos addr port:%+v) = error %+v", split[1], err) continue } svrconf := nacosconst.ServerConfig{ From 77e07652ec91855abec4f7a3078cd00e63cd4cdc Mon Sep 17 00:00:00 2001 From: lizhipeng Date: Fri, 5 Jun 2020 09:20:37 +0800 Subject: [PATCH 004/242] fix review comments --- config_center/nacos/client.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/config_center/nacos/client.go b/config_center/nacos/client.go index c4f987c4c6..0fdc07b21b 100644 --- a/config_center/nacos/client.go +++ b/config_center/nacos/client.go @@ -99,7 +99,7 @@ func ValidateNacosClient(container nacosClientFacade, opts ...option) error { //in dubbo ,every registry only connect one node ,so this is []string{r.Address} newClient, err := newNacosClient(os.nacosName, nacosAddresses, timeout, url) if err != nil { - logger.Warnf("newNacosClient(name{%s}, nacos address{%v}, timeout{%d}) = error{%v}", + logger.Errorf("newNacosClient(name{%s}, nacos address{%v}, timeout{%d}) = error{%v}", os.nacosName, url.Location, timeout.String(), err) return perrors.WithMessagef(err, "newNacosClient(address:%+v)", url.Location) } @@ -153,7 +153,7 @@ func initNacosConfigClient(nacosAddrs []string, timeout time.Duration, url commo split := strings.Split(nacosAddr, ":") port, err := strconv.ParseUint(split[1], 10, 64) if err != nil { - logger.Warnf("strconv.ParseUint(nacos addr port:%+v) = error %+v", split[1], err) + logger.Errorf("strconv.ParseUint(nacos addr port:%+v) = error %+v", split[1], err) continue } svrconf := nacosconst.ServerConfig{ @@ -221,5 +221,4 @@ func (n *NacosClient) Close() { n.stop() n.SetClient(nil) - logger.Warnf("nacosClient{name:%s, nacos addr:%s} exit now.", n.name, n.NacosAddrs) } From 3b51b992483108c8783c838d6d1b7f181c8d0882 Mon Sep 17 00:00:00 2001 From: cvictory Date: Fri, 19 Jun 2020 14:52:01 +0800 Subject: [PATCH 005/242] add lock --- protocol/invocation/rpcinvocation.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/protocol/invocation/rpcinvocation.go b/protocol/invocation/rpcinvocation.go index b8b5b50970..953d50ca46 100644 --- a/protocol/invocation/rpcinvocation.go +++ b/protocol/invocation/rpcinvocation.go @@ -157,6 +157,8 @@ func (r *RPCInvocation) Invoker() protocol.Invoker { // nolint func (r *RPCInvocation) SetInvoker(invoker protocol.Invoker) { + r.lock.Lock() + defer r.lock.Unlock() r.invoker = invoker } From 03fc55215eb9708d9049bf48e1dbc041f684f812 Mon Sep 17 00:00:00 2001 From: liuxiaomin Date: Mon, 6 Jul 2020 16:28:48 +0800 Subject: [PATCH 006/242] support distributed transaction, by experimentally [seata-golang](https://github.com/dk-lockdown/seata-golang) --- filter/filter_impl/seata_filter.go | 63 +++++++++++++++++++++++++ filter/filter_impl/seata_filter_test.go | 56 ++++++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 filter/filter_impl/seata_filter.go create mode 100644 filter/filter_impl/seata_filter_test.go diff --git a/filter/filter_impl/seata_filter.go b/filter/filter_impl/seata_filter.go new file mode 100644 index 0000000000..23e2a70b8c --- /dev/null +++ b/filter/filter_impl/seata_filter.go @@ -0,0 +1,63 @@ +/* + * 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 filter_impl + +import ( + "context" +) + +import ( + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/filter" + "github.com/apache/dubbo-go/protocol" +) + +const ( + SEATA = "SEATA" + SEATA_XID = "SEATA_XID" +) + +func init() { + extension.SetFilter(SEATA, GetSeataFilter) +} + +// SeataFilter ... +type SeataFilter struct {} + +// When use Seata, transfer xid by attachments +// Invoke Get Xid by attachment key `SEATA_XID` +func (sf *SeataFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + logger.Infof("invoking seata filter.") + xid := invocation.AttachmentsByKey(SEATA_XID,"") + if xid != "" { + logger.Debugf("Method: %v,Xid: %v", invocation.MethodName(), xid) + return invoker.Invoke(context.WithValue(ctx,SEATA_XID, xid), invocation) + } + return invoker.Invoke(ctx,invocation) +} + +// OnResponse ... +func (sf *SeataFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + return result +} + +// GetSeataFilter ... +func GetSeataFilter() filter.Filter { + return &SeataFilter{} +} \ No newline at end of file diff --git a/filter/filter_impl/seata_filter_test.go b/filter/filter_impl/seata_filter_test.go new file mode 100644 index 0000000000..825b471d43 --- /dev/null +++ b/filter/filter_impl/seata_filter_test.go @@ -0,0 +1,56 @@ +/* + * 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 filter_impl + +import ( + "context" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" +) + +type testMockSeataInvoker struct { + protocol.BaseInvoker +} + +func (iv *testMockSeataInvoker) Invoke(ctx context.Context, _ protocol.Invocation) protocol.Result { + val := ctx.Value(SEATA_XID) + if val != nil { + xid, ok := val.(string) + if ok { + return &protocol.RPCResult{Rest:xid} + } + } + return &protocol.RPCResult{} +} + +func TestSeataFilter_Invoke(t *testing.T) { + filter := GetSeataFilter() + result := filter.Invoke(context.Background(), &testMockSeataInvoker{}, invocation.NewRPCInvocation("$echo", []interface{}{"OK"}, map[string]string{ + SEATA_XID: "10.30.21.227:8091:2000047792", + })) + assert.Equal(t, "10.30.21.227:8091:2000047792", result.Result()) +} + From 6ffa278879c1c0a050e30737541b548bd11c97c7 Mon Sep 17 00:00:00 2001 From: liuxiaomin Date: Mon, 6 Jul 2020 20:49:27 +0800 Subject: [PATCH 007/242] add comments --- filter/filter_impl/seata_filter.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/filter/filter_impl/seata_filter.go b/filter/filter_impl/seata_filter.go index 23e2a70b8c..696667f2ad 100644 --- a/filter/filter_impl/seata_filter.go +++ b/filter/filter_impl/seata_filter.go @@ -37,7 +37,7 @@ func init() { extension.SetFilter(SEATA, GetSeataFilter) } -// SeataFilter ... +// SeataFilter when use seata-golang, use this filter to transfer xid type SeataFilter struct {} // When use Seata, transfer xid by attachments @@ -52,12 +52,12 @@ func (sf *SeataFilter) Invoke(ctx context.Context, invoker protocol.Invoker, inv return invoker.Invoke(ctx,invocation) } -// OnResponse ... +// OnResponse dummy process, returns the result directly func (sf *SeataFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { return result } -// GetSeataFilter ... +// GetSeataFilter create SeataFilter instance func GetSeataFilter() filter.Filter { return &SeataFilter{} } \ No newline at end of file From 2a46f849246914b0d7a9058d8cb7c18f6c4f742d Mon Sep 17 00:00:00 2001 From: liuxiaomin Date: Mon, 6 Jul 2020 22:21:02 +0800 Subject: [PATCH 008/242] formant adjustment --- filter/filter_impl/seata_filter.go | 12 ++++++------ filter/filter_impl/seata_filter_test.go | 3 +-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/filter/filter_impl/seata_filter.go b/filter/filter_impl/seata_filter.go index 696667f2ad..5731468f03 100644 --- a/filter/filter_impl/seata_filter.go +++ b/filter/filter_impl/seata_filter.go @@ -29,7 +29,7 @@ import ( ) const ( - SEATA = "SEATA" + SEATA = "SEATA" SEATA_XID = "SEATA_XID" ) @@ -38,18 +38,18 @@ func init() { } // SeataFilter when use seata-golang, use this filter to transfer xid -type SeataFilter struct {} +type SeataFilter struct{} // When use Seata, transfer xid by attachments // Invoke Get Xid by attachment key `SEATA_XID` func (sf *SeataFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { logger.Infof("invoking seata filter.") - xid := invocation.AttachmentsByKey(SEATA_XID,"") + xid := invocation.AttachmentsByKey(SEATA_XID, "") if xid != "" { logger.Debugf("Method: %v,Xid: %v", invocation.MethodName(), xid) - return invoker.Invoke(context.WithValue(ctx,SEATA_XID, xid), invocation) + return invoker.Invoke(context.WithValue(ctx, SEATA_XID, xid), invocation) } - return invoker.Invoke(ctx,invocation) + return invoker.Invoke(ctx, invocation) } // OnResponse dummy process, returns the result directly @@ -60,4 +60,4 @@ func (sf *SeataFilter) OnResponse(ctx context.Context, result protocol.Result, i // GetSeataFilter create SeataFilter instance func GetSeataFilter() filter.Filter { return &SeataFilter{} -} \ No newline at end of file +} diff --git a/filter/filter_impl/seata_filter_test.go b/filter/filter_impl/seata_filter_test.go index 825b471d43..97f524c87c 100644 --- a/filter/filter_impl/seata_filter_test.go +++ b/filter/filter_impl/seata_filter_test.go @@ -40,7 +40,7 @@ func (iv *testMockSeataInvoker) Invoke(ctx context.Context, _ protocol.Invocatio if val != nil { xid, ok := val.(string) if ok { - return &protocol.RPCResult{Rest:xid} + return &protocol.RPCResult{Rest: xid} } } return &protocol.RPCResult{} @@ -53,4 +53,3 @@ func TestSeataFilter_Invoke(t *testing.T) { })) assert.Equal(t, "10.30.21.227:8091:2000047792", result.Result()) } - From 09c5cafeb8ef71d8e6a9aa089b5bfe3b30de1705 Mon Sep 17 00:00:00 2001 From: liuxiaomin Date: Tue, 7 Jul 2020 14:18:05 +0800 Subject: [PATCH 009/242] update `GetSeataFilter` to `getSeataFilter` --- filter/filter_impl/seata_filter.go | 6 +++--- filter/filter_impl/seata_filter_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/filter/filter_impl/seata_filter.go b/filter/filter_impl/seata_filter.go index 5731468f03..d2771bdf9a 100644 --- a/filter/filter_impl/seata_filter.go +++ b/filter/filter_impl/seata_filter.go @@ -34,7 +34,7 @@ const ( ) func init() { - extension.SetFilter(SEATA, GetSeataFilter) + extension.SetFilter(SEATA, getSeataFilter) } // SeataFilter when use seata-golang, use this filter to transfer xid @@ -57,7 +57,7 @@ func (sf *SeataFilter) OnResponse(ctx context.Context, result protocol.Result, i return result } -// GetSeataFilter create SeataFilter instance -func GetSeataFilter() filter.Filter { +// getSeataFilter create SeataFilter instance +func getSeataFilter() filter.Filter { return &SeataFilter{} } diff --git a/filter/filter_impl/seata_filter_test.go b/filter/filter_impl/seata_filter_test.go index 97f524c87c..6c39897f7a 100644 --- a/filter/filter_impl/seata_filter_test.go +++ b/filter/filter_impl/seata_filter_test.go @@ -47,7 +47,7 @@ func (iv *testMockSeataInvoker) Invoke(ctx context.Context, _ protocol.Invocatio } func TestSeataFilter_Invoke(t *testing.T) { - filter := GetSeataFilter() + filter := getSeataFilter() result := filter.Invoke(context.Background(), &testMockSeataInvoker{}, invocation.NewRPCInvocation("$echo", []interface{}{"OK"}, map[string]string{ SEATA_XID: "10.30.21.227:8091:2000047792", })) From 697fa50696f3a468538453a3e8bdcbdca46e705a Mon Sep 17 00:00:00 2001 From: liuxiaomin Date: Thu, 9 Jul 2020 09:11:13 +0800 Subject: [PATCH 010/242] optimization --- filter/filter_impl/seata_filter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filter/filter_impl/seata_filter.go b/filter/filter_impl/seata_filter.go index d2771bdf9a..5cbfdb927e 100644 --- a/filter/filter_impl/seata_filter.go +++ b/filter/filter_impl/seata_filter.go @@ -29,7 +29,7 @@ import ( ) const ( - SEATA = "SEATA" + SEATA = "seata" SEATA_XID = "SEATA_XID" ) From 1d2278d946a71d1860edf3b5e8d96bfdd32acf0c Mon Sep 17 00:00:00 2001 From: liuxiaomin Date: Fri, 10 Jul 2020 09:31:46 +0800 Subject: [PATCH 011/242] bugfix --- filter/filter_impl/seata_filter.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/filter/filter_impl/seata_filter.go b/filter/filter_impl/seata_filter.go index 5cbfdb927e..7722d2954f 100644 --- a/filter/filter_impl/seata_filter.go +++ b/filter/filter_impl/seata_filter.go @@ -19,6 +19,7 @@ package filter_impl import ( "context" + "strings" ) import ( @@ -45,7 +46,7 @@ type SeataFilter struct{} func (sf *SeataFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { logger.Infof("invoking seata filter.") xid := invocation.AttachmentsByKey(SEATA_XID, "") - if xid != "" { + if strings.TrimSpace(xid) != "" { logger.Debugf("Method: %v,Xid: %v", invocation.MethodName(), xid) return invoker.Invoke(context.WithValue(ctx, SEATA_XID, xid), invocation) } From 00f9946dc5efe5138d2076fc77a22dbbd4b849f2 Mon Sep 17 00:00:00 2001 From: cvictory Date: Mon, 13 Jul 2020 14:33:08 +0800 Subject: [PATCH 012/242] modify return error and log --- .../cluster_impl/failover_cluster_invoker.go | 7 +++++-- common/proxy/proxy.go | 17 ++++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/cluster/cluster_impl/failover_cluster_invoker.go b/cluster/cluster_impl/failover_cluster_invoker.go index 66adabd104..5ff6dd1dad 100644 --- a/cluster/cluster_impl/failover_cluster_invoker.go +++ b/cluster/cluster_impl/failover_cluster_invoker.go @@ -19,6 +19,7 @@ package cluster_impl import ( "context" + "fmt" "strconv" ) @@ -91,8 +92,10 @@ func (invoker *failoverClusterInvoker) Invoke(ctx context.Context, invocation pr invokerSvc := invoker.GetUrl().Service() invokerUrl := invoker.directory.GetUrl() return &protocol.RPCResult{ - Err: perrors.Errorf("Failed to invoke the method %v in the service %v. Tried %v times of the providers %v (%v/%v)from the registry %v on the consumer %v using the dubbo version %v. Last error is %v.", - methodName, invokerSvc, retries, providers, len(providers), len(invokers), invokerUrl, ip, constant.Version, result.Error().Error(), + Err: perrors.Wrap(result.Error(), fmt.Sprintf("Failed to invoke the method %v in the service %v. "+ + "Tried %v times of the providers %v (%v/%v)from the registry %v on the consumer %v using the dubbo version %v. "+ + "Last error is %+v.", methodName, invokerSvc, retries, providers, len(providers), len(invokers), + invokerUrl, ip, constant.Version, result.Error().Error()), )} } diff --git a/common/proxy/proxy.go b/common/proxy/proxy.go index abcf87cd9d..27fa538765 100644 --- a/common/proxy/proxy.go +++ b/common/proxy/proxy.go @@ -23,6 +23,11 @@ import ( "sync" ) +import ( + "github.com/apache/dubbo-go-hessian2/java_exception" + perrors "github.com/pkg/errors" +) + import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" @@ -154,7 +159,17 @@ func (p *Proxy) Implement(v common.RPCService) { } err = result.Error() - logger.Debugf("[makeDubboCallProxy] result: %v, err: %v", result.Result(), err) + if err != nil { + // the cause reason + err = perrors.Cause(err) + if throwabler, ok := err.(java_exception.Throwabler); ok { + logger.Errorf("invoke service throw exception: %v , stackTraceElements: %v", err.Error(), throwabler.GetStackTrace()) + } else { + logger.Errorf("result err: %v", err) + } + } else { + logger.Debugf("[makeDubboCallProxy] result: %v, err: %v", result.Result(), err) + } if len(outs) == 1 { return []reflect.Value{reflect.ValueOf(&err).Elem()} } From 9c857ed94c8cbb75f15568644a581d017f377e01 Mon Sep 17 00:00:00 2001 From: cvictory Date: Tue, 14 Jul 2020 11:17:13 +0800 Subject: [PATCH 013/242] change log level --- common/proxy/proxy.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/common/proxy/proxy.go b/common/proxy/proxy.go index 27fa538765..ce0f4d1d3f 100644 --- a/common/proxy/proxy.go +++ b/common/proxy/proxy.go @@ -162,10 +162,11 @@ func (p *Proxy) Implement(v common.RPCService) { if err != nil { // the cause reason err = perrors.Cause(err) + // if some error happened, it should be log some info in the seperate file. if throwabler, ok := err.(java_exception.Throwabler); ok { - logger.Errorf("invoke service throw exception: %v , stackTraceElements: %v", err.Error(), throwabler.GetStackTrace()) + logger.Warnf("invoke service throw exception: %v , stackTraceElements: %v", err.Error(), throwabler.GetStackTrace()) } else { - logger.Errorf("result err: %v", err) + logger.Warnf("result err: %v", err) } } else { logger.Debugf("[makeDubboCallProxy] result: %v, err: %v", result.Result(), err) From 380ad21e8d5fa54b316c71af52d8713f6c4a3458 Mon Sep 17 00:00:00 2001 From: tiecheng <18768171164@163.com> Date: Wed, 15 Jul 2020 20:56:51 +0800 Subject: [PATCH 014/242] Nearest first for multiple registry #597 --- cluster/cluster_impl/failover_cluster.go | 4 + .../registry_aware_cluster_invoker.go | 55 ------ .../registry_aware_cluster_test.go | 71 ------- ...aware_cluster.go => zone_aware_cluster.go} | 20 +- .../zone_aware_cluster_invoker.go | 95 +++++++++ .../zone_aware_cluster_invoker_test.go | 186 ++++++++++++++++++ cluster/loadbalance/util.go | 41 ++-- common/constant/key.go | 5 + config/config_loader_test.go | 6 +- config/reference_config.go | 16 +- config/reference_config_test.go | 10 +- config/registry_config.go | 21 +- 12 files changed, 366 insertions(+), 164 deletions(-) delete mode 100644 cluster/cluster_impl/registry_aware_cluster_invoker.go delete mode 100644 cluster/cluster_impl/registry_aware_cluster_test.go rename cluster/cluster_impl/{registry_aware_cluster.go => zone_aware_cluster.go} (70%) create mode 100644 cluster/cluster_impl/zone_aware_cluster_invoker.go create mode 100644 cluster/cluster_impl/zone_aware_cluster_invoker_test.go diff --git a/cluster/cluster_impl/failover_cluster.go b/cluster/cluster_impl/failover_cluster.go index d30a743e03..d02a0c5810 100644 --- a/cluster/cluster_impl/failover_cluster.go +++ b/cluster/cluster_impl/failover_cluster.go @@ -43,3 +43,7 @@ func NewFailoverCluster() cluster.Cluster { func (cluster *failoverCluster) Join(directory cluster.Directory) protocol.Invoker { return newFailoverClusterInvoker(directory) } + +func GetFailoverName() string { + return name +} diff --git a/cluster/cluster_impl/registry_aware_cluster_invoker.go b/cluster/cluster_impl/registry_aware_cluster_invoker.go deleted file mode 100644 index cded5bf164..0000000000 --- a/cluster/cluster_impl/registry_aware_cluster_invoker.go +++ /dev/null @@ -1,55 +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 cluster_impl - -import ( - "context" -) -import ( - "github.com/apache/dubbo-go/cluster" - "github.com/apache/dubbo-go/common/constant" - "github.com/apache/dubbo-go/protocol" -) - -type registryAwareClusterInvoker struct { - baseClusterInvoker -} - -func newRegistryAwareClusterInvoker(directory cluster.Directory) protocol.Invoker { - return ®istryAwareClusterInvoker{ - baseClusterInvoker: newBaseClusterInvoker(directory), - } -} - -func (invoker *registryAwareClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { - invokers := invoker.directory.List(invocation) - //First, pick the invoker (XXXClusterInvoker) that comes from the local registry, distinguish by a 'default' key. - for _, invoker := range invokers { - if invoker.IsAvailable() && invoker.GetUrl().GetParam(constant.REGISTRY_DEFAULT_KEY, "false") == "true" { - return invoker.Invoke(ctx, invocation) - } - } - - //If none of the invokers has a local signal, pick the first one available. - for _, invoker := range invokers { - if invoker.IsAvailable() { - return invoker.Invoke(ctx, invocation) - } - } - return nil -} diff --git a/cluster/cluster_impl/registry_aware_cluster_test.go b/cluster/cluster_impl/registry_aware_cluster_test.go deleted file mode 100644 index 74584b4480..0000000000 --- a/cluster/cluster_impl/registry_aware_cluster_test.go +++ /dev/null @@ -1,71 +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 cluster_impl - -import ( - "context" - "fmt" - "testing" -) -import ( - "github.com/stretchr/testify/assert" -) - -import ( - "github.com/apache/dubbo-go/cluster/directory" - "github.com/apache/dubbo-go/common" - "github.com/apache/dubbo-go/protocol" - "github.com/apache/dubbo-go/protocol/invocation" -) - -func TestRegAwareInvokeSuccess(t *testing.T) { - - regAwareCluster := NewRegistryAwareCluster() - - invokers := []protocol.Invoker{} - for i := 0; i < 10; i++ { - url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) - invokers = append(invokers, NewMockInvoker(url, 1)) - } - - staticDir := directory.NewStaticDirectory(invokers) - clusterInvoker := regAwareCluster.Join(staticDir) - result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) - assert.NoError(t, result.Error()) - count = 0 -} - -func TestDestroy(t *testing.T) { - regAwareCluster := NewRegistryAwareCluster() - - invokers := []protocol.Invoker{} - for i := 0; i < 10; i++ { - url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) - invokers = append(invokers, NewMockInvoker(url, 1)) - } - - staticDir := directory.NewStaticDirectory(invokers) - clusterInvoker := regAwareCluster.Join(staticDir) - assert.Equal(t, true, clusterInvoker.IsAvailable()) - result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) - assert.NoError(t, result.Error()) - count = 0 - clusterInvoker.Destroy() - assert.Equal(t, false, clusterInvoker.IsAvailable()) - -} diff --git a/cluster/cluster_impl/registry_aware_cluster.go b/cluster/cluster_impl/zone_aware_cluster.go similarity index 70% rename from cluster/cluster_impl/registry_aware_cluster.go rename to cluster/cluster_impl/zone_aware_cluster.go index fcefa52862..eadc5da6d8 100644 --- a/cluster/cluster_impl/registry_aware_cluster.go +++ b/cluster/cluster_impl/zone_aware_cluster.go @@ -23,17 +23,23 @@ import ( "github.com/apache/dubbo-go/protocol" ) -type registryAwareCluster struct{} +type zoneAwareCluster struct{} + +const zoneAware = "zoneAware" func init() { - extension.SetCluster("registryAware", NewRegistryAwareCluster) + extension.SetCluster(zoneAware, NewZoneAwareCluster) +} + +// NewZoneAwareCluster ... +func NewZoneAwareCluster() cluster.Cluster { + return &zoneAwareCluster{} } -// NewRegistryAwareCluster returns a registry aware cluster instance -func NewRegistryAwareCluster() cluster.Cluster { - return ®istryAwareCluster{} +func (cluster *zoneAwareCluster) Join(directory cluster.Directory) protocol.Invoker { + return newZoneAwareClusterInvoker(directory) } -func (cluster *registryAwareCluster) Join(directory cluster.Directory) protocol.Invoker { - return newRegistryAwareClusterInvoker(directory) +func GetZoneAwareName() string { + return zoneAware } diff --git a/cluster/cluster_impl/zone_aware_cluster_invoker.go b/cluster/cluster_impl/zone_aware_cluster_invoker.go new file mode 100644 index 0000000000..5e9b63326c --- /dev/null +++ b/cluster/cluster_impl/zone_aware_cluster_invoker.go @@ -0,0 +1,95 @@ +/* + * 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 cluster_impl + +import ( + "context" + "fmt" + "github.com/apache/dubbo-go/cluster" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/protocol" +) + +// When there're more than one registry for subscription. +// +// This extension provides a strategy to decide how to distribute traffics among them: +// 1. registry marked as 'preferred=true' has the highest priority. +// 2. check the zone the current request belongs, pick the registry that has the same zone first. +// 3. Evenly balance traffic between all registries based on each registry's weight. +// 4. Pick anyone that's available. +type zoneAwareClusterInvoker struct { + baseClusterInvoker +} + +func newZoneAwareClusterInvoker(directory cluster.Directory) protocol.Invoker { + return &zoneAwareClusterInvoker{ + baseClusterInvoker: newBaseClusterInvoker(directory), + } +} + +func (invoker *zoneAwareClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { + invokers := invoker.directory.List(invocation) + + err := invoker.checkInvokers(invokers, invocation) + if err != nil { + return &protocol.RPCResult{Err: err} + } + + // First, pick the invoker (XXXClusterInvoker) that comes from the local registry, distinguish by a 'preferred' key. + for _, invoker := range invokers { + if invoker.IsAvailable() && "true" == invoker.GetUrl().GetParam(constant.REGISTRY_KEY+"."+constant.PREFERRED_KEY, "false") { + return invoker.Invoke(ctx, invocation) + } + } + + // providers in the registry with the same zone + zone := invocation.AttachmentsByKey(constant.REGISTRY_ZONE, "") + if "" != zone { + for _, invoker := range invokers { + if invoker.IsAvailable() && zone == invoker.GetUrl().GetParam(constant.REGISTRY_KEY+"."+constant.ZONE_KEY, "") { + return invoker.Invoke(ctx, invocation) + } + } + + force := invocation.AttachmentsByKey(constant.REGISTRY_ZONE_FORCE, "") + if "true" == force { + return &protocol.RPCResult{ + Err: fmt.Errorf("no registry instance in zone or no available providers in the registry, zone: %v, "+ + " registries: %v", zone, invoker.GetUrl()), + } + } + } + + // load balance among all registries, with registry weight count in. + loadBalance := getLoadBalance(invokers[0], invocation) + ivk := invoker.doSelect(loadBalance, invocation, invokers, nil) + if ivk != nil && ivk.IsAvailable() { + return ivk.Invoke(ctx, invocation) + } + + // If none of the invokers has a preferred signal or is picked by the loadBalancer, pick the first one available. + for _, invoker := range invokers { + if invoker.IsAvailable() { + return invoker.Invoke(ctx, invocation) + } + } + + return &protocol.RPCResult{ + Err: fmt.Errorf("no provider available in %v", invokers), + } +} diff --git a/cluster/cluster_impl/zone_aware_cluster_invoker_test.go b/cluster/cluster_impl/zone_aware_cluster_invoker_test.go new file mode 100644 index 0000000000..a7d550c604 --- /dev/null +++ b/cluster/cluster_impl/zone_aware_cluster_invoker_test.go @@ -0,0 +1,186 @@ +/* + * 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 cluster_impl + +import ( + "context" + "fmt" + "github.com/apache/dubbo-go/cluster/directory" + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/protocol/mock" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "testing" +) + +func Test_ZoneWareInvokerWithPreferredSuccess(t *testing.T) { + ctrl := gomock.NewController(t) + // In Go versions 1.14+, if you pass a *testing.T into gomock.NewController(t) you no longer need to call ctrl.Finish(). + //defer ctrl.Finish() + + mockResult := &protocol.RPCResult{Attrs: map[string]string{constant.PREFERRED_KEY: "true"}, Rest: rest{tried: 0, success: true}} + + var invokers []protocol.Invoker + for i := 0; i < 2; i++ { + url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) + invoker := mock.NewMockInvoker(ctrl) + invoker.EXPECT().IsAvailable().Return(true).AnyTimes() + invoker.EXPECT().GetUrl().Return(url).AnyTimes() + if 0 == i { + url.SetParam(constant.REGISTRY_KEY+"."+constant.PREFERRED_KEY, "true") + invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn( + func(invocation protocol.Invocation) protocol.Result { + return mockResult + }) + } else { + invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn( + func(invocation protocol.Invocation) protocol.Result { + return &protocol.RPCResult{} + }) + } + + invokers = append(invokers, invoker) + } + + zoneAwareCluster := NewZoneAwareCluster() + staticDir := directory.NewStaticDirectory(invokers) + clusterInvoker := zoneAwareCluster.Join(staticDir) + + result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) + + assert.Equal(t, mockResult, result) +} + +func Test_ZoneWareInvokerWithWeightSuccess(t *testing.T) { + ctrl := gomock.NewController(t) + // In Go versions 1.14+, if you pass a *testing.T into gomock.NewController(t) you no longer need to call ctrl.Finish(). + //defer ctrl.Finish() + + w1 := "50" + w2 := "200" + + var invokers []protocol.Invoker + for i := 0; i < 2; i++ { + url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) + invoker := mock.NewMockInvoker(ctrl) + invoker.EXPECT().IsAvailable().Return(true).AnyTimes() + invoker.EXPECT().GetUrl().Return(url).AnyTimes() + if 1 == i { + url.SetParam(constant.REGISTRY_KEY+"."+constant.WEIGHT_KEY, w1) + invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn( + func(invocation protocol.Invocation) protocol.Result { + return &protocol.RPCResult{Attrs: map[string]string{constant.WEIGHT_KEY: w1}, Rest: rest{tried: 0, success: true}} + }).MaxTimes(100) + } else { + url.SetParam(constant.REGISTRY_KEY+"."+constant.WEIGHT_KEY, w2) + invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn( + func(invocation protocol.Invocation) protocol.Result { + return &protocol.RPCResult{Attrs: map[string]string{constant.WEIGHT_KEY: w2}, Rest: rest{tried: 0, success: true}} + }).MaxTimes(100) + } + invokers = append(invokers, invoker) + } + + zoneAwareCluster := NewZoneAwareCluster() + staticDir := directory.NewStaticDirectory(invokers) + clusterInvoker := zoneAwareCluster.Join(staticDir) + + var w2Count, w1Count int + loop := 50 + for i := 0; i < loop; i++ { + result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) + if w2 == result.Attachment(constant.WEIGHT_KEY, "0") { + w2Count++ + } + if w1 == result.Attachment(constant.WEIGHT_KEY, "0") { + w1Count++ + } + assert.NoError(t, result.Error()) + } + t.Logf("loop count : %d, w1 value : %s | count : %d, w2 value : %s | count : %d", loop, + w1, w1Count, w2, w2Count) +} + +func Test_ZoneWareInvokerWithZoneSuccess(t *testing.T) { + var zoneArray = []string{"hangzhou", "shanghai"} + + ctrl := gomock.NewController(t) + // In Go versions 1.14+, if you pass a *testing.T into gomock.NewController(t) you no longer need to call ctrl.Finish(). + //defer ctrl.Finish() + + var invokers []protocol.Invoker + for i := 0; i < 2; i++ { + zoneValue := zoneArray[i] + url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) + url.SetParam(constant.REGISTRY_KEY+"."+constant.ZONE_KEY, zoneValue) + + invoker := mock.NewMockInvoker(ctrl) + invoker.EXPECT().IsAvailable().Return(true).AnyTimes() + invoker.EXPECT().GetUrl().Return(url).AnyTimes() + invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn( + func(invocation protocol.Invocation) protocol.Result { + return &protocol.RPCResult{Attrs: map[string]string{constant.ZONE_KEY: zoneValue}, Rest: rest{tried: 0, success: true}} + }) + invokers = append(invokers, invoker) + } + + zoneAwareCluster := NewZoneAwareCluster() + staticDir := directory.NewStaticDirectory(invokers) + clusterInvoker := zoneAwareCluster.Join(staticDir) + + inv := &invocation.RPCInvocation{} + // zone hangzhou + hz := zoneArray[0] + inv.SetAttachments(constant.REGISTRY_ZONE, hz) + + result := clusterInvoker.Invoke(context.Background(), inv) + + assert.Equal(t, hz, result.Attachment(constant.ZONE_KEY, "")) +} + +func Test_ZoneWareInvokerWithZoneForceFail(t *testing.T) { + ctrl := gomock.NewController(t) + // In Go versions 1.14+, if you pass a *testing.T into gomock.NewController(t) you no longer need to call ctrl.Finish(). + //defer ctrl.Finish() + + var invokers []protocol.Invoker + for i := 0; i < 2; i++ { + url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) + + invoker := mock.NewMockInvoker(ctrl) + invoker.EXPECT().IsAvailable().Return(true).AnyTimes() + invoker.EXPECT().GetUrl().Return(url).AnyTimes() + invokers = append(invokers, invoker) + } + + zoneAwareCluster := NewZoneAwareCluster() + staticDir := directory.NewStaticDirectory(invokers) + clusterInvoker := zoneAwareCluster.Join(staticDir) + + inv := &invocation.RPCInvocation{} + // zone hangzhou + inv.SetAttachments(constant.REGISTRY_ZONE, "hangzhou") + inv.SetAttachments(constant.REGISTRY_ZONE_FORCE, "true") + + result := clusterInvoker.Invoke(context.Background(), inv) + + assert.NotNil(t, result.Error()) +} diff --git a/cluster/loadbalance/util.go b/cluster/loadbalance/util.go index b6c013852b..c76bdbae7e 100644 --- a/cluster/loadbalance/util.go +++ b/cluster/loadbalance/util.go @@ -17,34 +17,43 @@ package loadbalance -import ( - "time" -) - import ( "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/protocol" + "time" ) // GetWeight gets weight for load balance strategy func GetWeight(invoker protocol.Invoker, invocation protocol.Invocation) int64 { + var weight int64 url := invoker.GetUrl() - weight := url.GetMethodParamInt64(invocation.MethodName(), constant.WEIGHT_KEY, constant.DEFAULT_WEIGHT) + // Multiple registry scenario, load balance among multiple registries. + isRegIvk := url.GetParamBool(constant.REGISTRY_KEY+"."+constant.REGISTRY_LABEL_KEY, false) + if isRegIvk { + weight = url.GetParamInt(constant.REGISTRY_KEY+"."+constant.WEIGHT_KEY, constant.DEFAULT_WEIGHT) + } else { + weight = url.GetMethodParamInt64(invocation.MethodName(), constant.WEIGHT_KEY, constant.DEFAULT_WEIGHT) - if weight > 0 { - //get service register time an do warm up time - now := time.Now().Unix() - timestamp := url.GetParamInt(constant.REMOTE_TIMESTAMP_KEY, now) - if uptime := now - timestamp; uptime > 0 { - warmup := url.GetParamInt(constant.WARMUP_KEY, constant.DEFAULT_WARMUP) - if uptime < warmup { - if ww := float64(uptime) / float64(warmup) / float64(weight); ww < 1 { - weight = 1 - } else if int64(ww) <= weight { - weight = int64(ww) + if weight > 0 { + //get service register time an do warm up time + now := time.Now().Unix() + timestamp := url.GetParamInt(constant.REMOTE_TIMESTAMP_KEY, now) + if uptime := now - timestamp; uptime > 0 { + warmup := url.GetParamInt(constant.WARMUP_KEY, constant.DEFAULT_WARMUP) + if uptime < warmup { + if ww := float64(uptime) / float64(warmup) / float64(weight); ww < 1 { + weight = 1 + } else if int64(ww) <= weight { + weight = int64(ww) + } } } } } + + if weight < 0 { + weight = 0 + } + return weight } diff --git a/common/constant/key.go b/common/constant/key.go index 5be63fe82c..ded6de9efa 100644 --- a/common/constant/key.go +++ b/common/constant/key.go @@ -89,6 +89,11 @@ const ( ROLE_KEY = "registry.role" REGISTRY_DEFAULT_KEY = "registry.default" REGISTRY_TIMEOUT_KEY = "registry.timeout" + REGISTRY_LABEL_KEY = "label" + PREFERRED_KEY = "preferred" + ZONE_KEY = "zone" + REGISTRY_ZONE = "registry_zone" + REGISTRY_ZONE_FORCE = "registry_zone_force" ) const ( diff --git a/config/config_loader_test.go b/config/config_loader_test.go index a21a4998aa..0351289f46 100644 --- a/config/config_loader_test.go +++ b/config/config_loader_test.go @@ -71,7 +71,7 @@ func TestLoad(t *testing.T) { SetProviderService(ms) extension.SetProtocol("registry", GetProtocol) - extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster) + extension.SetCluster(cluster_impl.GetZoneAwareName(), cluster_impl.NewZoneAwareCluster) extension.SetProxyFactory("default", proxy_factory.NewDefaultProxyFactory) Load() @@ -100,7 +100,7 @@ func TestLoadWithSingleReg(t *testing.T) { SetProviderService(ms) extension.SetProtocol("registry", GetProtocol) - extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster) + extension.SetCluster(cluster_impl.GetZoneAwareName(), cluster_impl.NewZoneAwareCluster) extension.SetProxyFactory("default", proxy_factory.NewDefaultProxyFactory) Load() @@ -129,7 +129,7 @@ func TestWithNoRegLoad(t *testing.T) { SetProviderService(ms) extension.SetProtocol("registry", GetProtocol) - extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster) + extension.SetCluster(cluster_impl.GetZoneAwareName(), cluster_impl.NewZoneAwareCluster) extension.SetProxyFactory("default", proxy_factory.NewDefaultProxyFactory) Load() diff --git a/config/reference_config.go b/config/reference_config.go index 5b7a8e9eac..af645e2e30 100644 --- a/config/reference_config.go +++ b/config/reference_config.go @@ -20,6 +20,7 @@ package config import ( "context" "fmt" + "github.com/apache/dubbo-go/cluster/cluster_impl" "net/url" "strconv" "time" @@ -145,10 +146,21 @@ func (c *ReferenceConfig) Refer(_ interface{}) { } } if regUrl != nil { - cluster := extension.GetCluster("registryAware") + // for multi-subscription scenario, use 'zone-aware' policy by default + cluster := extension.GetCluster(cluster_impl.GetZoneAwareName()) + // The invoker wrap sequence would be: ZoneAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, routing happens here) -> Invoker c.invoker = cluster.Join(directory.NewStaticDirectory(invokers)) } else { - cluster := extension.GetCluster(c.Cluster) + // not a registry url, must be direct invoke. + clu := cluster_impl.GetFailoverName() + if len(invokers) > 0 { + u := invokers[0].GetUrl() + if nil != &u { + clu = u.GetParam(constant.CLUSTER_KEY, cluster_impl.GetZoneAwareName()) + } + } + + cluster := extension.GetCluster(clu) c.invoker = cluster.Join(directory.NewStaticDirectory(invokers)) } } diff --git a/config/reference_config_test.go b/config/reference_config_test.go index e43f5aa40a..3d38dff864 100644 --- a/config/reference_config_test.go +++ b/config/reference_config_test.go @@ -179,10 +179,10 @@ func doInitConsumerWithSingleRegistry() { } } -func TestReferMultireg(t *testing.T) { +func TestReferMultiReg(t *testing.T) { doInitConsumer() extension.SetProtocol("registry", GetProtocol) - extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster) + extension.SetCluster(cluster_impl.GetZoneAwareName(), cluster_impl.NewZoneAwareCluster) for _, reference := range consumerConfig.References { reference.Refer(nil) @@ -195,7 +195,7 @@ func TestReferMultireg(t *testing.T) { func TestRefer(t *testing.T) { doInitConsumer() extension.SetProtocol("registry", GetProtocol) - extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster) + extension.SetCluster(cluster_impl.GetZoneAwareName(), cluster_impl.NewZoneAwareCluster) for _, reference := range consumerConfig.References { reference.Refer(nil) @@ -209,7 +209,7 @@ func TestRefer(t *testing.T) { func TestReferAsync(t *testing.T) { doInitConsumerAsync() extension.SetProtocol("registry", GetProtocol) - extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster) + extension.SetCluster(cluster_impl.GetZoneAwareName(), cluster_impl.NewZoneAwareCluster) for _, reference := range consumerConfig.References { reference.Refer(nil) @@ -267,7 +267,7 @@ func TestReferMultiP2PWithReg(t *testing.T) { func TestImplement(t *testing.T) { doInitConsumer() extension.SetProtocol("registry", GetProtocol) - extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster) + extension.SetCluster(cluster_impl.GetZoneAwareName(), cluster_impl.NewZoneAwareCluster) for _, reference := range consumerConfig.References { reference.Refer(nil) reference.Implement(&MockService{}) diff --git a/config/registry_config.go b/config/registry_config.go index ef527c827e..92a174d8fb 100644 --- a/config/registry_config.go +++ b/config/registry_config.go @@ -40,11 +40,17 @@ type RegistryConfig struct { TimeoutStr string `yaml:"timeout" default:"5s" json:"timeout,omitempty" property:"timeout"` // unit: second Group string `yaml:"group" json:"group,omitempty" property:"group"` // for registry - Address string `yaml:"address" json:"address,omitempty" property:"address"` - Username string `yaml:"username" json:"username,omitempty" property:"username"` - Password string `yaml:"password" json:"password,omitempty" property:"password"` - Simplified bool `yaml:"simplified" json:"simplified,omitempty" property:"simplified"` - Params map[string]string `yaml:"params" json:"params,omitempty" property:"params"` + Address string `yaml:"address" json:"address,omitempty" property:"address"` + Username string `yaml:"username" json:"username,omitempty" property:"username"` + Password string `yaml:"password" json:"password,omitempty" property:"password"` + Simplified bool `yaml:"simplified" json:"simplified,omitempty" property:"simplified"` + // Always use this registry first if set to true, useful when subscribe to multiple registries + Preferred bool `yaml:"preferred" json:"params,omitempty" property:"preferred"` + // The region where the registry belongs, usually used to isolate traffics + Zone string `yaml:"zone" json:"params,omitempty" property:"zone"` + // Affects traffic distribution among registries, useful when subscribe to multiple registries Take effect only when no preferred registry is specified. + Weight int64 `yaml:"weight" json:"params,omitempty" property:"weight"` + Params map[string]string `yaml:"params" json:"params,omitempty" property:"params"` } // UnmarshalYAML unmarshals the RegistryConfig by @unmarshal function @@ -118,6 +124,11 @@ func (c *RegistryConfig) getUrlMap(roleType common.RoleType) url.Values { urlMap.Set(constant.ROLE_KEY, strconv.Itoa(int(roleType))) urlMap.Set(constant.REGISTRY_KEY, c.Protocol) urlMap.Set(constant.REGISTRY_TIMEOUT_KEY, c.TimeoutStr) + // multi registry invoker weight label for load balance + urlMap.Set(constant.REGISTRY_KEY+"."+constant.REGISTRY_LABEL_KEY, strconv.FormatBool(true)) + urlMap.Set(constant.REGISTRY_KEY+"."+constant.PREFERRED_KEY, strconv.FormatBool(c.Preferred)) + urlMap.Set(constant.REGISTRY_KEY+"."+constant.ZONE_KEY, c.Zone) + urlMap.Set(constant.REGISTRY_KEY+"."+constant.WEIGHT_KEY, strconv.FormatInt(c.Weight, 10)) for k, v := range c.Params { urlMap.Set(k, v) } From 8f8a0281348d4f4dfecde230e3c38bd5859bed25 Mon Sep 17 00:00:00 2001 From: tiecheng <18768171164@163.com> Date: Thu, 16 Jul 2020 10:43:29 +0800 Subject: [PATCH 015/242] make zone_aware_cluster_invoker_test for weight better --- cluster/cluster_impl/zone_aware_cluster_invoker_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cluster/cluster_impl/zone_aware_cluster_invoker_test.go b/cluster/cluster_impl/zone_aware_cluster_invoker_test.go index a7d550c604..28a312ee3c 100644 --- a/cluster/cluster_impl/zone_aware_cluster_invoker_test.go +++ b/cluster/cluster_impl/zone_aware_cluster_invoker_test.go @@ -83,6 +83,7 @@ func Test_ZoneWareInvokerWithWeightSuccess(t *testing.T) { invoker := mock.NewMockInvoker(ctrl) invoker.EXPECT().IsAvailable().Return(true).AnyTimes() invoker.EXPECT().GetUrl().Return(url).AnyTimes() + url.SetParam(constant.REGISTRY_KEY+"."+constant.REGISTRY_LABEL_KEY, "true") if 1 == i { url.SetParam(constant.REGISTRY_KEY+"."+constant.WEIGHT_KEY, w1) invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn( @@ -115,7 +116,7 @@ func Test_ZoneWareInvokerWithWeightSuccess(t *testing.T) { } assert.NoError(t, result.Error()) } - t.Logf("loop count : %d, w1 value : %s | count : %d, w2 value : %s | count : %d", loop, + t.Logf("loop count : %d, w1 height : %s | count : %d, w2 height : %s | count : %d", loop, w1, w1Count, w2, w2Count) } From 6808199975063f2a3574db74541eac61166e822f Mon Sep 17 00:00:00 2001 From: lzp0412 <641785844@qq.com> Date: Fri, 17 Jul 2020 09:47:59 +0800 Subject: [PATCH 016/242] deregistry url when destroy --- registry/nacos/registry.go | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/registry/nacos/registry.go b/registry/nacos/registry.go index fe911cbfba..a3fa06cbc2 100644 --- a/registry/nacos/registry.go +++ b/registry/nacos/registry.go @@ -59,6 +59,7 @@ func init() { type nacosRegistry struct { *common.URL namingClient naming_client.INamingClient + registryUrls []common.URL } func getNacosConfig(url *common.URL) (map[string]interface{}, error) { @@ -116,6 +117,7 @@ func newNacosRegistry(url *common.URL) (registry.Registry, error) { registry := nacosRegistry{ URL: url, namingClient: client, + registryUrls: make([]common.URL, 16, 16), } return ®istry, nil } @@ -176,6 +178,21 @@ func createRegisterParam(url common.URL, serviceName string) vo.RegisterInstance return instance } +func createDegisterParam(url common.URL, serviceName string) vo.DeregisterInstanceParam { + if len(url.Ip) == 0 { + url.Ip = localIP + } + if len(url.Port) == 0 || url.Port == "0" { + url.Port = "80" + } + port, _ := strconv.Atoi(url.Port) + return vo.DeregisterInstanceParam{ + Ip: url.Ip, + Port: uint64(port), + ServiceName: serviceName, + Ephemeral: true, + } +} func (nr *nacosRegistry) Register(url common.URL) error { serviceName := getServiceName(url) param := createRegisterParam(url, serviceName) @@ -186,6 +203,20 @@ func (nr *nacosRegistry) Register(url common.URL) error { if !isRegistry { return perrors.New("registry [" + serviceName + "] to nacos failed") } + nr.registryUrls = append(nr.registryUrls, url) + return nil +} + +func (nr *nacosRegistry) DeRegister(url common.URL) error { + serviceName := getServiceName(url) + param := createDegisterParam(url, serviceName) + isDeRegistry, err := nr.namingClient.DeregisterInstance(param) + if err != nil { + return err + } + if !isDeRegistry { + return perrors.New("DeRegistry [" + serviceName + "] to nacos failed") + } return nil } @@ -236,5 +267,12 @@ func (nr *nacosRegistry) IsAvailable() bool { } func (nr *nacosRegistry) Destroy() { + logger.Info("Destroy nacos") + for _, url := range nr.registryUrls { + err := nr.DeRegister(url) + if err != nil { + logger.Errorf("Deregister url:%+v err:%v", url, err.Error()) + } + } return } From 468187409db242ef44e7015bac2c8ae63fb0a710 Mon Sep 17 00:00:00 2001 From: william feng <> Date: Sat, 11 Jul 2020 16:11:02 +0800 Subject: [PATCH 017/242] add scope and key support for condition router --- cluster/router/condition/file.go | 5 +- cluster/router/condition/router_rule.go | 5 +- cluster/router/condition/router_rule_test.go | 26 ++++++++- cluster/router/condition/router_test.go | 60 ++++++++++++++++++++ cluster/router/tag/router_rule.go | 2 +- common/constant/key.go | 9 ++- 6 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 cluster/router/condition/router_test.go diff --git a/cluster/router/condition/file.go b/cluster/router/condition/file.go index eabdf1c263..85658e7eb3 100644 --- a/cluster/router/condition/file.go +++ b/cluster/router/condition/file.go @@ -71,7 +71,10 @@ func (f *FileConditionRouter) URL() common.URL { common.WithParamsValue(constant.RouterPriority, strconv.Itoa(routerRule.Priority)), common.WithParamsValue(constant.RULE_KEY, base64.URLEncoding.EncodeToString([]byte(rule))), common.WithParamsValue(constant.ROUTER_KEY, constant.CONDITION_ROUTE_PROTOCOL), - common.WithParamsValue(constant.CATEGORY_KEY, constant.ROUTERS_CATEGORY)) + common.WithParamsValue(constant.CATEGORY_KEY, constant.ROUTERS_CATEGORY), + common.WithParamsValue(constant.RouterRuleKey, routerRule.Key), + common.WithParamsValue(constant.RouterScope, routerRule.Scope), + ) }) return f.url } diff --git a/cluster/router/condition/router_rule.go b/cluster/router/condition/router_rule.go index ce397d6cc0..f8c2c3ed0d 100644 --- a/cluster/router/condition/router_rule.go +++ b/cluster/router/condition/router_rule.go @@ -26,6 +26,7 @@ import ( ) import ( + "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/cluster/router" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/yaml" @@ -33,7 +34,7 @@ import ( // RouterRule RouterRule config read from config file or config center type RouterRule struct { - router.BaseRouterRule `yaml:",inline""` + router.BaseRouterRule `yaml:",inline"` Conditions []string } @@ -57,7 +58,7 @@ func getRule(rawRule string) (*RouterRule, error) { return r, err } r.RawRule = rawRule - if len(r.Conditions) != 0 { + if len(r.Conditions) != 0 && r.Key != "" && (r.Scope == constant.RouterApplicationScope || r.Scope == constant.RouterServiceScope){ r.Valid = true } diff --git a/cluster/router/condition/router_rule_test.go b/cluster/router/condition/router_rule_test.go index 675acaec91..1376058ed3 100644 --- a/cluster/router/condition/router_rule_test.go +++ b/cluster/router/condition/router_rule_test.go @@ -32,6 +32,7 @@ import ( func TestGetRule(t *testing.T) { testyml := ` scope: application +key: test-provider runtime: true force: false conditions: @@ -50,10 +51,31 @@ conditions: assert.True(t, rule.Runtime) assert.Equal(t, false, rule.Force) assert.Equal(t, testyml, rule.RawRule) - assert.True(t, true, rule.Valid) + assert.True(t, rule.Valid) assert.Equal(t, false, rule.Enabled) assert.Equal(t, false, rule.Dynamic) - assert.Equal(t, "", rule.Key) + assert.Equal(t, "test-provider", rule.Key) + + testyml = ` +key: test-provider +runtime: true +force: false +conditions: + - > + method!=sayHello =>` + rule, e = getRule(testyml) + assert.Nil(t, e) + assert.False(t, rule.Valid) + + testyml = ` +scope: noApplication +key: test-provider +conditions: + - > + method!=sayHello =>` + rule, e = getRule(testyml) + assert.Nil(t, e) + assert.False(t, rule.Valid) } func TestIsMatchGlobPattern(t *testing.T) { diff --git a/cluster/router/condition/router_test.go b/cluster/router/condition/router_test.go new file mode 100644 index 0000000000..151f6ccfca --- /dev/null +++ b/cluster/router/condition/router_test.go @@ -0,0 +1,60 @@ +/* + * 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 condition + + import ( + "testing" + ) + + import ( + "github.com/stretchr/testify/assert" + ) + + import ( + "github.com/dubbogo/gost/container/set" + ) + + func TestParseRule(t *testing.T) { + testString := `` + matchPair, err := parseRule(testString) + assert.Nil(t, err) + assert.EqualValues(t, matchPair, make(map[string]MatchPair, 16)) + + testString = `method!=sayHello&application=sayGoodBye` + matchPair, err = parseRule(testString) + assert.Nil(t, err) + assert.EqualValues(t, matchPair["method"].Mismatches, gxset.NewSet("sayHello")) + assert.EqualValues(t, matchPair["application"].Matches, gxset.NewSet("sayGoodBye")) + + testString = `noRule` + matchPair, err = parseRule(testString) + assert.Nil(t, err) + assert.EqualValues(t, matchPair["noRule"].Mismatches, gxset.NewSet()) + assert.EqualValues(t, matchPair["noRule"].Matches, gxset.NewSet()) + + testString = `method!=sayHello,sayGoodBye` + matchPair, err = parseRule(testString) + assert.Nil(t, err) + assert.EqualValues(t, matchPair["method"].Mismatches, gxset.NewSet(`sayHello`, `sayGoodBye`)) + + testString = `method!=sayHello,sayGoodDay=sayGoodBye` + matchPair, err = parseRule(testString) + assert.Nil(t, err) + assert.EqualValues(t, matchPair["method"].Mismatches, gxset.NewSet(`sayHello`, `sayGoodDay`)) + assert.EqualValues(t, matchPair["method"].Matches, gxset.NewSet(`sayGoodBye`)) + } \ No newline at end of file diff --git a/cluster/router/tag/router_rule.go b/cluster/router/tag/router_rule.go index 926446dcb2..fcf5542fdd 100644 --- a/cluster/router/tag/router_rule.go +++ b/cluster/router/tag/router_rule.go @@ -24,7 +24,7 @@ import ( // RouterRule RouterRule config read from config file or config center type RouterRule struct { - router.BaseRouterRule `yaml:",inline""` + router.BaseRouterRule `yaml:",inline"` } func getRule(rawRule string) (*RouterRule, error) { diff --git a/common/constant/key.go b/common/constant/key.go index cd23dd0f1a..48d7a6dd10 100644 --- a/common/constant/key.go +++ b/common/constant/key.go @@ -196,7 +196,14 @@ const ( RouterEnabled = "enabled" // Priority Priority key in router module RouterPriority = "priority" - + // RouterScope Scope key in router module + RouterScope = "scope" + // RouterApplicationScope Scope key in router module + RouterApplicationScope = "application" + // RouterServiceScope Scope key in router module + RouterServiceScope = "service" + // RouterRuleKey defines the key of the router, service's/application's name + RouterRuleKey = "key" // ForceUseTag is the tag in attachment ForceUseTag = "dubbo.force.tag" Tagkey = "dubbo.tag" From 08a087706d85dd1c0f8e6178b790e0203045839e Mon Sep 17 00:00:00 2001 From: william feng <> Date: Sat, 18 Jul 2020 23:33:29 +0800 Subject: [PATCH 018/242] enable digesting multiple router rule in router_config.yml --- common/yaml/yaml.go | 10 +++++++-- config/router_config.go | 28 +++++++++++++++++++++---- config/router_config_test.go | 6 ++++++ config/service_config_test.go | 2 +- config/testdata/router_config.yml | 13 +++++++----- config/testdata/router_config_error.yml | 11 +++++----- config/testdata/router_multi_config.yml | 16 ++++++++++++++ 7 files changed, 69 insertions(+), 17 deletions(-) create mode 100644 config/testdata/router_multi_config.yml diff --git a/common/yaml/yaml.go b/common/yaml/yaml.go index 5edda1b3c7..d7e1ca4e89 100644 --- a/common/yaml/yaml.go +++ b/common/yaml/yaml.go @@ -27,7 +27,7 @@ import ( "gopkg.in/yaml.v2" ) -// loadYMLConfig Load yml config byte from file +// LoadYMLConfig Load yml config byte from file func LoadYMLConfig(confProFile string) ([]byte, error) { if len(confProFile) == 0 { return nil, perrors.Errorf("application configure(provider) file name is nil") @@ -40,7 +40,7 @@ func LoadYMLConfig(confProFile string) ([]byte, error) { return ioutil.ReadFile(confProFile) } -// unmarshalYMLConfig Load yml config byte from file, then unmarshal to object +// UnmarshalYMLConfig Load yml config byte from file, then unmarshal to object func UnmarshalYMLConfig(confProFile string, out interface{}) ([]byte, error) { confFileStream, err := LoadYMLConfig(confProFile) if err != nil { @@ -49,6 +49,12 @@ func UnmarshalYMLConfig(confProFile string, out interface{}) ([]byte, error) { return confFileStream, yaml.Unmarshal(confFileStream, out) } +//UnmarshalYML unmarshals decodes the first document found within the in byte slice and assigns decoded values into the out value. func UnmarshalYML(data []byte, out interface{}) error { return yaml.Unmarshal(data, out) } + +// MarshalYML serializes the value provided into a YAML document. +func MarshalYML(in interface{}) ([]byte, error) { + return yaml.Marshal(in) +} diff --git a/config/router_config.go b/config/router_config.go index 16a2bec918..e9cd5dc749 100644 --- a/config/router_config.go +++ b/config/router_config.go @@ -23,6 +23,7 @@ import ( ) import ( + "github.com/apache/dubbo-go/cluster/router" "github.com/apache/dubbo-go/common/extension" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/common/yaml" @@ -32,16 +33,35 @@ var ( routerURLSet = gxset.NewSet() ) +type LocalRouterRules struct { + RouterRules []interface{} `yaml:"routerRules"` +} // RouterInit Load config file to init router config func RouterInit(confRouterFile string) error { - fileRouterFactories := extension.GetFileRouterFactories() bytes, err := yaml.LoadYMLConfig(confRouterFile) if err != nil { return perrors.Errorf("ioutil.ReadFile(file:%s) = error:%v", confRouterFile, perrors.WithStack(err)) } - logger.Warnf("get fileRouterFactories len{%+v})", len(fileRouterFactories)) - for k, factory := range fileRouterFactories { - r, e := factory.NewFileRouter(bytes) + routerRules := &LocalRouterRules{} + err = yaml.UnmarshalYML(bytes, routerRules) + if err != nil { + return perrors.Errorf("Load router file %s failed due to error: %v", confRouterFile, perrors.WithStack(err)) + } + if len(routerRules.RouterRules) == 0 { + return perrors.Errorf("No router configurations in file %s", confRouterFile) + } + fileRouterFactories := extension.GetFileRouterFactories() + for _, v := range routerRules.RouterRules { + content, _ := yaml.MarshalYML(v) + err = initRouterConfig(content, fileRouterFactories) + } + return err +} + +func initRouterConfig (content []byte,factories map[string]router.FilePriorityRouterFactory) error { + logger.Warnf("get fileRouterFactories len{%+v})", len(factories)) + for k, factory := range factories { + r, e := factory.NewFileRouter(content) if e == nil { url := r.URL() routerURLSet.Add(&url) diff --git a/config/router_config_test.go b/config/router_config_test.go index 72e51c1c82..e4b09ef57d 100644 --- a/config/router_config_test.go +++ b/config/router_config_test.go @@ -31,6 +31,7 @@ import ( ) const testYML = "testdata/router_config.yml" +const testMultiRouterYML = "testdata/router_multi_config.yml" const errorTestYML = "testdata/router_config_error.yml" func TestString(t *testing.T) { @@ -63,4 +64,9 @@ func TestRouterInit(t *testing.T) { assert.NoError(t, errPro) assert.Equal(t, 1, routerURLSet.Size()) + + errPro = RouterInit(testMultiRouterYML) + assert.NoError(t, errPro) + + assert.Equal(t, 3, GetRouterURLSet().Size()) } diff --git a/config/service_config_test.go b/config/service_config_test.go index e7d55077be..a48cc6121a 100644 --- a/config/service_config_test.go +++ b/config/service_config_test.go @@ -153,7 +153,7 @@ func TestExport(t *testing.T) { providerConfig = nil } -func TestgetRandomPort(t *testing.T) { +func TestGetRandomPort(t *testing.T) { protocolConfigs := make([]*ProtocolConfig, 0, 3) ip, err := gxnet.GetLocalIP() diff --git a/config/testdata/router_config.yml b/config/testdata/router_config.yml index f6b91f5da7..1845650d93 100644 --- a/config/testdata/router_config.yml +++ b/config/testdata/router_config.yml @@ -1,6 +1,9 @@ # dubbo router yaml configure file -priority: 1 -force: true -conditions : - - "a => b" - - "c => d" \ No newline at end of file +routerRules: + - scope: application + key: mock-app + priority: 1 + force: true + conditions : + - "a => b" + - "c => d" \ No newline at end of file diff --git a/config/testdata/router_config_error.yml b/config/testdata/router_config_error.yml index 37894ac964..74e89cc52e 100644 --- a/config/testdata/router_config_error.yml +++ b/config/testdata/router_config_error.yml @@ -1,6 +1,7 @@ # dubbo router yaml configure file -priority: 1 -force: true -noConditions : - - "a => b" - - "c => d" \ No newline at end of file +routerRules: + - priority: 1 + force: true + noConditions : + - "a => b" + - "c => d" \ No newline at end of file diff --git a/config/testdata/router_multi_config.yml b/config/testdata/router_multi_config.yml new file mode 100644 index 0000000000..42bb4cbe70 --- /dev/null +++ b/config/testdata/router_multi_config.yml @@ -0,0 +1,16 @@ +# dubbo router yaml configure file +routerRules: + - scope: application + key: mock-app + priority: 1 + force: true + conditions : + - "a => b" + - "c => d" + - scope: application + key: mock-app2 + priority: 1 + force: true + conditions : + - "a => b" + - "c => d" \ No newline at end of file From a41c36eddaea0c76165ac102ec483cc3a93b39e3 Mon Sep 17 00:00:00 2001 From: william feng <> Date: Sun, 19 Jul 2020 00:15:39 +0800 Subject: [PATCH 019/242] format the change --- cluster/router/condition/router_rule.go | 4 +- cluster/router/condition/router_rule_test.go | 6 +- cluster/router/condition/router_test.go | 77 ++++++++++---------- common/constant/key.go | 4 +- config/router_config.go | 5 +- config/router_config_test.go | 4 +- 6 files changed, 51 insertions(+), 49 deletions(-) diff --git a/cluster/router/condition/router_rule.go b/cluster/router/condition/router_rule.go index f8c2c3ed0d..576dea87f9 100644 --- a/cluster/router/condition/router_rule.go +++ b/cluster/router/condition/router_rule.go @@ -26,9 +26,9 @@ import ( ) import ( - "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/cluster/router" "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/yaml" ) @@ -58,7 +58,7 @@ func getRule(rawRule string) (*RouterRule, error) { return r, err } r.RawRule = rawRule - if len(r.Conditions) != 0 && r.Key != "" && (r.Scope == constant.RouterApplicationScope || r.Scope == constant.RouterServiceScope){ + if len(r.Conditions) != 0 && r.Key != "" && (r.Scope == constant.RouterApplicationScope || r.Scope == constant.RouterServiceScope) { r.Valid = true } diff --git a/cluster/router/condition/router_rule_test.go b/cluster/router/condition/router_rule_test.go index 1376058ed3..369b14f08a 100644 --- a/cluster/router/condition/router_rule_test.go +++ b/cluster/router/condition/router_rule_test.go @@ -73,9 +73,9 @@ key: test-provider conditions: - > method!=sayHello =>` - rule, e = getRule(testyml) - assert.Nil(t, e) - assert.False(t, rule.Valid) + rule, e = getRule(testyml) + assert.Nil(t, e) + assert.False(t, rule.Valid) } func TestIsMatchGlobPattern(t *testing.T) { diff --git a/cluster/router/condition/router_test.go b/cluster/router/condition/router_test.go index 151f6ccfca..35b8656b5f 100644 --- a/cluster/router/condition/router_test.go +++ b/cluster/router/condition/router_test.go @@ -15,46 +15,43 @@ * limitations under the License. */ - package condition +package condition - import ( - "testing" - ) - - import ( - "github.com/stretchr/testify/assert" - ) +import ( + "testing" +) - import ( +import ( "github.com/dubbogo/gost/container/set" - ) - - func TestParseRule(t *testing.T) { - testString := `` - matchPair, err := parseRule(testString) - assert.Nil(t, err) - assert.EqualValues(t, matchPair, make(map[string]MatchPair, 16)) - - testString = `method!=sayHello&application=sayGoodBye` - matchPair, err = parseRule(testString) - assert.Nil(t, err) - assert.EqualValues(t, matchPair["method"].Mismatches, gxset.NewSet("sayHello")) - assert.EqualValues(t, matchPair["application"].Matches, gxset.NewSet("sayGoodBye")) - - testString = `noRule` - matchPair, err = parseRule(testString) - assert.Nil(t, err) - assert.EqualValues(t, matchPair["noRule"].Mismatches, gxset.NewSet()) - assert.EqualValues(t, matchPair["noRule"].Matches, gxset.NewSet()) - - testString = `method!=sayHello,sayGoodBye` - matchPair, err = parseRule(testString) - assert.Nil(t, err) - assert.EqualValues(t, matchPair["method"].Mismatches, gxset.NewSet(`sayHello`, `sayGoodBye`)) - - testString = `method!=sayHello,sayGoodDay=sayGoodBye` - matchPair, err = parseRule(testString) - assert.Nil(t, err) - assert.EqualValues(t, matchPair["method"].Mismatches, gxset.NewSet(`sayHello`, `sayGoodDay`)) - assert.EqualValues(t, matchPair["method"].Matches, gxset.NewSet(`sayGoodBye`)) - } \ No newline at end of file + "github.com/stretchr/testify/assert" +) + +func TestParseRule(t *testing.T) { + testString := `` + matchPair, err := parseRule(testString) + assert.Nil(t, err) + assert.EqualValues(t, matchPair, make(map[string]MatchPair, 16)) + + testString = `method!=sayHello&application=sayGoodBye` + matchPair, err = parseRule(testString) + assert.Nil(t, err) + assert.EqualValues(t, matchPair["method"].Mismatches, gxset.NewSet("sayHello")) + assert.EqualValues(t, matchPair["application"].Matches, gxset.NewSet("sayGoodBye")) + + testString = `noRule` + matchPair, err = parseRule(testString) + assert.Nil(t, err) + assert.EqualValues(t, matchPair["noRule"].Mismatches, gxset.NewSet()) + assert.EqualValues(t, matchPair["noRule"].Matches, gxset.NewSet()) + + testString = `method!=sayHello,sayGoodBye` + matchPair, err = parseRule(testString) + assert.Nil(t, err) + assert.EqualValues(t, matchPair["method"].Mismatches, gxset.NewSet(`sayHello`, `sayGoodBye`)) + + testString = `method!=sayHello,sayGoodDay=sayGoodBye` + matchPair, err = parseRule(testString) + assert.Nil(t, err) + assert.EqualValues(t, matchPair["method"].Mismatches, gxset.NewSet(`sayHello`, `sayGoodDay`)) + assert.EqualValues(t, matchPair["method"].Matches, gxset.NewSet(`sayGoodBye`)) +} diff --git a/common/constant/key.go b/common/constant/key.go index 48d7a6dd10..f22debd58c 100644 --- a/common/constant/key.go +++ b/common/constant/key.go @@ -197,11 +197,11 @@ const ( // Priority Priority key in router module RouterPriority = "priority" // RouterScope Scope key in router module - RouterScope = "scope" + RouterScope = "scope" // RouterApplicationScope Scope key in router module RouterApplicationScope = "application" // RouterServiceScope Scope key in router module - RouterServiceScope = "service" + RouterServiceScope = "service" // RouterRuleKey defines the key of the router, service's/application's name RouterRuleKey = "key" // ForceUseTag is the tag in attachment diff --git a/config/router_config.go b/config/router_config.go index e9cd5dc749..ed42577ed3 100644 --- a/config/router_config.go +++ b/config/router_config.go @@ -33,9 +33,11 @@ var ( routerURLSet = gxset.NewSet() ) +// LocalRouterRules defines the local router config structure type LocalRouterRules struct { RouterRules []interface{} `yaml:"routerRules"` } + // RouterInit Load config file to init router config func RouterInit(confRouterFile string) error { bytes, err := yaml.LoadYMLConfig(confRouterFile) @@ -58,7 +60,7 @@ func RouterInit(confRouterFile string) error { return err } -func initRouterConfig (content []byte,factories map[string]router.FilePriorityRouterFactory) error { +func initRouterConfig(content []byte, factories map[string]router.FilePriorityRouterFactory) error { logger.Warnf("get fileRouterFactories len{%+v})", len(factories)) for k, factory := range factories { r, e := factory.NewFileRouter(content) @@ -72,6 +74,7 @@ func initRouterConfig (content []byte,factories map[string]router.FilePriorityRo return perrors.Errorf("no file router exists for parse %s , implement router.FIleRouterFactory please.", confRouterFile) } +// GetRouterURLSet exposes the routerURLSet func GetRouterURLSet() *gxset.HashSet { return routerURLSet } diff --git a/config/router_config_test.go b/config/router_config_test.go index e4b09ef57d..13af7056d5 100644 --- a/config/router_config_test.go +++ b/config/router_config_test.go @@ -23,6 +23,7 @@ import ( ) import ( + "github.com/dubbogo/gost/container/set" "github.com/stretchr/testify/assert" ) @@ -65,8 +66,9 @@ func TestRouterInit(t *testing.T) { assert.Equal(t, 1, routerURLSet.Size()) + routerURLSet = gxset.NewSet() errPro = RouterInit(testMultiRouterYML) assert.NoError(t, errPro) - assert.Equal(t, 3, GetRouterURLSet().Size()) + assert.Equal(t, 2, routerURLSet.Size()) } From b501734ad66ace709b5ce33b9e6dece9130e2b08 Mon Sep 17 00:00:00 2001 From: william feng <> Date: Sun, 19 Jul 2020 17:18:23 +0800 Subject: [PATCH 020/242] fix the test error --- cluster/router/chain/chain_test.go | 16 +++++++++++----- cluster/router/condition/app_router_test.go | 19 +++++++++++++------ cluster/router/condition/file_test.go | 10 +++++++--- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/cluster/router/chain/chain_test.go b/cluster/router/chain/chain_test.go index c1f723525f..065f0e49d9 100644 --- a/cluster/router/chain/chain_test.go +++ b/cluster/router/chain/chain_test.go @@ -66,7 +66,9 @@ func TestNewRouterChain(t *testing.T) { err = z.Create(path) assert.NoError(t, err) - testyml := `enabled: true + testyml := `scope: application +key: mock-app +enabled: true force: true runtime: false conditions: @@ -93,7 +95,7 @@ conditions: assert.NotNil(t, appRouter) assert.NotNil(t, appRouter.RouterRule()) rule := appRouter.RouterRule() - assert.Equal(t, "", rule.Scope) + assert.Equal(t, "application", rule.Scope) assert.True(t, rule.Force) assert.True(t, rule.Enabled) assert.True(t, rule.Valid) @@ -101,7 +103,7 @@ conditions: assert.Equal(t, testyml, rule.RawRule) assert.Equal(t, false, rule.Runtime) assert.Equal(t, false, rule.Dynamic) - assert.Equal(t, "", rule.Key) + assert.Equal(t, "mock-app", rule.Key) } func TestNewRouterChainURLNil(t *testing.T) { @@ -116,7 +118,9 @@ func TestRouterChainAddRouters(t *testing.T) { err = z.Create(path) assert.NoError(t, err) - testyml := `enabled: true + testyml := `scope: application +key: mock-app +enabled: true force: true runtime: false conditions: @@ -182,7 +186,9 @@ func TestRouterChainRouteAppRouter(t *testing.T) { err = z.Create(path) assert.NoError(t, err) - testyml := `enabled: true + testyml := `scope: application +key: mock-app +enabled: true force: true runtime: false conditions: diff --git a/cluster/router/condition/app_router_test.go b/cluster/router/condition/app_router_test.go index 8b38f2dd61..53465f9051 100644 --- a/cluster/router/condition/app_router_test.go +++ b/cluster/router/condition/app_router_test.go @@ -52,7 +52,9 @@ var ( func TestNewAppRouter(t *testing.T) { - testYML := `enabled: true + testYML := `scope: application +key: mock-app +enabled: true force: true runtime: false conditions: @@ -83,7 +85,7 @@ conditions: assert.NotNil(t, appRouter) assert.NotNil(t, appRouter.RouterRule()) rule := appRouter.RouterRule() - assert.Equal(t, "", rule.Scope) + assert.Equal(t, "application", rule.Scope) assert.True(t, rule.Force) assert.True(t, rule.Enabled) assert.True(t, rule.Valid) @@ -91,13 +93,15 @@ conditions: assert.Equal(t, testYML, rule.RawRule) assert.Equal(t, false, rule.Runtime) assert.Equal(t, false, rule.Dynamic) - assert.Equal(t, "", rule.Key) + assert.Equal(t, "mock-app", rule.Key) assert.Equal(t, 0, rule.Priority) } func TestGenerateConditions(t *testing.T) { - testYML := `enabled: true + testYML := `scope: application +key: mock-app +enabled: true force: true runtime: false conditions: @@ -135,7 +139,9 @@ conditions: func TestProcess(t *testing.T) { - testYML := `enabled: true + testYML := `scope: application +key: mock-app +enabled: true force: true runtime: false conditions: @@ -165,7 +171,8 @@ conditions: assert.Equal(t, 1, len(appRouter.conditionRouters)) - testNewYML := ` + testNewYML := `scope: application +key: mock-app enabled: true force: true runtime: false diff --git a/cluster/router/condition/file_test.go b/cluster/router/condition/file_test.go index 3092b12ff8..a568e08fa8 100644 --- a/cluster/router/condition/file_test.go +++ b/cluster/router/condition/file_test.go @@ -26,7 +26,9 @@ import ( ) func TestLoadYmlConfig(t *testing.T) { - router, e := NewFileConditionRouter([]byte(`priority: 1 + router, e := NewFileConditionRouter([]byte(`scope: application +key: mock-app +priority: 1 force: true conditions : - "a => b" @@ -47,12 +49,14 @@ func TestParseCondition(t *testing.T) { } func TestFileRouterURL(t *testing.T) { - router, e := NewFileConditionRouter([]byte(`priority: 1 + router, e := NewFileConditionRouter([]byte(`scope: application +key: mock-app +priority: 1 force: true conditions : - "a => b" - "c => d"`)) assert.Nil(t, e) assert.NotNil(t, router) - assert.Equal(t, "condition://0.0.0.0:?category=routers&force=true&priority=1&router=condition&rule=YSAmIGMgPT4gYiAmIGQ%3D", router.URL().String()) + assert.Equal(t, "condition://0.0.0.0:?category=routers&force=true&key=mock-app&priority=1&router=condition&rule=YSAmIGMgPT4gYiAmIGQ%3D&scope=application", router.URL().String()) } From 667540a5a68b0f45da34b57bd232bbc4b3c605ad Mon Sep 17 00:00:00 2001 From: lihaowei <2421565398@qq.com> Date: Sun, 19 Jul 2020 18:37:29 +0800 Subject: [PATCH 021/242] Imp: add some comments --- NOTICE | 2 +- .../cluster_impl/broadcast_cluster_invoker.go | 1 + common/constant/env.go | 1 + config/base_config_test.go | 8 +++--- config/config_loader_test.go | 4 +-- config/method_config.go | 4 +-- config/reference_config.go | 2 +- config/reference_config_test.go | 8 +++--- config/service_config.go | 2 +- config/service_config_test.go | 8 +++--- config_center/apollo/factory.go | 1 + config_center/mock_dynamic_config.go | 8 +++--- config_center/parser/configuration_parser.go | 17 +++++++---- config_center/zookeeper/listener.go | 2 +- filter/filter.go | 2 +- filter/filter_impl/access_log_filter.go | 12 ++++---- filter/filter_impl/echo_filter.go | 2 +- filter/filter_impl/execute_limit_filter.go | 2 +- filter/filter_impl/generic_filter.go | 2 +- filter/filter_impl/generic_service_filter.go | 12 ++++---- .../generic_service_filter_test.go | 4 +-- filter/filter_impl/hystrix_filter.go | 16 +++++------ filter/filter_impl/token_filter.go | 4 +-- .../tps/tps_limit_fix_window_strategy.go | 2 +- .../tps/tps_limit_strategy_mock.go | 5 +++- filter/filter_impl/tracing_filter_test.go | 5 +--- go.mod | 2 -- .../exporter/configurable/exporter_test.go | 4 +-- protocol/dubbo/codec.go | 6 ++-- protocol/grpc/grpc_exporter.go | 2 +- protocol/grpc/grpc_invoker.go | 12 ++++---- protocol/jsonrpc/http.go | 2 +- protocol/jsonrpc/json.go | 2 +- .../protocol_filter_wrapper.go | 12 ++++---- protocol/rest/config/rest_config.go | 28 +++++++++---------- protocol/rest/rest_exporter.go | 3 ++ protocol/rest/rest_invoker.go | 5 ++++ protocol/rest/rest_protocol.go | 9 ++++++ .../subscribed_urls_synthesizer.go | 1 + remoting/listener.go | 2 ++ 40 files changed, 126 insertions(+), 100 deletions(-) diff --git a/NOTICE b/NOTICE index d7aa899d1c..1120c200c9 100644 --- a/NOTICE +++ b/NOTICE @@ -1,4 +1,4 @@ -Apache Dubbo Go +Apache Dubbo-go Copyright 2018-2020 The Apache Software Foundation This product includes software developed at diff --git a/cluster/cluster_impl/broadcast_cluster_invoker.go b/cluster/cluster_impl/broadcast_cluster_invoker.go index 3a97d3d9b4..b117dbb246 100644 --- a/cluster/cluster_impl/broadcast_cluster_invoker.go +++ b/cluster/cluster_impl/broadcast_cluster_invoker.go @@ -36,6 +36,7 @@ func newBroadcastClusterInvoker(directory cluster.Directory) protocol.Invoker { } } +// nolint func (invoker *broadcastClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { invokers := invoker.directory.List(invocation) err := invoker.checkInvokers(invokers, invocation) diff --git a/common/constant/env.go b/common/constant/env.go index 5376323328..f8402b9cb8 100644 --- a/common/constant/env.go +++ b/common/constant/env.go @@ -17,6 +17,7 @@ package constant +// nolint const ( // CONF_CONSUMER_FILE_PATH ... CONF_CONSUMER_FILE_PATH = "CONF_CONSUMER_FILE_PATH" diff --git a/config/base_config_test.go b/config/base_config_test.go index 15b468753d..6db6a8dcb8 100644 --- a/config/base_config_test.go +++ b/config/base_config_test.go @@ -95,14 +95,14 @@ var baseMockRef = map[string]*ReferenceConfig{ InterfaceName: "com.MockService", Name: "GetUser", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", }, { InterfaceId: "MockService", InterfaceName: "com.MockService", Name: "GetUser1", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", }, }, }, @@ -258,14 +258,14 @@ func TestRefreshProvider(t *testing.T) { InterfaceName: "com.MockService", Name: "GetUser", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", }, { InterfaceId: "MockService", InterfaceName: "com.MockService", Name: "GetUser1", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", }, }, }, diff --git a/config/config_loader_test.go b/config/config_loader_test.go index 2cbf526a70..01d2ca812a 100644 --- a/config/config_loader_test.go +++ b/config/config_loader_test.go @@ -278,13 +278,13 @@ func mockInitProviderWithSingleRegistry() { { Name: "GetUser", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", Weight: 200, }, { Name: "GetUser1", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", Weight: 200, }, }, diff --git a/config/method_config.go b/config/method_config.go index e64773eb13..b64306fd6a 100644 --- a/config/method_config.go +++ b/config/method_config.go @@ -25,13 +25,13 @@ import ( "github.com/apache/dubbo-go/common/constant" ) -// MethodConfig ... +// MethodConfig defines method config type MethodConfig struct { InterfaceId string InterfaceName string Name string `yaml:"name" json:"name,omitempty" property:"name"` Retries string `yaml:"retries" json:"retries,omitempty" property:"retries"` - Loadbalance string `yaml:"loadbalance" json:"loadbalance,omitempty" property:"loadbalance"` + LoadBalance string `yaml:"loadbalance" json:"loadbalance,omitempty" property:"loadbalance"` Weight int64 `yaml:"weight" json:"weight,omitempty" property:"weight"` TpsLimitInterval string `yaml:"tps.limit.interval" json:"tps.limit.interval,omitempty" property:"tps.limit.interval"` TpsLimitRate string `yaml:"tps.limit.rate" json:"tps.limit.rate,omitempty" property:"tps.limit.rate"` diff --git a/config/reference_config.go b/config/reference_config.go index 748b2d403f..e9a895d57a 100644 --- a/config/reference_config.go +++ b/config/reference_config.go @@ -220,7 +220,7 @@ func (c *ReferenceConfig) getUrlMap() url.Values { urlMap.Set(constant.REFERENCE_FILTER_KEY, mergeValue(consumerConfig.Filter, c.Filter, defaultReferenceFilter)) for _, v := range c.Methods { - urlMap.Set("methods."+v.Name+"."+constant.LOADBALANCE_KEY, v.Loadbalance) + urlMap.Set("methods."+v.Name+"."+constant.LOADBALANCE_KEY, v.LoadBalance) urlMap.Set("methods."+v.Name+"."+constant.RETRIES_KEY, v.Retries) urlMap.Set("methods."+v.Name+"."+constant.STICKY_KEY, strconv.FormatBool(v.Sticky)) if len(v.RequestTimeout) != 0 { diff --git a/config/reference_config_test.go b/config/reference_config_test.go index 3fbf8da44c..45cdb2dfac 100644 --- a/config/reference_config_test.go +++ b/config/reference_config_test.go @@ -103,12 +103,12 @@ func doInitConsumer() { { Name: "GetUser", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", }, { Name: "GetUser1", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", Sticky: true, }, }, @@ -174,12 +174,12 @@ func doInitConsumerWithSingleRegistry() { { Name: "GetUser", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", }, { Name: "GetUser1", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", }, }, }, diff --git a/config/service_config.go b/config/service_config.go index 57fce028fa..4351eea7c9 100644 --- a/config/service_config.go +++ b/config/service_config.go @@ -303,7 +303,7 @@ func (c *ServiceConfig) getUrlMap() url.Values { for _, v := range c.Methods { prefix := "methods." + v.Name + "." - urlMap.Set(prefix+constant.LOADBALANCE_KEY, v.Loadbalance) + urlMap.Set(prefix+constant.LOADBALANCE_KEY, v.LoadBalance) urlMap.Set(prefix+constant.RETRIES_KEY, v.Retries) urlMap.Set(prefix+constant.WEIGHT_KEY, strconv.FormatInt(v.Weight, 10)) diff --git a/config/service_config_test.go b/config/service_config_test.go index e7d55077be..0f7e404f6e 100644 --- a/config/service_config_test.go +++ b/config/service_config_test.go @@ -56,13 +56,13 @@ func doInitProvider() { { Name: "GetUser", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", Weight: 200, }, { Name: "GetUser1", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", Weight: 200, }, }, @@ -81,13 +81,13 @@ func doInitProvider() { { Name: "GetUser", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", Weight: 200, }, { Name: "GetUser1", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", Weight: 200, }, }, diff --git a/config_center/apollo/factory.go b/config_center/apollo/factory.go index f975ce13d8..8d873eaabc 100644 --- a/config_center/apollo/factory.go +++ b/config_center/apollo/factory.go @@ -34,6 +34,7 @@ func createDynamicConfigurationFactory() config_center.DynamicConfigurationFacto type apolloConfigurationFactory struct{} +// GetDynamicConfiguration returns the dynamic configuration func (f *apolloConfigurationFactory) GetDynamicConfiguration(url *common.URL) (config_center.DynamicConfiguration, error) { dynamicConfiguration, err := newApolloConfiguration(url) if err != nil { diff --git a/config_center/mock_dynamic_config.go b/config_center/mock_dynamic_config.go index de208946f1..8fe0a25123 100644 --- a/config_center/mock_dynamic_config.go +++ b/config_center/mock_dynamic_config.go @@ -33,7 +33,7 @@ import ( "github.com/apache/dubbo-go/remoting" ) -// MockDynamicConfigurationFactory ... +// MockDynamicConfigurationFactory defines content type MockDynamicConfigurationFactory struct { Content string } @@ -96,7 +96,7 @@ func (c *MockDynamicConfiguration) GetConfigKeysByGroup(group string) (*gxset.Ha return gxset.NewSet(c.content), nil } -// MockDynamicConfiguration ... +// MockDynamicConfiguration uses to parse content and defines listener type MockDynamicConfiguration struct { parser parser.ConfigurationParser content string @@ -149,7 +149,7 @@ func (c *MockDynamicConfiguration) GetRule(key string, opts ...Option) (string, return c.GetProperties(key, opts...) } -// MockServiceConfigEvent ... +// MockServiceConfigEvent returns ConfiguratorConfig func (c *MockDynamicConfiguration) MockServiceConfigEvent() { config := &parser.ConfiguratorConfig{ ConfigVersion: "2.7.1", @@ -171,7 +171,7 @@ func (c *MockDynamicConfiguration) MockServiceConfigEvent() { c.listener[key].Process(&ConfigChangeEvent{Key: key, Value: string(value), ConfigType: remoting.EventTypeAdd}) } -// MockApplicationConfigEvent ... +// MockApplicationConfigEvent returns ConfiguratorConfig func (c *MockDynamicConfiguration) MockApplicationConfigEvent() { config := &parser.ConfiguratorConfig{ ConfigVersion: "2.7.1", diff --git a/config_center/parser/configuration_parser.go b/config_center/parser/configuration_parser.go index 6fbdc27d43..f794221f9c 100644 --- a/config_center/parser/configuration_parser.go +++ b/config_center/parser/configuration_parser.go @@ -35,13 +35,13 @@ import ( ) const ( - // ScopeApplication ... + // ScopeApplication : scope application ScopeApplication = "application" - // GeneralType ... + // GeneralType defines the general type GeneralType = "general" ) -// ConfigurationParser ... +// ConfigurationParser interface type ConfigurationParser interface { Parse(string) (map[string]string, error) ParseToUrls(content string) ([]*common.URL, error) @@ -50,7 +50,7 @@ type ConfigurationParser interface { // DefaultConfigurationParser for supporting properties file in config center type DefaultConfigurationParser struct{} -// ConfiguratorConfig ... +// ConfiguratorConfig defines configurator config type ConfiguratorConfig struct { ConfigVersion string `yaml:"configVersion"` Scope string `yaml:"scope"` @@ -59,7 +59,7 @@ type ConfiguratorConfig struct { Configs []ConfigItem `yaml:"configs"` } -// ConfigItem ... +// ConfigItem defines config item type ConfigItem struct { Type string `yaml:"type"` Enabled bool `yaml:"enabled"` @@ -81,7 +81,7 @@ func (parser *DefaultConfigurationParser) Parse(content string) (map[string]stri return pps.Map(), nil } -// ParseToUrls ... +// ParseToUrls is used to parse content to urls func (parser *DefaultConfigurationParser) ParseToUrls(content string) ([]*common.URL, error) { config := ConfiguratorConfig{} if err := yaml.Unmarshal([]byte(content), &config); err != nil { @@ -110,6 +110,7 @@ func (parser *DefaultConfigurationParser) ParseToUrls(content string) ([]*common return allUrls, nil } +// serviceItemToUrls is used to transfer item and config to urls func serviceItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.URL, error) { var addresses = item.Addresses if len(addresses) == 0 { @@ -156,6 +157,7 @@ func serviceItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.UR return urls, nil } +// nolint func appItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.URL, error) { var addresses = item.Addresses if len(addresses) == 0 { @@ -196,6 +198,7 @@ func appItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.URL, e return urls, nil } +// getServiceString returns service string func getServiceString(service string) (string, error) { if len(service) == 0 { return "", perrors.New("service field in configuration is null.") @@ -219,6 +222,7 @@ func getServiceString(service string) (string, error) { return serviceStr, nil } +// nolint func getParamString(item ConfigItem) (string, error) { var retStr string retStr = retStr + "category=" @@ -241,6 +245,7 @@ func getParamString(item ConfigItem) (string, error) { return retStr, nil } +// getEnabledString returns enabled string func getEnabledString(item ConfigItem, config ConfiguratorConfig) string { retStr := "&enabled=" if len(item.Type) == 0 || item.Type == GeneralType { diff --git a/config_center/zookeeper/listener.go b/config_center/zookeeper/listener.go index 747c4be352..bc6eb6d6ee 100644 --- a/config_center/zookeeper/listener.go +++ b/config_center/zookeeper/listener.go @@ -27,7 +27,7 @@ import ( "github.com/apache/dubbo-go/remoting" ) -// CacheListener ... +// CacheListener defines keyListeners and rootPath type CacheListener struct { keyListeners sync.Map rootPath string diff --git a/filter/filter.go b/filter/filter.go index d20ca72c34..804bf3b9df 100644 --- a/filter/filter.go +++ b/filter/filter.go @@ -27,7 +27,7 @@ import ( // Filter interface defines the functions of a filter // Extension - Filter type Filter interface { - // Invoke is the core function of a filter, it determins the process of the filter + // Invoke is the core function of a filter, it determines the process of the filter Invoke(context.Context, protocol.Invoker, protocol.Invocation) protocol.Result // OnResponse updates the results from Invoke and then returns the modified results. OnResponse(context.Context, protocol.Result, protocol.Invoker, protocol.Invocation) protocol.Result diff --git a/filter/filter_impl/access_log_filter.go b/filter/filter_impl/access_log_filter.go index 49cdc2287c..621012c24c 100644 --- a/filter/filter_impl/access_log_filter.go +++ b/filter/filter_impl/access_log_filter.go @@ -36,20 +36,20 @@ import ( const ( //used in URL. - // FileDateFormat ... + // nolint FileDateFormat = "2006-01-02" - // MessageDateLayout ... + // nolint MessageDateLayout = "2006-01-02 15:04:05" - // LogMaxBuffer ... + // nolint LogMaxBuffer = 5000 - // LogFileMode ... + // nolint LogFileMode = 0600 // those fields are the data collected by this filter - // Types ... + // nolint Types = "types" - // Arguments ... + // nolint Arguments = "arguments" ) diff --git a/filter/filter_impl/echo_filter.go b/filter/filter_impl/echo_filter.go index 7da5ec7029..06e443e807 100644 --- a/filter/filter_impl/echo_filter.go +++ b/filter/filter_impl/echo_filter.go @@ -65,7 +65,7 @@ func (ef *EchoFilter) OnResponse(_ context.Context, result protocol.Result, _ pr return result } -// GetFilter ... +// GetFilter returns the Filter func GetFilter() filter.Filter { return &EchoFilter{} } diff --git a/filter/filter_impl/execute_limit_filter.go b/filter/filter_impl/execute_limit_filter.go index bfc5096ca0..5fc309cfb4 100644 --- a/filter/filter_impl/execute_limit_filter.go +++ b/filter/filter_impl/execute_limit_filter.go @@ -74,7 +74,7 @@ type ExecuteLimitFilter struct { executeState *concurrent.Map } -// ExecuteState ... +// ExecuteState defines the concurrent count type ExecuteState struct { concurrentCount int64 } diff --git a/filter/filter_impl/generic_filter.go b/filter/filter_impl/generic_filter.go index 3f4d714e6b..d385054ed9 100644 --- a/filter/filter_impl/generic_filter.go +++ b/filter/filter_impl/generic_filter.go @@ -47,7 +47,7 @@ func init() { // when do a generic invoke, struct need to be map -// GenericFilter ... +// nolint type GenericFilter struct{} // Invoke turns the parameters to map for generic method diff --git a/filter/filter_impl/generic_service_filter.go b/filter/filter_impl/generic_service_filter.go index 6272df6b39..3711e68cce 100644 --- a/filter/filter_impl/generic_service_filter.go +++ b/filter/filter_impl/generic_service_filter.go @@ -40,9 +40,9 @@ import ( ) const ( - // GENERIC_SERVICE ... + // GENERIC_SERVICE defines the filter name GENERIC_SERVICE = "generic_service" - // GENERIC_SERIALIZATION_DEFAULT ... + // nolint GENERIC_SERIALIZATION_DEFAULT = "true" ) @@ -50,10 +50,10 @@ func init() { extension.SetFilter(GENERIC_SERVICE, GetGenericServiceFilter) } -// GenericServiceFilter ... +// nolint type GenericServiceFilter struct{} -// Invoke ... +// Invoke is used to call service method by invocation func (ef *GenericServiceFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { logger.Infof("invoking generic service filter.") logger.Debugf("generic service filter methodName:%v,args:%v", invocation.MethodName(), len(invocation.Arguments())) @@ -115,7 +115,7 @@ func (ef *GenericServiceFilter) Invoke(ctx context.Context, invoker protocol.Inv return invoker.Invoke(ctx, newInvocation) } -// OnResponse ... +// nolint func (ef *GenericServiceFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { if invocation.MethodName() == constant.GENERIC && len(invocation.Arguments()) == 3 && result.Result() != nil { v := reflect.ValueOf(result.Result()) @@ -127,7 +127,7 @@ func (ef *GenericServiceFilter) OnResponse(ctx context.Context, result protocol. return result } -// GetGenericServiceFilter ... +// nolint func GetGenericServiceFilter() filter.Filter { return &GenericServiceFilter{} } diff --git a/filter/filter_impl/generic_service_filter_test.go b/filter/filter_impl/generic_service_filter_test.go index f0bdb7fb33..67819717cf 100644 --- a/filter/filter_impl/generic_service_filter_test.go +++ b/filter/filter_impl/generic_service_filter_test.go @@ -51,7 +51,7 @@ func (c *TestStruct) JavaClassName() string { type TestService struct{} -// MethodOne ... +// nolint func (ts *TestService) MethodOne(_ context.Context, test1 *TestStruct, test2 []TestStruct, test3 interface{}, test4 []interface{}, test5 *string) (*TestStruct, error) { if test1 == nil { @@ -72,7 +72,7 @@ func (ts *TestService) MethodOne(_ context.Context, test1 *TestStruct, test2 []T return &TestStruct{}, nil } -// Reference ... +// nolint func (*TestService) Reference() string { return "com.test.Path" } diff --git a/filter/filter_impl/hystrix_filter.go b/filter/filter_impl/hystrix_filter.go index 711ef71c44..e2275149f1 100644 --- a/filter/filter_impl/hystrix_filter.go +++ b/filter/filter_impl/hystrix_filter.go @@ -37,11 +37,11 @@ import ( ) const ( - // HYSTRIX_CONSUMER ... + // nolint HYSTRIX_CONSUMER = "hystrix_consumer" - // HYSTRIX_PROVIDER ... + // nolint HYSTRIX_PROVIDER = "hystrix_provider" - // HYSTRIX ... + // nolint HYSTRIX = "hystrix" ) @@ -85,14 +85,14 @@ func NewHystrixFilterError(err error, failByHystrix bool) error { } } -// HystrixFilter ... +// nolint type HystrixFilter struct { COrP bool //true for consumer res map[string][]*regexp.Regexp ifNewMap sync.Map } -// Invoke is an implentation of filter, provides Hystrix pattern latency and fault tolerance +// Invoke is an implementation of filter, provides Hystrix pattern latency and fault tolerance func (hf *HystrixFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { cmdName := fmt.Sprintf("%s&method=%s", invoker.GetUrl().Key(), invocation.MethodName()) @@ -256,7 +256,7 @@ func initHystrixConfigProvider() error { // return initHystrixConfig() //} -// CommandConfigWithError ... +// nolint type CommandConfigWithError struct { Timeout int `yaml:"timeout"` MaxConcurrentRequests int `yaml:"max_concurrent_requests"` @@ -274,14 +274,14 @@ type CommandConfigWithError struct { //- ErrorPercentThreshold: it causes circuits to open once the rolling measure of errors exceeds this percent of requests //See hystrix doc -// HystrixFilterConfig ... +// nolint type HystrixFilterConfig struct { Configs map[string]*CommandConfigWithError Default string Services map[string]ServiceHystrixConfig } -// ServiceHystrixConfig ... +// nolint type ServiceHystrixConfig struct { ServiceConfig string `yaml:"service_config"` Methods map[string]string diff --git a/filter/filter_impl/token_filter.go b/filter/filter_impl/token_filter.go index 23742c66e9..fe4e38747e 100644 --- a/filter/filter_impl/token_filter.go +++ b/filter/filter_impl/token_filter.go @@ -34,7 +34,7 @@ import ( ) const ( - // TOKEN ... + // nolint TOKEN = "token" ) @@ -66,7 +66,7 @@ func (tf *TokenFilter) OnResponse(ctx context.Context, result protocol.Result, i return result } -// GetTokenFilter ... +// nolint func GetTokenFilter() filter.Filter { return &TokenFilter{} } diff --git a/filter/filter_impl/tps/tps_limit_fix_window_strategy.go b/filter/filter_impl/tps/tps_limit_fix_window_strategy.go index 7419a45761..d495e035de 100644 --- a/filter/filter_impl/tps/tps_limit_fix_window_strategy.go +++ b/filter/filter_impl/tps/tps_limit_fix_window_strategy.go @@ -29,7 +29,7 @@ import ( ) const ( - // FixedWindowKey ... + // FixedWindowKey defines tps limit algorithm FixedWindowKey = "fixedWindow" ) diff --git a/filter/filter_impl/tps/tps_limit_strategy_mock.go b/filter/filter_impl/tps/tps_limit_strategy_mock.go index c228c7349c..3a1688e4d8 100644 --- a/filter/filter_impl/tps/tps_limit_strategy_mock.go +++ b/filter/filter_impl/tps/tps_limit_strategy_mock.go @@ -23,7 +23,10 @@ package tps import ( gomock "github.com/golang/mock/gomock" - reflect "reflect" +) + +import ( + "reflect" ) // MockTpsLimitStrategy is a mock of TpsLimitStrategy interface diff --git a/filter/filter_impl/tracing_filter_test.go b/filter/filter_impl/tracing_filter_test.go index 15dc32e7ec..57f4095d49 100644 --- a/filter/filter_impl/tracing_filter_test.go +++ b/filter/filter_impl/tracing_filter_test.go @@ -26,12 +26,9 @@ import ( "github.com/opentracing/opentracing-go" ) -import ( - "github.com/apache/dubbo-go/common/constant" -) - import ( "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/protocol" "github.com/apache/dubbo-go/protocol/invocation" ) diff --git a/go.mod b/go.mod index e82a04b279..a16b906225 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,7 @@ module github.com/apache/dubbo-go require ( github.com/Workiva/go-datastructures v1.0.50 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 - github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5 // indirect github.com/apache/dubbo-go-hessian2 v1.6.1 - github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect github.com/coreos/bbolt v1.3.3 // indirect github.com/coreos/etcd v3.3.13+incompatible github.com/coreos/go-semver v0.3.0 // indirect diff --git a/metadata/service/exporter/configurable/exporter_test.go b/metadata/service/exporter/configurable/exporter_test.go index 4689c6660b..9fdbd76757 100644 --- a/metadata/service/exporter/configurable/exporter_test.go +++ b/metadata/service/exporter/configurable/exporter_test.go @@ -97,13 +97,13 @@ func mockInitProviderWithSingleRegistry() { { Name: "GetUser", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", Weight: 200, }, { Name: "GetUser1", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", Weight: 200, }, }, diff --git a/protocol/dubbo/codec.go b/protocol/dubbo/codec.go index 1f7d107544..8ba725e3ce 100644 --- a/protocol/dubbo/codec.go +++ b/protocol/dubbo/codec.go @@ -54,10 +54,10 @@ const ( // dubbo package //////////////////////////////////////////// -// SequenceType ... +// SequenceType sequence type type SequenceType int64 -// DubboPackage ... +// nolint type DubboPackage struct { Header hessian.DubboHeader Service hessian.Service @@ -82,7 +82,7 @@ func (p *DubboPackage) Marshal() (*bytes.Buffer, error) { return bytes.NewBuffer(pkg), nil } -// Unmarshal dncode hessian package. +// Unmarshal decode hessian package. func (p *DubboPackage) Unmarshal(buf *bytes.Buffer, opts ...interface{}) error { // fix issue https://github.com/apache/dubbo-go/issues/380 bufLen := buf.Len() diff --git a/protocol/grpc/grpc_exporter.go b/protocol/grpc/grpc_exporter.go index 79962b59e2..0dc764854d 100644 --- a/protocol/grpc/grpc_exporter.go +++ b/protocol/grpc/grpc_exporter.go @@ -28,7 +28,7 @@ import ( "github.com/apache/dubbo-go/protocol" ) -// GrpcExporter ... +// nolint type GrpcExporter struct { *protocol.BaseExporter } diff --git a/protocol/grpc/grpc_invoker.go b/protocol/grpc/grpc_invoker.go index e150d05e6f..737e8c40a0 100644 --- a/protocol/grpc/grpc_invoker.go +++ b/protocol/grpc/grpc_invoker.go @@ -37,14 +37,14 @@ import ( var errNoReply = errors.New("request need @response") -// GrpcInvoker ... +// nolint type GrpcInvoker struct { protocol.BaseInvoker quitOnce sync.Once client *Client } -// NewGrpcInvoker ... +// NewGrpcInvoker returns a Grpc invoker instance func NewGrpcInvoker(url common.URL, client *Client) *GrpcInvoker { return &GrpcInvoker{ BaseInvoker: *protocol.NewBaseInvoker(url), @@ -52,7 +52,7 @@ func NewGrpcInvoker(url common.URL, client *Client) *GrpcInvoker { } } -// Invoke ... +// Invoke is used to call service method by invocation func (gi *GrpcInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { var ( result protocol.RPCResult @@ -81,17 +81,17 @@ func (gi *GrpcInvoker) Invoke(ctx context.Context, invocation protocol.Invocatio return &result } -// IsAvailable ... +// IsAvailable get available status func (gi *GrpcInvoker) IsAvailable() bool { return gi.BaseInvoker.IsAvailable() && gi.client.GetState() != connectivity.Shutdown } -// IsDestroyed ... +// IsDestroyed get destroyed status func (gi *GrpcInvoker) IsDestroyed() bool { return gi.BaseInvoker.IsDestroyed() && gi.client.GetState() == connectivity.Shutdown } -// Destroy ... +// Destroy will destroy gRPC's invoker and client, so it is only called once func (gi *GrpcInvoker) Destroy() { gi.quitOnce.Do(func() { gi.BaseInvoker.Destroy() diff --git a/protocol/jsonrpc/http.go b/protocol/jsonrpc/http.go index 5fca66d993..2a2ddfeeeb 100644 --- a/protocol/jsonrpc/http.go +++ b/protocol/jsonrpc/http.go @@ -47,7 +47,7 @@ import ( // Request // //////////////////////////////////////////// -// Request ... +// Request is HTTP protocol request type Request struct { ID int64 group string diff --git a/protocol/jsonrpc/json.go b/protocol/jsonrpc/json.go index 7b05a22943..506c4c953b 100644 --- a/protocol/jsonrpc/json.go +++ b/protocol/jsonrpc/json.go @@ -288,7 +288,7 @@ type ServerCodec struct { var ( null = json.RawMessage([]byte("null")) - // Version ... + // Version is json RPC's version Version = "2.0" ) diff --git a/protocol/protocolwrapper/protocol_filter_wrapper.go b/protocol/protocolwrapper/protocol_filter_wrapper.go index cba1d5d5bc..87f90d3b7c 100644 --- a/protocol/protocolwrapper/protocol_filter_wrapper.go +++ b/protocol/protocolwrapper/protocol_filter_wrapper.go @@ -86,7 +86,7 @@ func buildInvokerChain(invoker protocol.Invoker, key string) protocol.Invoker { return next } -// GetProtocol ... +// nolint func GetProtocol() protocol.Protocol { return &ProtocolFilterWrapper{} } @@ -95,30 +95,30 @@ func GetProtocol() protocol.Protocol { // filter invoker /////////////////////////// -// FilterInvoker ... +// FilterInvoker defines invoker and filter type FilterInvoker struct { next protocol.Invoker invoker protocol.Invoker filter filter.Filter } -// GetUrl ... +// GetUrl is used to get url from FilterInvoker func (fi *FilterInvoker) GetUrl() common.URL { return fi.invoker.GetUrl() } -// IsAvailable ... +// IsAvailable is used to get available status func (fi *FilterInvoker) IsAvailable() bool { return fi.invoker.IsAvailable() } -// Invoke ... +// Invoke is used to call service method by invocation func (fi *FilterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { result := fi.filter.Invoke(ctx, fi.next, invocation) return fi.filter.OnResponse(ctx, result, fi.invoker, invocation) } -// Destroy ... +// Destroy will destroy invoker func (fi *FilterInvoker) Destroy() { fi.invoker.Destroy() } diff --git a/protocol/rest/config/rest_config.go b/protocol/rest/config/rest_config.go index 168ec8ce52..4732dd8e4e 100644 --- a/protocol/rest/config/rest_config.go +++ b/protocol/rest/config/rest_config.go @@ -26,7 +26,7 @@ var ( restProviderServiceConfigMap map[string]*RestServiceConfig ) -// RestConsumerConfig ... +// nolint type RestConsumerConfig struct { Client string `default:"resty" yaml:"rest_client" json:"rest_client,omitempty" property:"rest_client"` Produces string `default:"application/json" yaml:"rest_produces" json:"rest_produces,omitempty" property:"rest_produces"` @@ -34,7 +34,7 @@ type RestConsumerConfig struct { RestServiceConfigsMap map[string]*RestServiceConfig `yaml:"references" json:"references,omitempty" property:"references"` } -// UnmarshalYAML ... +// UnmarshalYAML unmarshals the RestConsumerConfig by @unmarshal function func (c *RestConsumerConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := defaults.Set(c); err != nil { return err @@ -46,7 +46,7 @@ func (c *RestConsumerConfig) UnmarshalYAML(unmarshal func(interface{}) error) er return nil } -// RestProviderConfig ... +// nolint type RestProviderConfig struct { Server string `default:"go-restful" yaml:"rest_server" json:"rest_server,omitempty" property:"rest_server"` Produces string `default:"*/*" yaml:"rest_produces" json:"rest_produces,omitempty" property:"rest_produces"` @@ -54,7 +54,7 @@ type RestProviderConfig struct { RestServiceConfigsMap map[string]*RestServiceConfig `yaml:"services" json:"services,omitempty" property:"services"` } -// UnmarshalYAML ... +// UnmarshalYAML unmarshals the RestProviderConfig by @unmarshal function func (c *RestProviderConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := defaults.Set(c); err != nil { return err @@ -66,7 +66,7 @@ func (c *RestProviderConfig) UnmarshalYAML(unmarshal func(interface{}) error) er return nil } -// RestServiceConfig ... +// nolint type RestServiceConfig struct { InterfaceName string `required:"true" yaml:"interface" json:"interface,omitempty" property:"interface"` Url string `yaml:"url" json:"url,omitempty" property:"url"` @@ -80,7 +80,7 @@ type RestServiceConfig struct { RestMethodConfigsMap map[string]*RestMethodConfig } -// UnmarshalYAML ... +// UnmarshalYAML unmarshals the RestServiceConfig by @unmarshal function func (c *RestServiceConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := defaults.Set(c); err != nil { return err @@ -92,7 +92,7 @@ func (c *RestServiceConfig) UnmarshalYAML(unmarshal func(interface{}) error) err return nil } -// RestMethodConfig ... +// nolint type RestMethodConfig struct { InterfaceName string MethodName string `required:"true" yaml:"name" json:"name,omitempty" property:"name"` @@ -110,7 +110,7 @@ type RestMethodConfig struct { HeadersMap map[int]string } -// UnmarshalYAML ... +// UnmarshalYAML unmarshals the RestMethodConfig by @unmarshal function func (c *RestMethodConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := defaults.Set(c); err != nil { return err @@ -122,32 +122,32 @@ func (c *RestMethodConfig) UnmarshalYAML(unmarshal func(interface{}) error) erro return nil } -// GetRestConsumerServiceConfig ... +// nolint func GetRestConsumerServiceConfig(path string) *RestServiceConfig { return restConsumerServiceConfigMap[path] } -// GetRestProviderServiceConfig ... +// nolint func GetRestProviderServiceConfig(path string) *RestServiceConfig { return restProviderServiceConfigMap[path] } -// SetRestConsumerServiceConfigMap ... +// nolint func SetRestConsumerServiceConfigMap(configMap map[string]*RestServiceConfig) { restConsumerServiceConfigMap = configMap } -// SetRestProviderServiceConfigMap ... +// nolint func SetRestProviderServiceConfigMap(configMap map[string]*RestServiceConfig) { restProviderServiceConfigMap = configMap } -// GetRestConsumerServiceConfigMap ... +// nolint func GetRestConsumerServiceConfigMap() map[string]*RestServiceConfig { return restConsumerServiceConfigMap } -// GetRestProviderServiceConfigMap ... +// nolint func GetRestProviderServiceConfigMap() map[string]*RestServiceConfig { return restProviderServiceConfigMap } diff --git a/protocol/rest/rest_exporter.go b/protocol/rest/rest_exporter.go index 1ee208615e..e39558caea 100644 --- a/protocol/rest/rest_exporter.go +++ b/protocol/rest/rest_exporter.go @@ -28,16 +28,19 @@ import ( "github.com/apache/dubbo-go/protocol" ) +// nolint type RestExporter struct { protocol.BaseExporter } +// NewRestExporter returns a RestExporter func NewRestExporter(key string, invoker protocol.Invoker, exporterMap *sync.Map) *RestExporter { return &RestExporter{ BaseExporter: *protocol.NewBaseExporter(key, invoker, exporterMap), } } +// Unexport unexport the RestExporter func (re *RestExporter) Unexport() { serviceId := re.GetInvoker().GetUrl().GetParam(constant.BEAN_NAME_KEY, "") interfaceName := re.GetInvoker().GetUrl().GetParam(constant.INTERFACE_KEY, "") diff --git a/protocol/rest/rest_invoker.go b/protocol/rest/rest_invoker.go index 121d1217ef..691beeda40 100644 --- a/protocol/rest/rest_invoker.go +++ b/protocol/rest/rest_invoker.go @@ -35,12 +35,14 @@ import ( "github.com/apache/dubbo-go/protocol/rest/config" ) +// nolint type RestInvoker struct { protocol.BaseInvoker client client.RestClient restMethodConfigMap map[string]*config.RestMethodConfig } +// NewRestInvoker returns a RestInvoker func NewRestInvoker(url common.URL, client *client.RestClient, restMethodConfig map[string]*config.RestMethodConfig) *RestInvoker { return &RestInvoker{ BaseInvoker: *protocol.NewBaseInvoker(url), @@ -49,6 +51,7 @@ func NewRestInvoker(url common.URL, client *client.RestClient, restMethodConfig } } +// Invoke is used to call service method by invocation func (ri *RestInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { inv := invocation.(*invocation_impl.RPCInvocation) methodConfig := ri.restMethodConfigMap[inv.MethodName()] @@ -95,6 +98,7 @@ func (ri *RestInvoker) Invoke(ctx context.Context, invocation protocol.Invocatio return &result } +// restStringMapTransform is used to transform rest map func restStringMapTransform(paramsMap map[int]string, args []interface{}) (map[string]string, error) { resMap := make(map[string]string, len(paramsMap)) for k, v := range paramsMap { @@ -106,6 +110,7 @@ func restStringMapTransform(paramsMap map[int]string, args []interface{}) (map[s return resMap, nil } +// nolint func getRestHttpHeader(methodConfig *config.RestMethodConfig, args []interface{}) (http.Header, error) { header := http.Header{} headersMap := methodConfig.HeadersMap diff --git a/protocol/rest/rest_protocol.go b/protocol/rest/rest_protocol.go index e15eeb39d7..0cd26c240a 100644 --- a/protocol/rest/rest_protocol.go +++ b/protocol/rest/rest_protocol.go @@ -44,10 +44,12 @@ var ( const REST = "rest" +// nolint func init() { extension.SetProtocol(REST, GetRestProtocol) } +// nolint type RestProtocol struct { protocol.BaseProtocol serverLock sync.Mutex @@ -56,6 +58,7 @@ type RestProtocol struct { clientMap map[client.RestOptions]client.RestClient } +// NewRestProtocol returns a RestProtocol func NewRestProtocol() *RestProtocol { return &RestProtocol{ BaseProtocol: protocol.NewBaseProtocol(), @@ -64,6 +67,7 @@ func NewRestProtocol() *RestProtocol { } } +// Export export rest service func (rp *RestProtocol) Export(invoker protocol.Invoker) protocol.Exporter { url := invoker.GetUrl() serviceKey := url.ServiceKey() @@ -81,6 +85,7 @@ func (rp *RestProtocol) Export(invoker protocol.Invoker) protocol.Exporter { return exporter } +// Refer create rest service reference func (rp *RestProtocol) Refer(url common.URL) protocol.Invoker { // create rest_invoker var requestTimeout = config.GetConsumerConfig().RequestTimeout @@ -101,6 +106,7 @@ func (rp *RestProtocol) Refer(url common.URL) protocol.Invoker { return invoker } +// nolint func (rp *RestProtocol) getServer(url common.URL, serverType string) server.RestServer { restServer, ok := rp.serverMap[url.Location] if ok { @@ -122,6 +128,7 @@ func (rp *RestProtocol) getServer(url common.URL, serverType string) server.Rest return restServer } +// nolint func (rp *RestProtocol) getClient(restOptions client.RestOptions, clientType string) client.RestClient { restClient, ok := rp.clientMap[restOptions] if ok { @@ -138,6 +145,7 @@ func (rp *RestProtocol) getClient(restOptions client.RestOptions, clientType str return restClient } +// Destroy destroy rest service func (rp *RestProtocol) Destroy() { // destroy rest_server rp.BaseProtocol.Destroy() @@ -150,6 +158,7 @@ func (rp *RestProtocol) Destroy() { } } +// GetRestProtocol get a rest protocol func GetRestProtocol() protocol.Protocol { if restProtocol == nil { restProtocol = NewRestProtocol() diff --git a/registry/servicediscovery/synthesizer/subscribed_urls_synthesizer.go b/registry/servicediscovery/synthesizer/subscribed_urls_synthesizer.go index 949a5822c2..415ca35fba 100644 --- a/registry/servicediscovery/synthesizer/subscribed_urls_synthesizer.go +++ b/registry/servicediscovery/synthesizer/subscribed_urls_synthesizer.go @@ -22,6 +22,7 @@ import ( "github.com/apache/dubbo-go/registry" ) +// SubscribedURLsSynthesizer is used to synthesize the subscribed url type SubscribedURLsSynthesizer interface { // Supports the synthesis of the subscribed url or not Support(subscribedURL *common.URL) bool diff --git a/remoting/listener.go b/remoting/listener.go index 6cbb883181..eb27c71dfd 100644 --- a/remoting/listener.go +++ b/remoting/listener.go @@ -48,6 +48,7 @@ var serviceEventTypeStrings = [...]string{ "update", } +// nolint func (t EventType) String() string { return serviceEventTypeStrings[t] } @@ -63,6 +64,7 @@ type Event struct { Content string } +// nolint func (e Event) String() string { return fmt.Sprintf("Event{Action{%s}, Content{%s}}", e.Action, e.Content) } From d3db7dab6fccd991d0b8a66aaee9f8dbf15e07aa Mon Sep 17 00:00:00 2001 From: lihaowei <2421565398@qq.com> Date: Sun, 19 Jul 2020 20:23:14 +0800 Subject: [PATCH 022/242] Imp: some improvements including add comments --- cluster/cluster_impl/broadcast_cluster.go | 1 + cluster/cluster_impl/failback_cluster.go | 1 + cluster/cluster_impl/failback_cluster_invoker.go | 1 + cluster/cluster_impl/failfast_cluster.go | 1 + cluster/cluster_impl/failfast_cluster_invoker.go | 1 + cluster/cluster_impl/failover_cluster.go | 1 + cluster/cluster_impl/failover_cluster_invoker.go | 1 + cluster/cluster_impl/failover_cluster_test.go | 15 +++++++++++++++ cluster/cluster_impl/failsafe_cluster.go | 1 + cluster/cluster_impl/failsafe_cluster_invoker.go | 1 + cluster/cluster_impl/forking_cluster.go | 1 + cluster/cluster_impl/forking_cluster_invoker.go | 2 +- cluster/cluster_impl/mock_cluster.go | 1 + cluster/cluster_impl/registry_aware_cluster.go | 1 + .../registry_aware_cluster_invoker.go | 1 + cluster/loadbalance/consistent_hash.go | 1 + cluster/loadbalance/least_active.go | 1 + cluster/loadbalance/round_robin.go | 1 + cluster/router/healthcheck/factory_test.go | 9 +++++++++ cluster/router/tag/tag_router.go | 8 ++++++++ 20 files changed, 49 insertions(+), 1 deletion(-) diff --git a/cluster/cluster_impl/broadcast_cluster.go b/cluster/cluster_impl/broadcast_cluster.go index ba454af6a8..024cf4f533 100644 --- a/cluster/cluster_impl/broadcast_cluster.go +++ b/cluster/cluster_impl/broadcast_cluster.go @@ -39,6 +39,7 @@ func NewBroadcastCluster() cluster.Cluster { return &broadcastCluster{} } +// Join would return baseClusterInvoker instance func (cluster *broadcastCluster) Join(directory cluster.Directory) protocol.Invoker { return newBroadcastClusterInvoker(directory) } diff --git a/cluster/cluster_impl/failback_cluster.go b/cluster/cluster_impl/failback_cluster.go index 432e33122c..589b6bfc46 100644 --- a/cluster/cluster_impl/failback_cluster.go +++ b/cluster/cluster_impl/failback_cluster.go @@ -39,6 +39,7 @@ func NewFailbackCluster() cluster.Cluster { return &failbackCluster{} } +// Join would return baseClusterInvoker instance func (cluster *failbackCluster) Join(directory cluster.Directory) protocol.Invoker { return newFailbackClusterInvoker(directory) } diff --git a/cluster/cluster_impl/failback_cluster_invoker.go b/cluster/cluster_impl/failback_cluster_invoker.go index af17a93756..62f48045ec 100644 --- a/cluster/cluster_impl/failback_cluster_invoker.go +++ b/cluster/cluster_impl/failback_cluster_invoker.go @@ -126,6 +126,7 @@ func (invoker *failbackClusterInvoker) checkRetry(retryTask *retryTimerTask, err } } +// nolint func (invoker *failbackClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { invokers := invoker.directory.List(invocation) if err := invoker.checkInvokers(invokers, invocation); err != nil { diff --git a/cluster/cluster_impl/failfast_cluster.go b/cluster/cluster_impl/failfast_cluster.go index ac9ec6b821..d8b7e9543b 100644 --- a/cluster/cluster_impl/failfast_cluster.go +++ b/cluster/cluster_impl/failfast_cluster.go @@ -39,6 +39,7 @@ func NewFailFastCluster() cluster.Cluster { return &failfastCluster{} } +// Join would return baseClusterInvoker instance func (cluster *failfastCluster) Join(directory cluster.Directory) protocol.Invoker { return newFailFastClusterInvoker(directory) } diff --git a/cluster/cluster_impl/failfast_cluster_invoker.go b/cluster/cluster_impl/failfast_cluster_invoker.go index 3b4dc9b721..d71ef5f5a1 100644 --- a/cluster/cluster_impl/failfast_cluster_invoker.go +++ b/cluster/cluster_impl/failfast_cluster_invoker.go @@ -35,6 +35,7 @@ func newFailFastClusterInvoker(directory cluster.Directory) protocol.Invoker { } } +// nolint func (invoker *failfastClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { invokers := invoker.directory.List(invocation) err := invoker.checkInvokers(invokers, invocation) diff --git a/cluster/cluster_impl/failover_cluster.go b/cluster/cluster_impl/failover_cluster.go index d30a743e03..ecc3596f8a 100644 --- a/cluster/cluster_impl/failover_cluster.go +++ b/cluster/cluster_impl/failover_cluster.go @@ -40,6 +40,7 @@ func NewFailoverCluster() cluster.Cluster { return &failoverCluster{} } +// Join would return baseClusterInvoker instance func (cluster *failoverCluster) Join(directory cluster.Directory) protocol.Invoker { return newFailoverClusterInvoker(directory) } diff --git a/cluster/cluster_impl/failover_cluster_invoker.go b/cluster/cluster_impl/failover_cluster_invoker.go index 66adabd104..2ce393cc3c 100644 --- a/cluster/cluster_impl/failover_cluster_invoker.go +++ b/cluster/cluster_impl/failover_cluster_invoker.go @@ -44,6 +44,7 @@ func newFailoverClusterInvoker(directory cluster.Directory) protocol.Invoker { } } +// nolint func (invoker *failoverClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { var ( result protocol.Result diff --git a/cluster/cluster_impl/failover_cluster_test.go b/cluster/cluster_impl/failover_cluster_test.go index e05b79202c..d3ac2c8a5f 100644 --- a/cluster/cluster_impl/failover_cluster_test.go +++ b/cluster/cluster_impl/failover_cluster_test.go @@ -43,6 +43,7 @@ import ( // mock invoker // /////////////////////////// +// nolint type MockInvoker struct { url common.URL available bool @@ -51,6 +52,7 @@ type MockInvoker struct { successCount int } +// nolint func NewMockInvoker(url common.URL, successCount int) *MockInvoker { return &MockInvoker{ url: url, @@ -60,23 +62,28 @@ func NewMockInvoker(url common.URL, successCount int) *MockInvoker { } } +// nolint func (bi *MockInvoker) GetUrl() common.URL { return bi.url } +// nolint func (bi *MockInvoker) IsAvailable() bool { return bi.available } +// nolint func (bi *MockInvoker) IsDestroyed() bool { return bi.destroyed } +// nolint type rest struct { tried int success bool } +// nolint func (bi *MockInvoker) Invoke(c context.Context, invocation protocol.Invocation) protocol.Result { count++ var ( @@ -93,14 +100,17 @@ func (bi *MockInvoker) Invoke(c context.Context, invocation protocol.Invocation) return result } +// nolint func (bi *MockInvoker) Destroy() { logger.Infof("Destroy invoker: %v", bi.GetUrl().String()) bi.destroyed = true bi.available = false } +// nolint var count int +// nolint func normalInvoke(successCount int, urlParam url.Values, invocations ...*invocation.RPCInvocation) protocol.Result { extension.SetLoadbalance("random", loadbalance.NewRandomLoadBalance) failoverCluster := NewFailoverCluster() @@ -119,6 +129,7 @@ func normalInvoke(successCount int, urlParam url.Values, invocations ...*invocat return clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) } +// nolint func TestFailoverInvokeSuccess(t *testing.T) { urlParams := url.Values{} result := normalInvoke(3, urlParams) @@ -126,6 +137,7 @@ func TestFailoverInvokeSuccess(t *testing.T) { count = 0 } +// nolint func TestFailoverInvokeFail(t *testing.T) { urlParams := url.Values{} result := normalInvoke(4, urlParams) @@ -133,6 +145,7 @@ func TestFailoverInvokeFail(t *testing.T) { count = 0 } +// nolint func TestFailoverInvoke1(t *testing.T) { urlParams := url.Values{} urlParams.Set(constant.RETRIES_KEY, "3") @@ -141,6 +154,7 @@ func TestFailoverInvoke1(t *testing.T) { count = 0 } +// nolint func TestFailoverInvoke2(t *testing.T) { urlParams := url.Values{} urlParams.Set(constant.RETRIES_KEY, "2") @@ -152,6 +166,7 @@ func TestFailoverInvoke2(t *testing.T) { count = 0 } +// nolint func TestFailoverDestroy(t *testing.T) { extension.SetLoadbalance("random", loadbalance.NewRandomLoadBalance) failoverCluster := NewFailoverCluster() diff --git a/cluster/cluster_impl/failsafe_cluster.go b/cluster/cluster_impl/failsafe_cluster.go index f708b7fb91..25f42dd0a9 100644 --- a/cluster/cluster_impl/failsafe_cluster.go +++ b/cluster/cluster_impl/failsafe_cluster.go @@ -39,6 +39,7 @@ func NewFailsafeCluster() cluster.Cluster { return &failsafeCluster{} } +// Join would return baseClusterInvoker instance func (cluster *failsafeCluster) Join(directory cluster.Directory) protocol.Invoker { return newFailsafeClusterInvoker(directory) } diff --git a/cluster/cluster_impl/failsafe_cluster_invoker.go b/cluster/cluster_impl/failsafe_cluster_invoker.go index 4d8fe27719..27c59fff18 100644 --- a/cluster/cluster_impl/failsafe_cluster_invoker.go +++ b/cluster/cluster_impl/failsafe_cluster_invoker.go @@ -45,6 +45,7 @@ func newFailsafeClusterInvoker(directory cluster.Directory) protocol.Invoker { } } +// nolint func (invoker *failsafeClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { invokers := invoker.directory.List(invocation) diff --git a/cluster/cluster_impl/forking_cluster.go b/cluster/cluster_impl/forking_cluster.go index 0e6cd26882..9dd366d0b5 100644 --- a/cluster/cluster_impl/forking_cluster.go +++ b/cluster/cluster_impl/forking_cluster.go @@ -39,6 +39,7 @@ func NewForkingCluster() cluster.Cluster { return &forkingCluster{} } +// Join would return baseClusterInvoker instance func (cluster *forkingCluster) Join(directory cluster.Directory) protocol.Invoker { return newForkingClusterInvoker(directory) } diff --git a/cluster/cluster_impl/forking_cluster_invoker.go b/cluster/cluster_impl/forking_cluster_invoker.go index a5a3f2ec66..1684448816 100644 --- a/cluster/cluster_impl/forking_cluster_invoker.go +++ b/cluster/cluster_impl/forking_cluster_invoker.go @@ -44,7 +44,7 @@ func newForkingClusterInvoker(directory cluster.Directory) protocol.Invoker { } } -// Invoke ... +// nolint func (invoker *forkingClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { if err := invoker.checkWhetherDestroyed(); err != nil { return &protocol.RPCResult{Err: err} diff --git a/cluster/cluster_impl/mock_cluster.go b/cluster/cluster_impl/mock_cluster.go index d887cfb45b..a7fa5d4f02 100644 --- a/cluster/cluster_impl/mock_cluster.go +++ b/cluster/cluster_impl/mock_cluster.go @@ -33,6 +33,7 @@ func NewMockCluster() cluster.Cluster { return &mockCluster{} } +// nolint func (cluster *mockCluster) Join(directory cluster.Directory) protocol.Invoker { return protocol.NewBaseInvoker(directory.GetUrl()) } diff --git a/cluster/cluster_impl/registry_aware_cluster.go b/cluster/cluster_impl/registry_aware_cluster.go index fcefa52862..f4c0897371 100644 --- a/cluster/cluster_impl/registry_aware_cluster.go +++ b/cluster/cluster_impl/registry_aware_cluster.go @@ -34,6 +34,7 @@ func NewRegistryAwareCluster() cluster.Cluster { return ®istryAwareCluster{} } +// nolint func (cluster *registryAwareCluster) Join(directory cluster.Directory) protocol.Invoker { return newRegistryAwareClusterInvoker(directory) } diff --git a/cluster/cluster_impl/registry_aware_cluster_invoker.go b/cluster/cluster_impl/registry_aware_cluster_invoker.go index cded5bf164..7840da5218 100644 --- a/cluster/cluster_impl/registry_aware_cluster_invoker.go +++ b/cluster/cluster_impl/registry_aware_cluster_invoker.go @@ -36,6 +36,7 @@ func newRegistryAwareClusterInvoker(directory cluster.Directory) protocol.Invoke } } +// nolint func (invoker *registryAwareClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { invokers := invoker.directory.List(invocation) //First, pick the invoker (XXXClusterInvoker) that comes from the local registry, distinguish by a 'default' key. diff --git a/cluster/loadbalance/consistent_hash.go b/cluster/loadbalance/consistent_hash.go index 84fbb268c7..3266b4f11c 100644 --- a/cluster/loadbalance/consistent_hash.go +++ b/cluster/loadbalance/consistent_hash.go @@ -157,6 +157,7 @@ func (c *ConsistentHashSelector) selectForKey(hash uint32) protocol.Invoker { return c.virtualInvokers[c.keys[idx]] } +// nolint func (c *ConsistentHashSelector) hash(digest [16]byte, i int) uint32 { return uint32((digest[3+i*4]&0xFF)<<24) | uint32((digest[2+i*4]&0xFF)<<16) | uint32((digest[1+i*4]&0xFF)<<8) | uint32(digest[i*4]&0xFF)&0xFFFFFFF diff --git a/cluster/loadbalance/least_active.go b/cluster/loadbalance/least_active.go index 37ad91c3ed..87767359a9 100644 --- a/cluster/loadbalance/least_active.go +++ b/cluster/loadbalance/least_active.go @@ -46,6 +46,7 @@ func NewLeastActiveLoadBalance() cluster.LoadBalance { return &leastActiveLoadBalance{} } +// Select gets invoker based on least active load balancing strategy func (lb *leastActiveLoadBalance) Select(invokers []protocol.Invoker, invocation protocol.Invocation) protocol.Invoker { count := len(invokers) if count == 0 { diff --git a/cluster/loadbalance/round_robin.go b/cluster/loadbalance/round_robin.go index c44b239dbc..8d1324e27a 100644 --- a/cluster/loadbalance/round_robin.go +++ b/cluster/loadbalance/round_robin.go @@ -59,6 +59,7 @@ func NewRoundRobinLoadBalance() cluster.LoadBalance { return &roundRobinLoadBalance{} } +// Select gets invoker based on round robin load balancing strategy func (lb *roundRobinLoadBalance) Select(invokers []protocol.Invoker, invocation protocol.Invocation) protocol.Invoker { count := len(invokers) if count == 0 { diff --git a/cluster/router/healthcheck/factory_test.go b/cluster/router/healthcheck/factory_test.go index c3a26a9389..e80fd4c4d3 100644 --- a/cluster/router/healthcheck/factory_test.go +++ b/cluster/router/healthcheck/factory_test.go @@ -31,34 +31,43 @@ import ( "github.com/apache/dubbo-go/protocol" ) +// nolint type MockInvoker struct { url common.URL } +// nolint func NewMockInvoker(url common.URL) *MockInvoker { return &MockInvoker{ url: url, } } +// nolint func (bi *MockInvoker) GetUrl() common.URL { return bi.url } + +// nolint func (bi *MockInvoker) IsAvailable() bool { return true } +// nolint func (bi *MockInvoker) IsDestroyed() bool { return true } +// nolint func (bi *MockInvoker) Invoke(_ context.Context, _ protocol.Invocation) protocol.Result { return nil } +// nolint func (bi *MockInvoker) Destroy() { } +// nolint func TestHealthCheckRouteFactory(t *testing.T) { factory := newHealthCheckRouteFactory() assert.NotNil(t, factory) diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index 87da418943..74f51075c7 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -31,12 +31,14 @@ import ( "github.com/apache/dubbo-go/protocol" ) +// tagRouter defines url, enable and the priority type tagRouter struct { url *common.URL enabled bool priority int64 } +// NewTagRouter would return tagRouter instance if url is not nil func NewTagRouter(url *common.URL) (*tagRouter, error) { if url == nil { return nil, perrors.Errorf("Illegal route URL!") @@ -48,10 +50,12 @@ func NewTagRouter(url *common.URL) (*tagRouter, error) { }, nil } +// nolint func (c *tagRouter) isEnabled() bool { return c.enabled } +// Route gets a list of invoker func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { if !c.isEnabled() { return invokers @@ -62,14 +66,17 @@ func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocati return filterUsingStaticTag(invokers, url, invocation) } +// URL gets the url of tagRouter func (c *tagRouter) URL() common.URL { return *c.url } +// Priority gets the priority of tagRouter func (c *tagRouter) Priority() int64 { return c.priority } +// filterUsingStaticTag gets a list of invoker using static tag func filterUsingStaticTag(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { if tag, ok := invocation.Attachments()[constant.Tagkey]; ok { result := make([]protocol.Invoker, 0, 8) @@ -86,6 +93,7 @@ func filterUsingStaticTag(invokers []protocol.Invoker, url *common.URL, invocati return invokers } +// isForceUseTag returns whether force use tag func isForceUseTag(url *common.URL, invocation protocol.Invocation) bool { if b, e := strconv.ParseBool(invocation.AttachmentsByKey(constant.ForceUseTag, url.GetParam(constant.ForceUseTag, "false"))); e == nil { return b From 892d8593d45881b8e732547c2e9cfdc4392cac5e Mon Sep 17 00:00:00 2001 From: lihaowei <2421565398@qq.com> Date: Sun, 19 Jul 2020 20:32:24 +0800 Subject: [PATCH 023/242] Imp: three improvements --- config_center/apollo/factory.go | 2 +- filter/filter_impl/echo_filter.go | 2 +- filter/filter_impl/tps/tps_limit_strategy_mock.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config_center/apollo/factory.go b/config_center/apollo/factory.go index 8d873eaabc..c52d942c4f 100644 --- a/config_center/apollo/factory.go +++ b/config_center/apollo/factory.go @@ -34,7 +34,7 @@ func createDynamicConfigurationFactory() config_center.DynamicConfigurationFacto type apolloConfigurationFactory struct{} -// GetDynamicConfiguration returns the dynamic configuration +// GetDynamicConfiguration gets the dynamic configuration func (f *apolloConfigurationFactory) GetDynamicConfiguration(url *common.URL) (config_center.DynamicConfiguration, error) { dynamicConfiguration, err := newApolloConfiguration(url) if err != nil { diff --git a/filter/filter_impl/echo_filter.go b/filter/filter_impl/echo_filter.go index 06e443e807..24ba6e47ac 100644 --- a/filter/filter_impl/echo_filter.go +++ b/filter/filter_impl/echo_filter.go @@ -65,7 +65,7 @@ func (ef *EchoFilter) OnResponse(_ context.Context, result protocol.Result, _ pr return result } -// GetFilter returns the Filter +// GetFilter gets the Filter func GetFilter() filter.Filter { return &EchoFilter{} } diff --git a/filter/filter_impl/tps/tps_limit_strategy_mock.go b/filter/filter_impl/tps/tps_limit_strategy_mock.go index 3a1688e4d8..be76466092 100644 --- a/filter/filter_impl/tps/tps_limit_strategy_mock.go +++ b/filter/filter_impl/tps/tps_limit_strategy_mock.go @@ -26,7 +26,7 @@ import ( ) import ( - "reflect" + reflect "reflect" ) // MockTpsLimitStrategy is a mock of TpsLimitStrategy interface From 5868ed12d8cbb831a5c6324783dda9a8f7236e39 Mon Sep 17 00:00:00 2001 From: lihaowei <2421565398@qq.com> Date: Sun, 19 Jul 2020 20:48:35 +0800 Subject: [PATCH 024/242] Imp: three improvements --- cluster/loadbalance/consistent_hash.go | 4 ++-- cluster/loadbalance/round_robin.go | 6 +++--- config_center/apollo/listener.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cluster/loadbalance/consistent_hash.go b/cluster/loadbalance/consistent_hash.go index 3266b4f11c..85eb96417c 100644 --- a/cluster/loadbalance/consistent_hash.go +++ b/cluster/loadbalance/consistent_hash.go @@ -38,9 +38,9 @@ import ( ) const ( - // ConsistentHash ... + // ConsistentHash consistent hash ConsistentHash = "consistenthash" - // HashNodes ... + // HashNodes hash nodes HashNodes = "hash.nodes" // HashArguments key of hash arguments in url HashArguments = "hash.arguments" diff --git a/cluster/loadbalance/round_robin.go b/cluster/loadbalance/round_robin.go index 8d1324e27a..51a76da998 100644 --- a/cluster/loadbalance/round_robin.go +++ b/cluster/loadbalance/round_robin.go @@ -31,12 +31,12 @@ import ( ) const ( - // RoundRobin ... + // RoundRobin load balancing way RoundRobin = "roundrobin" - // COMPLETE ... + // nolint COMPLETE = 0 - // UPDATING ... + // nolint UPDATING = 1 ) diff --git a/config_center/apollo/listener.go b/config_center/apollo/listener.go index 1cf65ed22b..48a35093d2 100644 --- a/config_center/apollo/listener.go +++ b/config_center/apollo/listener.go @@ -36,7 +36,7 @@ func NewApolloListener() *apolloListener { } } -// OnChange ... +// OnChange process each listener func (a *apolloListener) OnChange(changeEvent *agollo.ChangeEvent) { for key, change := range changeEvent.Changes { for listener := range a.listeners { From 53f2b372c5e6778b851a546f5f76e49a13b3d730 Mon Sep 17 00:00:00 2001 From: lihaowei <2421565398@qq.com> Date: Sun, 19 Jul 2020 21:42:14 +0800 Subject: [PATCH 025/242] Imp: replace 'would return' to returns xxx --- cluster/cluster_impl/available_cluster.go | 1 + cluster/cluster_impl/broadcast_cluster.go | 2 +- cluster/cluster_impl/failback_cluster.go | 2 +- cluster/cluster_impl/failfast_cluster.go | 2 +- cluster/cluster_impl/failover_cluster.go | 2 +- cluster/cluster_impl/failsafe_cluster.go | 2 +- cluster/cluster_impl/forking_cluster.go | 2 +- cluster/router/tag/tag_router.go | 2 +- 8 files changed, 8 insertions(+), 7 deletions(-) diff --git a/cluster/cluster_impl/available_cluster.go b/cluster/cluster_impl/available_cluster.go index b70a97fad2..ebd5767e4c 100644 --- a/cluster/cluster_impl/available_cluster.go +++ b/cluster/cluster_impl/available_cluster.go @@ -38,6 +38,7 @@ func NewAvailableCluster() cluster.Cluster { return &availableCluster{} } +// Join returns a baseClusterInvoker instance func (cluser *availableCluster) Join(directory cluster.Directory) protocol.Invoker { return NewAvailableClusterInvoker(directory) } diff --git a/cluster/cluster_impl/broadcast_cluster.go b/cluster/cluster_impl/broadcast_cluster.go index 024cf4f533..ea3dee9218 100644 --- a/cluster/cluster_impl/broadcast_cluster.go +++ b/cluster/cluster_impl/broadcast_cluster.go @@ -39,7 +39,7 @@ func NewBroadcastCluster() cluster.Cluster { return &broadcastCluster{} } -// Join would return baseClusterInvoker instance +// Join returns a baseClusterInvoker instance func (cluster *broadcastCluster) Join(directory cluster.Directory) protocol.Invoker { return newBroadcastClusterInvoker(directory) } diff --git a/cluster/cluster_impl/failback_cluster.go b/cluster/cluster_impl/failback_cluster.go index 589b6bfc46..278ac5432e 100644 --- a/cluster/cluster_impl/failback_cluster.go +++ b/cluster/cluster_impl/failback_cluster.go @@ -39,7 +39,7 @@ func NewFailbackCluster() cluster.Cluster { return &failbackCluster{} } -// Join would return baseClusterInvoker instance +// Join returns a baseClusterInvoker instance func (cluster *failbackCluster) Join(directory cluster.Directory) protocol.Invoker { return newFailbackClusterInvoker(directory) } diff --git a/cluster/cluster_impl/failfast_cluster.go b/cluster/cluster_impl/failfast_cluster.go index d8b7e9543b..a5ea7a0585 100644 --- a/cluster/cluster_impl/failfast_cluster.go +++ b/cluster/cluster_impl/failfast_cluster.go @@ -39,7 +39,7 @@ func NewFailFastCluster() cluster.Cluster { return &failfastCluster{} } -// Join would return baseClusterInvoker instance +// Join returns a baseClusterInvoker instance func (cluster *failfastCluster) Join(directory cluster.Directory) protocol.Invoker { return newFailFastClusterInvoker(directory) } diff --git a/cluster/cluster_impl/failover_cluster.go b/cluster/cluster_impl/failover_cluster.go index ecc3596f8a..254cc097e3 100644 --- a/cluster/cluster_impl/failover_cluster.go +++ b/cluster/cluster_impl/failover_cluster.go @@ -40,7 +40,7 @@ func NewFailoverCluster() cluster.Cluster { return &failoverCluster{} } -// Join would return baseClusterInvoker instance +// Join returns a baseClusterInvoker instance func (cluster *failoverCluster) Join(directory cluster.Directory) protocol.Invoker { return newFailoverClusterInvoker(directory) } diff --git a/cluster/cluster_impl/failsafe_cluster.go b/cluster/cluster_impl/failsafe_cluster.go index 25f42dd0a9..d9465c034c 100644 --- a/cluster/cluster_impl/failsafe_cluster.go +++ b/cluster/cluster_impl/failsafe_cluster.go @@ -39,7 +39,7 @@ func NewFailsafeCluster() cluster.Cluster { return &failsafeCluster{} } -// Join would return baseClusterInvoker instance +// Join returns a baseClusterInvoker instance func (cluster *failsafeCluster) Join(directory cluster.Directory) protocol.Invoker { return newFailsafeClusterInvoker(directory) } diff --git a/cluster/cluster_impl/forking_cluster.go b/cluster/cluster_impl/forking_cluster.go index 9dd366d0b5..8c99113275 100644 --- a/cluster/cluster_impl/forking_cluster.go +++ b/cluster/cluster_impl/forking_cluster.go @@ -39,7 +39,7 @@ func NewForkingCluster() cluster.Cluster { return &forkingCluster{} } -// Join would return baseClusterInvoker instance +// Join returns a baseClusterInvoker instance func (cluster *forkingCluster) Join(directory cluster.Directory) protocol.Invoker { return newForkingClusterInvoker(directory) } diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index 74f51075c7..e1376fd96a 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -38,7 +38,7 @@ type tagRouter struct { priority int64 } -// NewTagRouter would return tagRouter instance if url is not nil +// NewTagRouter returns a tagRouter instance if url is not nil func NewTagRouter(url *common.URL) (*tagRouter, error) { if url == nil { return nil, perrors.Errorf("Illegal route URL!") From 2e01ed5bee64bac3a09d859278768be3dfa386cc Mon Sep 17 00:00:00 2001 From: watermelo <80680489@qq.com> Date: Sun, 19 Jul 2020 22:38:24 +0800 Subject: [PATCH 026/242] Ftr: add dynamic tag router --- cluster/router/condition/listenable_router.go | 2 +- cluster/router/tag/file.go | 2 +- cluster/router/tag/router_rule.go | 45 ++++++++ cluster/router/tag/tag.go | 39 +++++++ cluster/router/tag/tag_router.go | 105 +++++++++++++++++- 5 files changed, 187 insertions(+), 6 deletions(-) create mode 100644 cluster/router/tag/tag.go diff --git a/cluster/router/condition/listenable_router.go b/cluster/router/condition/listenable_router.go index 4ccc19e955..7f4f14a8e4 100644 --- a/cluster/router/condition/listenable_router.go +++ b/cluster/router/condition/listenable_router.go @@ -85,7 +85,7 @@ func newListenableRouter(url *common.URL, ruleKey string) (*AppRouter, error) { return l, nil } -// Process Process config change event , generate routers and set them to the listenableRouter instance +// Process Process config change event, generate routers and set them to the listenableRouter instance func (l *listenableRouter) Process(event *config_center.ConfigChangeEvent) { logger.Infof("Notification of condition rule, change type is:[%s] , raw rule is:[%v]", event.ConfigType, event.Value) if remoting.EventTypeDel == event.ConfigType { diff --git a/cluster/router/tag/file.go b/cluster/router/tag/file.go index 8144c83203..433abcb72e 100644 --- a/cluster/router/tag/file.go +++ b/cluster/router/tag/file.go @@ -42,7 +42,7 @@ type FileTagRouter struct { force bool } -// NewFileTagRouter Create file tag router instance with content ( from config file) +// NewFileTagRouter Create file tag router instance with content (from config file) func NewFileTagRouter(content []byte) (*FileTagRouter, error) { fileRouter := &FileTagRouter{} rule, err := getRule(string(content)) diff --git a/cluster/router/tag/router_rule.go b/cluster/router/tag/router_rule.go index 926446dcb2..b2b6659054 100644 --- a/cluster/router/tag/router_rule.go +++ b/cluster/router/tag/router_rule.go @@ -22,9 +22,27 @@ import ( "github.com/apache/dubbo-go/common/yaml" ) +/** + * %YAML1.2 + * --- + * force: true + * runtime: false + * enabled: true + * priority: 1 + * key: demo-provider + * tags: + * - name: tag1 + * addresses: [ip1, ip2] + * - name: tag2 + * addresses: [ip3, ip4] + * ... + */ // RouterRule RouterRule config read from config file or config center type RouterRule struct { router.BaseRouterRule `yaml:",inline""` + tags []tag + addressToTagNames map[string][]string + tagNameToAddresses map[string][]string } func getRule(rawRule string) (*RouterRule, error) { @@ -34,5 +52,32 @@ func getRule(rawRule string) (*RouterRule, error) { return r, err } r.RawRule = rawRule + // TODO init tags return r, nil } + +func (t *RouterRule) getAddresses() []string { + // TODO get all tag addresses + return nil +} + +func (t *RouterRule) getTagNames() []string { + // TODO get all tag names + return nil +} + +func (t *RouterRule) getAddressToTagNames() map[string][]string { + return t.addressToTagNames +} + +func (t *RouterRule) getTagNameToAddresses() map[string][]string { + return t.tagNameToAddresses +} + +func (t *RouterRule) getTags() []tag { + return t.tags +} + +func (t *RouterRule) setTags(tags []tag) { + t.tags = tags +} diff --git a/cluster/router/tag/tag.go b/cluster/router/tag/tag.go new file mode 100644 index 0000000000..07d719af86 --- /dev/null +++ b/cluster/router/tag/tag.go @@ -0,0 +1,39 @@ +/* + * 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 tag + +type tag struct { + name string + addresses []string +} + +func (t *tag) getName() string { + return t.name +} + +func (t *tag) setName(name string) { + t.name = name +} + +func (t *tag) getAddresses() []string { + return t.addresses +} + +func (t *tag) setAddresses(addresses []string) { + t.addresses = addresses +} diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index 87da418943..ff1209dd45 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -18,6 +18,7 @@ package tag import ( + "fmt" "strconv" ) @@ -28,13 +29,17 @@ import ( import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config_center" "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/remoting" ) type tagRouter struct { - url *common.URL - enabled bool - priority int64 + url *common.URL + tagRouterRule *RouterRule + enabled bool + priority int64 } func NewTagRouter(url *common.URL) (*tagRouter, error) { @@ -59,7 +64,81 @@ func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocati if len(invokers) == 0 { return invokers } - return filterUsingStaticTag(invokers, url, invocation) + // since the rule can be changed by config center, we should copy one to use. + tagRouterRuleCopy := c.tagRouterRule + if tagRouterRuleCopy == nil || !tagRouterRuleCopy.Valid || !tagRouterRuleCopy.Enabled { + return filterUsingStaticTag(invokers, url, invocation) + } + tag, ok := invocation.Attachments()[constant.Tagkey] + if !ok { + tag = url.GetParam(constant.Tagkey, "") + } + var ( + result []protocol.Invoker + addresses []string + ) + if tag != "" { + addresses, _ = tagRouterRuleCopy.getTagNameToAddresses()[tag] + // filter by dynamic tag group first + if len(addresses) > 0 { + // TODO filter invokers + result = nil + if len(result) > 0 || tagRouterRuleCopy.Force { + return result + } + } else { + // dynamic tag group doesn't have any item about the requested app OR it's null after filtered by + // dynamic tag group but force=false. check static tag + // TODO filter invokers + return result + } + // If there's no tagged providers that can match the current tagged request. force.tag is set by default + // to false, which means it will invoke any providers without a tag unless it's explicitly disallowed. + if len(result) > 0 || isForceUseTag(url, invocation) { + return result + } else { + // FAILOVER: return all Providers without any tags. + // TODO filter invokers + return result + } + } else { + // return all addresses in dynamic tag group. + addresses = tagRouterRuleCopy.getAddresses() + if len(addresses) > 0 { + // TODO filter invokers + // 1. all addresses are in dynamic tag group, return empty list. + if len(result) == 0 { + return result + } + // 2. if there are some addresses that are not in any dynamic tag group, continue to filter using the + // static tag group. + } + // TODO filter invokers + return result + } +} + +func (c *tagRouter) Process(event *config_center.ConfigChangeEvent) { + logger.Infof("Notification of dynamic tag rule, change type is:[%s] , raw rule is:[%v]", event.ConfigType, event.Value) + if remoting.EventTypeDel == event.ConfigType { + c.tagRouterRule = nil + return + } else { + content, ok := event.Value.(string) + if !ok { + msg := fmt.Sprintf("Convert event content fail,raw content:[%s] ", event.Value) + logger.Error(msg) + return + } + + routerRule, err := getRule(content) + if err != nil { + logger.Errorf("Parse dynamic tag router rule fail,error:[%s] ", err) + return + } + c.tagRouterRule = routerRule + return + } } func (c *tagRouter) URL() common.URL { @@ -92,3 +171,21 @@ func isForceUseTag(url *common.URL, invocation protocol.Invocation) bool { } return false } + +func addressMatches(url *common.URL, addresses []string) bool { + return len(addresses) > 0 && checkAddressMatch(addresses, url.Ip, url.Port) +} + +func addressNotMatches(url *common.URL, addresses []string) bool { + return len(addresses) == 0 || !checkAddressMatch(addresses, url.Ip, url.Port) +} + +func checkAddressMatch(addresses []string, host, port string) bool { + for _, address := range addresses { + // TODO address parse + if address == (host + port) { + return true + } + } + return false +} From 340efda3640e375826ef3e5106d9368c9439a114 Mon Sep 17 00:00:00 2001 From: lzp0412 <641785844@qq.com> Date: Tue, 21 Jul 2020 10:05:47 +0800 Subject: [PATCH 027/242] fix not invoke nacos destroy when graceful shutdown --- config/graceful_shutdown_signal_darwin.go | 2 +- registry/nacos/registry.go | 4 ++-- registry/protocol/protocol.go | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/config/graceful_shutdown_signal_darwin.go b/config/graceful_shutdown_signal_darwin.go index 8ad79ffa62..3129ab32b1 100644 --- a/config/graceful_shutdown_signal_darwin.go +++ b/config/graceful_shutdown_signal_darwin.go @@ -26,7 +26,7 @@ var ( // ShutdownSignals ... ShutdownSignals = []os.Signal{os.Interrupt, os.Kill, syscall.SIGKILL, syscall.SIGSTOP, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGILL, syscall.SIGTRAP, - syscall.SIGABRT, syscall.SIGSYS} + syscall.SIGABRT, syscall.SIGSYS, syscall.SIGTERM} // DumpHeapShutdownSignals ... DumpHeapShutdownSignals = []os.Signal{syscall.SIGQUIT, syscall.SIGILL, diff --git a/registry/nacos/registry.go b/registry/nacos/registry.go index a3fa06cbc2..0dfb64622e 100644 --- a/registry/nacos/registry.go +++ b/registry/nacos/registry.go @@ -117,7 +117,7 @@ func newNacosRegistry(url *common.URL) (registry.Registry, error) { registry := nacosRegistry{ URL: url, namingClient: client, - registryUrls: make([]common.URL, 16, 16), + registryUrls: []common.URL{}, } return ®istry, nil } @@ -267,9 +267,9 @@ func (nr *nacosRegistry) IsAvailable() bool { } func (nr *nacosRegistry) Destroy() { - logger.Info("Destroy nacos") for _, url := range nr.registryUrls { err := nr.DeRegister(url) + logger.Infof("DeRegister Nacos url:%+v", url) if err != nil { logger.Errorf("Deregister url:%+v err:%v", url, err.Error()) } diff --git a/registry/protocol/protocol.go b/registry/protocol/protocol.go index a7678ba4e2..458cde2e86 100644 --- a/registry/protocol/protocol.go +++ b/registry/protocol/protocol.go @@ -44,6 +44,7 @@ import ( ) var ( + once sync.Once regProtocol *registryProtocol ) @@ -154,6 +155,7 @@ func (proto *registryProtocol) Export(invoker protocol.Invoker) protocol.Exporte if regI, loaded := proto.registries.Load(registryUrl.Key()); !loaded { reg = getRegistry(registryUrl) proto.registries.Store(registryUrl.Key(), reg) + logger.Infof("Export proto:%p registries address:%p", proto, proto.registries) } else { reg = regI.(registry.Registry) } @@ -307,14 +309,12 @@ func (proto *registryProtocol) Destroy() { ivk.Destroy() } proto.invokers = []protocol.Invoker{} - proto.bounds.Range(func(key, value interface{}) bool { exporter := value.(protocol.Exporter) exporter.Unexport() proto.bounds.Delete(key) return true }) - proto.registries.Range(func(key, value interface{}) bool { reg := value.(registry.Registry) if reg.IsAvailable() { @@ -348,10 +348,10 @@ func setProviderUrl(regURL *common.URL, providerURL *common.URL) { // GetProtocol ... func GetProtocol() protocol.Protocol { - if regProtocol != nil { - return regProtocol - } - return newRegistryProtocol() + once.Do(func() { + regProtocol = newRegistryProtocol() + }) + return regProtocol } type wrappedInvoker struct { From 1e7e05a3edd372a1b12160d5f9897b2a1980db60 Mon Sep 17 00:00:00 2001 From: lzp0412 <641785844@qq.com> Date: Tue, 21 Jul 2020 17:55:31 +0800 Subject: [PATCH 028/242] add term signal --- config/graceful_shutdown_signal_linux.go | 2 +- config/graceful_shutdown_signal_windows.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/graceful_shutdown_signal_linux.go b/config/graceful_shutdown_signal_linux.go index 8ad79ffa62..3129ab32b1 100644 --- a/config/graceful_shutdown_signal_linux.go +++ b/config/graceful_shutdown_signal_linux.go @@ -26,7 +26,7 @@ var ( // ShutdownSignals ... ShutdownSignals = []os.Signal{os.Interrupt, os.Kill, syscall.SIGKILL, syscall.SIGSTOP, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGILL, syscall.SIGTRAP, - syscall.SIGABRT, syscall.SIGSYS} + syscall.SIGABRT, syscall.SIGSYS, syscall.SIGTERM} // DumpHeapShutdownSignals ... DumpHeapShutdownSignals = []os.Signal{syscall.SIGQUIT, syscall.SIGILL, diff --git a/config/graceful_shutdown_signal_windows.go b/config/graceful_shutdown_signal_windows.go index 815a05ecb2..1722f0263e 100644 --- a/config/graceful_shutdown_signal_windows.go +++ b/config/graceful_shutdown_signal_windows.go @@ -26,7 +26,7 @@ var ( // ShutdownSignals ... ShutdownSignals = []os.Signal{os.Interrupt, os.Kill, syscall.SIGKILL, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGILL, syscall.SIGTRAP, - syscall.SIGABRT} + syscall.SIGABRT, syscall.SIGTERM} // DumpHeapShutdownSignals ... DumpHeapShutdownSignals = []os.Signal{syscall.SIGQUIT, syscall.SIGILL, syscall.SIGTRAP, syscall.SIGABRT} From e59c00b1af96544230243dfb2f88b8f14d2968a1 Mon Sep 17 00:00:00 2001 From: lzp0412 <641785844@qq.com> Date: Tue, 21 Jul 2020 19:32:08 +0800 Subject: [PATCH 029/242] solve nacos unit test failed --- registry/nacos/service_discovery_test.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/registry/nacos/service_discovery_test.go b/registry/nacos/service_discovery_test.go index 720c44a6f9..4b069c2e82 100644 --- a/registry/nacos/service_discovery_test.go +++ b/registry/nacos/service_discovery_test.go @@ -18,7 +18,10 @@ package nacos import ( + "math/rand" + "strconv" "testing" + "time" ) import ( @@ -84,8 +87,8 @@ func TestNacosServiceDiscovery_CRUD(t *testing.T) { }) extension.SetAndInitGlobalDispatcher("mock") - - serviceName := "service-name" + rand.Seed(time.Now().Unix()) + serviceName := "service-name" + strconv.Itoa(rand.Intn(10000)) id := "id" host := "host" port := 123 @@ -113,7 +116,9 @@ func TestNacosServiceDiscovery_CRUD(t *testing.T) { err := serviceDiscovry.Register(instance) assert.Nil(t, err) - + //sometimes nacos may be failed to push update of instance, + //so it need 10s to pull, we sleep 10 second to make sure instance has been update + time.Sleep(11 * time.Second) page := serviceDiscovry.GetHealthyInstancesByPage(serviceName, 0, 10, true) assert.NotNil(t, page) From 227441c85e97850d7395414004534cd29a947c1d Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Tue, 21 Jul 2020 23:46:31 +0800 Subject: [PATCH 030/242] update apollo client --- config_center/apollo/impl.go | 36 +++---- config_center/apollo/listener.go | 38 +++++--- go.mod | 16 +--- go.sum | 160 ++++++++++++++++++++++++++++--- 4 files changed, 189 insertions(+), 61 deletions(-) diff --git a/config_center/apollo/impl.go b/config_center/apollo/impl.go index 3b5d1f4ebe..d81f7836ea 100644 --- a/config_center/apollo/impl.go +++ b/config_center/apollo/impl.go @@ -26,7 +26,9 @@ import ( import ( "github.com/pkg/errors" - "github.com/zouyx/agollo" + "github.com/zouyx/agollo/v3" + agolloConstant "github.com/zouyx/agollo/v3/constant" + "github.com/zouyx/agollo/v3/env/config" ) import ( @@ -34,19 +36,18 @@ import ( "github.com/apache/dubbo-go/common/constant" cc "github.com/apache/dubbo-go/config_center" "github.com/apache/dubbo-go/config_center/parser" - "github.com/apache/dubbo-go/remoting" ) const ( apolloProtocolPrefix = "http://" - apolloConfigFormat = "%s.%s" + apolloConfigFormat = "%s%s" ) type apolloConfiguration struct { url *common.URL listeners sync.Map - appConf *agollo.AppConfig + appConf *config.AppConfig parser parser.ConfigurationParser } @@ -59,31 +60,20 @@ func newApolloConfiguration(url *common.URL) (*apolloConfiguration, error) { appId := url.GetParam(constant.CONFIG_APP_ID_KEY, "") namespaces := getProperties(url.GetParam(constant.CONFIG_NAMESPACE_KEY, cc.DEFAULT_GROUP)) - c.appConf = &agollo.AppConfig{ - AppId: appId, + c.appConf = &config.AppConfig{ + AppID: appId, Cluster: configCluster, NamespaceName: namespaces, - Ip: configAddr, + IP: configAddr, } - agollo.InitCustomConfig(func() (*agollo.AppConfig, error) { + agollo.InitCustomConfig(func() (*config.AppConfig, error) { return c.appConf, nil }) return c, agollo.Start() } -func getChangeType(change agollo.ConfigChangeType) remoting.EventType { - switch change { - case agollo.ADDED: - return remoting.EventTypeAdd - case agollo.DELETED: - return remoting.EventTypeDel - default: - return remoting.EventTypeUpdate - } -} - func (c *apolloConfiguration) AddListener(key string, listener cc.ConfigurationListener, opts ...cc.Option) { k := &cc.Options{} for _, opt := range opts { @@ -91,7 +81,7 @@ func (c *apolloConfiguration) AddListener(key string, listener cc.ConfigurationL } key = k.Group + key - l, _ := c.listeners.LoadOrStore(key, NewApolloListener()) + l, _ := c.listeners.LoadOrStore(key, newApolloListener()) l.(*apolloListener).AddListener(listener) } @@ -109,10 +99,10 @@ func (c *apolloConfiguration) RemoveListener(key string, listener cc.Configurati } func getProperties(namespace string) string { - return getNamespaceName(namespace, agollo.Properties) + return getNamespaceName(namespace, agolloConstant.Properties) } -func getNamespaceName(namespace string, configFileFormat agollo.ConfigFileFormat) string { +func getNamespaceName(namespace string, configFileFormat agolloConstant.ConfigFileFormat) string { return fmt.Sprintf(apolloConfigFormat, namespace, configFileFormat) } @@ -137,7 +127,7 @@ func (c *apolloConfiguration) GetProperties(key string, opts ...cc.Option) (stri if config == nil { return "", errors.New(fmt.Sprintf("nothing in namespace:%s ", key)) } - return config.GetContent(agollo.Properties), nil + return config.GetContent(), nil } func (c *apolloConfiguration) getAddressWithProtocolPrefix(url *common.URL) string { diff --git a/config_center/apollo/listener.go b/config_center/apollo/listener.go index fb257a4828..4d28b9969c 100644 --- a/config_center/apollo/listener.go +++ b/config_center/apollo/listener.go @@ -18,34 +18,48 @@ package apollo import ( - "github.com/zouyx/agollo" + "github.com/zouyx/agollo/v3" + "github.com/zouyx/agollo/v3/storage" + "gopkg.in/yaml.v2" ) import ( + "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/config_center" + "github.com/apache/dubbo-go/remoting" ) type apolloListener struct { listeners map[config_center.ConfigurationListener]struct{} } -// NewApolloListener ... -func NewApolloListener() *apolloListener { +// newApolloListener ... +func newApolloListener() *apolloListener { return &apolloListener{ listeners: make(map[config_center.ConfigurationListener]struct{}, 0), } } // OnChange ... -func (a *apolloListener) OnChange(changeEvent *agollo.ChangeEvent) { - for key, change := range changeEvent.Changes { - for listener := range a.listeners { - listener.Process(&config_center.ConfigChangeEvent{ - ConfigType: getChangeType(change.ChangeType), - Key: key, - Value: change.NewValue, - }) - } +func (a *apolloListener) OnChange(changeEvent *storage.ChangeEvent) { + +} + +// OnNewestChange ... +func (a *apolloListener) OnNewestChange(changeEvent *storage.FullChangeEvent) { + b, err := yaml.Marshal(changeEvent.Changes) + if err != nil { + logger.Errorf("apollo onNewestChange err %+v", + err) + return + } + content := string(b) + for listener := range a.listeners { + listener.Process(&config_center.ConfigChangeEvent{ + ConfigType: remoting.EventTypeUpdate, + Key: changeEvent.Namespace, + Value: content, + }) } } diff --git a/go.mod b/go.mod index 4aa26cf2b4..283014421c 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,10 @@ module github.com/apache/dubbo-go require ( github.com/Workiva/go-datastructures v1.0.50 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 - github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5 // indirect github.com/apache/dubbo-go-hessian2 v1.4.0 - github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect github.com/coreos/bbolt v1.3.3 // indirect github.com/coreos/etcd v3.3.13+incompatible - github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect - github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect github.com/creasty/defaults v1.3.0 github.com/dubbogo/getty v1.3.3 github.com/dubbogo/go-zookeeper v1.0.0 @@ -20,9 +16,6 @@ require ( github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect github.com/golang/mock v1.3.1 github.com/golang/protobuf v1.3.2 - github.com/google/btree v1.0.0 // indirect - github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 // indirect - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.9.5 // indirect github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 github.com/hashicorp/consul v1.5.3 @@ -36,19 +29,14 @@ require ( github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.1.0 github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b - github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 // indirect - github.com/soheilhy/cmux v0.1.4 // indirect github.com/stretchr/testify v1.5.1 - github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect - github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect - github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8 + github.com/zouyx/agollo/v3 v3.4.3 go.etcd.io/bbolt v1.3.3 // indirect go.etcd.io/etcd v3.3.13+incompatible go.uber.org/atomic v1.4.0 go.uber.org/zap v1.10.0 - golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect google.golang.org/grpc v1.22.1 - gopkg.in/yaml.v2 v2.2.2 + gopkg.in/yaml.v2 v2.2.4 k8s.io/api v0.0.0-20190325185214-7544f9db76f6 k8s.io/apimachinery v0.0.0-20190223001710-c182ff3b9841 k8s.io/client-go v8.0.0+incompatible diff --git a/go.sum b/go.sum index 1bf70600bc..1b1494b782 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,18 @@ cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3 h1:AVXDdKsrtX33oR9fbCMu/+c1o8Ofjq6Ku/MInaLVg5Y= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-sdk-for-go v16.0.0+incompatible h1:gr1qKY/Ll72VjFTZmaBwRK1yQHAxCnV25ekOKroc9ws= github.com/Azure/azure-sdk-for-go v16.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= @@ -7,7 +20,9 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX github.com/Azure/go-autorest v10.7.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v10.15.3+incompatible h1:nhKI/bvazIs3C3TFGoSqKY6hZ8f5od5mb5/UcS6HVIY= github.com/Azure/go-autorest v10.15.3+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v2.2.0+incompatible h1:V5BKkxACZLjzHjSgBbr2gvLA2Ae49yhc6CSY7MLy5k4= github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/Jeffail/gabs v1.1.0 h1:kw5zCcl9tlJNHTDme7qbi21fDHZmXrnjMoXos3Jw/NI= @@ -19,6 +34,7 @@ github.com/NYTimes/gziphandler v1.0.1 h1:iLrQrdwjDd52kHDA5op2UBJFjmOb9g+7scBan4R github.com/NYTimes/gziphandler v1.0.1/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/SAP/go-hdb v0.12.0 h1:5hBQZ2jjyZ268qjDmoDZJuCyLzR6oRLI60eYzmTW9m4= @@ -35,11 +51,8 @@ github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vaj github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190802083043-4cd0c391755e h1:MSuLXx/mveDbpDNhVrcWTMeV4lbYWKcyO4rH+jAxmX0= -github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190802083043-4cd0c391755e/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ= github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA= github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk= -github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/apache/dubbo-go-hessian2 v1.4.0 h1:Cb9FQVTy3G93dnDr7P93U8DeKFYpDTJjQp44JG5TafA= github.com/apache/dubbo-go-hessian2 v1.4.0/go.mod h1:VwEnsOMidkM1usya2uPfGpSLO9XUF//WQcWn3y+jFz8= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= @@ -53,7 +66,6 @@ github.com/asaskevich/govalidator v0.0.0-20180319081651-7d2e70ef918f h1:/8NcnxL6 github.com/asaskevich/govalidator v0.0.0-20180319081651-7d2e70ef918f/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.15.24 h1:xLAdTA/ore6xdPAljzZRed7IGqQgC+nY+ERS5vaj4Ro= github.com/aws/aws-sdk-go v1.15.24/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= -github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -62,6 +74,7 @@ github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= @@ -70,6 +83,8 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 h1:D21IyuvjDCshj1 github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/cenkalti/backoff v2.1.1+incompatible h1:tKJnvO2kl0zmb/jA5UKAt4VoEVw1qxKWjE/Bpp46npY= github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible h1:C29Ae4G5GtYyYMm1aztcyj/J5ckgJm2zwdDajFbx1NY= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3 h1:TJH+oke8D16535+jHExHj4nQvzlZrj7ug5D7I/orNUA= @@ -79,12 +94,14 @@ github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882b github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/coredns/coredns v1.1.2 h1:bAFHrSsBeTeRG5W3Nf2su3lUGw7Npw2UKeCJm/3A638= github.com/coredns/coredns v1.1.2/go.mod h1:zASH/MVDgR6XZTbxvOnsZfffS+31vg6Ackf/wo1+AM0= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY= github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= @@ -101,6 +118,7 @@ github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661 h1:lrWnAyy/F72 github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/digitalocean/godo v1.1.1/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= github.com/digitalocean/godo v1.10.0 h1:uW1/FcvZE/hoixnJcnlmIUvTVNdZCLjRLzmDtRi1xXY= github.com/digitalocean/godo v1.10.0/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= @@ -140,6 +158,7 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-ini/ini v1.25.4 h1:Mujh4R/dH6YL8bxuISne3xX2+qcQ9p0IxKAP6ExWoUo= github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -170,9 +189,11 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -198,6 +219,13 @@ github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSN github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= @@ -208,6 +236,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI= github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= @@ -216,6 +246,7 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmo github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= @@ -269,8 +300,12 @@ github.com/hashicorp/go-version v0.0.0-20170202080759-03c5bf6be031/go.mod h1:flt github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v0.0.0-20180906183839-65a6292f0157 h1:PJ+K03hio6ADVjEc6lFu5r866o67xEEMQ73CFdI6R2U= github.com/hashicorp/hcl v0.0.0-20180906183839-65a6292f0157/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hil v0.0.0-20160711231837-1e86c6b523c5 h1:uk280DXEbQiCOZgCOI3elFSeNxf8YIZiNsbr2pQLYD0= github.com/hashicorp/hil v0.0.0-20160711231837-1e86c6b523c5/go.mod h1:KHvg/R2/dPtaePb16oW4qIyzkMxXOL38xjRN64adsts= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= @@ -321,6 +356,7 @@ github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -383,14 +419,13 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nacos-group/nacos-sdk-go v0.0.0-20190723125407-0242d42e3dbb h1:lbmvw8r9W55w+aQgWn35W1nuleRIECMoqUrmwAOAvoI= -github.com/nacos-group/nacos-sdk-go v0.0.0-20190723125407-0242d42e3dbb/go.mod h1:CEkSvEpoveoYjA81m4HNeYQ0sge0LFGKSEqO3JKHllo= github.com/nacos-group/nacos-sdk-go v0.3.2 h1:q+ukmIImL6u0zBtbceMZl2frgeAc45QT6cIrTZZz50c= github.com/nacos-group/nacos-sdk-go v0.3.2/go.mod h1:4TdsN7eZnnVCDlOlBa61b0gsRnvNJI74m9+2+OKZkcw= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 h1:BQ1HW7hr4IVovMwWg0E0PYcyW8CzqDcVmaew9cujU4s= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk= github.com/oklog/run v0.0.0-20180308005104-6934b124db28 h1:Hbr3fbVPXea52oPQeP7KLSxP52g6SFaNY1IqAmUyEW0= github.com/oklog/run v0.0.0-20180308005104-6934b124db28/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -415,6 +450,8 @@ github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0Mw github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v0.0.0-20180527043350-9f6ff22cfff8 h1:BR6MM54q4W9pn0SySwg6yctZtBKlTdUq6a+b0kArBnE= github.com/patrickmn/go-cache v0.0.0-20180527043350-9f6ff22cfff8/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -429,29 +466,33 @@ github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 h1:Wdi9nwnhFNAlseAOekn6B5G/+GMtks9UKbvRU/CMM/o= github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03/go.mod h1:gRAiPF5C5Nd0eyyRdqIu9qTiFSoZzpTq727b5B8fkkU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1Tj9sZEYsmarJEQfMVYpd/Vyy/A8dqE= github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= @@ -468,16 +509,25 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykE github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 h1:N8Bg45zpk/UcpNGnfJt2y/3lRWASHNTUET8owPYCgYI= -github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d h1:bVQRCxQvfjNUeRqaY/uT0tFuvuFY0ulgnczuR684Xic= github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d/go.mod h1:Cw4GTlQccdRGSEf6KiMju767x0NEHE0YIVPJSaXjlsw= github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -487,6 +537,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tebeka/strftime v0.1.3 h1:5HQXOqWKYRFfNyBMNVc9z5+QzuBtIXy03psIhtdJYto= github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIUJUJH6XQ= github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9/go.mod h1:RHkNRtSLfOK7qBTHaeSX1D6BNpI3qw7NTxsmNr4RvN8= @@ -502,12 +554,16 @@ github.com/vmware/govmomi v0.18.0 h1:f7QxSmP7meCtoAmiKZogvVbLInT+CZx6Px6K5rYsJZo github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8 h1:k8TV7Gz7cpWpOw/dz71fx8cCZdWoPuckHJ/wkJl+meg= -github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8/go.mod h1:S1cAa98KMFv4Sa8SbJ6ZtvOmf0VlgH0QJ1gXI0lBfBY= +github.com/zouyx/agollo/v3 v3.4.3 h1:1Ni6VbcwMkEyJBNOk8ApRSAuh42YsnYuhW79lNE+TBA= +github.com/zouyx/agollo/v3 v3.4.3/go.mod h1:ag0XmE1r4iAgPd6PUnU9TJ0DMEjM1VKX1HUNqQJ2ywU= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v3.3.13+incompatible h1:jCejD5EMnlGxFvcGRyEV4VGlENZc7oPQX6o0t7n3xbw= go.etcd.io/etcd v3.3.13+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= @@ -519,26 +575,56 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20170807180024-9a379c6b3e95/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -550,33 +636,74 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190508220229-2d0786266e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.0.0-20180829000535-087779f1d2c9 h1:z1TeLUmxf9ws9KLICfmX+KGXTs+rjm+aGWzfsv7MZ9w= google.golang.org/api v0.0.0-20180829000535-087779f1d2c9/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0 h1:Q3Ui3V3/CVinFWFiW39Iw0kMuVrRzYX0wN6OPFp0lTA= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.22.1 h1:/7cs52RnTJmD43s3uxzlq2U7nqVTd/37viQwMrMNlOM= google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= @@ -586,6 +713,7 @@ gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUy gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= @@ -593,6 +721,8 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mgo.v2 v2.0.0-20160818020120-3f83fa500528 h1:/saqWwm73dLmuzbNhe92F0QsZ/KiFND+esHco2v1hiY= gopkg.in/mgo.v2 v2.0.0-20160818020120-3f83fa500528/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/ory-am/dockertest.v3 v3.3.4 h1:oen8RiwxVNxtQ1pRoV4e4jqh6UjNsOuIZ1NXns6jdcw= @@ -606,10 +736,15 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= istio.io/gogo-genproto v0.0.0-20190124151557-6d926a6e6feb/go.mod h1:eIDJ6jNk/IeJz6ODSksHl5Aiczy5JUq6vFhJWI5OtiI= k8s.io/api v0.0.0-20180806132203-61b11ee65332/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= k8s.io/api v0.0.0-20190325185214-7544f9db76f6 h1:9MWtbqhwTyDvF4cS1qAhxDb9Mi8taXiAu+5nEacl7gY= @@ -623,5 +758,6 @@ k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8 k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= From d452e5c4d0207ecc93ac9a4d155d25a8375003f4 Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Wed, 22 Jul 2020 10:39:37 +0800 Subject: [PATCH 031/242] fix review comment --- config_center/apollo/listener.go | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config_center/apollo/listener.go b/config_center/apollo/listener.go index 4d28b9969c..33afbc5d8f 100644 --- a/config_center/apollo/listener.go +++ b/config_center/apollo/listener.go @@ -33,7 +33,7 @@ type apolloListener struct { listeners map[config_center.ConfigurationListener]struct{} } -// newApolloListener ... +// nolint func newApolloListener() *apolloListener { return &apolloListener{ listeners: make(map[config_center.ConfigurationListener]struct{}, 0), @@ -45,7 +45,7 @@ func (a *apolloListener) OnChange(changeEvent *storage.ChangeEvent) { } -// OnNewestChange ... +// OnNewestChange process each listener by all changes func (a *apolloListener) OnNewestChange(changeEvent *storage.FullChangeEvent) { b, err := yaml.Marshal(changeEvent.Changes) if err != nil { diff --git a/go.mod b/go.mod index 283014421c..038d238795 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/prometheus/client_golang v1.1.0 github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b github.com/stretchr/testify v1.5.1 - github.com/zouyx/agollo/v3 v3.4.3 + github.com/zouyx/agollo/v3 v3.4.4 go.etcd.io/bbolt v1.3.3 // indirect go.etcd.io/etcd v3.3.13+incompatible go.uber.org/atomic v1.4.0 diff --git a/go.sum b/go.sum index 1b1494b782..ce9d4e1f34 100644 --- a/go.sum +++ b/go.sum @@ -554,8 +554,8 @@ github.com/vmware/govmomi v0.18.0 h1:f7QxSmP7meCtoAmiKZogvVbLInT+CZx6Px6K5rYsJZo github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/zouyx/agollo/v3 v3.4.3 h1:1Ni6VbcwMkEyJBNOk8ApRSAuh42YsnYuhW79lNE+TBA= -github.com/zouyx/agollo/v3 v3.4.3/go.mod h1:ag0XmE1r4iAgPd6PUnU9TJ0DMEjM1VKX1HUNqQJ2ywU= +github.com/zouyx/agollo/v3 v3.4.4 h1:5G7QNw3fw74Ns8SfnHNhjndV2mlz5Fg8bB7q84ydFYI= +github.com/zouyx/agollo/v3 v3.4.4/go.mod h1:ag0XmE1r4iAgPd6PUnU9TJ0DMEjM1VKX1HUNqQJ2ywU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= From 8dbe4851f300b4429512bac5fd292c055bca6229 Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Wed, 22 Jul 2020 10:59:22 +0800 Subject: [PATCH 032/242] fix review comment --- config_center/apollo/impl_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config_center/apollo/impl_test.go b/config_center/apollo/impl_test.go index a95524b41b..eec02b5211 100644 --- a/config_center/apollo/impl_test.go +++ b/config_center/apollo/impl_test.go @@ -217,7 +217,7 @@ func TestAddListener(t *testing.T) { }` apollo.AddListener(mockNamespace, listener) listener.wg.Wait() - assert.Equal(t, "registries.hangzhouzk.username", listener.event) + assert.Equal(t, "mockDubbog.properties", listener.event) assert.Greater(t, listener.count, 0) deleteMockJson(t) } From ea57c44e7dca4d4da2f29f25d46826a057980463 Mon Sep 17 00:00:00 2001 From: "xg.gao" Date: Thu, 23 Jul 2020 12:07:26 +0800 Subject: [PATCH 033/242] code format --- remoting/etcdv3/client.go | 128 ++++++++++---------------------------- 1 file changed, 32 insertions(+), 96 deletions(-) diff --git a/remoting/etcdv3/client.go b/remoting/etcdv3/client.go index 7632a7cd04..a2a5764538 100644 --- a/remoting/etcdv3/client.go +++ b/remoting/etcdv3/client.go @@ -35,7 +35,7 @@ import ( ) const ( - // ConnDelay connection dalay + // ConnDelay connection delay ConnDelay = 3 // MaxFailTimes max failure times MaxFailTimes = 15 @@ -93,7 +93,6 @@ func WithHeartbeat(heartbeat int) Option { // ValidateClient validates client and sets options func ValidateClient(container clientFacade, opts ...Option) error { - options := &Options{ heartbeat: 1, // default heartbeat } @@ -118,7 +117,6 @@ func ValidateClient(container clientFacade, opts ...Option) error { // Client lose connection with etcd server if container.Client().rawClient == nil { - newClient, err := NewClient(options.name, options.endpoints, options.timeout, options.heartbeat) if err != nil { logger.Warnf("new etcd client (name{%s}, etcd addresses{%v}, timeout{%d}) = error{%v}", @@ -136,7 +134,6 @@ func NewServiceDiscoveryClient(opts ...Option) *Client { options := &Options{ heartbeat: 1, // default heartbeat } - for _, opt := range opts { opt(options) } @@ -171,7 +168,6 @@ type Client struct { // nolint func NewClient(name string, endpoints []string, timeout time.Duration, heartbeat int) (*Client, error) { - ctx, cancel := context.WithCancel(context.Background()) rawClient, err := clientv3.New(clientv3.Config{ Context: ctx, @@ -184,7 +180,6 @@ func NewClient(name string, endpoints []string, timeout time.Duration, heartbeat } c := &Client{ - name: name, timeout: timeout, endpoints: endpoints, @@ -205,7 +200,6 @@ func NewClient(name string, endpoints []string, timeout time.Duration, heartbeat // NOTICE: need to get the lock before calling this method func (c *Client) clean() { - // close raw client c.rawClient.Close() @@ -217,7 +211,6 @@ func (c *Client) clean() { } func (c *Client) stop() bool { - select { case <-c.exit: return true @@ -229,7 +222,6 @@ func (c *Client) stop() bool { // nolint func (c *Client) Close() { - if c == nil { return } @@ -241,15 +233,14 @@ func (c *Client) Close() { c.Wait.Wait() c.lock.Lock() + defer c.lock.Unlock() if c.rawClient != nil { c.clean() } - c.lock.Unlock() logger.Warnf("etcd client{name:%s, endpoints:%s} exit now.", c.name, c.endpoints) } func (c *Client) maintenanceStatus() error { - s, err := concurrency.NewSession(c.rawClient, concurrency.WithTTL(c.heartbeat)) if err != nil { return perrors.WithMessage(err, "new session with server") @@ -262,7 +253,6 @@ func (c *Client) maintenanceStatus() error { } func (c *Client) maintenanceStatusLoop(s *concurrency.Session) { - defer func() { c.Wait.Done() logger.Infof("etcd client {endpoints:%v, name:%s} maintenance goroutine game over.", c.endpoints, c.name) @@ -288,7 +278,6 @@ func (c *Client) maintenanceStatusLoop(s *concurrency.Session) { // if k not exist will put k/v in etcd, otherwise return nil func (c *Client) put(k string, v string, opts ...clientv3.OpOption) error { - c.lock.RLock() defer c.lock.RUnlock() @@ -300,17 +289,12 @@ func (c *Client) put(k string, v string, opts ...clientv3.OpOption) error { If(clientv3.Compare(clientv3.Version(k), "<", 1)). Then(clientv3.OpPut(k, v, opts...)). Commit() - if err != nil { - return err - - } - return nil + return err } // if k not exist will put k/v in etcd // if k is already exist in etcd, replace it func (c *Client) update(k string, v string, opts ...clientv3.OpOption) error { - c.lock.RLock() defer c.lock.RUnlock() @@ -322,15 +306,10 @@ func (c *Client) update(k string, v string, opts ...clientv3.OpOption) error { If(clientv3.Compare(clientv3.Version(k), "!=", -1)). Then(clientv3.OpPut(k, v, opts...)). Commit() - if err != nil { - return err - - } - return nil + return err } func (c *Client) delete(k string) error { - c.lock.RLock() defer c.lock.RUnlock() @@ -339,15 +318,10 @@ func (c *Client) delete(k string) error { } _, err := c.rawClient.Delete(c.ctx, k) - if err != nil { - return err - - } - return nil + return err } func (c *Client) get(k string) (string, error) { - c.lock.RLock() defer c.lock.RUnlock() @@ -369,7 +343,6 @@ func (c *Client) get(k string) (string, error) { // nolint func (c *Client) CleanKV() error { - c.lock.RLock() defer c.lock.RUnlock() @@ -378,15 +351,10 @@ func (c *Client) CleanKV() error { } _, err := c.rawClient.Delete(c.ctx, "", clientv3.WithPrefix()) - if err != nil { - return err - } - - return nil + return err } func (c *Client) getChildren(k string) ([]string, []string, error) { - c.lock.RLock() defer c.lock.RUnlock() @@ -403,21 +371,16 @@ func (c *Client) getChildren(k string) ([]string, []string, error) { return nil, nil, ErrKVPairNotFound } - var ( - kList []string - vList []string - ) - + kList := make([]string, 0, len(resp.Kvs)) + vList := make([]string, 0, len(resp.Kvs)) for _, kv := range resp.Kvs { kList = append(kList, string(kv.Key)) vList = append(vList, string(kv.Value)) } - return kList, vList, nil } func (c *Client) watchWithPrefix(prefix string) (clientv3.WatchChan, error) { - c.lock.RLock() defer c.lock.RUnlock() @@ -429,7 +392,6 @@ func (c *Client) watchWithPrefix(prefix string) (clientv3.WatchChan, error) { } func (c *Client) watch(k string) (clientv3.WatchChan, error) { - c.lock.RLock() defer c.lock.RUnlock() @@ -441,7 +403,6 @@ func (c *Client) watch(k string) (clientv3.WatchChan, error) { } func (c *Client) keepAliveKV(k string, v string) error { - c.lock.RLock() defer c.lock.RUnlock() @@ -457,14 +418,16 @@ func (c *Client) keepAliveKV(k string, v string) error { keepAlive, err := c.rawClient.KeepAlive(c.ctx, lease.ID) if err != nil || keepAlive == nil { c.rawClient.Revoke(c.ctx, lease.ID) - return perrors.WithMessage(err, "keep alive lease") + if err != nil { + return perrors.WithMessage(err, "keep alive lease") + } else { + return perrors.New("keep alive lease") + } } _, err = c.rawClient.Put(c.ctx, k, v, clientv3.WithLease(lease.ID)) - if err != nil { - return perrors.WithMessage(err, "put k/v with lease") - } - return nil + err = perrors.WithMessage(err, "put k/v with lease") + return err } // nolint @@ -481,92 +444,65 @@ func (c *Client) Valid() bool { } c.lock.RLock() + defer c.lock.RUnlock() if c.rawClient == nil { - c.lock.RUnlock() return false } - c.lock.RUnlock() return true } // nolint func (c *Client) Create(k string, v string) error { - err := c.put(k, v) - if err != nil { - return perrors.WithMessagef(err, "put k/v (key: %s value %s)", k, v) - } - return nil + err = perrors.WithMessagef(err, "put k/v (key: %s value %s)", k, v) + return err } // Update key value ... func (c *Client) Update(k, v string) error { err := c.update(k, v) - if err != nil { - return perrors.WithMessagef(err, "Update k/v (key: %s value %s)", k, v) - } - return nil + err = perrors.WithMessagef(err, "Update k/v (key: %s value %s)", k, v) + return err } // nolint func (c *Client) Delete(k string) error { - err := c.delete(k) - if err != nil { - return perrors.WithMessagef(err, "delete k/v (key %s)", k) - } - - return nil + err = perrors.WithMessagef(err, "delete k/v (key %s)", k) + return err } // RegisterTemp registers a temporary node func (c *Client) RegisterTemp(k, v string) error { - err := c.keepAliveKV(k, v) - if err != nil { - return perrors.WithMessagef(err, "keepalive kv (key %s)", k) - } - - return nil + err = perrors.WithMessagef(err, "keepalive kv (key %s)", k) + return err } // GetChildrenKVList gets children kv list by @k func (c *Client) GetChildrenKVList(k string) ([]string, []string, error) { - kList, vList, err := c.getChildren(k) - if err != nil { - return nil, nil, perrors.WithMessagef(err, "get key children (key %s)", k) - } - return kList, vList, nil + err = perrors.WithMessagef(err, "get key children (key %s)", k) + return kList, vList, err } // Get gets value by @k func (c *Client) Get(k string) (string, error) { - v, err := c.get(k) - if err != nil { - return "", perrors.WithMessagef(err, "get key value (key %s)", k) - } - - return v, nil + err = perrors.WithMessagef(err, "get key value (key %s)", k) + return v, err } // Watch watches on spec key func (c *Client) Watch(k string) (clientv3.WatchChan, error) { - wc, err := c.watch(k) - if err != nil { - return nil, perrors.WithMessagef(err, "watch prefix (key %s)", k) - } - return wc, nil + err = perrors.WithMessagef(err, "watch prefix (key %s)", k) + return wc, err } // WatchWithPrefix watches on spec prefix func (c *Client) WatchWithPrefix(prefix string) (clientv3.WatchChan, error) { - wc, err := c.watchWithPrefix(prefix) - if err != nil { - return nil, perrors.WithMessagef(err, "watch prefix (key %s)", prefix) - } - return wc, nil + err = perrors.WithMessagef(err, "watch prefix (key %s)", prefix) + return wc, err } From 1a16b04ddab27aa69b9c496e0c7d2b8400723727 Mon Sep 17 00:00:00 2001 From: "xg.gao" Date: Thu, 23 Jul 2020 12:11:34 +0800 Subject: [PATCH 034/242] code_format --- registry/consul/listener.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/consul/listener.go b/registry/consul/listener.go index cf3888dd16..b159834503 100644 --- a/registry/consul/listener.go +++ b/registry/consul/listener.go @@ -197,7 +197,7 @@ func (l *consulListener) Next() (*registry.ServiceEvent, error) { } } -// Close closes this listener +// Close the listener. func (l *consulListener) Close() { close(l.done) l.plan.Stop() From f85a42a6a1fac5db409d5c05c22bdac5092ecfdb Mon Sep 17 00:00:00 2001 From: "xg.gao" Date: Sat, 25 Jul 2020 11:40:01 +0800 Subject: [PATCH 035/242] go mod tidy --- go.mod | 2 -- go.sum | 26 +++++--------------------- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/go.mod b/go.mod index e82a04b279..a16b906225 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,7 @@ module github.com/apache/dubbo-go require ( github.com/Workiva/go-datastructures v1.0.50 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 - github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5 // indirect github.com/apache/dubbo-go-hessian2 v1.6.1 - github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect github.com/coreos/bbolt v1.3.3 // indirect github.com/coreos/etcd v3.3.13+incompatible github.com/coreos/go-semver v0.3.0 // indirect diff --git a/go.sum b/go.sum index 8c8c4ef132..0fe2dfa050 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,7 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX github.com/Azure/go-autorest v10.7.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v10.15.3+incompatible h1:nhKI/bvazIs3C3TFGoSqKY6hZ8f5od5mb5/UcS6HVIY= github.com/Azure/go-autorest v10.15.3+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DataDog/datadog-go v2.2.0+incompatible h1:V5BKkxACZLjzHjSgBbr2gvLA2Ae49yhc6CSY7MLy5k4= github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= @@ -35,16 +36,10 @@ github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vaj github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190802083043-4cd0c391755e/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ= github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA= github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA= github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk= github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk= -github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= -github.com/apache/dubbo-go-hessian2 v1.5.0 h1:fzulDG5G7nX0ccgKdiN9XipJ7tZ4WXKgmk4stdlDS6s= -github.com/apache/dubbo-go-hessian2 v1.5.0/go.mod h1:VwEnsOMidkM1usya2uPfGpSLO9XUF//WQcWn3y+jFz8= -github.com/apache/dubbo-go-hessian2 v1.6.1-0.20200623062814-707fde850279 h1:1g3IJdaUjXWs++NA9Ail8+r6WgrkfhjS6hD/YXvRzjk= -github.com/apache/dubbo-go-hessian2 v1.6.1-0.20200623062814-707fde850279/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= github.com/apache/dubbo-go-hessian2 v1.6.1 h1:mFKeCZzaCkk4mMOyP+LQ85GHbRyqKT7858KS21JQYA4= github.com/apache/dubbo-go-hessian2 v1.6.1/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= @@ -58,7 +53,6 @@ github.com/asaskevich/govalidator v0.0.0-20180319081651-7d2e70ef918f h1:/8NcnxL6 github.com/asaskevich/govalidator v0.0.0-20180319081651-7d2e70ef918f/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.15.24 h1:xLAdTA/ore6xdPAljzZRed7IGqQgC+nY+ERS5vaj4Ro= github.com/aws/aws-sdk-go v1.15.24/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= -github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -113,13 +107,10 @@ github.com/docker/go-connections v0.3.0 h1:3lOnM9cSzgGwx8VfK/NGOW5fLQ0GjIlCkaktF github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dubbogo/getty v1.3.5 h1:xJxdDj9jm7wlrRSsVZSk2TDNxJbbac5GpxV0QpjO+Tw= -github.com/dubbogo/getty v1.3.5/go.mod h1:T55vN8Q6tZjf2AQZiGmkujneD3LfqYbv2b3QjacwYOY= github.com/dubbogo/getty v1.3.7 h1:xlkYD2/AH34iGteuLMsGjLl2PwBVrbIhHjf3tlUsv1M= github.com/dubbogo/getty v1.3.7/go.mod h1:XWO4+wAaMqgnBN9Ykv2YxxOAkGxymg6LGO9RK+EiCDY= github.com/dubbogo/go-zookeeper v1.0.1 h1:irLzvOsDOTNsN8Sv9tvYYxVu6DCQfLtziZQtUHmZgz8= github.com/dubbogo/go-zookeeper v1.0.1/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c= -github.com/dubbogo/gost v1.5.1/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= github.com/dubbogo/gost v1.9.0 h1:UT+dWwvLyJiDotxJERO75jB3Yxgsdy10KztR5ycxRAk= github.com/dubbogo/gost v1.9.0/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74 h1:2MIhn2R6oXQbgW5yHfS+d6YqyMfXiu2L55rFZC4UD/M= @@ -217,8 +208,6 @@ github.com/gophercloud/gophercloud v0.0.0-20180828235145-f29afc2cceca/go.mod h1: github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI= @@ -402,8 +391,6 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nacos-group/nacos-sdk-go v0.3.2 h1:q+ukmIImL6u0zBtbceMZl2frgeAc45QT6cIrTZZz50c= -github.com/nacos-group/nacos-sdk-go v0.3.2/go.mod h1:4TdsN7eZnnVCDlOlBa61b0gsRnvNJI74m9+2+OKZkcw= github.com/nacos-group/nacos-sdk-go v0.3.3-0.20200617023039-50c7537d6a5f h1:gid5/0AkHvINWK69Fgbidb3BVIXqlf1YEm7wO0NVPsw= github.com/nacos-group/nacos-sdk-go v0.3.3-0.20200617023039-50c7537d6a5f/go.mod h1:fti1GlX/EB6RDKvzK/P7Vuibqj0JMPJHQwrcTU1tLXk= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 h1:BQ1HW7hr4IVovMwWg0E0PYcyW8CzqDcVmaew9cujU4s= @@ -474,7 +461,6 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1Tj9sZEYsmarJEQfMVYpd/Vyy/A8dqE= github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= @@ -531,17 +517,12 @@ github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8 h1:k8TV7Gz7cpWpOw/dz7 github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8/go.mod h1:S1cAa98KMFv4Sa8SbJ6ZtvOmf0VlgH0QJ1gXI0lBfBY= go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg= go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -553,6 +534,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -614,6 +596,7 @@ golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.0.0-20180829000535-087779f1d2c9 h1:z1TeLUmxf9ws9KLICfmX+KGXTs+rjm+aGWzfsv7MZ9w= @@ -658,6 +641,7 @@ gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= istio.io/gogo-genproto v0.0.0-20190124151557-6d926a6e6feb/go.mod h1:eIDJ6jNk/IeJz6ODSksHl5Aiczy5JUq6vFhJWI5OtiI= k8s.io/api v0.0.0-20180806132203-61b11ee65332/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= From b587f644f7b3052d0ba1160188956e1111974a02 Mon Sep 17 00:00:00 2001 From: "xg.gao" Date: Sat, 25 Jul 2020 17:11:11 +0800 Subject: [PATCH 036/242] update consul version --- go.mod | 46 ++- go.sum | 523 ++++++++++++++++++++------- registry/etcdv3/service_discovery.go | 2 +- 3 files changed, 415 insertions(+), 156 deletions(-) diff --git a/go.mod b/go.mod index a16b906225..052aa15bbb 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,9 @@ module github.com/apache/dubbo-go require ( + cloud.google.com/go v0.39.0 // indirect + github.com/Microsoft/go-winio v0.4.13 // indirect + github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/Workiva/go-datastructures v1.0.50 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 github.com/apache/dubbo-go-hessian2 v1.6.1 @@ -8,52 +11,61 @@ require ( github.com/coreos/etcd v3.3.13+incompatible github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect - github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect github.com/creasty/defaults v1.3.0 + github.com/docker/go-connections v0.4.0 // indirect github.com/dubbogo/getty v1.3.7 github.com/dubbogo/go-zookeeper v1.0.1 github.com/dubbogo/gost v1.9.0 + github.com/elazarl/go-bindata-assetfs v1.0.0 // indirect github.com/emicklei/go-restful/v3 v3.0.0 + github.com/frankban/quicktest v1.4.1 // indirect + github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect github.com/go-co-op/gocron v0.1.1 github.com/go-resty/resty/v2 v2.1.0 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect github.com/golang/mock v1.3.1 github.com/golang/protobuf v1.3.2 - github.com/google/btree v1.0.0 // indirect - github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 // indirect - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect + github.com/google/go-cmp v0.3.1 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 // indirect github.com/grpc-ecosystem/grpc-gateway v1.9.5 // indirect github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 - github.com/hashicorp/consul v1.5.3 - github.com/hashicorp/consul/api v1.1.0 - github.com/hashicorp/vault v0.10.3 + github.com/hashicorp/consul v1.8.0 + github.com/hashicorp/consul/api v1.5.0 + github.com/hashicorp/go-raftchunking v0.6.3-0.20191002164813-7e9e8525653a // indirect + github.com/hashicorp/golang-lru v0.5.3 // indirect + github.com/hashicorp/vault/api v1.0.5-0.20191108163347-bdd38fca2cff // indirect + github.com/hashicorp/vault/sdk v0.1.14-0.20191112033314-390e96e22eb2 github.com/jinzhu/copier v0.0.0-20190625015134-976e0346caa8 github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b // indirect github.com/magiconair/properties v1.8.1 - github.com/mitchellh/mapstructure v1.1.2 + github.com/mitchellh/hashstructure v1.0.0 // indirect + github.com/mitchellh/mapstructure v1.2.3 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd github.com/nacos-group/nacos-sdk-go v0.3.3-0.20200617023039-50c7537d6a5f github.com/opentracing/opentracing-go v1.1.0 + github.com/pierrec/lz4 v2.2.6+incompatible // indirect github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.1.0 github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b + github.com/shirou/gopsutil v2.19.9+incompatible // indirect github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 // indirect - github.com/soheilhy/cmux v0.1.4 // indirect + github.com/stretchr/objx v0.2.0 // indirect github.com/stretchr/testify v1.5.1 - github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect - github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8 go.etcd.io/bbolt v1.3.4 // indirect go.uber.org/atomic v1.6.0 go.uber.org/zap v1.15.0 - golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect - google.golang.org/grpc v1.22.1 - gopkg.in/yaml.v2 v2.2.2 - k8s.io/api v0.0.0-20190325185214-7544f9db76f6 - k8s.io/apimachinery v0.0.0-20190223001710-c182ff3b9841 - k8s.io/client-go v8.0.0+incompatible + google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64 // indirect + google.golang.org/grpc v1.23.0 + gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce // indirect + gopkg.in/yaml.v2 v2.2.8 + k8s.io/api v0.16.9 + k8s.io/apimachinery v0.16.9 + k8s.io/client-go v0.16.9 k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a // indirect ) go 1.13 + +replace github.com/ugorji/go v1.1.4 => github.com/ugorji/go v1.1.7 diff --git a/go.sum b/go.sum index 0fe2dfa050..ee42317071 100644 --- a/go.sum +++ b/go.sum @@ -1,31 +1,58 @@ cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/Azure/azure-sdk-for-go v16.0.0+incompatible h1:gr1qKY/Ll72VjFTZmaBwRK1yQHAxCnV25ekOKroc9ws= -github.com/Azure/azure-sdk-for-go v16.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-autorest v10.7.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest v10.15.3+incompatible h1:nhKI/bvazIs3C3TFGoSqKY6hZ8f5od5mb5/UcS6HVIY= -github.com/Azure/go-autorest v10.15.3+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.39.0 h1:UgQP9na6OTfp4dsAiz/eFpFA1C6tPdH5wiRdi19tuMw= +cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts= +github.com/Azure/azure-sdk-for-go v40.3.0+incompatible h1:NthZg3psrLxvQLN6rVm07pZ9mv2wvGNaBNGQ3fnPvLE= +github.com/Azure/azure-sdk-for-go v40.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0= +github.com/Azure/go-autorest/autorest v0.10.0 h1:mvdtztBqcL8se7MdrUweNieTNi4kfNG6GOJuurQJpuY= +github.com/Azure/go-autorest/autorest v0.10.0/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= +github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.8.2 h1:O1X4oexUxnZCaEUGsvMnr8ZGj8HI37tNezwY4npRqA0= +github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/azure/auth v0.4.2 h1:iM6UAvjR97ZIeR93qTcwpKNMpV+/FTWjwEbuPD495Tk= +github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM= +github.com/Azure/go-autorest/autorest/azure/cli v0.3.1 h1:LXl088ZQlP0SBppGFsRZonW6hSvwgL5gRByMbvUbx8U= +github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0 h1:yW+Zlqf26583pE43KhfnhFcdmSWlm5Ew6bxipnr/tbM= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0 h1:qJumjCaCudz+OcqE9/XtEPfvtOjOmKaui4EOpFI6zZc= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/autorest/to v0.3.0 h1:zebkZaadz7+wIQYgC7GXaz3Wb28yKYfVkkBKwc38VF8= +github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= +github.com/Azure/go-autorest/autorest/validation v0.2.0 h1:15vMO4y76dehZSq7pAaOLQxC6dZYsSrj2GQpflyM/L4= +github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= +github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DataDog/datadog-go v2.2.0+incompatible h1:V5BKkxACZLjzHjSgBbr2gvLA2Ae49yhc6CSY7MLy5k4= github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/Jeffail/gabs v1.1.0 h1:kw5zCcl9tlJNHTDme7qbi21fDHZmXrnjMoXos3Jw/NI= -github.com/Jeffail/gabs v1.1.0/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc= +github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/Microsoft/go-winio v0.4.3 h1:M3NHMuPgMSUPdE5epwNUHlRPSVzHs8HpRTrVXhR0myo= github.com/Microsoft/go-winio v0.4.3/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/go-winio v0.4.13 h1:Hmi80lzZuI/CaYmlJp/b+FjZdRZhKu9c2mDVqKlLWVs= +github.com/Microsoft/go-winio v0.4.13/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.0.1 h1:iLrQrdwjDd52kHDA5op2UBJFjmOb9g+7scBan4RN8F0= github.com/NYTimes/gziphandler v1.0.1/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/SAP/go-hdb v0.12.0 h1:5hBQZ2jjyZ268qjDmoDZJuCyLzR6oRLI60eYzmTW9m4= -github.com/SAP/go-hdb v0.12.0/go.mod h1:etBT+FAi1t5k3K3tf5vQTnosgYmhDkRi8jEnQqCnxF0= -github.com/SermoDigital/jose v0.0.0-20180104203859-803625baeddc h1:LkkwnbY+S8WmwkWq1SVyRWMH9nYWO1P5XN3OD1tts/w= -github.com/SermoDigital/jose v0.0.0-20180104203859-803625baeddc/go.mod h1:ARgCUhI1MHQH+ONky/PAtmVHQrP5JlGY0F3poXOp/fA= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/Workiva/go-datastructures v1.0.50 h1:slDmfW6KCHcC7U+LP3DDBbm4fqTwZGn1beOFPfGaLvo= @@ -44,79 +71,90 @@ github.com/apache/dubbo-go-hessian2 v1.6.1 h1:mFKeCZzaCkk4mMOyP+LQ85GHbRyqKT7858 github.com/apache/dubbo-go-hessian2 v1.6.1/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM= github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= +github.com/armon/go-metrics v0.3.0 h1:B7AQgHi8QSEi4uHu7Sbsga+IJDU+CENgjxoo81vDUqU= +github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26FmByPp2eC2qla6E1Tw+scpcg/to= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/asaskevich/govalidator v0.0.0-20180319081651-7d2e70ef918f h1:/8NcnxL60YFll4ehCwibKotx0BR9v2ND40fomga8qDs= -github.com/asaskevich/govalidator v0.0.0-20180319081651-7d2e70ef918f/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/aws/aws-sdk-go v1.15.24 h1:xLAdTA/ore6xdPAljzZRed7IGqQgC+nY+ERS5vaj4Ro= -github.com/aws/aws-sdk-go v1.15.24/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aws/aws-sdk-go v1.25.41 h1:/hj7nZ0586wFqpwjNpzWiUTwtaMgxAZNZKHay80MdXw= +github.com/aws/aws-sdk-go v1.25.41/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= -github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 h1:D21IyuvjDCshj1/qq+pCNd3VZOAEI9jy6Bi131YlXgI= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= -github.com/cenkalti/backoff v2.1.1+incompatible h1:tKJnvO2kl0zmb/jA5UKAt4VoEVw1qxKWjE/Bpp46npY= -github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible h1:C29Ae4G5GtYyYMm1aztcyj/J5ckgJm2zwdDajFbx1NY= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3 h1:TJH+oke8D16535+jHExHj4nQvzlZrj7ug5D7I/orNUA= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882bXEDKfWIf0wa8HRvpnBoPszJJXL+TVbBw4M= -github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/coredns/coredns v1.1.2 h1:bAFHrSsBeTeRG5W3Nf2su3lUGw7Npw2UKeCJm/3A638= github.com/coredns/coredns v1.1.2/go.mod h1:zASH/MVDgR6XZTbxvOnsZfffS+31vg6Ackf/wo1+AM0= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY= github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-oidc v2.1.0+incompatible h1:sdJrfw8akMnCuUlaZU3tE/uYXFgfqom8DBE9so9EBsM= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/creasty/defaults v1.3.0 h1:uG+RAxYbJgOPCOdKEcec9ZJXeva7Y6mj/8egdzwmLtw= github.com/creasty/defaults v1.3.0/go.mod h1:CIEEvs7oIVZm30R8VxtFJs+4k201gReYyuYHJxZc68I= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denisenkom/go-mssqldb v0.0.0-20180620032804-94c9c97e8c9f h1:JtRnQbMXb3TcSIm1j452zI45lPMiAQ0puF8iK5EnY9M= -github.com/denisenkom/go-mssqldb v0.0.0-20180620032804-94c9c97e8c9f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661 h1:lrWnAyy/F72MbxIxFUzKmcMCdt9Oi8RzpAxzTNQHD7o= github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/digitalocean/godo v1.1.1/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= github.com/digitalocean/godo v1.10.0 h1:uW1/FcvZE/hoixnJcnlmIUvTVNdZCLjRLzmDtRi1xXY= github.com/digitalocean/godo v1.10.0/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= +github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= +github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/go-connections v0.3.0 h1:3lOnM9cSzgGwx8VfK/NGOW5fLQ0GjIlCkaktF+n1M6o= github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk= -github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/dubbogo/getty v1.3.7 h1:xlkYD2/AH34iGteuLMsGjLl2PwBVrbIhHjf3tlUsv1M= github.com/dubbogo/getty v1.3.7/go.mod h1:XWO4+wAaMqgnBN9Ykv2YxxOAkGxymg6LGO9RK+EiCDY= github.com/dubbogo/go-zookeeper v1.0.1 h1:irLzvOsDOTNsN8Sv9tvYYxVu6DCQfLtziZQtUHmZgz8= github.com/dubbogo/go-zookeeper v1.0.1/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c= github.com/dubbogo/gost v1.9.0 h1:UT+dWwvLyJiDotxJERO75jB3Yxgsdy10KztR5ycxRAk= github.com/dubbogo/gost v1.9.0/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= -github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74 h1:2MIhn2R6oXQbgW5yHfS+d6YqyMfXiu2L55rFZC4UD/M= -github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74/go.mod h1:UqXY1lYT/ERa4OEAywUqdok1T4RCRdArkhic1Opuavo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0 h1:ZoRgc53qJCfSLimXqJDrmBhnt5GChDsExMCK7t48o0Y= github.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= +github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk= +github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 h1:H2pdYOb3KQ1/YsqVWoWNLQO+fusocsw354rqGTZtAgw= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful/v3 v3.0.0 h1:Duxxa4x0WIHW3bYEDmoAPNjmy8Rbqn+utcF74dlF/G8= @@ -125,28 +163,39 @@ github.com/envoyproxy/go-control-plane v0.8.0 h1:uE6Fp4fOcAJdc1wTQXLJ+SYistkbG1d github.com/envoyproxy/go-control-plane v0.8.0/go.mod h1:GSSbY9P1neVhdY7G4wu+IK1rk/dqhiCC/4ExuWJZVuk= github.com/envoyproxy/protoc-gen-validate v0.0.14 h1:YBW6/cKy9prEGRYLnaGa4IDhzxZhRCtKsax8srGKDnM= github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 h1:Ghm4eQYC0nEPnSJdVkTrXpu9KtoVCSo1hg7mtI7G9KU= github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/structs v0.0.0-20180123065059-ebf56d35bba7 h1:bGT+Ub6bpzHl7AAYQhBrZ5nYTAH2SF/848WducU0Ao4= -github.com/fatih/structs v0.0.0-20180123065059-ebf56d35bba7/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/frankban/quicktest v1.4.1 h1:Wv2VwvNn73pAdFIVUQRXYDFp31lXKbqblIXo/Q5GPSg= +github.com/frankban/quicktest v1.4.1/go.mod h1:36zfPVQyHxymz4cH7wlDmVwDrJuljRB60qkgn7rorfQ= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew= +github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= +github.com/go-asn1-ber/asn1-ber v1.3.1 h1:gvPdv/Hr++TRFCl0UbPFHC54P9N9jgsRPnmnr419Uck= +github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-co-op/gocron v0.1.1 h1:OfDmkqkCguFtFMsm6Eaayci3DADLa8pXvdmOlPU/JcU= github.com/go-co-op/gocron v0.1.1/go.mod h1:Y9PWlYqDChf2Nbgg7kfS+ZsXHDTZbMZYPEQ0MILqH+M= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-ini/ini v1.25.4 h1:Mujh4R/dH6YL8bxuISne3xX2+qcQ9p0IxKAP6ExWoUo= -github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-ldap/ldap v3.0.2+incompatible h1:kD5HQcAzlQ7yrhfn+h+MSABeAy/jAJhvIJ/QDllP44g= github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= +github.com/go-ldap/ldap/v3 v3.1.3 h1:RIgdpHXJpsUqUK5WXwKyVsESrGFqo5BRWPk3RR4/ogQ= +github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= @@ -156,24 +205,26 @@ github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dp github.com/go-redis/redis v6.15.5+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-resty/resty/v2 v2.1.0 h1:Z6IefCpUMfnvItVJaJXWv/pMiiD11So35QgwEELsldE= github.com/go-resty/resty/v2 v2.1.0/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8= -github.com/go-sql-driver/mysql v0.0.0-20180618115901-749ddf1598b4 h1:1LlmVz15APoKz9dnm5j2ePptburJlwEH+/v/pUuoxck= -github.com/go-sql-driver/mysql v0.0.0-20180618115901-749ddf1598b4/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/gocql/gocql v0.0.0-20180617115710-e06f8c1bcd78 h1:G7iRamCffNivybfZvsJjtk3k2qHa73xW+OysVkukcGk= -github.com/gocql/gocql v0.0.0-20180617115710-e06f8c1bcd78/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= github.com/gogo/googleapis v1.1.0 h1:kFkMAZBNAn4j7K0GiZr8cRYzejq68VbheufiV3YuyFI= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -181,8 +232,6 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -191,139 +240,187 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= -github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0= github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= -github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2 h1:AtvtonGEH/fZK0XPNNBdB6swgy7Iudfx88wzyIpwqJ8= +github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2/go.mod h1:DavVbd41y+b7ukKDmlnPR4nGYmkWXR6vHUkjQNiHPBs= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4 h1:hU4mGcQI4DaAYW+IbTun+2qEZVFxK0ySjQLTbS0VQKc= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/gophercloud/gophercloud v0.0.0-20180828235145-f29afc2cceca h1:wobTb8SE189AuxzEKClyYxiI4nUGWlpVtl13eLiFlOE= -github.com/gophercloud/gophercloud v0.0.0-20180828235145-f29afc2cceca/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= -github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI= -github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 h1:z53tR0945TRRQO/fLEVPI6SMv7ZflF0TEaTAoU7tOzg= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= -github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= -github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= -github.com/hashicorp/consul v1.5.3 h1:EmTWRf/cuqZk6Ug9tgFUVE9xNgJPpmBvJwJMvm+agSk= -github.com/hashicorp/consul v1.5.3/go.mod h1:61E2GJCPEP3oq8La7sfDdWGQ66+Zbxzw5ecOdFD7xIE= -github.com/hashicorp/consul/api v1.1.0 h1:BNQPM9ytxj6jbjjdRPioQ94T6YXriSopn0i8COv6SRA= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1 h1:LnuDWGNsoajlhGyHJvuWW6FVqRl8JOTPqS6CPTsYjhY= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul v1.8.0 h1:yRKMKZyPLqUxl37t4nFt5OuGmTXoFhTJrakhfnYKCYA= +github.com/hashicorp/consul v1.8.0/go.mod h1:Gg9/UgAQ9rdY3CTvzQZ6g2jcIb7NlIfjI+0pvLk5D1A= +github.com/hashicorp/consul/api v1.5.0 h1:Yo2bneoGy68A7aNwmuETFnPhjyBEm7n3vzRacEVMjvI= +github.com/hashicorp/consul/api v1.5.0/go.mod h1:LqwrLNW876eYSuUOo4ZLHBcdKc038txr/IMfbLPATa4= +github.com/hashicorp/consul/sdk v0.5.0 h1:WC4594Wp/LkEeML/OdQKEC1yqBmEYkRp6i7X5u0zDAs= +github.com/hashicorp/consul/sdk v0.5.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-bexpr v0.1.0 h1:hA/9CWGPsQ6YZXvPvizD+VEEjBG4V6Un0Qcyav5ghK4= -github.com/hashicorp/go-bexpr v0.1.0/go.mod h1:ANbpTX1oAql27TZkKVeW8p1w8NTdnyzPe/0qqPCKohU= +github.com/hashicorp/go-bexpr v0.1.2 h1:ijMXI4qERbzxbCnkxmfUtwMyjrrk3y+Vt0MxojNCbBs= +github.com/hashicorp/go-bexpr v0.1.2/go.mod h1:ANbpTX1oAql27TZkKVeW8p1w8NTdnyzPe/0qqPCKohU= +github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de h1:XDCSythtg8aWSRSO29uwhgh7b127fWr+m5SemqjSUL8= github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de/go.mod h1:xIwEieBHERyEvaeKF/TcHh1Hu+lxPM+n2vT1+g9I4m4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-discover v0.0.0-20190403160810-22221edb15cd h1:SynRxs8h2h7lLSA5py5a3WWkYpImhREtju0CuRd97wc= -github.com/hashicorp/go-discover v0.0.0-20190403160810-22221edb15cd/go.mod h1:ueUgD9BeIocT7QNuvxSyJyPAM9dfifBcaWmeybb67OY= +github.com/hashicorp/go-connlimit v0.2.0 h1:OZjcfNxH/hPh/bT2Iw5yOJcLzz+zuIWpsp3I1S4Pjw4= +github.com/hashicorp/go-connlimit v0.2.0/go.mod h1:OUj9FGL1tPIhl/2RCfzYHrIiWj+VVPGNyVPnUX8AqS0= +github.com/hashicorp/go-discover v0.0.0-20200501174627-ad1e96bde088 h1:jBvElOilnIl6mm8S6gva/dfeTJCcMs9TGO6/2C6k52E= +github.com/hashicorp/go-discover v0.0.0-20200501174627-ad1e96bde088/go.mod h1:vZu6Opqf49xX5lsFAu7iFNewkcVF1sn/wyapZh5ytlg= +github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= +github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.9.1 h1:9PZfAcVEvez4yhLH2TBU64/h/z4xlFI80cWXRrxuKuM= github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.12.0 h1:d4QkX8FRTYaKaCZBoXYY8zJX2BXjWxurN/GA2tkrmZM= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-memdb v0.0.0-20180223233045-1289e7fffe71 h1:yxxFgVz31vFoKKTtRUNbXLNe4GFnbLKqg+0N7yG42L8= -github.com/hashicorp/go-memdb v0.0.0-20180223233045-1289e7fffe71/go.mod h1:kbfItVoBJwCfKXDXN4YoAXjxcFVZ7MRrJzyTX6H4giE= +github.com/hashicorp/go-immutable-radix v1.1.0 h1:vN9wG1D6KG6YHRTWr8512cxGOVgTMEfgEdSj/hr8MPc= +github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-memdb v1.0.3 h1:iiqzNk8jKB6/sLRj623Ui/Vi1zf21LOUpgzGjTge6a8= +github.com/hashicorp/go-memdb v1.0.3/go.mod h1:LWQ8R70vPrS4OEY9k28D2z8/Zzyu34NVzeRibGAzHO0= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-plugin v0.0.0-20180331002553-e8d22c780116 h1:Y4V/yReWjQo/Ngyc0w6C3EKXKincp4YgvXeo8lI4LrI= -github.com/hashicorp/go-plugin v0.0.0-20180331002553-e8d22c780116/go.mod h1:JSqWYsict+jzcj0+xElxyrBQRPNoiWQuddnxArJ7XHQ= +github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-plugin v1.0.1 h1:4OtAfUGbnKC6yS48p0CtMX2oFYtzFZVv6rok3cRWgnE= +github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= github.com/hashicorp/go-raftchunking v0.6.1 h1:moEnaG3gcwsWNyIBJoD5PCByE+Ewkqxh6N05CT+MbwA= github.com/hashicorp/go-raftchunking v0.6.1/go.mod h1:cGlg3JtDy7qy6c/3Bu660Mic1JF+7lWqIwCFSb08fX0= +github.com/hashicorp/go-raftchunking v0.6.3-0.20191002164813-7e9e8525653a h1:FmnBDwGwlTgugDGbVxwV8UavqSMACbGrUpfc98yFLR4= +github.com/hashicorp/go-raftchunking v0.6.3-0.20191002164813-7e9e8525653a/go.mod h1:xbXnmKqX9/+RhPkJ4zrEx4738HacP72aaUPlT2RZ4sU= github.com/hashicorp/go-retryablehttp v0.5.3 h1:QlWt0KvWT0lq8MFppF9tsJGF+ynG7ztc2KIPhzRGk7s= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-retryablehttp v0.5.4 h1:1BZvpawXoJCWX6pNtow9+rpEj+3itIlutiqnntI6jOE= +github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-retryablehttp v0.6.2 h1:bHM2aVXwBtBJWxHtkSrWuI4umABCUczs52eiUS9nSiw= +github.com/hashicorp/go-retryablehttp v0.6.2/go.mod h1:gEx6HMUGxYYhJScX7W1Il64m6cc2C1mDaW3NQ9sY1FY= +github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v0.0.0-20170202080759-03c5bf6be031 h1:c3Xdf5fTpk+hqhxqCO+ymqjfUXV9+GZqNgTtlnVzDos= -github.com/hashicorp/go-version v0.0.0-20170202080759-03c5bf6be031/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/go-uuid v1.0.2-0.20191001231223-f32f5fe8d6a8/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v0.0.0-20180906183839-65a6292f0157 h1:PJ+K03hio6ADVjEc6lFu5r866o67xEEMQ73CFdI6R2U= -github.com/hashicorp/hcl v0.0.0-20180906183839-65a6292f0157/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hil v0.0.0-20160711231837-1e86c6b523c5 h1:uk280DXEbQiCOZgCOI3elFSeNxf8YIZiNsbr2pQLYD0= github.com/hashicorp/hil v0.0.0-20160711231837-1e86c6b523c5/go.mod h1:KHvg/R2/dPtaePb16oW4qIyzkMxXOL38xjRN64adsts= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.1 h1:XFSOubp8KWB+Jd2PDyaX5xUd5bhSP/+pTDZVDMzZJM8= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/memberlist v0.1.4 h1:gkyML/r71w3FL8gUi74Vk76avkj/9lYAY9lvg0OcoGs= -github.com/hashicorp/memberlist v0.1.4/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/memberlist v0.2.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.2.2 h1:5+RffWKwqJ71YPu9mWsF7ZOscZmwfasdA8kbdC7AO2g= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/net-rpc-msgpackrpc v0.0.0-20151116020338-a14192a58a69 h1:lc3c72qGlIMDqQpQH82Y4vaglRMMFdJbziYWriR4UcE= github.com/hashicorp/net-rpc-msgpackrpc v0.0.0-20151116020338-a14192a58a69/go.mod h1:/z+jUGRBlwVpUZfjute9jWaF6/HuhjuFQuL1YXzVD1Q= github.com/hashicorp/raft v1.1.1 h1:HJr7UE1x/JrJSc9Oy6aDBHtNHUUBHjcQjTgvUVihoZs= github.com/hashicorp/raft v1.1.1/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= +github.com/hashicorp/raft v1.1.2-0.20191002163536-9c6bd3e3eb17/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= +github.com/hashicorp/raft v1.1.2 h1:oxEL5DDeurYxLd3UbcY/hccgSPhLLpiBZ1YxtWEq59c= +github.com/hashicorp/raft v1.1.2/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= github.com/hashicorp/raft-boltdb v0.0.0-20171010151810-6e5ba93211ea h1:xykPFhrBAS2J0VBzVa5e80b5ZtYuNQtgXjN40qBZlD4= github.com/hashicorp/raft-boltdb v0.0.0-20171010151810-6e5ba93211ea/go.mod h1:pNv7Wc3ycL6F5oOWn+tPGo2gWD4a5X+yp/ntwdKLjRk= -github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/vault v0.10.3 h1:3Hf6mwC4rggOq6ViWSoJ2yfk1oBS5ed58LLcP33gmEg= -github.com/hashicorp/vault v0.10.3/go.mod h1:KfSyffbKxoVyspOdlaGVjIuwLobi07qD1bAbosPMpP0= -github.com/hashicorp/vault-plugin-secrets-kv v0.0.0-20190318174639-195e0e9d07f1 h1:7IvvWArBoSjStPohKqHj3ksXNjcyPsyXYDIhCQw6Vtg= -github.com/hashicorp/vault-plugin-secrets-kv v0.0.0-20190318174639-195e0e9d07f1/go.mod h1:VJHHT2SC1tAPrfENQeBhLlb5FbZoKZM+oC/ROmEftz0= +github.com/hashicorp/serf v0.9.0/go.mod h1:YL0HO+FifKOW2u1ke99DGVu1zhcpZzNwrLIqBC7vbYU= +github.com/hashicorp/serf v0.9.2 h1:yJoyfZXo4Pk2p/M/viW+YLibBFiIbKoP79gu7kDAFP0= +github.com/hashicorp/serf v0.9.2/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/hashicorp/vault/api v1.0.4 h1:j08Or/wryXT4AcHj1oCbMd7IijXcKzYUGw59LGu9onU= +github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= +github.com/hashicorp/vault/api v1.0.5-0.20191108163347-bdd38fca2cff h1:cl94LQIrs/mNbh3ny1R8lM1gtYcUBa7HnGtOCi35SlQ= +github.com/hashicorp/vault/api v1.0.5-0.20191108163347-bdd38fca2cff/go.mod h1:Uf8LaHyrYsgVgHzO2tMZKhqRGlL3UJ6XaSwW2EA1Iqo= +github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= +github.com/hashicorp/vault/sdk v0.1.14-0.20191108161836-82f2b5571044/go.mod h1:PcekaFGiPJyHnFy+NZhP6ll650zEw51Ag7g/YEa+EOU= +github.com/hashicorp/vault/sdk v0.1.14-0.20191112033314-390e96e22eb2 h1:mKYi4Fm2uSfe94Ji89CoAaP7SPEEkfdtaUlgRGGb2go= +github.com/hashicorp/vault/sdk v0.1.14-0.20191112033314-390e96e22eb2/go.mod h1:PcekaFGiPJyHnFy+NZhP6ll650zEw51Ag7g/YEa+EOU= github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 h1:O/pT5C1Q3mVXMyuqg7yuAWUg/jMZR1/0QTzTRdNR6Uw= github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443/go.mod h1:bEpDU35nTu0ey1EXjwNwPjI9xErAsoOCmcMb9GKvyxo= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= +github.com/jackc/pgx v3.3.0+incompatible h1:Wa90/+qsITBAPkAZjiByeIGHFcj3Ztu+VzrrIpHjL90= +github.com/jackc/pgx v3.3.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da h1:FjHUJJ7oBW4G/9j1KzlHaXL09LyMVM9rupS39lncbXk= github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= -github.com/jefferai/jsonx v0.0.0-20160721235117-9cc31c3135ee h1:AQ/QmCk6x8ECPpf2pkPtA4lyncEEBbs8VFnVXPYKhIs= -github.com/jefferai/jsonx v0.0.0-20160721235117-9cc31c3135ee/go.mod h1:N0t2vlmpe8nyZB5ouIbJQPDSR+mH6oe7xHB9VZHSUzM= github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uczJe5YQdrYB16oTJlGSC/OyZDqUk9xX4= github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag= github.com/jinzhu/copier v0.0.0-20190625015134-976e0346caa8 h1:mGIXW/lubQ4B+3bXTLxcTMTjUNDqoF6T/HUW9LbFx9s= github.com/jinzhu/copier v0.0.0-20190625015134-976e0346caa8/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= -github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62 h1:JHCT6xuyPUrbbgAPE/3dqlvUKzRHMNuTBKKUb6OeR/k= github.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA= +github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f h1:ENpDacvnr8faw5ugQmEF1QYk+f/Y9lXFvuYmRxykago= +github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f/go.mod h1:KDSfL7qe5ZfQqvlDMkVjCztbmcpp/c8M77vhQP8ZPvk= github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/errors v0.0.0-20190930114154-d42613fe1ab9 h1:hJix6idebFclqlfZCHE7EUX7uqLCyb70nHNHH1XKGBg= @@ -333,15 +430,18 @@ github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVE github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b h1:Rrp0ByJXEjhREMPGTt3aWYjoIsUGCbt21ekbeJcTWv0= github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/keybase/go-crypto v0.0.0-20180614160407-5114a9a81e1b h1:VE6r2OwP5gj+Z9aCkSKl3MlmnZbfMAjhvR5T7abKHEo= -github.com/keybase/go-crypto v0.0.0-20180614160407-5114a9a81e1b/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -351,34 +451,58 @@ github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f h1:sgU github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f/go.mod h1:UGmTpUd3rjbtfIpwAPrcfmGf/Z1HS95TATB+m57TPB8= github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 h1:Bvq8AziQ5jFF4BHGAEDSqwPW1NJS3XshxbRCxtjFAZc= github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042/go.mod h1:TPpsiPUEh0zFL1Snz4crhMlBe60PYxRHr5oFF3rRYg0= -github.com/lib/pq v0.0.0-20180523175426-90697d60dd84 h1:it29sI2IM490luSc3RAhp5WuCYnc6RtbfLVAB7nmC5M= -github.com/lib/pq v0.0.0-20180523175426-90697d60dd84/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/linode/linodego v0.7.1 h1:4WZmMpSA2NRwlPZcc0+4Gyn7rr99Evk9bnr0B3gXRKE= +github.com/linode/linodego v0.7.1/go.mod h1:ga11n3ivecUrPCHN0rANxKmfWBJVkOXfLMZinAbj2sY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.0 h1:tEElEatulEHDeedTxwckzyYMA5c86fbmNIUL1hBIiTg= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= -github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/go-testing-interface v1.14.0 h1:/x0XQ6h+3U3nAyk1yx+bHPURrKa9sVVvYbuqZ7pIAtI= +github.com/mitchellh/go-testing-interface v1.14.0/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452 h1:hOY53G+kBFhbYFpRVxHl5eS7laP6B1+Cq+Z9Dry1iMU= github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/hashstructure v1.0.0 h1:ZkRJX1CyOoTkar7p/mLS5TZU4nJ1Rn/F8u9dGS02Q3Y= +github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.2.3 h1:f/MjBEBDLttYCGfRaKBbKSRVF5aV2O6fnBpzknuE3jU= +github.com/mitchellh/mapstructure v1.2.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/pointerstructure v1.0.0 h1:ATSdz4NWrmWPOF1CeCBU4sMCno2hgqdbSrRPFWQSVZI= +github.com/mitchellh/pointerstructure v1.0.0/go.mod h1:k4XwG94++jLVsSiTxo7qdIfXA9pj9EAeo0QsNNJOLZ8= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= @@ -391,42 +515,41 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nacos-group/nacos-sdk-go v0.3.3-0.20200617023039-50c7537d6a5f h1:gid5/0AkHvINWK69Fgbidb3BVIXqlf1YEm7wO0NVPsw= github.com/nacos-group/nacos-sdk-go v0.3.3-0.20200617023039-50c7537d6a5f/go.mod h1:fti1GlX/EB6RDKvzK/P7Vuibqj0JMPJHQwrcTU1tLXk= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 h1:BQ1HW7hr4IVovMwWg0E0PYcyW8CzqDcVmaew9cujU4s= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk= -github.com/oklog/run v0.0.0-20180308005104-6934b124db28 h1:Hbr3fbVPXea52oPQeP7KLSxP52g6SFaNY1IqAmUyEW0= -github.com/oklog/run v0.0.0-20180308005104-6934b124db28/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20180130162743-b8a9be070da4/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I= -github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= -github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= -github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= -github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/ory/dockertest v3.3.4+incompatible h1:VrpM6Gqg7CrPm3bL4Wm1skO+zFWLbh7/Xb5kGEbJRh8= -github.com/ory/dockertest v3.3.4+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c h1:vwpFWvAO8DeIZfFeqASzZfsxuWPno9ncAebBEP0N3uE= github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c/go.mod h1:otzZQXgoO96RTzDB/Hycg0qZcXZsWJGJRSXbmEIJ+4M= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/patrickmn/go-cache v0.0.0-20180527043350-9f6ff22cfff8 h1:BR6MM54q4W9pn0SySwg6yctZtBKlTdUq6a+b0kArBnE= -github.com/patrickmn/go-cache v0.0.0-20180527043350-9f6ff22cfff8/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4 v2.2.6+incompatible h1:6aCX4/YZ9v8q69hTyiR7dNLnTA3fgtKHVVW5BCd5Znw= +github.com/pierrec/lz4 v2.2.6+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -437,47 +560,69 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU= +github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rboyer/safeio v0.2.1 h1:05xhhdRNAdS3apYm7JRjOqngf4xruaW959jmRxGDuSU= +github.com/rboyer/safeio v0.2.1/go.mod h1:Cq/cEPK+YXFn622lsQ0K4KsPZSPtaptHHEldsy7Fmig= github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 h1:Wdi9nwnhFNAlseAOekn6B5G/+GMtks9UKbvRU/CMM/o= github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03/go.mod h1:gRAiPF5C5Nd0eyyRdqIu9qTiFSoZzpTq727b5B8fkkU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/zerolog v1.4.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1Tj9sZEYsmarJEQfMVYpd/Vyy/A8dqE= -github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/ryanuber/columnize v2.1.0+incompatible h1:j1Wcmh8OrK4Q7GXY+V7SVSY8nUWQxHW5TkBe7YUl+2s= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/conswriter v0.0.0-20180208195008-f5ae3917a627/go.mod h1:7zjs06qF79/FKAJpBvFx3P8Ww4UTIMAe+lpNXDHziac= +github.com/sean-/pager v0.0.0-20180208200047-666be9bf53b5/go.mod h1:BeybITEsBEg6qbIiqJ6/Bqeq25bCLbL7YFmpaFfJDuM= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shirou/gopsutil v0.0.0-20181107111621-48177ef5f880 h1:1Ge4j/3uB2rxzPWD3TC+daeCw+w91z8UCUL/7WH5gn8= github.com/shirou/gopsutil v0.0.0-20181107111621-48177ef5f880/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil v2.19.9+incompatible h1:IrPVlK4nfwW10DF7pW+7YJKws9NkgNzWozwwWv9FsgY= +github.com/shirou/gopsutil v2.19.9+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U= github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/smartystreets/assertions v0.0.0-20180820201707-7c9eb446e3cf/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 h1:N8Bg45zpk/UcpNGnfJt2y/3lRWASHNTUET8owPYCgYI= github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= @@ -485,13 +630,25 @@ github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d h1:bVQRCxQv github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d/go.mod h1:Cw4GTlQccdRGSEf6KiMju767x0NEHE0YIVPJSaXjlsw= github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -500,6 +657,8 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/tebeka/strftime v0.1.3 h1:5HQXOqWKYRFfNyBMNVc9z5+QzuBtIXy03psIhtdJYto= github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIUJUJH6XQ= +github.com/tencentcloud/tencentcloud-sdk-go v3.0.83+incompatible h1:8uRvJleFpqLsO77WaAh2UrasMOzd8MxXrNj20e7El+Q= +github.com/tencentcloud/tencentcloud-sdk-go v3.0.83+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4= github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9/go.mod h1:RHkNRtSLfOK7qBTHaeSX1D6BNpI3qw7NTxsmNr4RvN8= github.com/tevid/gohamcrest v1.1.1 h1:ou+xSqlIw1xfGTg1uq1nif/htZ2S3EzRqLm2BP+tYU0= github.com/tevid/gohamcrest v1.1.1/go.mod h1:3UvtWlqm8j5JbwYZh80D/PVBt0mJ1eJiYgZMibh0H/k= @@ -509,56 +668,88 @@ github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 h1:kF/7m/ZU+0D github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3/go.mod h1:QDlpd3qS71vYtakd2hmdpqhJ9nwv6mD6A30bQ1BPBFE= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/vmware/govmomi v0.18.0 h1:f7QxSmP7meCtoAmiKZogvVbLInT+CZx6Px6K5rYsJZo= github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8 h1:k8TV7Gz7cpWpOw/dz71fx8cCZdWoPuckHJ/wkJl+meg= github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8/go.mod h1:S1cAa98KMFv4Sa8SbJ6ZtvOmf0VlgH0QJ1gXI0lBfBY= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg= go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI= -golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/oauth2 v0.0.0-20170807180024-9a379c6b3e95/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= @@ -571,44 +762,89 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190508220229-2d0786266e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.0.0-20180829000535-087779f1d2c9 h1:z1TeLUmxf9ws9KLICfmX+KGXTs+rjm+aGWzfsv7MZ9w= -google.golang.org/api v0.0.0-20180829000535-087779f1d2c9/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0 h1:9sdfJOzWlkqPltHAuzT2Cp+yrBeY1KRVYgms8soxMwM= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.0 h1:Tfd7cKwKbFRsI8RMAD3oqqw7JPFRrvFlOsfbgVkjOOw= +google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101 h1:wuGevabY6r+ivPNagjUXGGxF+GqgMd+dBhjsxW4q9u4= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64 h1:iKtrH9Y8mcbADOP0YFaEMth7OfuHY9xHOwNj4znpM1A= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.22.1 h1:/7cs52RnTJmD43s3uxzlq2U7nqVTd/37viQwMrMNlOM= -google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM= @@ -616,6 +852,8 @@ gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUy gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= @@ -624,37 +862,46 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/mgo.v2 v2.0.0-20160818020120-3f83fa500528 h1:/saqWwm73dLmuzbNhe92F0QsZ/KiFND+esHco2v1hiY= -gopkg.in/mgo.v2 v2.0.0-20160818020120-3f83fa500528/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= -gopkg.in/ory-am/dockertest.v3 v3.3.4 h1:oen8RiwxVNxtQ1pRoV4e4jqh6UjNsOuIZ1NXns6jdcw= -gopkg.in/ory-am/dockertest.v3 v3.3.4/go.mod h1:s9mmoLkaGeAh97qygnNj4xWkiN7e1SKekYC6CovU+ek= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y= +gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= istio.io/gogo-genproto v0.0.0-20190124151557-6d926a6e6feb/go.mod h1:eIDJ6jNk/IeJz6ODSksHl5Aiczy5JUq6vFhJWI5OtiI= -k8s.io/api v0.0.0-20180806132203-61b11ee65332/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= -k8s.io/api v0.0.0-20190325185214-7544f9db76f6 h1:9MWtbqhwTyDvF4cS1qAhxDb9Mi8taXiAu+5nEacl7gY= -k8s.io/api v0.0.0-20190325185214-7544f9db76f6/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= -k8s.io/apimachinery v0.0.0-20180821005732-488889b0007f/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= -k8s.io/apimachinery v0.0.0-20190223001710-c182ff3b9841 h1:Q4RZrHNtlC/mSdC1sTrcZ5RchC/9vxLVj57pWiCBKv4= -k8s.io/apimachinery v0.0.0-20190223001710-c182ff3b9841/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= -k8s.io/client-go v8.0.0+incompatible h1:tTI4hRmb1DRMl4fG6Vclfdi6nTM82oIrTT7HfitmxC4= -k8s.io/client-go v8.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= +k8s.io/api v0.16.9 h1:3vCx0WX9qcg1Hv4aQ/G1tiIKectGVuimvPVTJU4VOCA= +k8s.io/api v0.16.9/go.mod h1:Y7dZNHs1Xy0mSwSlzL9QShi6qkljnN41yR8oWCRTDe8= +k8s.io/apimachinery v0.16.9 h1:ESUZ4hMBUKF2kn2HBFL5zM/wQv4j/0uRbR7AjgqGJ4o= +k8s.io/apimachinery v0.16.9/go.mod h1:Xk2vD2TRRpuWYLQNM6lT9R7DSFZUYG03SarNkbGrnKE= +k8s.io/client-go v0.16.9 h1:6Eh4lMDxFtDzBkqid1AOL3bQ/pPYrulx8l23DXw4mRU= +k8s.io/client-go v0.16.9/go.mod h1:ThjPlh7Kx+XoBFOCt775vx5J7atwY7F/zaFzTco5gL0= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/utils v0.0.0-20190801114015-581e00157fb1 h1:+ySTxfHnfzZb9ys375PXNlLhkJPLKgHajBU0N62BDvE= +k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/registry/etcdv3/service_discovery.go b/registry/etcdv3/service_discovery.go index 10396049fb..f381ba70d6 100644 --- a/registry/etcdv3/service_discovery.go +++ b/registry/etcdv3/service_discovery.go @@ -26,7 +26,7 @@ import ( import ( gxset "github.com/dubbogo/gost/container/set" gxpage "github.com/dubbogo/gost/page" - "github.com/hashicorp/vault/helper/jsonutil" + "github.com/hashicorp/vault/sdk/helper/jsonutil" perrors "github.com/pkg/errors" ) From 7970243e81f092ea2a51bdbec1ab02f1086ae763 Mon Sep 17 00:00:00 2001 From: "xg.gao" Date: Sat, 25 Jul 2020 17:29:11 +0800 Subject: [PATCH 037/242] remove replace --- go.mod | 2 -- go.sum | 5 +---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 052aa15bbb..7a472daef7 100644 --- a/go.mod +++ b/go.mod @@ -67,5 +67,3 @@ require ( ) go 1.13 - -replace github.com/ugorji/go v1.1.4 => github.com/ugorji/go v1.1.7 diff --git a/go.sum b/go.sum index ee42317071..424a11087b 100644 --- a/go.sum +++ b/go.sum @@ -668,11 +668,8 @@ github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 h1:kF/7m/ZU+0D github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3/go.mod h1:QDlpd3qS71vYtakd2hmdpqhJ9nwv6mD6A30bQ1BPBFE= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/vmware/govmomi v0.18.0 h1:f7QxSmP7meCtoAmiKZogvVbLInT+CZx6Px6K5rYsJZo= github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= From 9632fd7fa363061b180fcb9afc40dda6c5f1c101 Mon Sep 17 00:00:00 2001 From: "xg.gao" Date: Sat, 25 Jul 2020 21:13:33 +0800 Subject: [PATCH 038/242] update consul test --- metadata/report/consul/report_test.go | 2 +- registry/consul/utils_test.go | 8 ++++--- remoting/consul/test_agent.go | 30 ++------------------------- remoting/consul/test_agent_test.go | 2 +- 4 files changed, 9 insertions(+), 33 deletions(-) diff --git a/metadata/report/consul/report_test.go b/metadata/report/consul/report_test.go index 34ee29de94..e07a7429f1 100644 --- a/metadata/report/consul/report_test.go +++ b/metadata/report/consul/report_test.go @@ -143,7 +143,7 @@ func (suite *consulMetadataReportTestSuite) testGetServiceDefinition() { func test1(t *testing.T) { consulAgent := consul.NewConsulAgent(t, 8500) - defer consulAgent.Close() + defer consulAgent.Shutdown() url := newProviderRegistryUrl("localhost", 8500) mf := extension.GetMetadataReportFactory("consul") diff --git a/registry/consul/utils_test.go b/registry/consul/utils_test.go index 327dd95f71..939352dc08 100644 --- a/registry/consul/utils_test.go +++ b/registry/consul/utils_test.go @@ -148,7 +148,7 @@ func (suite *consulRegistryTestSuite) close() { // register -> subscribe -> unregister func test1(t *testing.T) { consulAgent := consul.NewConsulAgent(t, registryPort) - defer consulAgent.Close() + defer consulAgent.Shutdown() server := newServer(providerHost, providerPort) defer server.close() @@ -165,10 +165,10 @@ func test1(t *testing.T) { suite.testListener(remoting.EventTypeDel) } -// subscribe -> register +// subscribe -> register -> unregister func test2(t *testing.T) { consulAgent := consul.NewConsulAgent(t, registryPort) - defer consulAgent.Close() + defer consulAgent.Shutdown() server := newServer(providerHost, providerPort) defer server.close() @@ -181,6 +181,8 @@ func test2(t *testing.T) { suite.testNewProviderRegistry() suite.testRegister() suite.testListener(remoting.EventTypeAdd) + suite.testUnregister() + suite.testListener(remoting.EventTypeDel) } func TestConsulRegistry(t *testing.T) { diff --git a/remoting/consul/test_agent.go b/remoting/consul/test_agent.go index 1744da7bd9..f6ba336a95 100644 --- a/remoting/consul/test_agent.go +++ b/remoting/consul/test_agent.go @@ -18,8 +18,6 @@ package consul import ( - "io/ioutil" - "os" "strconv" "testing" ) @@ -30,35 +28,11 @@ import ( // Consul agent, used for test, simulates // an embedded consul server. -type ConsulAgent struct { - dataDir string - testAgent *agent.TestAgent -} - -func NewConsulAgent(t *testing.T, port int) *ConsulAgent { - dataDir, _ := ioutil.TempDir("./", "agent") +func NewConsulAgent(t *testing.T, port int) *agent.TestAgent { hcl := ` ports { http = ` + strconv.Itoa(port) + ` } - data_dir = "` + dataDir + `" ` - testAgent := &agent.TestAgent{Name: t.Name(), DataDir: dataDir, HCL: hcl} - testAgent.Start(t) - - consulAgent := &ConsulAgent{ - dataDir: dataDir, - testAgent: testAgent, - } - return consulAgent -} - -func (consulAgent *ConsulAgent) Close() error { - var err error - - err = consulAgent.testAgent.Shutdown() - if err != nil { - return err - } - return os.RemoveAll(consulAgent.dataDir) + return agent.NewTestAgent(t, hcl) } diff --git a/remoting/consul/test_agent_test.go b/remoting/consul/test_agent_test.go index 8cf0ac6cd8..066e5e3c2d 100644 --- a/remoting/consul/test_agent_test.go +++ b/remoting/consul/test_agent_test.go @@ -27,6 +27,6 @@ import ( func TestNewConsulAgent(t *testing.T) { consulAgent := NewConsulAgent(t, 8500) - err := consulAgent.Close() + err := consulAgent.Shutdown() assert.NoError(t, err) } From 655d1df27d8d3c645f4949968cd19992bbec7ced Mon Sep 17 00:00:00 2001 From: "xg.gao" Date: Sat, 25 Jul 2020 23:00:12 +0800 Subject: [PATCH 039/242] code format --- remoting/etcdv3/client.go | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/remoting/etcdv3/client.go b/remoting/etcdv3/client.go index a2a5764538..4e7436e445 100644 --- a/remoting/etcdv3/client.go +++ b/remoting/etcdv3/client.go @@ -142,9 +142,7 @@ func NewServiceDiscoveryClient(opts ...Option) *Client { if err != nil { logger.Errorf("new etcd client (name{%s}, etcd addresses{%v}, timeout{%d}) = error{%v}", options.name, options.endpoints, options.timeout, err) - return nil } - return newClient } @@ -426,8 +424,7 @@ func (c *Client) keepAliveKV(k string, v string) error { } _, err = c.rawClient.Put(c.ctx, k, v, clientv3.WithLease(lease.ID)) - err = perrors.WithMessage(err, "put k/v with lease") - return err + return perrors.WithMessage(err, "put k/v with lease") } // nolint @@ -445,64 +442,53 @@ func (c *Client) Valid() bool { c.lock.RLock() defer c.lock.RUnlock() - if c.rawClient == nil { - return false - } - return true + return c.rawClient != nil } // nolint func (c *Client) Create(k string, v string) error { err := c.put(k, v) - err = perrors.WithMessagef(err, "put k/v (key: %s value %s)", k, v) - return err + return perrors.WithMessagef(err, "put k/v (key: %s value %s)", k, v) } // Update key value ... func (c *Client) Update(k, v string) error { err := c.update(k, v) - err = perrors.WithMessagef(err, "Update k/v (key: %s value %s)", k, v) - return err + return perrors.WithMessagef(err, "Update k/v (key: %s value %s)", k, v) } // nolint func (c *Client) Delete(k string) error { err := c.delete(k) - err = perrors.WithMessagef(err, "delete k/v (key %s)", k) - return err + return perrors.WithMessagef(err, "delete k/v (key %s)", k) } // RegisterTemp registers a temporary node func (c *Client) RegisterTemp(k, v string) error { err := c.keepAliveKV(k, v) - err = perrors.WithMessagef(err, "keepalive kv (key %s)", k) - return err + return perrors.WithMessagef(err, "keepalive kv (key %s)", k) } // GetChildrenKVList gets children kv list by @k func (c *Client) GetChildrenKVList(k string) ([]string, []string, error) { kList, vList, err := c.getChildren(k) - err = perrors.WithMessagef(err, "get key children (key %s)", k) - return kList, vList, err + return kList, vList, perrors.WithMessagef(err, "get key children (key %s)", k) } // Get gets value by @k func (c *Client) Get(k string) (string, error) { v, err := c.get(k) - err = perrors.WithMessagef(err, "get key value (key %s)", k) - return v, err + return v, perrors.WithMessagef(err, "get key value (key %s)", k) } // Watch watches on spec key func (c *Client) Watch(k string) (clientv3.WatchChan, error) { wc, err := c.watch(k) - err = perrors.WithMessagef(err, "watch prefix (key %s)", k) - return wc, err + return wc, perrors.WithMessagef(err, "watch prefix (key %s)", k) } // WatchWithPrefix watches on spec prefix func (c *Client) WatchWithPrefix(prefix string) (clientv3.WatchChan, error) { wc, err := c.watchWithPrefix(prefix) - err = perrors.WithMessagef(err, "watch prefix (key %s)", prefix) - return wc, err + return wc, perrors.WithMessagef(err, "watch prefix (key %s)", prefix) } From d795e2a350e7fca7793ac40b154950e04dd83fce Mon Sep 17 00:00:00 2001 From: "xg.gao" Date: Sun, 26 Jul 2020 10:05:47 +0800 Subject: [PATCH 040/242] decrease stack level in log --- common/logger/logger.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/common/logger/logger.go b/common/logger/logger.go index 9bc6a46100..88ed0c29f8 100644 --- a/common/logger/logger.go +++ b/common/logger/logger.go @@ -100,7 +100,6 @@ func InitLog(logConfFile string) error { func InitLogger(conf *zap.Config) { var zapLoggerConfig zap.Config if conf == nil { - zapLoggerConfig = zap.NewDevelopmentConfig() zapLoggerEncoderConfig := zapcore.EncoderConfig{ TimeKey: "time", LevelKey: "level", @@ -113,12 +112,18 @@ func InitLogger(conf *zap.Config) { EncodeDuration: zapcore.SecondsDurationEncoder, EncodeCaller: zapcore.ShortCallerEncoder, } - zapLoggerConfig.EncoderConfig = zapLoggerEncoderConfig + zapLoggerConfig = zap.Config{ + Level: zap.NewAtomicLevelAt(zap.DebugLevel), + Development: false, + Encoding: "console", + EncoderConfig: zapLoggerEncoderConfig, + OutputPaths: []string{"stderr"}, + ErrorOutputPaths: []string{"stderr"}, + } } else { zapLoggerConfig = *conf } zapLogger, _ := zapLoggerConfig.Build(zap.AddCallerSkip(1)) - //logger = zapLogger.Sugar() logger = &DubboLogger{Logger: zapLogger.Sugar(), dynamicLevel: zapLoggerConfig.Level} // set getty log From 7d9abd2010ccec94f3742328c7da139df0dd1161 Mon Sep 17 00:00:00 2001 From: "xg.gao" Date: Sun, 26 Jul 2020 10:12:52 +0800 Subject: [PATCH 041/242] add consul metadata in readme --- README.md | 1 + README_CN.md | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index d0080b33b6..a1c09fc3ca 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,7 @@ Finished List: * [Nacos](https://github.com/apache/dubbo-go/pull/522) * [Zookeeper](https://github.com/apache/dubbo-go/pull/633) * [Etcd](https://github.com/apache/dubbo-go/blob/9a5990d9a9c3d5e6633c0d7d926c156416bcb931/metadata/report/etcd/report.go) + * [Consul](https://github.com/apache/dubbo-go/pull/633) - Service discovery * [Nacos](https://github.com/apache/dubbo-go/blob/9a5990d9a9c3d5e6633c0d7d926c156416bcb931/registry/nacos/service_discovery.go) diff --git a/README_CN.md b/README_CN.md index 9dd10d6272..552685c7bb 100644 --- a/README_CN.md +++ b/README_CN.md @@ -115,6 +115,7 @@ Apache License, Version 2.0 * [Nacos](https://github.com/apache/dubbo-go/pull/522) * [Zookeeper](https://github.com/apache/dubbo-go/pull/633) * [Etcd](https://github.com/apache/dubbo-go/blob/9a5990d9a9c3d5e6633c0d7d926c156416bcb931/metadata/report/etcd/report.go) + * [Consul](https://github.com/apache/dubbo-go/pull/633) - 服务发现 * [Nacos](https://github.com/apache/dubbo-go/blob/9a5990d9a9c3d5e6633c0d7d926c156416bcb931/registry/nacos/service_discovery.go) From cb15ad055ea25beb6fd14dd046731c995423646d Mon Sep 17 00:00:00 2001 From: "xg.gao" Date: Sun, 26 Jul 2020 10:24:17 +0800 Subject: [PATCH 042/242] code format --- remoting/zookeeper/client.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/remoting/zookeeper/client.go b/remoting/zookeeper/client.go index 4ca34a6aec..3db743ed58 100644 --- a/remoting/zookeeper/client.go +++ b/remoting/zookeeper/client.go @@ -363,14 +363,9 @@ func (z *ZookeeperClient) ZkConnValid() bool { default: } - valid := true z.RLock() - if z.Conn == nil { - valid = false - } - z.RUnlock() - - return valid + defer z.RUnlock() + return z.Conn != nil } // nolint From 66e0f4de81f338224f0c45743715aaa556a25ab9 Mon Sep 17 00:00:00 2001 From: "xg.gao" Date: Sun, 26 Jul 2020 14:07:43 +0800 Subject: [PATCH 043/242] wait nacos client cache flush --- registry/nacos/service_discovery_test.go | 26 ++++++++++++++---------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/registry/nacos/service_discovery_test.go b/registry/nacos/service_discovery_test.go index 4b069c2e82..24412999c5 100644 --- a/registry/nacos/service_discovery_test.go +++ b/registry/nacos/service_discovery_test.go @@ -103,25 +103,26 @@ func TestNacosServiceDiscovery_CRUD(t *testing.T) { } // clean data - - serviceDiscovry, _ := extension.GetServiceDiscovery(constant.NACOS_KEY, testName) + serviceDiscovery, err := extension.GetServiceDiscovery(constant.NACOS_KEY, testName) + assert.Nil(t, err) // clean data for local test - serviceDiscovry.Unregister(®istry.DefaultServiceInstance{ + err = serviceDiscovery.Unregister(®istry.DefaultServiceInstance{ Id: id, ServiceName: serviceName, Host: host, Port: port, }) + assert.Nil(t, err) - err := serviceDiscovry.Register(instance) + err = serviceDiscovery.Register(instance) assert.Nil(t, err) + //sometimes nacos may be failed to push update of instance, //so it need 10s to pull, we sleep 10 second to make sure instance has been update time.Sleep(11 * time.Second) - page := serviceDiscovry.GetHealthyInstancesByPage(serviceName, 0, 10, true) + page := serviceDiscovery.GetHealthyInstancesByPage(serviceName, 0, 10, true) assert.NotNil(t, page) - assert.Equal(t, 0, page.GetOffset()) assert.Equal(t, 10, page.GetPageSize()) assert.Equal(t, 1, page.GetDataSize()) @@ -135,12 +136,15 @@ func TestNacosServiceDiscovery_CRUD(t *testing.T) { assert.Equal(t, 0, len(instance.GetMetadata())) instance.Metadata["a"] = "b" - - err = serviceDiscovry.Update(instance) + err = serviceDiscovery.Update(instance) assert.Nil(t, err) - pageMap := serviceDiscovry.GetRequestInstances([]string{serviceName}, 0, 1) + //sometimes nacos may be failed to push update of instance, + //so it need 10s to pull, we sleep 10 second to make sure instance has been update + time.Sleep(11 * time.Second) + pageMap := serviceDiscovery.GetRequestInstances([]string{serviceName}, 0, 1) assert.Equal(t, 1, len(pageMap)) + page = pageMap[serviceName] assert.NotNil(t, page) assert.Equal(t, 1, len(page.GetData())) @@ -150,11 +154,11 @@ func TestNacosServiceDiscovery_CRUD(t *testing.T) { assert.Equal(t, "b", v) // test dispatcher event - err = serviceDiscovry.DispatchEventByServiceName(serviceName) + err = serviceDiscovery.DispatchEventByServiceName(serviceName) assert.Nil(t, err) // test AddListener - err = serviceDiscovry.AddListener(®istry.ServiceInstancesChangedListener{}) + err = serviceDiscovery.AddListener(®istry.ServiceInstancesChangedListener{}) assert.Nil(t, err) } From e60bb0850d57006f7a6f1281061582b35cf0ef53 Mon Sep 17 00:00:00 2001 From: "xg.gao" Date: Sun, 26 Jul 2020 14:42:59 +0800 Subject: [PATCH 044/242] fix --- registry/nacos/service_discovery_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/registry/nacos/service_discovery_test.go b/registry/nacos/service_discovery_test.go index 24412999c5..44f9b2fb1c 100644 --- a/registry/nacos/service_discovery_test.go +++ b/registry/nacos/service_discovery_test.go @@ -118,9 +118,6 @@ func TestNacosServiceDiscovery_CRUD(t *testing.T) { err = serviceDiscovery.Register(instance) assert.Nil(t, err) - //sometimes nacos may be failed to push update of instance, - //so it need 10s to pull, we sleep 10 second to make sure instance has been update - time.Sleep(11 * time.Second) page := serviceDiscovery.GetHealthyInstancesByPage(serviceName, 0, 10, true) assert.NotNil(t, page) assert.Equal(t, 0, page.GetOffset()) From 2083dba1918d6a6c3cfac0567c10ea61ce52073a Mon Sep 17 00:00:00 2001 From: "xg.gao" Date: Sun, 26 Jul 2020 15:28:13 +0800 Subject: [PATCH 045/242] revert --- registry/nacos/service_discovery_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/registry/nacos/service_discovery_test.go b/registry/nacos/service_discovery_test.go index 44f9b2fb1c..24412999c5 100644 --- a/registry/nacos/service_discovery_test.go +++ b/registry/nacos/service_discovery_test.go @@ -118,6 +118,9 @@ func TestNacosServiceDiscovery_CRUD(t *testing.T) { err = serviceDiscovery.Register(instance) assert.Nil(t, err) + //sometimes nacos may be failed to push update of instance, + //so it need 10s to pull, we sleep 10 second to make sure instance has been update + time.Sleep(11 * time.Second) page := serviceDiscovery.GetHealthyInstancesByPage(serviceName, 0, 10, true) assert.NotNil(t, page) assert.Equal(t, 0, page.GetOffset()) From b8893fc37b643fb5e746c703a51d53909f7c33e8 Mon Sep 17 00:00:00 2001 From: lzp0412 <641785844@qq.com> Date: Sun, 26 Jul 2020 17:34:10 +0800 Subject: [PATCH 046/242] fix review comment --- registry/nacos/registry.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/registry/nacos/registry.go b/registry/nacos/registry.go index 0dfb64622e..f8363a7504 100644 --- a/registry/nacos/registry.go +++ b/registry/nacos/registry.go @@ -178,7 +178,7 @@ func createRegisterParam(url common.URL, serviceName string) vo.RegisterInstance return instance } -func createDegisterParam(url common.URL, serviceName string) vo.DeregisterInstanceParam { +func createDeregisterParam(url common.URL, serviceName string) vo.DeregisterInstanceParam { if len(url.Ip) == 0 { url.Ip = localIP } @@ -209,13 +209,13 @@ func (nr *nacosRegistry) Register(url common.URL) error { func (nr *nacosRegistry) DeRegister(url common.URL) error { serviceName := getServiceName(url) - param := createDegisterParam(url, serviceName) + param := createDeregisterParam(url, serviceName) isDeRegistry, err := nr.namingClient.DeregisterInstance(param) if err != nil { return err } if !isDeRegistry { - return perrors.New("DeRegistry [" + serviceName + "] to nacos failed") + return perrors.New("DeRegistry [" + serviceName + "] to nacos failed") } return nil } @@ -271,7 +271,7 @@ func (nr *nacosRegistry) Destroy() { err := nr.DeRegister(url) logger.Infof("DeRegister Nacos url:%+v", url) if err != nil { - logger.Errorf("Deregister url:%+v err:%v", url, err.Error()) + logger.Errorf("Deregister url:%+v err:%v", url, err.Error()) } } return From 71095a35998513b9a8632e59e85c81cc1b45556e Mon Sep 17 00:00:00 2001 From: watermelo <80680489@qq.com> Date: Mon, 27 Jul 2020 01:13:24 +0800 Subject: [PATCH 047/242] Add: add unit tests for tag router --- cluster/router/tag/router_rule.go | 46 ++++++++++++---- cluster/router/tag/router_rule_test.go | 55 ++++++++++++++++--- cluster/router/tag/tag.go | 22 ++++---- cluster/router/tag/tag_router.go | 76 +++++++++++++++++++++----- cluster/router/tag/tag_router_test.go | 23 ++++++++ 5 files changed, 177 insertions(+), 45 deletions(-) diff --git a/cluster/router/tag/router_rule.go b/cluster/router/tag/router_rule.go index b2b6659054..dc7a446783 100644 --- a/cluster/router/tag/router_rule.go +++ b/cluster/router/tag/router_rule.go @@ -40,7 +40,7 @@ import ( // RouterRule RouterRule config read from config file or config center type RouterRule struct { router.BaseRouterRule `yaml:",inline""` - tags []tag + Tags []Tag addressToTagNames map[string][]string tagNameToAddresses map[string][]string } @@ -52,18 +52,44 @@ func getRule(rawRule string) (*RouterRule, error) { return r, err } r.RawRule = rawRule - // TODO init tags + r.init() return r, nil } +func (t *RouterRule) init() { + t.addressToTagNames = make(map[string][]string) + t.tagNameToAddresses = make(map[string][]string) + for _, tag := range t.Tags { + for _, address := range tag.Addresses { + t.addressToTagNames[address] = append(t.addressToTagNames[address], tag.Name) + } + t.tagNameToAddresses[tag.Name] = tag.Addresses + } +} + func (t *RouterRule) getAddresses() []string { - // TODO get all tag addresses - return nil + var result []string + for _, tag := range t.Tags { + result = append(result, tag.Addresses...) + } + return result } func (t *RouterRule) getTagNames() []string { - // TODO get all tag names - return nil + var result []string + for _, tag := range t.Tags { + result = append(result, tag.Name) + } + return result +} + +func (t *RouterRule) hasTag(tag string) bool { + for _, t := range t.Tags { + if tag == t.Name { + return true + } + } + return false } func (t *RouterRule) getAddressToTagNames() map[string][]string { @@ -74,10 +100,10 @@ func (t *RouterRule) getTagNameToAddresses() map[string][]string { return t.tagNameToAddresses } -func (t *RouterRule) getTags() []tag { - return t.tags +func (t *RouterRule) getTags() []Tag { + return t.Tags } -func (t *RouterRule) setTags(tags []tag) { - t.tags = tags +func (t *RouterRule) setTags(tags []Tag) { + t.Tags = tags } diff --git a/cluster/router/tag/router_rule_test.go b/cluster/router/tag/router_rule_test.go index 2df65193f9..4e0f5b729e 100644 --- a/cluster/router/tag/router_rule_test.go +++ b/cluster/router/tag/router_rule_test.go @@ -22,19 +22,56 @@ import ( ) import ( - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" ) -func TestGetRule(t *testing.T) { +type RuleTestSuite struct { + suite.Suite + rule *RouterRule +} + +func (suite *RuleTestSuite) SetupTest() { + var err error yml := ` scope: application -runtime: true force: true +runtime: false +enabled: true +priority: 1 +key: demo-provider +tags: + - name: tag1 + addresses: [ip1, ip2] + - name: tag2 + addresses: [ip3, ip4] ` - rule, e := getRule(yml) - assert.Nil(t, e) - assert.NotNil(t, rule) - assert.Equal(t, true, rule.Force) - assert.Equal(t, true, rule.Runtime) - assert.Equal(t, "application", rule.Scope) + suite.rule, err = getRule(yml) + suite.Nil(err) +} + +func (suite *RuleTestSuite) TestGetRule() { + var err error + suite.Equal(true, suite.rule.Force) + suite.Equal(false, suite.rule.Runtime) + suite.Equal("application", suite.rule.Scope) + suite.Equal(1, suite.rule.Priority) + suite.Equal("demo-provider", suite.rule.Key) + suite.Nil(err) +} + +func (suite *RuleTestSuite) TestGetTagNames() { + suite.Equal([]string{"tag1", "tag2"}, suite.rule.getTagNames()) +} + +func (suite *RuleTestSuite) TestGetAddresses() { + suite.Equal([]string{"ip1", "ip2", "ip3", "ip4"}, suite.rule.getAddresses()) +} + +func (suite *RuleTestSuite) TestHasTag() { + suite.Equal(true, suite.rule.hasTag("tag1")) + suite.Equal(false, suite.rule.hasTag("tag404")) +} + +func TestRuleTestSuite(t *testing.T) { + suite.Run(t, new(RuleTestSuite)) } diff --git a/cluster/router/tag/tag.go b/cluster/router/tag/tag.go index 07d719af86..73d10b5db4 100644 --- a/cluster/router/tag/tag.go +++ b/cluster/router/tag/tag.go @@ -17,23 +17,23 @@ package tag -type tag struct { - name string - addresses []string +type Tag struct { + Name string + Addresses []string } -func (t *tag) getName() string { - return t.name +func (t *Tag) getName() string { + return t.Name } -func (t *tag) setName(name string) { - t.name = name +func (t *Tag) setName(name string) { + t.Name = name } -func (t *tag) getAddresses() []string { - return t.addresses +func (t *Tag) getAddresses() []string { + return t.Addresses } -func (t *tag) setAddresses(addresses []string) { - t.addresses = addresses +func (t *Tag) setAddresses(addresses []string) { + t.Addresses = addresses } diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index ff1209dd45..7417efcf4d 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -81,16 +81,20 @@ func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocati addresses, _ = tagRouterRuleCopy.getTagNameToAddresses()[tag] // filter by dynamic tag group first if len(addresses) > 0 { - // TODO filter invokers - result = nil + result = filterAddressMatches(invokers, addresses) if len(result) > 0 || tagRouterRuleCopy.Force { return result } } else { // dynamic tag group doesn't have any item about the requested app OR it's null after filtered by // dynamic tag group but force=false. check static tag - // TODO filter invokers - return result + cond := func(invoker protocol.Invoker) bool { + if invoker.GetUrl().GetParam(constant.Tagkey, "") == tag { + return true + } + return false + } + result = filterCondition(invokers, cond) } // If there's no tagged providers that can match the current tagged request. force.tag is set by default // to false, which means it will invoke any providers without a tag unless it's explicitly disallowed. @@ -98,14 +102,20 @@ func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocati return result } else { // FAILOVER: return all Providers without any tags. - // TODO filter invokers - return result + result = filterAddressNotMatches(invokers, tagRouterRuleCopy.getAddresses()) + cond := func(invoker protocol.Invoker) bool { + if invoker.GetUrl().GetParam(constant.Tagkey, "") == "" { + return true + } + return false + } + return filterCondition(result, cond) } } else { // return all addresses in dynamic tag group. addresses = tagRouterRuleCopy.getAddresses() if len(addresses) > 0 { - // TODO filter invokers + result = filterAddressNotMatches(invokers, addresses) // 1. all addresses are in dynamic tag group, return empty list. if len(result) == 0 { return result @@ -113,8 +123,11 @@ func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocati // 2. if there are some addresses that are not in any dynamic tag group, continue to filter using the // static tag group. } - // TODO filter invokers - return result + cond := func(invoker protocol.Invoker) bool { + localTag := invoker.GetUrl().GetParam(constant.Tagkey, "") + return localTag == "" || !(tagRouterRuleCopy.hasTag(localTag)) + } + return filterCondition(result, cond) } } @@ -172,18 +185,51 @@ func isForceUseTag(url *common.URL, invocation protocol.Invocation) bool { return false } -func addressMatches(url *common.URL, addresses []string) bool { - return len(addresses) > 0 && checkAddressMatch(addresses, url.Ip, url.Port) +func filterAddressMatches(invokers []protocol.Invoker, addresses []string) []protocol.Invoker { + var idx int + for _, invoker := range invokers { + url := invoker.GetUrl() + if !(len(addresses) > 0 && checkAddressMatch(addresses, url.Ip, url.Port)) { + continue + } + invokers[idx] = invoker + idx++ + } + return invokers[:idx] +} + +func filterAddressNotMatches(invokers []protocol.Invoker, addresses []string) []protocol.Invoker { + var idx int + for _, invoker := range invokers { + url := invoker.GetUrl() + if !(len(addresses) == 0 || !checkAddressMatch(addresses, url.Ip, url.Port)) { + continue + } + invokers[idx] = invoker + idx++ + } + return invokers[:idx] } -func addressNotMatches(url *common.URL, addresses []string) bool { - return len(addresses) == 0 || !checkAddressMatch(addresses, url.Ip, url.Port) +func filterCondition(invokers []protocol.Invoker, condition func(protocol.Invoker) bool) []protocol.Invoker { + var idx int + for _, invoker := range invokers { + if !condition(invoker) { + continue + } + invokers[idx] = invoker + idx++ + } + return invokers[:idx] } func checkAddressMatch(addresses []string, host, port string) bool { for _, address := range addresses { - // TODO address parse - if address == (host + port) { + // TODO ip match + if address == host+":"+port { + return true + } + if address == constant.ANYHOST_VALUE+":"+port { return true } } diff --git a/cluster/router/tag/tag_router_test.go b/cluster/router/tag/tag_router_test.go index 000b3ec672..3a053de4b0 100644 --- a/cluster/router/tag/tag_router_test.go +++ b/cluster/router/tag/tag_router_test.go @@ -19,6 +19,7 @@ package tag import ( "context" + "github.com/apache/dubbo-go/common/constant" "testing" ) @@ -160,3 +161,25 @@ func TestTagRouterRouteNoForce(t *testing.T) { invRst2 := tagRouter.Route(invokers, &u1, inv) assert.Equal(t, 3, len(invRst2)) } + +func TestFilterCondition(t *testing.T) { + u2, e2 := common.NewURL(tagRouterTestHangZhouUrl) + u3, e3 := common.NewURL(tagRouterTestShangHaiUrl) + u4, e4 := common.NewURL(tagRouterTestBeijingUrl) + assert.Nil(t, e2) + assert.Nil(t, e3) + assert.Nil(t, e4) + inv2 := NewMockInvoker(u2) + inv3 := NewMockInvoker(u3) + inv4 := NewMockInvoker(u4) + var invokers []protocol.Invoker + invokers = append(invokers, inv2, inv3, inv4) + cond := func(invoker protocol.Invoker) bool { + if invoker.GetUrl().GetParam(constant.Tagkey, "") == "beijing" { + return true + } + return false + } + res := filterCondition(invokers, cond) + assert.Equal(t, []protocol.Invoker{inv4}, res) +} From 41a0fd134f43905fd65c24732a67cba5483d9b6b Mon Sep 17 00:00:00 2001 From: tiecheng <18768171164@163.com> Date: Mon, 27 Jul 2020 12:55:56 +0800 Subject: [PATCH 048/242] import split --- cluster/cluster_impl/zone_aware_cluster_invoker.go | 3 +++ .../cluster_impl/zone_aware_cluster_invoker_test.go | 12 +++++++++--- cluster/loadbalance/util.go | 5 ++++- config/reference_config.go | 2 +- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/cluster/cluster_impl/zone_aware_cluster_invoker.go b/cluster/cluster_impl/zone_aware_cluster_invoker.go index 5e9b63326c..19dcca6d8f 100644 --- a/cluster/cluster_impl/zone_aware_cluster_invoker.go +++ b/cluster/cluster_impl/zone_aware_cluster_invoker.go @@ -20,6 +20,9 @@ package cluster_impl import ( "context" "fmt" +) + +import ( "github.com/apache/dubbo-go/cluster" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/protocol" diff --git a/cluster/cluster_impl/zone_aware_cluster_invoker_test.go b/cluster/cluster_impl/zone_aware_cluster_invoker_test.go index 28a312ee3c..f1c0ac04b9 100644 --- a/cluster/cluster_impl/zone_aware_cluster_invoker_test.go +++ b/cluster/cluster_impl/zone_aware_cluster_invoker_test.go @@ -20,15 +20,21 @@ package cluster_impl import ( "context" "fmt" + "testing" +) + +import ( + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" +) + +import ( "github.com/apache/dubbo-go/cluster/directory" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/protocol" "github.com/apache/dubbo-go/protocol/invocation" "github.com/apache/dubbo-go/protocol/mock" - "github.com/golang/mock/gomock" - "github.com/stretchr/testify/assert" - "testing" ) func Test_ZoneWareInvokerWithPreferredSuccess(t *testing.T) { diff --git a/cluster/loadbalance/util.go b/cluster/loadbalance/util.go index c76bdbae7e..684ffe11a7 100644 --- a/cluster/loadbalance/util.go +++ b/cluster/loadbalance/util.go @@ -17,10 +17,13 @@ package loadbalance +import ( + "time" +) + import ( "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/protocol" - "time" ) // GetWeight gets weight for load balance strategy diff --git a/config/reference_config.go b/config/reference_config.go index af645e2e30..8d572dc60e 100644 --- a/config/reference_config.go +++ b/config/reference_config.go @@ -20,13 +20,13 @@ package config import ( "context" "fmt" - "github.com/apache/dubbo-go/cluster/cluster_impl" "net/url" "strconv" "time" ) import ( + "github.com/apache/dubbo-go/cluster/cluster_impl" "github.com/creasty/defaults" gxstrings "github.com/dubbogo/gost/strings" ) From 491edb62d9565ef2b318297ceba19812c3966040 Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Mon, 27 Jul 2020 13:38:55 +0800 Subject: [PATCH 049/242] fix review comment --- config_center/apollo/impl_test.go | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/config_center/apollo/impl_test.go b/config_center/apollo/impl_test.go index eec02b5211..c7676ee852 100644 --- a/config_center/apollo/impl_test.go +++ b/config_center/apollo/impl_test.go @@ -202,9 +202,9 @@ func initMockApollo(t *testing.T) *apolloConfiguration { return configuration } -func TestAddListener(t *testing.T) { +func TestListener(t *testing.T) { listener := &apolloDataListener{} - listener.wg.Add(1) + listener.wg.Add(2) apollo := initMockApollo(t) mockConfigRes = `{ "appId": "testApplication_yang", @@ -215,28 +215,14 @@ func TestAddListener(t *testing.T) { }, "releaseKey": "20191104105242-0f13805d89f834a4" }` + //test add apollo.AddListener(mockNamespace, listener) listener.wg.Wait() assert.Equal(t, "mockDubbog.properties", listener.event) assert.Greater(t, listener.count, 0) - deleteMockJson(t) -} -func TestRemoveListener(t *testing.T) { - listener := &apolloDataListener{} - apollo := initMockApollo(t) - mockConfigRes = `{ - "appId": "testApplication_yang", - "cluster": "default", - "namespaceName": "mockDubbog.properties", - "configurations": { - "registries.hangzhouzk.username": "11111" - }, - "releaseKey": "20191104105242-0f13805d89f834a4" -}` - apollo.AddListener(mockNamespace, listener) + //test remove apollo.RemoveListener(mockNamespace, listener) - assert.Equal(t, "", listener.event) listenerCount := 0 apollo.listeners.Range(func(key, value interface{}) bool { apolloListener := value.(*apolloListener) @@ -247,7 +233,6 @@ func TestRemoveListener(t *testing.T) { return true }) assert.Equal(t, listenerCount, 0) - assert.Equal(t, listener.count, 0) deleteMockJson(t) } From b33167b117323d2697466dc35ef64395b9b2cf9d Mon Sep 17 00:00:00 2001 From: william feng <> Date: Mon, 27 Jul 2020 00:33:13 +0800 Subject: [PATCH 050/242] enable service level router configuration --- cluster/directory/base_directory.go | 38 +++++++++++--- cluster/directory/base_directory_test.go | 12 +++-- cluster/router/condition/file.go | 32 +++++++++++- cluster/router/condition/file_test.go | 66 +++++++++++++++++++++++- cluster/router/condition/router.go | 8 ++- cluster/router/condition/router_test.go | 31 +++++++++++ common/url.go | 2 +- 7 files changed, 173 insertions(+), 16 deletions(-) diff --git a/cluster/directory/base_directory.go b/cluster/directory/base_directory.go index 8a7e0c0e83..634cee4263 100644 --- a/cluster/directory/base_directory.go +++ b/cluster/directory/base_directory.go @@ -85,15 +85,21 @@ func (dir *BaseDirectory) SetRouters(urls []*common.URL) { for _, url := range urls { routerKey := url.GetParam(constant.ROUTER_KEY, "") - if len(routerKey) > 0 { - factory := extension.GetRouterFactory(url.Protocol) - r, err := factory.NewPriorityRouter(url) - if err != nil { - logger.Errorf("Create router fail. router key: %s, url:%s, error: %+v", routerKey, url.Service(), err) - return + if len(routerKey) == 0 { + continue + } + if url.Protocol == constant.CONDITION_ROUTE_PROTOCOL { + if !dir.isProperRouter(url) { + continue } - routers = append(routers, r) } + factory := extension.GetRouterFactory(url.Protocol) + r, err := factory.NewPriorityRouter(url) + if err != nil { + logger.Errorf("Create router fail. router key: %s, url:%s, error: %+v", routerKey, url.Service(), err) + return + } + routers = append(routers, r) } logger.Infof("Init file condition router success, size: %v", len(routers)) @@ -104,6 +110,24 @@ func (dir *BaseDirectory) SetRouters(urls []*common.URL) { rc.AddRouters(routers) } +func (dir *BaseDirectory) isProperRouter(url *common.URL) bool { + app := url.GetParam(constant.APPLICATION_KEY, "") + serviceKey := dir.GetUrl().ServiceKey() + if serviceKey == "" { + serviceKey = dir.GetUrl().SubURL.ServiceKey() + } + if len(app) > 0 { + if app != dir.GetUrl().GetParam(constant.APPLICATION_KEY, "") { + return false + } + } else { + if url.ServiceKey() != serviceKey { + return false + } + } + return true +} + // Destroy Destroy func (dir *BaseDirectory) Destroy(doDestroy func()) { if dir.destroyed.CAS(false, true) { diff --git a/cluster/directory/base_directory_test.go b/cluster/directory/base_directory_test.go index 8b60163b79..f60eddad79 100644 --- a/cluster/directory/base_directory_test.go +++ b/cluster/directory/base_directory_test.go @@ -48,13 +48,17 @@ func TestNewBaseDirectory(t *testing.T) { } func TestBuildRouterChain(t *testing.T) { - directory := NewBaseDirectory(&url) + + regURL := url + regURL.AddParam(constant.INTERFACE_KEY, "mock-app") + directory := NewBaseDirectory(®URL) assert.NotNil(t, directory) localIP, _ := gxnet.GetLocalIP() rule := base64.URLEncoding.EncodeToString([]byte("true => " + " host = " + localIP)) - routeURL := getRouteUrl(rule) + routeURL := getRouteURL(rule) + routeURL.AddParam(constant.INTERFACE_KEY, "mock-app") routerURLs := make([]*common.URL, 0) routerURLs = append(routerURLs, routeURL) directory.SetRouters(routerURLs) @@ -63,9 +67,9 @@ func TestBuildRouterChain(t *testing.T) { assert.NotNil(t, chain) } -func getRouteUrl(rule string) *common.URL { +func getRouteURL(rule string) *common.URL { anyUrl.AddParam("rule", rule) anyUrl.AddParam("force", "true") anyUrl.AddParam(constant.ROUTER_KEY, "router") - return &url + return &anyUrl } diff --git a/cluster/router/condition/file.go b/cluster/router/condition/file.go index 85658e7eb3..1eba3d6924 100644 --- a/cluster/router/condition/file.go +++ b/cluster/router/condition/file.go @@ -20,6 +20,7 @@ package condition import ( "encoding/base64" "net/url" + "regexp" "strconv" "strings" "sync" @@ -72,13 +73,40 @@ func (f *FileConditionRouter) URL() common.URL { common.WithParamsValue(constant.RULE_KEY, base64.URLEncoding.EncodeToString([]byte(rule))), common.WithParamsValue(constant.ROUTER_KEY, constant.CONDITION_ROUTE_PROTOCOL), common.WithParamsValue(constant.CATEGORY_KEY, constant.ROUTERS_CATEGORY), - common.WithParamsValue(constant.RouterRuleKey, routerRule.Key), - common.WithParamsValue(constant.RouterScope, routerRule.Scope), ) + if routerRule.Scope == constant.RouterApplicationScope { + f.url.AddParam(constant.APPLICATION_KEY, routerRule.Key) + } else { + grp, srv, ver, _ := parseServiceRouterKey(routerRule.Key) + if len(grp) > 0 { + f.url.AddParam(constant.GROUP_KEY, grp) + } + if len(ver) > 0 { + f.url.AddParam(constant.VERSION_KEY, ver) + } + if len(srv) > 0 { + f.url.AddParam(constant.INTERFACE_KEY, srv) + } + } }) return f.url } +func parseServiceRouterKey(key string) (string, string, string, error) { + if len(strings.TrimSpace(key)) == 0 { + return "", "", "", nil + } + reg := regexp.MustCompile(`(.*/{1})?([^:/]+)(:{1}[^:]*)?`) + strs := reg.FindAllStringSubmatch(key, -1) + if strs == nil || len(strs) != 1 { + return "", "", "", perrors.Errorf("Invalid key, service key must follow [{group}/]{service}[:{version}] pattern") + } + grp := strings.TrimSpace(strings.TrimRight(strs[0][1], "/")) + srv := strings.TrimSpace(strs[0][2]) + ver := strings.TrimSpace(strings.TrimLeft(strs[0][3], ":")) + return grp, srv, ver, nil +} + func parseCondition(conditions []string) string { var when, then string for _, condition := range conditions { diff --git a/cluster/router/condition/file_test.go b/cluster/router/condition/file_test.go index a568e08fa8..bd19a0d18c 100644 --- a/cluster/router/condition/file_test.go +++ b/cluster/router/condition/file_test.go @@ -58,5 +58,69 @@ conditions : - "c => d"`)) assert.Nil(t, e) assert.NotNil(t, router) - assert.Equal(t, "condition://0.0.0.0:?category=routers&force=true&key=mock-app&priority=1&router=condition&rule=YSAmIGMgPT4gYiAmIGQ%3D&scope=application", router.URL().String()) + assert.Equal(t, "condition://0.0.0.0:?application=mock-app&category=routers&force=true&priority=1&router=condition&rule=YSAmIGMgPT4gYiAmIGQ%3D", router.URL().String()) + + router, e = NewFileConditionRouter([]byte(`scope: service +key: mock-service +priority: 1 +force: true +conditions : + - "a => b" + - "c => d"`)) + assert.Nil(t, e) + assert.NotNil(t, router) + assert.Equal(t, "condition://0.0.0.0:?category=routers&force=true&interface=mock-service&priority=1&router=condition&rule=YSAmIGMgPT4gYiAmIGQ%3D", router.URL().String()) + + router, e = NewFileConditionRouter([]byte(`scope: service +key: grp1/mock-service:v1 +priority: 1 +force: true +conditions : + - "a => b" + - "c => d"`)) + assert.Nil(t, e) + assert.NotNil(t, router) + assert.Equal(t, "condition://0.0.0.0:?category=routers&force=true&group=grp1&interface=mock-service&priority=1&router=condition&rule=YSAmIGMgPT4gYiAmIGQ%3D&version=v1", router.URL().String()) +} + +func TestParseServiceRouterKey(t *testing.T) { + testString := " mock-group / mock-service:1.0.0" + grp, srv, ver, err := parseServiceRouterKey(testString) + assert.Equal(t, "mock-group", grp) + assert.Equal(t, "mock-service", srv) + assert.Equal(t, "1.0.0", ver) + + testString = "mock-group/mock-service" + grp, srv, ver, err = parseServiceRouterKey(testString) + assert.Equal(t, "mock-group", grp) + assert.Equal(t, "mock-service", srv) + assert.Equal(t, "", ver) + + testString = "mock-service:1.0.0" + grp, srv, ver, err = parseServiceRouterKey(testString) + assert.Equal(t, "", grp) + assert.Equal(t, "mock-service", srv) + assert.Equal(t, "1.0.0", ver) + + testString = "mock-service" + grp, srv, ver, err = parseServiceRouterKey(testString) + assert.Equal(t, "", grp) + assert.Equal(t, "mock-service", srv) + assert.Equal(t, "", ver) + + testString = "/mock-service:" + grp, srv, ver, err = parseServiceRouterKey(testString) + assert.Equal(t, "", grp) + assert.Equal(t, "mock-service", srv) + assert.Equal(t, "", ver) + + testString = "grp:mock-service:123" + grp, srv, ver, err = parseServiceRouterKey(testString) + assert.Error(t, err) + + testString = "" + grp, srv, ver, err = parseServiceRouterKey(testString) + assert.Equal(t, "", grp) + assert.Equal(t, "", srv) + assert.Equal(t, "", ver) } diff --git a/cluster/router/condition/router.go b/cluster/router/condition/router.go index 40a251573f..8940805061 100644 --- a/cluster/router/condition/router.go +++ b/cluster/router/condition/router.go @@ -117,7 +117,13 @@ func NewConditionRouter(url *common.URL) (*ConditionRouter, error) { } router.url = url - router.priority = url.GetParamInt(constant.RouterPriority, 0) + var defaultPriority int64 + if url.GetParam(constant.APPLICATION_KEY, "") != "" { + defaultPriority = 150 + } else { + defaultPriority = 140 + } + router.priority = url.GetParamInt(constant.RouterPriority, defaultPriority) router.Force = url.GetParamBool(constant.RouterForce, false) router.enabled = url.GetParamBool(constant.RouterEnabled, true) diff --git a/cluster/router/condition/router_test.go b/cluster/router/condition/router_test.go index 35b8656b5f..c27a1d9552 100644 --- a/cluster/router/condition/router_test.go +++ b/cluster/router/condition/router_test.go @@ -22,6 +22,7 @@ import ( ) import ( + "github.com/apache/dubbo-go/common" "github.com/dubbogo/gost/container/set" "github.com/stretchr/testify/assert" ) @@ -55,3 +56,33 @@ func TestParseRule(t *testing.T) { assert.EqualValues(t, matchPair["method"].Mismatches, gxset.NewSet(`sayHello`, `sayGoodDay`)) assert.EqualValues(t, matchPair["method"].Matches, gxset.NewSet(`sayGoodBye`)) } + +func TestNewConditionRouter(t *testing.T) { + url, _ := common.NewURL(`condition://0.0.0.0:?application=mock-app&category=routers&force=true&priority=1&router=condition&rule=YSAmIGMgPT4gYiAmIGQ%3D`) + router, err := NewConditionRouter(&url) + assert.Nil(t, err) + assert.Equal(t, true, router.Enabled()) + assert.Equal(t, true, router.Force) + assert.Equal(t, int64(1), router.Priority()) + whenRule, _ := parseRule("a & c") + thenRule, _ := parseRule("b & d") + assert.EqualValues(t, router.WhenCondition, whenRule) + assert.EqualValues(t, router.ThenCondition, thenRule) + + router, err = NewConditionRouter(nil) + assert.Error(t, err) + + url, _ = common.NewURL(`condition://0.0.0.0:?application=mock-app&category=routers&force=true&priority=1&router=condition&rule=YSAmT4gYiAmIGQ%3D`) + router, err = NewConditionRouter(&url) + assert.Error(t, err) + + url, _ = common.NewURL(`condition://0.0.0.0:?application=mock-app&category=routers&force=true&router=condition&rule=YSAmIGMgPT4gYiAmIGQ%3D`) + router, err = NewConditionRouter(&url) + assert.Nil(t, err) + assert.Equal(t, int64(150), router.Priority()) + + url, _ = common.NewURL(`condition://0.0.0.0:?category=routers&force=true&interface=mock-service&router=condition&rule=YSAmIGMgPT4gYiAmIGQ%3D`) + router, err = NewConditionRouter(&url) + assert.Nil(t, err) + assert.Equal(t, int64(140), router.Priority()) +} diff --git a/common/url.go b/common/url.go index 807d0ed5ef..ec6dce9175 100644 --- a/common/url.go +++ b/common/url.go @@ -381,7 +381,7 @@ func (c URL) Service() string { if service != "" { return service } else if c.SubURL != nil { - service = c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/")) + service = c.SubURL.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/")) if service != "" { // if url.path is "" then return suburl's path, special for registry url return service } From 740be76f4e2648ace14da1d18d2ff5335cc76885 Mon Sep 17 00:00:00 2001 From: william feng <> Date: Tue, 28 Jul 2020 12:39:53 +0800 Subject: [PATCH 051/242] resolve comment from zouyx --- cluster/router/condition/file.go | 30 +++++++++++++++---------- cluster/router/condition/router_rule.go | 2 +- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/cluster/router/condition/file.go b/cluster/router/condition/file.go index 1eba3d6924..d4293c9809 100644 --- a/cluster/router/condition/file.go +++ b/cluster/router/condition/file.go @@ -76,17 +76,20 @@ func (f *FileConditionRouter) URL() common.URL { ) if routerRule.Scope == constant.RouterApplicationScope { f.url.AddParam(constant.APPLICATION_KEY, routerRule.Key) - } else { - grp, srv, ver, _ := parseServiceRouterKey(routerRule.Key) - if len(grp) > 0 { - f.url.AddParam(constant.GROUP_KEY, grp) - } - if len(ver) > 0 { - f.url.AddParam(constant.VERSION_KEY, ver) - } - if len(srv) > 0 { - f.url.AddParam(constant.INTERFACE_KEY, srv) - } + return + } + grp, srv, ver, e := parseServiceRouterKey(routerRule.Key) + if e != nil { + return + } + if len(grp) > 0 { + f.url.AddParam(constant.GROUP_KEY, grp) + } + if len(ver) > 0 { + f.url.AddParam(constant.VERSION_KEY, ver) + } + if len(srv) > 0 { + f.url.AddParam(constant.INTERFACE_KEY, srv) } }) return f.url @@ -98,9 +101,12 @@ func parseServiceRouterKey(key string) (string, string, string, error) { } reg := regexp.MustCompile(`(.*/{1})?([^:/]+)(:{1}[^:]*)?`) strs := reg.FindAllStringSubmatch(key, -1) - if strs == nil || len(strs) != 1 { + if strs == nil || len(strs) > 1 { return "", "", "", perrors.Errorf("Invalid key, service key must follow [{group}/]{service}[:{version}] pattern") } + if len(strs[0]) != 4 { + return "", "", "", perrors.Errorf("Parse service router key failed") + } grp := strings.TrimSpace(strings.TrimRight(strs[0][1], "/")) srv := strings.TrimSpace(strs[0][2]) ver := strings.TrimSpace(strings.TrimLeft(strs[0][3], ":")) diff --git a/cluster/router/condition/router_rule.go b/cluster/router/condition/router_rule.go index 576dea87f9..9f2bf2d41a 100644 --- a/cluster/router/condition/router_rule.go +++ b/cluster/router/condition/router_rule.go @@ -58,7 +58,7 @@ func getRule(rawRule string) (*RouterRule, error) { return r, err } r.RawRule = rawRule - if len(r.Conditions) != 0 && r.Key != "" && (r.Scope == constant.RouterApplicationScope || r.Scope == constant.RouterServiceScope) { + if len(r.Conditions) > 0 && len(r.Key) > 0 && (r.Scope == constant.RouterApplicationScope || r.Scope == constant.RouterServiceScope) { r.Valid = true } From fdcd408f3986a047893bb71874510d9147b7d186 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 28 Jul 2020 21:49:59 +0800 Subject: [PATCH 052/242] let go_restful_server support same url and different methodType --- .../server/server_impl/go_restful_server.go | 24 ++++---- .../server_impl/go_restful_server_test.go | 57 +++++++++++++++++++ 2 files changed, 70 insertions(+), 11 deletions(-) create mode 100644 protocol/rest/server/server_impl/go_restful_server_test.go diff --git a/protocol/rest/server/server_impl/go_restful_server.go b/protocol/rest/server/server_impl/go_restful_server.go index c7d971fcaa..6fb9ee8daa 100644 --- a/protocol/rest/server/server_impl/go_restful_server.go +++ b/protocol/rest/server/server_impl/go_restful_server.go @@ -48,8 +48,8 @@ var filterSlice []restful.FilterFunction // GoRestfulServer a rest server implement by go-restful type GoRestfulServer struct { - srv *http.Server - container *restful.Container + srv *http.Server + ws *restful.WebService } // NewGoRestfulServer a constructor of GoRestfulServer @@ -60,13 +60,17 @@ func NewGoRestfulServer() server.RestServer { // Start go-restful server // It will add all go-restful filters func (grs *GoRestfulServer) Start(url common.URL) { - grs.container = restful.NewContainer() + container := restful.NewContainer() for _, filter := range filterSlice { - grs.container.Filter(filter) + container.Filter(filter) } grs.srv = &http.Server{ - Handler: grs.container, + Handler: container, } + grs.ws = &restful.WebService{} + grs.ws.Path("/") + grs.ws.SetDynamicRoutes(true) + container.Add(grs.ws) ln, err := net.Listen("tcp", url.Location) if err != nil { panic(perrors.New(fmt.Sprintf("Restful Server start error:%v", err))) @@ -83,23 +87,21 @@ func (grs *GoRestfulServer) Start(url common.URL) { // Publish a http api in go-restful server // The routeFunc should be invoked when the server receive a request func (grs *GoRestfulServer) Deploy(restMethodConfig *config.RestMethodConfig, routeFunc func(request server.RestServerRequest, response server.RestServerResponse)) { - ws := &restful.WebService{} + rf := func(req *restful.Request, resp *restful.Response) { routeFunc(NewGoRestfulRequestAdapter(req), resp) } - ws.Path(restMethodConfig.Path). + grs.ws.Route(grs.ws.Method(restMethodConfig.MethodType). Produces(strings.Split(restMethodConfig.Produces, ",")...). Consumes(strings.Split(restMethodConfig.Consumes, ",")...). - Route(ws.Method(restMethodConfig.MethodType).To(rf)) - grs.container.Add(ws) - + Path(restMethodConfig.Path).To(rf)) } // Delete a http api in go-restful server func (grs *GoRestfulServer) UnDeploy(restMethodConfig *config.RestMethodConfig) { ws := new(restful.WebService) ws.Path(restMethodConfig.Path) - err := grs.container.Remove(ws) + err := grs.ws.RemoveRoute(restMethodConfig.Path, restMethodConfig.MethodType) if err != nil { logger.Warnf("[Go restful] Remove web service error:%v", err) } diff --git a/protocol/rest/server/server_impl/go_restful_server_test.go b/protocol/rest/server/server_impl/go_restful_server_test.go new file mode 100644 index 0000000000..29a9ef89f7 --- /dev/null +++ b/protocol/rest/server/server_impl/go_restful_server_test.go @@ -0,0 +1,57 @@ +/* + * 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 server_impl + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/protocol/rest/config" + "github.com/apache/dubbo-go/protocol/rest/server" +) + +func TestGoRestfulServerDeploySameUrl(t *testing.T) { + grs := NewGoRestfulServer() + url, err := common.NewURL("http://127.0.0.1:43121") + assert.NoError(t, err) + grs.Start(url) + rmc := &config.RestMethodConfig{ + Produces: "*/*", + Consumes: "*/*", + MethodType: "POST", + Path: "/test", + } + f := func(request server.RestServerRequest, response server.RestServerResponse) {} + grs.Deploy(rmc, f) + rmc1 := &config.RestMethodConfig{ + Produces: "*/*", + Consumes: "*/*", + MethodType: "GET", + Path: "/test", + } + grs.Deploy(rmc1, f) + grs.UnDeploy(rmc) + grs.UnDeploy(rmc1) + grs.Destroy() +} From 08c36747999a757bf00a663722bf1278ca635c42 Mon Sep 17 00:00:00 2001 From: tiecheng <18768171164@163.com> Date: Wed, 29 Jul 2020 17:33:35 +0800 Subject: [PATCH 053/242] add comment for public method --- cluster/cluster_impl/zone_aware_cluster.go | 2 ++ cluster/cluster_impl/zone_aware_cluster_invoker.go | 1 + 2 files changed, 3 insertions(+) diff --git a/cluster/cluster_impl/zone_aware_cluster.go b/cluster/cluster_impl/zone_aware_cluster.go index eadc5da6d8..b5d43c7040 100644 --- a/cluster/cluster_impl/zone_aware_cluster.go +++ b/cluster/cluster_impl/zone_aware_cluster.go @@ -36,10 +36,12 @@ func NewZoneAwareCluster() cluster.Cluster { return &zoneAwareCluster{} } +// Join returns a zoneAwareClusterInvoker instance func (cluster *zoneAwareCluster) Join(directory cluster.Directory) protocol.Invoker { return newZoneAwareClusterInvoker(directory) } +// Get cluster name func GetZoneAwareName() string { return zoneAware } diff --git a/cluster/cluster_impl/zone_aware_cluster_invoker.go b/cluster/cluster_impl/zone_aware_cluster_invoker.go index 19dcca6d8f..f3b5fbe523 100644 --- a/cluster/cluster_impl/zone_aware_cluster_invoker.go +++ b/cluster/cluster_impl/zone_aware_cluster_invoker.go @@ -45,6 +45,7 @@ func newZoneAwareClusterInvoker(directory cluster.Directory) protocol.Invoker { } } +// nolint func (invoker *zoneAwareClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { invokers := invoker.directory.List(invocation) From 4ef21cbbd82241d0c02700a64aeb517b4887e125 Mon Sep 17 00:00:00 2001 From: watermelo <80680489@qq.com> Date: Thu, 30 Jul 2020 00:23:56 +0800 Subject: [PATCH 054/242] Add: add ip address match function --- cluster/router/tag/tag_router.go | 215 +++++++++++++++++++++----- cluster/router/tag/tag_router_test.go | 35 +++-- 2 files changed, 198 insertions(+), 52 deletions(-) diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index 7417efcf4d..0ef7387f25 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -18,8 +18,11 @@ package tag import ( + "errors" "fmt" + "net" "strconv" + "strings" ) import ( @@ -81,20 +84,27 @@ func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocati addresses, _ = tagRouterRuleCopy.getTagNameToAddresses()[tag] // filter by dynamic tag group first if len(addresses) > 0 { - result = filterAddressMatches(invokers, addresses) + filterAddressMatches := func(invoker protocol.Invoker) bool { + url := invoker.GetUrl() + if len(addresses) > 0 && checkAddressMatch(addresses, url.Ip, url.Port) { + return true + } + return false + } + result = filterInvoker(invokers, filterAddressMatches) if len(result) > 0 || tagRouterRuleCopy.Force { return result } } else { // dynamic tag group doesn't have any item about the requested app OR it's null after filtered by // dynamic tag group but force=false. check static tag - cond := func(invoker protocol.Invoker) bool { + filter := func(invoker protocol.Invoker) bool { if invoker.GetUrl().GetParam(constant.Tagkey, "") == tag { return true } return false } - result = filterCondition(invokers, cond) + result = filterInvoker(invokers, filter) } // If there's no tagged providers that can match the current tagged request. force.tag is set by default // to false, which means it will invoke any providers without a tag unless it's explicitly disallowed. @@ -102,20 +112,33 @@ func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocati return result } else { // FAILOVER: return all Providers without any tags. - result = filterAddressNotMatches(invokers, tagRouterRuleCopy.getAddresses()) - cond := func(invoker protocol.Invoker) bool { + filterAddressNotMatches := func(invoker protocol.Invoker) bool { + url := invoker.GetUrl() + if len(addresses) == 0 || !checkAddressMatch(tagRouterRuleCopy.getAddresses(), url.Ip, url.Port) { + return true + } + return false + } + filterTagIsEmpty := func(invoker protocol.Invoker) bool { if invoker.GetUrl().GetParam(constant.Tagkey, "") == "" { return true } return false } - return filterCondition(result, cond) + return filterInvoker(invokers, filterAddressNotMatches, filterTagIsEmpty) } } else { // return all addresses in dynamic tag group. addresses = tagRouterRuleCopy.getAddresses() if len(addresses) > 0 { - result = filterAddressNotMatches(invokers, addresses) + filterAddressNotMatches := func(invoker protocol.Invoker) bool { + url := invoker.GetUrl() + if len(addresses) == 0 || !checkAddressMatch(addresses, url.Ip, url.Port) { + return true + } + return false + } + result = filterInvoker(invokers, filterAddressNotMatches) // 1. all addresses are in dynamic tag group, return empty list. if len(result) == 0 { return result @@ -123,11 +146,11 @@ func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocati // 2. if there are some addresses that are not in any dynamic tag group, continue to filter using the // static tag group. } - cond := func(invoker protocol.Invoker) bool { + filter := func(invoker protocol.Invoker) bool { localTag := invoker.GetUrl().GetParam(constant.Tagkey, "") return localTag == "" || !(tagRouterRuleCopy.hasTag(localTag)) } - return filterCondition(result, cond) + return filterInvoker(result, filter) } } @@ -185,53 +208,163 @@ func isForceUseTag(url *common.URL, invocation protocol.Invocation) bool { return false } -func filterAddressMatches(invokers []protocol.Invoker, addresses []string) []protocol.Invoker { - var idx int +type filter func(protocol.Invoker) bool + +func filterInvoker(invokers []protocol.Invoker, filters ...filter) []protocol.Invoker { + var res []protocol.Invoker +OUTER: for _, invoker := range invokers { - url := invoker.GetUrl() - if !(len(addresses) > 0 && checkAddressMatch(addresses, url.Ip, url.Port)) { - continue + for _, filter := range filters { + if !filter(invoker) { + continue OUTER + } } - invokers[idx] = invoker - idx++ + res = append(res, invoker) } - return invokers[:idx] + return res } -func filterAddressNotMatches(invokers []protocol.Invoker, addresses []string) []protocol.Invoker { - var idx int - for _, invoker := range invokers { - url := invoker.GetUrl() - if !(len(addresses) == 0 || !checkAddressMatch(addresses, url.Ip, url.Port)) { - continue +// TODO 需要搬到 dubbogo/gost, 可以先 review +func checkAddressMatch(addresses []string, host, port string) bool { + for _, address := range addresses { + if matchIp(address, host, port) { + return true + } + if address == constant.ANYHOST_VALUE+":"+port { + return true } - invokers[idx] = invoker - idx++ } - return invokers[:idx] + return false } -func filterCondition(invokers []protocol.Invoker, condition func(protocol.Invoker) bool) []protocol.Invoker { - var idx int - for _, invoker := range invokers { - if !condition(invoker) { +func matchIp(pattern, host, port string) bool { + // if the pattern is subnet format, it will not be allowed to config port param in pattern. + if strings.Contains(pattern, "/") { + _, subnet, _ := net.ParseCIDR(pattern) + if subnet != nil && subnet.Contains(net.ParseIP(host)) { + return true + } + return false + } + return matchIpRange(pattern, host, port) +} + +func matchIpRange(pattern, host, port string) bool { + if pattern == "" || host == "" { + logger.Error("Illegal Argument pattern or hostName. Pattern:" + pattern + ", Host:" + host) + return false + } + + pattern = strings.TrimSpace(pattern) + if "*.*.*.*" == pattern || "*" == pattern { + return true + } + + isIpv4 := true + ip4 := net.ParseIP(host).To4() + + if ip4 == nil { + isIpv4 = false + } + + hostAndPort := getPatternHostAndPort(pattern, isIpv4) + if hostAndPort[1] != "" && hostAndPort[1] != port { + return false + } + + pattern = hostAndPort[0] + // TODO 常量化 + splitCharacter := "\\." + if !isIpv4 { + splitCharacter = ":" + } + + mask := strings.Split(pattern, splitCharacter) + // check format of pattern + if err := checkHostPattern(pattern, mask, isIpv4); err != nil { + logger.Error(err) + return false + } + + if pattern == host { + return true + } + + // short name condition + if !ipPatternContains(pattern) { + return pattern == host + } + + // ip 段 + ipAddress := strings.Split(host, splitCharacter) + for i := 0; i < len(mask); i++ { + if "*" == mask[i] || mask[i] == ipAddress[i] { + continue + } else if strings.Contains(mask[i], "-") { + rangeNumStrs := strings.Split(mask[i], "-") + if len(rangeNumStrs) != 2 { + logger.Error("There is wrong format of ip Address: " + mask[i]) + return false + } + min := getNumOfIpSegment(rangeNumStrs[0], isIpv4) + max := getNumOfIpSegment(rangeNumStrs[1], isIpv4) + ip := getNumOfIpSegment(ipAddress[i], isIpv4) + if ip < min || ip > max { + return false + } + } else if "0" == ipAddress[i] && "0" == mask[i] || "00" == mask[i] || "000" == mask[i] || "0000" == mask[i] { continue + } else if mask[i] != ipAddress[i] { + return false } - invokers[idx] = invoker - idx++ } - return invokers[:idx] + return true } -func checkAddressMatch(addresses []string, host, port string) bool { - for _, address := range addresses { - // TODO ip match - if address == host+":"+port { - return true +func ipPatternContains(pattern string) bool { + return strings.Contains(pattern, "*") || strings.Contains(pattern, "-") +} + +func checkHostPattern(pattern string, mask []string, isIpv4 bool) error { + if !isIpv4 { + if len(mask) != 8 && ipPatternContains(pattern) { + return errors.New("If you config ip expression that contains '*' or '-', please fill qualified ip pattern like 234e:0:4567:0:0:0:3d:*. ") } - if address == constant.ANYHOST_VALUE+":"+port { - return true + if len(mask) != 8 && !strings.Contains(pattern, "::") { + return errors.New("The host is ipv6, but the pattern is not ipv6 pattern : " + pattern) + } + } else { + if len(mask) != 4 { + return errors.New("The host is ipv4, but the pattern is not ipv4 pattern : " + pattern) } } - return false + return nil +} + +func getPatternHostAndPort(pattern string, isIpv4 bool) []string { + result := make([]string, 2) + if strings.HasPrefix(pattern, "[") && strings.Contains(pattern, "]:") { + end := strings.Index(pattern, "]:") + result[0] = pattern[1:end] + result[1] = pattern[end+2:] + } else if strings.HasPrefix(pattern, "[") && strings.HasSuffix(pattern, "]") { + result[0] = pattern[1 : len(pattern)-1] + result[1] = "" + } else if isIpv4 && strings.Contains(pattern, ":") { + end := strings.Index(pattern, ":") + result[0] = pattern[:end] + result[1] = pattern[end+1:] + } else { + result[0] = pattern + } + return result +} + +func getNumOfIpSegment(ipSegment string, isIpv4 bool) int { + if isIpv4 { + ipSeg, _ := strconv.Atoi(ipSegment) + return ipSeg + } + ipSeg, _ := strconv.ParseInt(ipSegment, 0, 16) + return int(ipSeg) } diff --git a/cluster/router/tag/tag_router_test.go b/cluster/router/tag/tag_router_test.go index 3a053de4b0..4f2a8a9fca 100644 --- a/cluster/router/tag/tag_router_test.go +++ b/cluster/router/tag/tag_router_test.go @@ -19,7 +19,6 @@ package tag import ( "context" - "github.com/apache/dubbo-go/common/constant" "testing" ) @@ -29,16 +28,18 @@ import ( import ( "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/protocol" "github.com/apache/dubbo-go/protocol/invocation" ) const ( - tagRouterTestHangZhouUrl = "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=hangzhou" - tagRouterTestShangHaiUrl = "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=shanghai" - tagRouterTestBeijingUrl = "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=beijing" - tagRouterTestUserConsumer = "dubbo://127.0.0.1:20000/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true" - tagRouterTestUserConsumerTag = "dubbo://127.0.0.1:20000/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&dubbo.force.tag=true" + tagRouterTestHangZhouUrl = "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=hangzhou" + tagRouterTestShangHaiUrl = "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=shanghai" + tagRouterTestBeijingUrl = "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=beijing" + tagRouterTestEnabledBeijingUrl = "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=false&dubbo.tag=beijing" + tagRouterTestUserConsumer = "dubbo://127.0.0.1:20000/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true" + tagRouterTestUserConsumerTag = "dubbo://127.0.0.1:20000/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&dubbo.force.tag=true" tagRouterTestDubboTag = "dubbo.tag" tagRouterTestDubboForceTag = "dubbo.force.tag" @@ -162,24 +163,36 @@ func TestTagRouterRouteNoForce(t *testing.T) { assert.Equal(t, 3, len(invRst2)) } -func TestFilterCondition(t *testing.T) { +func TestFilterInvoker(t *testing.T) { u2, e2 := common.NewURL(tagRouterTestHangZhouUrl) u3, e3 := common.NewURL(tagRouterTestShangHaiUrl) u4, e4 := common.NewURL(tagRouterTestBeijingUrl) + u5, e5 := common.NewURL(tagRouterTestEnabledBeijingUrl) assert.Nil(t, e2) assert.Nil(t, e3) assert.Nil(t, e4) + assert.Nil(t, e5) inv2 := NewMockInvoker(u2) inv3 := NewMockInvoker(u3) inv4 := NewMockInvoker(u4) + inv5 := NewMockInvoker(u5) var invokers []protocol.Invoker - invokers = append(invokers, inv2, inv3, inv4) - cond := func(invoker protocol.Invoker) bool { + invokers = append(invokers, inv2, inv3, inv4, inv5) + filterTag := func(invoker protocol.Invoker) bool { if invoker.GetUrl().GetParam(constant.Tagkey, "") == "beijing" { return true } return false } - res := filterCondition(invokers, cond) - assert.Equal(t, []protocol.Invoker{inv4}, res) + res := filterInvoker(invokers, filterTag) + assert.Equal(t, []protocol.Invoker{inv4, inv5}, res) + flag := true + filterEnabled := func(invoker protocol.Invoker) bool { + if invoker.GetUrl().GetParamBool(constant.RouterEnabled, false) == flag { + return true + } + return false + } + res2 := filterInvoker(invokers, filterTag, filterEnabled) + assert.Equal(t, []protocol.Invoker{inv4}, res2) } From 891ec6cbb7ed0bda93d9faebcde718fc6a97c56c Mon Sep 17 00:00:00 2001 From: watermelo <80680489@qq.com> Date: Thu, 30 Jul 2020 01:28:18 +0800 Subject: [PATCH 055/242] Add: add listener for tag router --- cluster/router/tag/tag_router.go | 57 +++++++++++++++++++++++++++++--- common/constant/key.go | 3 ++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index 0ef7387f25..8389fc26ad 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -20,6 +20,7 @@ package tag import ( "errors" "fmt" + "github.com/apache/dubbo-go/common/config" "net" "strconv" "strings" @@ -43,6 +44,7 @@ type tagRouter struct { tagRouterRule *RouterRule enabled bool priority int64 + application string } func NewTagRouter(url *common.URL) (*tagRouter, error) { @@ -60,6 +62,15 @@ func (c *tagRouter) isEnabled() bool { return c.enabled } +func (c *tagRouter) SetApplication(app string) { + c.application = app +} + +func (c *tagRouter) tagRouterRuleCopy() RouterRule { + routerRule := *c.tagRouterRule + return routerRule +} + func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { if !c.isEnabled() { return invokers @@ -68,8 +79,8 @@ func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocati return invokers } // since the rule can be changed by config center, we should copy one to use. - tagRouterRuleCopy := c.tagRouterRule - if tagRouterRuleCopy == nil || !tagRouterRuleCopy.Valid || !tagRouterRuleCopy.Enabled { + tagRouterRuleCopy := c.tagRouterRuleCopy() + if !tagRouterRuleCopy.Valid || !tagRouterRuleCopy.Enabled { return filterUsingStaticTag(invokers, url, invocation) } tag, ok := invocation.Attachments()[constant.Tagkey] @@ -155,7 +166,7 @@ func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocati } func (c *tagRouter) Process(event *config_center.ConfigChangeEvent) { - logger.Infof("Notification of dynamic tag rule, change type is:[%s] , raw rule is:[%v]", event.ConfigType, event.Value) + logger.Infof("Notification of tag rule, change type is:[%s] , raw rule is:[%v]", event.ConfigType, event.Value) if remoting.EventTypeDel == event.ConfigType { c.tagRouterRule = nil return @@ -177,6 +188,44 @@ func (c *tagRouter) Process(event *config_center.ConfigChangeEvent) { } } +func (c *tagRouter) Notify(invokers []protocol.Invoker) { + if len(invokers) == 0 { + return + } + invoker := invokers[0] + url := invoker.GetUrl() + providerApplication := url.GetParam(constant.RemoteApplicationKey, "") + if providerApplication == "" { + logger.Error("TagRouter must getConfig from or subscribe to a specific application, but the application " + + "in this TagRouter is not specified.") + return + } + dynamicConfiguration := config.GetEnvInstance().GetDynamicConfiguration() + if dynamicConfiguration == nil { + logger.Error("Get dynamicConfiguration fail, dynamicConfiguration is nil, init config center plugin please") + return + } + + if providerApplication != c.application { + dynamicConfiguration.RemoveListener(c.application+constant.TagRouterRuleSuffix, c) + } + + routerKey := providerApplication + constant.TagRouterRuleSuffix + dynamicConfiguration.AddListener(routerKey, c) + //get rule + rule, err := dynamicConfiguration.GetRule(routerKey, config_center.WithGroup(config_center.DEFAULT_GROUP)) + if len(rule) == 0 || err != nil { + logger.Errorf("Get rule fail, config rule{%s}, error{%v}", rule, err) + return + } + if rule != "" { + c.Process(&config_center.ConfigChangeEvent{ + Key: routerKey, + Value: rule, + ConfigType: remoting.EventTypeUpdate}) + } +} + func (c *tagRouter) URL() common.URL { return *c.url } @@ -185,6 +234,7 @@ func (c *tagRouter) Priority() int64 { return c.priority } +// If there's no dynamic tag rule being set, use static tag in URL func filterUsingStaticTag(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { if tag, ok := invocation.Attachments()[constant.Tagkey]; ok { result := make([]protocol.Invoker, 0, 8) @@ -295,7 +345,6 @@ func matchIpRange(pattern, host, port string) bool { return pattern == host } - // ip 段 ipAddress := strings.Split(host, splitCharacter) for i := 0; i < len(mask); i++ { if "*" == mask[i] || mask[i] == ipAddress[i] { diff --git a/common/constant/key.go b/common/constant/key.go index cd23dd0f1a..a86e797ae5 100644 --- a/common/constant/key.go +++ b/common/constant/key.go @@ -187,6 +187,9 @@ const ( HealthCheckRouterName = "health_check" // TagRouterName Specify the name of TagRouter TagRouterName = "tag" + // TagRouterRuleSuffix Specify tag router suffix + TagRouterRuleSuffix = ".tag-router" + RemoteApplicationKey = "remote.application" // ConditionRouterRuleSuffix Specify condition router suffix ConditionRouterRuleSuffix = ".condition-router" From 8580ad0798097de164d9b855584757faeee6eb50 Mon Sep 17 00:00:00 2001 From: watermelo <80680489@qq.com> Date: Thu, 30 Jul 2020 11:54:02 +0800 Subject: [PATCH 056/242] Mod: update tag router unit test --- cluster/router/tag/tag_router.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index 24170c101d..093a31ef36 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -20,7 +20,6 @@ package tag import ( "errors" "fmt" - "github.com/apache/dubbo-go/common/config" "net" "strconv" "strings" @@ -32,6 +31,7 @@ import ( import ( "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/config" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/config_center" @@ -70,6 +70,7 @@ func (c *tagRouter) SetApplication(app string) { } func (c *tagRouter) tagRouterRuleCopy() RouterRule { + fmt.Println(c.tagRouterRule, "fuck") routerRule := *c.tagRouterRule return routerRule } @@ -82,11 +83,11 @@ func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocati if len(invokers) == 0 { return invokers } - // since the rule can be changed by config center, we should copy one to use. - tagRouterRuleCopy := c.tagRouterRuleCopy() - if !tagRouterRuleCopy.Valid || !tagRouterRuleCopy.Enabled { + if c.tagRouterRule == nil || !c.tagRouterRule.Valid || !c.tagRouterRule.Enabled { return filterUsingStaticTag(invokers, url, invocation) } + // since the rule can be changed by config center, we should copy one to use. + tagRouterRuleCopy := c.tagRouterRuleCopy() tag, ok := invocation.Attachments()[constant.Tagkey] if !ok { tag = url.GetParam(constant.Tagkey, "") From f2261ce180054565e5b7ba3b38791c86fb648351 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 30 Jul 2020 19:27:45 +0800 Subject: [PATCH 057/242] format code --- .../server/server_impl/go_restful_server_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/protocol/rest/server/server_impl/go_restful_server_test.go b/protocol/rest/server/server_impl/go_restful_server_test.go index 29a9ef89f7..b1e66063bf 100644 --- a/protocol/rest/server/server_impl/go_restful_server_test.go +++ b/protocol/rest/server/server_impl/go_restful_server_test.go @@ -37,18 +37,18 @@ func TestGoRestfulServerDeploySameUrl(t *testing.T) { assert.NoError(t, err) grs.Start(url) rmc := &config.RestMethodConfig{ - Produces: "*/*", - Consumes: "*/*", - MethodType: "POST", - Path: "/test", + Produces: "*/*", + Consumes: "*/*", + MethodType: "POST", + Path: "/test", } f := func(request server.RestServerRequest, response server.RestServerResponse) {} grs.Deploy(rmc, f) rmc1 := &config.RestMethodConfig{ - Produces: "*/*", - Consumes: "*/*", - MethodType: "GET", - Path: "/test", + Produces: "*/*", + Consumes: "*/*", + MethodType: "GET", + Path: "/test", } grs.Deploy(rmc1, f) grs.UnDeploy(rmc) From 6f77a0cf7e0b494472ec9c5e40571e1c68bd238b Mon Sep 17 00:00:00 2001 From: tiecheng <18768171164@163.com> Date: Thu, 30 Jul 2020 19:31:26 +0800 Subject: [PATCH 058/242] add code format and comment --- cluster/cluster_impl/zone_aware_cluster.go | 7 +++-- .../zone_aware_cluster_invoker.go | 9 ++++-- .../zone_aware_cluster_invoker_test.go | 28 +++++++++++++------ config/reference_config.go | 4 ++- config/registry_config.go | 3 +- 5 files changed, 36 insertions(+), 15 deletions(-) diff --git a/cluster/cluster_impl/zone_aware_cluster.go b/cluster/cluster_impl/zone_aware_cluster.go index b5d43c7040..2e19be4762 100644 --- a/cluster/cluster_impl/zone_aware_cluster.go +++ b/cluster/cluster_impl/zone_aware_cluster.go @@ -31,7 +31,10 @@ func init() { extension.SetCluster(zoneAware, NewZoneAwareCluster) } -// NewZoneAwareCluster ... +// NewZoneAwareCluster returns a zoneaware cluster instance. +// +// More than one registry for subscription. +// Usually it is used for choose between registries. func NewZoneAwareCluster() cluster.Cluster { return &zoneAwareCluster{} } @@ -41,7 +44,7 @@ func (cluster *zoneAwareCluster) Join(directory cluster.Directory) protocol.Invo return newZoneAwareClusterInvoker(directory) } -// Get cluster name +// GetZoneAwareName get cluster name func GetZoneAwareName() string { return zoneAware } diff --git a/cluster/cluster_impl/zone_aware_cluster_invoker.go b/cluster/cluster_impl/zone_aware_cluster_invoker.go index f3b5fbe523..60683c32d1 100644 --- a/cluster/cluster_impl/zone_aware_cluster_invoker.go +++ b/cluster/cluster_impl/zone_aware_cluster_invoker.go @@ -56,7 +56,8 @@ func (invoker *zoneAwareClusterInvoker) Invoke(ctx context.Context, invocation p // First, pick the invoker (XXXClusterInvoker) that comes from the local registry, distinguish by a 'preferred' key. for _, invoker := range invokers { - if invoker.IsAvailable() && "true" == invoker.GetUrl().GetParam(constant.REGISTRY_KEY+"."+constant.PREFERRED_KEY, "false") { + if invoker.IsAvailable() && + "true" == invoker.GetUrl().GetParam(constant.REGISTRY_KEY+"."+constant.PREFERRED_KEY, "false") { return invoker.Invoke(ctx, invocation) } } @@ -65,7 +66,8 @@ func (invoker *zoneAwareClusterInvoker) Invoke(ctx context.Context, invocation p zone := invocation.AttachmentsByKey(constant.REGISTRY_ZONE, "") if "" != zone { for _, invoker := range invokers { - if invoker.IsAvailable() && zone == invoker.GetUrl().GetParam(constant.REGISTRY_KEY+"."+constant.ZONE_KEY, "") { + if invoker.IsAvailable() && + zone == invoker.GetUrl().GetParam(constant.REGISTRY_KEY+"."+constant.ZONE_KEY, "") { return invoker.Invoke(ctx, invocation) } } @@ -73,7 +75,8 @@ func (invoker *zoneAwareClusterInvoker) Invoke(ctx context.Context, invocation p force := invocation.AttachmentsByKey(constant.REGISTRY_ZONE_FORCE, "") if "true" == force { return &protocol.RPCResult{ - Err: fmt.Errorf("no registry instance in zone or no available providers in the registry, zone: %v, "+ + Err: fmt.Errorf("no registry instance in zone or "+ + "no available providers in the registry, zone: %v, "+ " registries: %v", zone, invoker.GetUrl()), } } diff --git a/cluster/cluster_impl/zone_aware_cluster_invoker_test.go b/cluster/cluster_impl/zone_aware_cluster_invoker_test.go index f1c0ac04b9..4ac865972a 100644 --- a/cluster/cluster_impl/zone_aware_cluster_invoker_test.go +++ b/cluster/cluster_impl/zone_aware_cluster_invoker_test.go @@ -39,10 +39,13 @@ import ( func Test_ZoneWareInvokerWithPreferredSuccess(t *testing.T) { ctrl := gomock.NewController(t) - // In Go versions 1.14+, if you pass a *testing.T into gomock.NewController(t) you no longer need to call ctrl.Finish(). + // In Go versions 1.14+, if you pass a *testing.T + // into gomock.NewController(t) you no longer need to call ctrl.Finish(). //defer ctrl.Finish() - mockResult := &protocol.RPCResult{Attrs: map[string]string{constant.PREFERRED_KEY: "true"}, Rest: rest{tried: 0, success: true}} + mockResult := &protocol.RPCResult{ + Attrs: map[string]string{constant.PREFERRED_KEY: "true"}, + Rest: rest{tried: 0, success: true}} var invokers []protocol.Invoker for i := 0; i < 2; i++ { @@ -77,7 +80,8 @@ func Test_ZoneWareInvokerWithPreferredSuccess(t *testing.T) { func Test_ZoneWareInvokerWithWeightSuccess(t *testing.T) { ctrl := gomock.NewController(t) - // In Go versions 1.14+, if you pass a *testing.T into gomock.NewController(t) you no longer need to call ctrl.Finish(). + // In Go versions 1.14+, if you pass a *testing.T + // into gomock.NewController(t) you no longer need to call ctrl.Finish(). //defer ctrl.Finish() w1 := "50" @@ -94,13 +98,17 @@ func Test_ZoneWareInvokerWithWeightSuccess(t *testing.T) { url.SetParam(constant.REGISTRY_KEY+"."+constant.WEIGHT_KEY, w1) invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn( func(invocation protocol.Invocation) protocol.Result { - return &protocol.RPCResult{Attrs: map[string]string{constant.WEIGHT_KEY: w1}, Rest: rest{tried: 0, success: true}} + return &protocol.RPCResult{ + Attrs: map[string]string{constant.WEIGHT_KEY: w1}, + Rest: rest{tried: 0, success: true}} }).MaxTimes(100) } else { url.SetParam(constant.REGISTRY_KEY+"."+constant.WEIGHT_KEY, w2) invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn( func(invocation protocol.Invocation) protocol.Result { - return &protocol.RPCResult{Attrs: map[string]string{constant.WEIGHT_KEY: w2}, Rest: rest{tried: 0, success: true}} + return &protocol.RPCResult{ + Attrs: map[string]string{constant.WEIGHT_KEY: w2}, + Rest: rest{tried: 0, success: true}} }).MaxTimes(100) } invokers = append(invokers, invoker) @@ -130,7 +138,8 @@ func Test_ZoneWareInvokerWithZoneSuccess(t *testing.T) { var zoneArray = []string{"hangzhou", "shanghai"} ctrl := gomock.NewController(t) - // In Go versions 1.14+, if you pass a *testing.T into gomock.NewController(t) you no longer need to call ctrl.Finish(). + // In Go versions 1.14+, if you pass a *testing.T + // into gomock.NewController(t) you no longer need to call ctrl.Finish(). //defer ctrl.Finish() var invokers []protocol.Invoker @@ -144,7 +153,9 @@ func Test_ZoneWareInvokerWithZoneSuccess(t *testing.T) { invoker.EXPECT().GetUrl().Return(url).AnyTimes() invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn( func(invocation protocol.Invocation) protocol.Result { - return &protocol.RPCResult{Attrs: map[string]string{constant.ZONE_KEY: zoneValue}, Rest: rest{tried: 0, success: true}} + return &protocol.RPCResult{ + Attrs: map[string]string{constant.ZONE_KEY: zoneValue}, + Rest: rest{tried: 0, success: true}} }) invokers = append(invokers, invoker) } @@ -165,7 +176,8 @@ func Test_ZoneWareInvokerWithZoneSuccess(t *testing.T) { func Test_ZoneWareInvokerWithZoneForceFail(t *testing.T) { ctrl := gomock.NewController(t) - // In Go versions 1.14+, if you pass a *testing.T into gomock.NewController(t) you no longer need to call ctrl.Finish(). + // In Go versions 1.14+, if you pass a *testing.T + // into gomock.NewController(t) you no longer need to call ctrl.Finish(). //defer ctrl.Finish() var invokers []protocol.Invoker diff --git a/config/reference_config.go b/config/reference_config.go index e6e4218979..1ce60d3677 100644 --- a/config/reference_config.go +++ b/config/reference_config.go @@ -151,7 +151,9 @@ func (c *ReferenceConfig) Refer(_ interface{}) { if regUrl != nil { // for multi-subscription scenario, use 'zone-aware' policy by default cluster := extension.GetCluster(cluster_impl.GetZoneAwareName()) - // The invoker wrap sequence would be: ZoneAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, routing happens here) -> Invoker + // The invoker wrap sequence would be: + // ZoneAwareClusterInvoker(StaticDirectory) -> + // FailoverClusterInvoker(RegistryDirectory, routing happens here) -> Invoker c.invoker = cluster.Join(directory.NewStaticDirectory(invokers)) } else { // not a registry url, must be direct invoke. diff --git a/config/registry_config.go b/config/registry_config.go index 92a174d8fb..53c0f9f5d2 100644 --- a/config/registry_config.go +++ b/config/registry_config.go @@ -48,7 +48,8 @@ type RegistryConfig struct { Preferred bool `yaml:"preferred" json:"params,omitempty" property:"preferred"` // The region where the registry belongs, usually used to isolate traffics Zone string `yaml:"zone" json:"params,omitempty" property:"zone"` - // Affects traffic distribution among registries, useful when subscribe to multiple registries Take effect only when no preferred registry is specified. + // Affects traffic distribution among registries, + // useful when subscribe to multiple registries Take effect only when no preferred registry is specified. Weight int64 `yaml:"weight" json:"params,omitempty" property:"weight"` Params map[string]string `yaml:"params" json:"params,omitempty" property:"params"` } From ddf63de278516787480c5ac592b054b7f8358856 Mon Sep 17 00:00:00 2001 From: tiecheng <18768171164@163.com> Date: Fri, 31 Jul 2020 09:30:11 +0800 Subject: [PATCH 059/242] ugly code to elegant --- cluster/cluster_impl/zone_aware_cluster_invoker.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/cluster/cluster_impl/zone_aware_cluster_invoker.go b/cluster/cluster_impl/zone_aware_cluster_invoker.go index 60683c32d1..dd4d319b40 100644 --- a/cluster/cluster_impl/zone_aware_cluster_invoker.go +++ b/cluster/cluster_impl/zone_aware_cluster_invoker.go @@ -56,8 +56,8 @@ func (invoker *zoneAwareClusterInvoker) Invoke(ctx context.Context, invocation p // First, pick the invoker (XXXClusterInvoker) that comes from the local registry, distinguish by a 'preferred' key. for _, invoker := range invokers { - if invoker.IsAvailable() && - "true" == invoker.GetUrl().GetParam(constant.REGISTRY_KEY+"."+constant.PREFERRED_KEY, "false") { + key := constant.REGISTRY_KEY + "." + constant.PREFERRED_KEY + if invoker.IsAvailable() && matchParam("true", key, "false", invoker) { return invoker.Invoke(ctx, invocation) } } @@ -66,8 +66,8 @@ func (invoker *zoneAwareClusterInvoker) Invoke(ctx context.Context, invocation p zone := invocation.AttachmentsByKey(constant.REGISTRY_ZONE, "") if "" != zone { for _, invoker := range invokers { - if invoker.IsAvailable() && - zone == invoker.GetUrl().GetParam(constant.REGISTRY_KEY+"."+constant.ZONE_KEY, "") { + key := constant.REGISTRY_KEY + "." + constant.ZONE_KEY + if invoker.IsAvailable() && matchParam(zone, key, "", invoker) { return invoker.Invoke(ctx, invocation) } } @@ -100,3 +100,7 @@ func (invoker *zoneAwareClusterInvoker) Invoke(ctx context.Context, invocation p Err: fmt.Errorf("no provider available in %v", invokers), } } + +func matchParam(target, key, def string, invoker protocol.Invoker) bool { + return target == invoker.GetUrl().GetParam(key, def) +} From 9b733133b71eb98590f4893bd4cb8c0c14874cf3 Mon Sep 17 00:00:00 2001 From: Shieber Date: Fri, 31 Jul 2020 22:31:05 +0800 Subject: [PATCH 060/242] Modify README_CN --- README_CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_CN.md b/README_CN.md index 552685c7bb..51f30baac5 100644 --- a/README_CN.md +++ b/README_CN.md @@ -180,7 +180,7 @@ go test ./... -coverprofile=coverage.txt -covermode=atomic ## [User List](https://github.com/apache/dubbo-go/issues/2) -若你正在使用 [apache/dubbo-go](github.com/apache/dubbo-go) 且认为其有用或者向对其做改进,请忝列贵司信息于 [用户列表](https://github.com/apache/dubbo-go/issues/2),以便我们知晓之。 +若你正在使用 [apache/dubbo-go](github.com/apache/dubbo-go) 且认为其有用或者向对其做改进,请添列贵司信息于 [用户列表](https://github.com/apache/dubbo-go/issues/2),以便我们知晓之。
From 55c84cdb0dcb230c71ed8d332a43eff040c6634d Mon Sep 17 00:00:00 2001 From: Shieber Date: Fri, 31 Jul 2020 22:34:35 +0800 Subject: [PATCH 061/242] Modify README_CN.md --- README_CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_CN.md b/README_CN.md index 51f30baac5..b76d8983de 100644 --- a/README_CN.md +++ b/README_CN.md @@ -180,7 +180,7 @@ go test ./... -coverprofile=coverage.txt -covermode=atomic ## [User List](https://github.com/apache/dubbo-go/issues/2) -若你正在使用 [apache/dubbo-go](github.com/apache/dubbo-go) 且认为其有用或者向对其做改进,请添列贵司信息于 [用户列表](https://github.com/apache/dubbo-go/issues/2),以便我们知晓之。 +若你正在使用 [apache/dubbo-go](github.com/apache/dubbo-go) 且认为其有用或者想对其做改进,请添列贵司信息于 [用户列表](https://github.com/apache/dubbo-go/issues/2),以便我们知晓。
From 2e96585d2001f529946af92b49d137b973f42786 Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Sun, 2 Aug 2020 22:42:40 +0800 Subject: [PATCH 062/242] refactor config center --- config/base_config.go | 76 +++++---------------------------- config/config_center_config.go | 78 ++++++++++++++++++++++++++++++++++ config/consumer_config.go | 5 ++- config/provider_config.go | 5 ++- 4 files changed, 94 insertions(+), 70 deletions(-) diff --git a/config/base_config.go b/config/base_config.go index 0ba5bc7ef9..e15b58f8bf 100644 --- a/config/base_config.go +++ b/config/base_config.go @@ -31,9 +31,7 @@ import ( import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/config" - "github.com/apache/dubbo-go/common/extension" "github.com/apache/dubbo-go/common/logger" - "github.com/apache/dubbo-go/config_center" ) type multiConfiger interface { @@ -52,7 +50,6 @@ type BaseConfig struct { // application config ApplicationConfig *ApplicationConfig `yaml:"application" json:"application,omitempty" property:"application"` - configCenterUrl *common.URL prefix string fatherConfig interface{} EventDispatcherType string `default:"direct" yaml:"event_dispatcher_type" json:"event_dispatcher_type,omitempty"` @@ -72,72 +69,19 @@ func (c *BaseConfig) GetRemoteConfig(name string) (config *RemoteConfig, ok bool return } -// startConfigCenter will start the config center. -// it will prepare the environment -func (c *BaseConfig) startConfigCenter() error { - url, err := common.NewURL(c.ConfigCenterConfig.Address, - common.WithProtocol(c.ConfigCenterConfig.Protocol), common.WithParams(c.ConfigCenterConfig.GetUrlMap())) - if err != nil { - return err - } - c.configCenterUrl = &url - if c.prepareEnvironment() != nil { - return perrors.WithMessagef(err, "start config center error!") - } - // c.fresh() - return err -} - -func (c *BaseConfig) prepareEnvironment() error { - factory := extension.GetConfigCenterFactory(c.ConfigCenterConfig.Protocol) - dynamicConfig, err := factory.GetDynamicConfiguration(c.configCenterUrl) - config.GetEnvInstance().SetDynamicConfiguration(dynamicConfig) - if err != nil { - logger.Errorf("Get dynamic configuration error , error message is %v", err) - return perrors.WithStack(err) - } - content, err := dynamicConfig.GetProperties(c.ConfigCenterConfig.ConfigFile, config_center.WithGroup(c.ConfigCenterConfig.Group)) - if err != nil { - logger.Errorf("Get config content in dynamic configuration error , error message is %v", err) - return perrors.WithStack(err) - } - var appGroup string - var appContent string - if providerConfig != nil && providerConfig.ApplicationConfig != nil && - reflect.ValueOf(c.fatherConfig).Elem().Type().Name() == "ProviderConfig" { - appGroup = providerConfig.ApplicationConfig.Name - } else if consumerConfig != nil && consumerConfig.ApplicationConfig != nil && - reflect.ValueOf(c.fatherConfig).Elem().Type().Name() == "ConsumerConfig" { - appGroup = consumerConfig.ApplicationConfig.Name - } +func (c *BaseConfig) newURL(name string, protocol string) (common.URL, error) { + rc, ok := GetBaseConfig().GetRemoteConfig(name) - if len(appGroup) != 0 { - configFile := c.ConfigCenterConfig.AppConfigFile - if len(configFile) == 0 { - configFile = c.ConfigCenterConfig.ConfigFile - } - appContent, err = dynamicConfig.GetProperties(configFile, config_center.WithGroup(appGroup)) - if err != nil { - return perrors.WithStack(err) - } + if !ok { + return common.URL{}, perrors.New("Could not find out the remote ref config, name: " + name) } - // global config file - mapContent, err := dynamicConfig.Parser().Parse(content) - if err != nil { - return perrors.WithStack(err) - } - config.GetEnvInstance().UpdateExternalConfigMap(mapContent) - // appGroup config file - if len(appContent) != 0 { - appMapConent, err := dynamicConfig.Parser().Parse(appContent) - if err != nil { - return perrors.WithStack(err) - } - config.GetEnvInstance().UpdateAppExternalConfigMap(appMapConent) - } - - return nil + return common.NewURL(rc.Address, + common.WithUsername(rc.Username), + common.WithPassword(rc.Password), + common.WithLocation(rc.Address), + common.WithProtocol(protocol), + ) } func getKeyPrefix(val reflect.Value) []string { diff --git a/config/config_center_config.go b/config/config_center_config.go index c9133dc26d..cd0612afb6 100644 --- a/config/config_center_config.go +++ b/config/config_center_config.go @@ -20,6 +20,7 @@ package config import ( "context" "net/url" + "reflect" "time" ) @@ -28,7 +29,13 @@ import ( ) import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/config" "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config_center" + perrors "github.com/pkg/errors" ) // ConfigCenterConfig is configuration for config center @@ -52,6 +59,7 @@ type ConfigCenterConfig struct { AppConfigFile string `default:"dubbo.properties" yaml:"app_config_file" json:"app_config_file,omitempty"` AppId string `default:"dubbo" yaml:"app_id" json:"app_id,omitempty"` TimeoutStr string `yaml:"timeout" json:"timeout,omitempty"` + RemoteRef string `required:"false" yaml:"remote_ref" json:"remote_ref,omitempty"` timeout time.Duration } @@ -77,3 +85,73 @@ func (c *ConfigCenterConfig) GetUrlMap() url.Values { urlMap.Set(constant.CONFIG_LOG_DIR_KEY, c.LogDir) return urlMap } + +type configCenter struct { +} + +// startConfigCenter will start the config center. +// it will prepare the environment +func (b *configCenter) startConfigCenter(baseConfig BaseConfig) error { + url, err := common.NewURL(baseConfig.ConfigCenterConfig.Address, + common.WithProtocol(baseConfig.ConfigCenterConfig.Protocol), common.WithParams(baseConfig.ConfigCenterConfig.GetUrlMap())) + if err != nil { + return err + } + if b.prepareEnvironment(baseConfig, &url) != nil { + return perrors.WithMessagef(err, "start config center error!") + } + // c.fresh() + return err +} + +func (b *configCenter) prepareEnvironment(baseConfig BaseConfig, configCenterUrl *common.URL) error { + factory := extension.GetConfigCenterFactory(baseConfig.ConfigCenterConfig.Protocol) + dynamicConfig, err := factory.GetDynamicConfiguration(configCenterUrl) + config.GetEnvInstance().SetDynamicConfiguration(dynamicConfig) + if err != nil { + logger.Errorf("Get dynamic configuration error , error message is %v", err) + return perrors.WithStack(err) + } + content, err := dynamicConfig.GetProperties(baseConfig.ConfigCenterConfig.ConfigFile, config_center.WithGroup(c.ConfigCenterConfig.Group)) + if err != nil { + logger.Errorf("Get config content in dynamic configuration error , error message is %v", err) + return perrors.WithStack(err) + } + var appGroup string + var appContent string + if providerConfig != nil && providerConfig.ApplicationConfig != nil && + reflect.ValueOf(baseConfig.fatherConfig).Elem().Type().Name() == "ProviderConfig" { + appGroup = providerConfig.ApplicationConfig.Name + } else if consumerConfig != nil && consumerConfig.ApplicationConfig != nil && + reflect.ValueOf(baseConfig.fatherConfig).Elem().Type().Name() == "ConsumerConfig" { + appGroup = consumerConfig.ApplicationConfig.Name + } + + if len(appGroup) != 0 { + configFile := baseConfig.ConfigCenterConfig.AppConfigFile + if len(configFile) == 0 { + configFile = baseConfig.ConfigCenterConfig.ConfigFile + } + appContent, err = dynamicConfig.GetProperties(configFile, config_center.WithGroup(appGroup)) + if err != nil { + return perrors.WithStack(err) + } + } + // global config file + mapContent, err := dynamicConfig.Parser().Parse(content) + if err != nil { + return perrors.WithStack(err) + } + config.GetEnvInstance().UpdateExternalConfigMap(mapContent) + + // appGroup config file + if len(appContent) != 0 { + appMapConent, err := dynamicConfig.Parser().Parse(appContent) + if err != nil { + return perrors.WithStack(err) + } + config.GetEnvInstance().UpdateAppExternalConfigMap(appMapConent) + } + + return nil +} diff --git a/config/consumer_config.go b/config/consumer_config.go index 48f29f0e70..d26709c978 100644 --- a/config/consumer_config.go +++ b/config/consumer_config.go @@ -41,7 +41,8 @@ import ( // ConsumerConfig is Consumer default configuration type ConsumerConfig struct { BaseConfig `yaml:",inline"` - Filter string `yaml:"filter" json:"filter,omitempty" property:"filter"` + configCenter + Filter string `yaml:"filter" json:"filter,omitempty" property:"filter"` // client Connect_Timeout string `default:"100ms" yaml:"connect_timeout" json:"connect_timeout,omitempty" property:"connect_timeout"` ConnectTimeout time.Duration @@ -127,7 +128,7 @@ func configCenterRefreshConsumer() error { var err error if consumerConfig.ConfigCenterConfig != nil { consumerConfig.SetFatherConfig(consumerConfig) - if err = consumerConfig.startConfigCenter(); err != nil { + if err = consumerConfig.startConfigCenter((*consumerConfig).BaseConfig); err != nil { return perrors.Errorf("start config center error , error message is {%v}", perrors.WithStack(err)) } consumerConfig.fresh() diff --git a/config/provider_config.go b/config/provider_config.go index 7cd3c1e98b..c710e48dc2 100644 --- a/config/provider_config.go +++ b/config/provider_config.go @@ -37,7 +37,8 @@ import ( // ProviderConfig is the default configuration of service provider type ProviderConfig struct { - BaseConfig `yaml:",inline"` + BaseConfig `yaml:",inline"` + configCenter Filter string `yaml:"filter" json:"filter,omitempty" property:"filter"` ProxyFactory string `yaml:"proxy_factory" default:"default" json:"proxy_factory,omitempty" property:"proxy_factory"` Services map[string]*ServiceConfig `yaml:"services" json:"services,omitempty" property:"services"` @@ -101,7 +102,7 @@ func configCenterRefreshProvider() error { // fresh it if providerConfig.ConfigCenterConfig != nil { providerConfig.fatherConfig = providerConfig - if err := providerConfig.startConfigCenter(); err != nil { + if err := providerConfig.startConfigCenter((*providerConfig).BaseConfig); err != nil { return perrors.Errorf("start config center error , error message is {%v}", perrors.WithStack(err)) } providerConfig.fresh() From bc46c9a3920db18b4a471f8c9d6b29c7b8665be4 Mon Sep 17 00:00:00 2001 From: Shieber Date: Fri, 31 Jul 2020 22:31:05 +0800 Subject: [PATCH 063/242] Modify README_CN --- README_CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_CN.md b/README_CN.md index 552685c7bb..51f30baac5 100644 --- a/README_CN.md +++ b/README_CN.md @@ -180,7 +180,7 @@ go test ./... -coverprofile=coverage.txt -covermode=atomic ## [User List](https://github.com/apache/dubbo-go/issues/2) -若你正在使用 [apache/dubbo-go](github.com/apache/dubbo-go) 且认为其有用或者向对其做改进,请忝列贵司信息于 [用户列表](https://github.com/apache/dubbo-go/issues/2),以便我们知晓之。 +若你正在使用 [apache/dubbo-go](github.com/apache/dubbo-go) 且认为其有用或者向对其做改进,请添列贵司信息于 [用户列表](https://github.com/apache/dubbo-go/issues/2),以便我们知晓之。
From 12680fdab94cbfebb75d58b28f8b71addee7269a Mon Sep 17 00:00:00 2001 From: Shieber Date: Fri, 31 Jul 2020 22:34:35 +0800 Subject: [PATCH 064/242] Modify README_CN.md --- README_CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_CN.md b/README_CN.md index 51f30baac5..b76d8983de 100644 --- a/README_CN.md +++ b/README_CN.md @@ -180,7 +180,7 @@ go test ./... -coverprofile=coverage.txt -covermode=atomic ## [User List](https://github.com/apache/dubbo-go/issues/2) -若你正在使用 [apache/dubbo-go](github.com/apache/dubbo-go) 且认为其有用或者向对其做改进,请添列贵司信息于 [用户列表](https://github.com/apache/dubbo-go/issues/2),以便我们知晓之。 +若你正在使用 [apache/dubbo-go](github.com/apache/dubbo-go) 且认为其有用或者想对其做改进,请添列贵司信息于 [用户列表](https://github.com/apache/dubbo-go/issues/2),以便我们知晓。
From 70c01a5b31c572b50a6139e0c48192345f818592 Mon Sep 17 00:00:00 2001 From: watermelo <80680489@qq.com> Date: Mon, 3 Aug 2020 01:46:22 +0800 Subject: [PATCH 065/242] Add: add unit test for dynamic tag --- cluster/router/condition/app_router_test.go | 2 +- cluster/router/tag/router_rule.go | 8 +- cluster/router/tag/tag_router.go | 18 +- cluster/router/tag/tag_router_test.go | 211 +++++++++++++++++++- 4 files changed, 218 insertions(+), 21 deletions(-) diff --git a/cluster/router/condition/app_router_test.go b/cluster/router/condition/app_router_test.go index 8b38f2dd61..ea18604964 100644 --- a/cluster/router/condition/app_router_test.go +++ b/cluster/router/condition/app_router_test.go @@ -24,7 +24,6 @@ import ( ) import ( - _ "github.com/apache/dubbo-go/config_center/zookeeper" "github.com/stretchr/testify/assert" ) @@ -34,6 +33,7 @@ import ( "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" "github.com/apache/dubbo-go/config_center" + _ "github.com/apache/dubbo-go/config_center/zookeeper" "github.com/apache/dubbo-go/remoting" "github.com/apache/dubbo-go/remoting/zookeeper" ) diff --git a/cluster/router/tag/router_rule.go b/cluster/router/tag/router_rule.go index dc7a446783..5fb7ab151c 100644 --- a/cluster/router/tag/router_rule.go +++ b/cluster/router/tag/router_rule.go @@ -57,8 +57,8 @@ func getRule(rawRule string) (*RouterRule, error) { } func (t *RouterRule) init() { - t.addressToTagNames = make(map[string][]string) - t.tagNameToAddresses = make(map[string][]string) + t.addressToTagNames = make(map[string][]string, 8) + t.tagNameToAddresses = make(map[string][]string, 8) for _, tag := range t.Tags { for _, address := range tag.Addresses { t.addressToTagNames[address] = append(t.addressToTagNames[address], tag.Name) @@ -68,7 +68,7 @@ func (t *RouterRule) init() { } func (t *RouterRule) getAddresses() []string { - var result []string + var result = make([]string, 0, 8*len(t.Tags)) for _, tag := range t.Tags { result = append(result, tag.Addresses...) } @@ -76,7 +76,7 @@ func (t *RouterRule) getAddresses() []string { } func (t *RouterRule) getTagNames() []string { - var result []string + var result = make([]string, 0, len(t.Tags)) for _, tag := range t.Tags { result = append(result, tag.Name) } diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index 093a31ef36..ece950ebc0 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -19,7 +19,6 @@ package tag import ( "errors" - "fmt" "net" "strconv" "strings" @@ -70,7 +69,6 @@ func (c *tagRouter) SetApplication(app string) { } func (c *tagRouter) tagRouterRuleCopy() RouterRule { - fmt.Println(c.tagRouterRule, "fuck") routerRule := *c.tagRouterRule return routerRule } @@ -96,7 +94,8 @@ func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocati result []protocol.Invoker addresses []string ) - if tag != "" { + // if we are requesting for a Provider with a specific tag + if len(tag) > 0 { addresses, _ = tagRouterRuleCopy.getTagNameToAddresses()[tag] // filter by dynamic tag group first if len(addresses) > 0 { @@ -159,9 +158,9 @@ func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocati if len(result) == 0 { return result } - // 2. if there are some addresses that are not in any dynamic tag group, continue to filter using the - // static tag group. } + // 2. if there are some addresses that are not in any dynamic tag group, continue to filter using the + // static tag group. filter := func(invoker protocol.Invoker) bool { localTag := invoker.GetUrl().GetParam(constant.Tagkey, "") return localTag == "" || !(tagRouterRuleCopy.hasTag(localTag)) @@ -178,8 +177,7 @@ func (c *tagRouter) Process(event *config_center.ConfigChangeEvent) { } else { content, ok := event.Value.(string) if !ok { - msg := fmt.Sprintf("Convert event content fail,raw content:[%s] ", event.Value) - logger.Error(msg) + logger.Errorf("Convert event content fail,raw content:[%s] ", event.Value) return } @@ -282,13 +280,13 @@ OUTER: return res } -// TODO 需要搬到 dubbogo/gost, 可以先 review +// TODO: need move to dubbogo/gost func checkAddressMatch(addresses []string, host, port string) bool { for _, address := range addresses { if matchIp(address, host, port) { return true } - if address == constant.ANYHOST_VALUE+":"+port { + if address == net.JoinHostPort(constant.ANYHOST_VALUE, port) { return true } } @@ -332,7 +330,7 @@ func matchIpRange(pattern, host, port string) bool { pattern = hostAndPort[0] // TODO 常量化 - splitCharacter := "\\." + splitCharacter := "." if !isIpv4 { splitCharacter = ":" } diff --git a/cluster/router/tag/tag_router_test.go b/cluster/router/tag/tag_router_test.go index 4f2a8a9fca..e5ddc2890c 100644 --- a/cluster/router/tag/tag_router_test.go +++ b/cluster/router/tag/tag_router_test.go @@ -19,27 +19,43 @@ package tag import ( "context" + "fmt" + "github.com/stretchr/testify/suite" "testing" + "time" ) import ( + "github.com/dubbogo/go-zookeeper/zk" "github.com/stretchr/testify/assert" ) import ( "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/config" "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/config_center" + _ "github.com/apache/dubbo-go/config_center/zookeeper" "github.com/apache/dubbo-go/protocol" "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/remoting" + "github.com/apache/dubbo-go/remoting/zookeeper" ) const ( - tagRouterTestHangZhouUrl = "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=hangzhou" - tagRouterTestShangHaiUrl = "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=shanghai" - tagRouterTestBeijingUrl = "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=beijing" - tagRouterTestEnabledBeijingUrl = "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=false&dubbo.tag=beijing" - tagRouterTestUserConsumer = "dubbo://127.0.0.1:20000/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true" - tagRouterTestUserConsumerTag = "dubbo://127.0.0.1:20000/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&dubbo.force.tag=true" + tagRouterTestHangZhouUrl = "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=hangzhou&remote.application=test-tag" + tagRouterTestShangHaiUrl = "dubbo://127.0.0.1:20002/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=shanghai&remote.application=test-tag" + tagRouterTestBeijingUrl = "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=beijing&remote.application=test-tag" + tagRouterTestEnabledBeijingUrl = "dubbo://127.0.0.1:20004/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=false&dubbo.tag=beijing&remote.application=test-tag" + tagRouterTestUserConsumer = "dubbo://127.0.0.1:20005/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&remote.application=test-tag" + tagRouterTestUserConsumerTag = "dubbo://127.0.0.1:20000/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&dubbo.force.tag=true&remote.application=test-tag" + + tagRouterTestDynamicIpv4Provider1 = "dubbo://127.0.0.1:20001/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&remote.application=test-tag" + tagRouterTestDynamicIpv4Provider2 = "dubbo://127.0.0.1:20002/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&remote.application=test-tag" + tagRouterTestDynamicIpv4Provider3 = "dubbo://127.0.0.1:20003/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&remote.application=test-tag" + tagRouterTestDynamicIpv4Provider4 = "dubbo://127.0.0.1:20004/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&remote.application=test-tag&dubbo.tag=tag4" + tagRouterTestDynamicIpv4Provider5 = "dubbo://127.0.0.1:20005/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&remote.application=test-tag&dubbo.tag=tag5" tagRouterTestDubboTag = "dubbo.tag" tagRouterTestDubboForceTag = "dubbo.force.tag" @@ -47,6 +63,15 @@ const ( tagRouterTestGuangZhou = "guangzhou" tagRouterTestFalse = "false" tagRouterTestTrue = "true" + + routerPath = "/dubbo/config/dubbo/test-tag.tag-router" + routerLocalIP = "127.0.0.1" + routerZk = "zookeeper" +) + +var ( + zkFormat = "zookeeper://%s:%d" + conditionFormat = "condition://%s/com.foo.BarService" ) // MockInvoker is only mock the Invoker to support test tagRouter @@ -196,3 +221,177 @@ func TestFilterInvoker(t *testing.T) { res2 := filterInvoker(invokers, filterTag, filterEnabled) assert.Equal(t, []protocol.Invoker{inv4}, res2) } + +type DynamicTagRouter struct { + suite.Suite + rule *RouterRule + + route *tagRouter + zkClient *zookeeper.ZookeeperClient + testCluster *zk.TestCluster + invokers []protocol.Invoker + url *common.URL +} + +func TestDynamicTagRouter(t *testing.T) { + dtg := &DynamicTagRouter{} + u1, _ := common.NewURL(tagRouterTestDynamicIpv4Provider1) + u2, _ := common.NewURL(tagRouterTestDynamicIpv4Provider2) + u3, _ := common.NewURL(tagRouterTestDynamicIpv4Provider3) + u4, _ := common.NewURL(tagRouterTestDynamicIpv4Provider4) + u5, _ := common.NewURL(tagRouterTestDynamicIpv4Provider5) + inv1 := NewMockInvoker(u1) + inv2 := NewMockInvoker(u2) + inv3 := NewMockInvoker(u3) + inv4 := NewMockInvoker(u4) + inv5 := NewMockInvoker(u5) + dtg.invokers = append(dtg.invokers, inv1, inv2, inv3, inv4, inv5) + suite.Run(t, dtg) +} + +func (suite *DynamicTagRouter) SetupTest() { + var err error + testYML := `enabled: true +scope: application +force: true +runtime: false +valid: true +priority: 1 +key: demo-provider +tags: + - name: tag1 + addresses: ["127.0.0.1:20001"] + - name: tag2 + addresses: ["127.0.0.1:20002"] + - name: tag3 + addresses: ["127.0.0.1:20003", "127.0.0.1:20004"] +` + ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second) + suite.NoError(err) + err = z.Create(routerPath) + suite.NoError(err) + + suite.zkClient = z + suite.testCluster = ts + + _, err = z.Conn.Set(routerPath, []byte(testYML), 0) + suite.NoError(err) + + zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, routerLocalIP, suite.testCluster.Servers[0].Port)) + configuration, err := extension.GetConfigCenterFactory(routerZk).GetDynamicConfiguration(&zkUrl) + config.GetEnvInstance().SetDynamicConfiguration(configuration) + + suite.Nil(err) + suite.NotNil(configuration) + + url, e1 := common.NewURL(tagRouterTestUserConsumerTag) + suite.Nil(e1) + + tagRouter, err := NewTagRouter(&url) + suite.Nil(err) + suite.NotNil(tagRouter) + suite.route = tagRouter + suite.url = &url +} + +func (suite *DynamicTagRouter) TearDownTest() { + suite.zkClient.Close() + suite.testCluster.Stop() +} + +func (suite *DynamicTagRouter) TestDynamicTagRouterSetByIPv4() { + invokers := suite.invokers + suite.route.Notify(invokers) + suite.NotNil(suite.route.tagRouterRule) + + consumer := &invocation.RPCInvocation{} + consumer.SetAttachments(tagRouterTestDubboTag, "tag1") + targetInvokers := suite.route.Route(invokers, suite.url, consumer) + suite.Equal(1, len(targetInvokers)) + suite.Equal(targetInvokers[0], suite.invokers[0]) + + consumer.SetAttachments(tagRouterTestDubboTag, "tag3") + targetInvokers = suite.route.Route(invokers, suite.url, consumer) + suite.Equal(2, len(targetInvokers)) + suite.Equal(targetInvokers, []protocol.Invoker{suite.invokers[2], suite.invokers[3]}) +} + +func (suite *DynamicTagRouter) TestDynamicTagRouterStaticTag() { + invokers := suite.invokers + consumer := &invocation.RPCInvocation{} + consumer.SetAttachments(tagRouterTestDubboTag, "tag4") + targetInvokers := suite.route.Route(invokers, suite.url, consumer) + suite.Equal(1, len(targetInvokers)) + suite.Equal(targetInvokers[0], suite.invokers[3]) +} + +// Teas no tag and return a address are not in dynamic tag group +func (suite *DynamicTagRouter) TestDynamicTagRouterByNoTagAndAddressMatch() { + invokers := suite.invokers + suite.route.Notify(invokers) + suite.NotNil(suite.route.tagRouterRule) + consumer := &invocation.RPCInvocation{} + targetInvokers := suite.route.Route(invokers, suite.url, consumer) + suite.Equal(1, len(targetInvokers)) + suite.Equal(targetInvokers[0], suite.invokers[4]) + // test if there are some addresses that are not in any dynamic tag group, continue to filter using the static tag group. + consumer.SetAttachments(tagRouterTestDubboTag, "tag5") + targetInvokers = suite.route.Route(invokers, suite.url, consumer) + suite.Equal(1, len(targetInvokers)) + suite.Equal(targetInvokers[0], suite.invokers[4]) +} + +func (suite *DynamicTagRouter) TestTODO() { + testYML := `enabled: true +scope: application +force: true +runtime: false +valid: true +priority: 1 +key: demo-provider +tags: + - name: tag1 + addresses: ["127.0.0.1:20001"] + - name: tag2 + addresses: ["127.0.0.1:20002"] + - name: tag3 + addresses: ["127.0.0.1:20003", "127.0.0.1:20004"] +` + _, err := suite.zkClient.Conn.Set(routerPath, []byte(testYML), 1) + suite.NoError(err) + + zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, routerLocalIP, suite.testCluster.Servers[0].Port)) + configuration, err := extension.GetConfigCenterFactory(routerZk).GetDynamicConfiguration(&zkUrl) + config.GetEnvInstance().SetDynamicConfiguration(configuration) +} + +func TestProcess(t *testing.T) { + u1, err := common.NewURL(tagRouterTestUserConsumerTag) + assert.Nil(t, err) + tagRouter, e := NewTagRouter(&u1) + assert.Nil(t, e) + assert.NotNil(t, tagRouter) + + testYML := ` +scope: application +force: true +runtime: false +enabled: true +valid: true +priority: 1 +key: demo-provider +tags: + - name: beijing + addresses: [192.168.1.1, 192.168.1.2] + - name: hangzhou + addresses: [192.168.1.3, 192.168.1.4] +` + tagRouter.Process(&config_center.ConfigChangeEvent{Value: testYML, ConfigType: remoting.EventTypeAdd}) + assert.NotNil(t, tagRouter.tagRouterRule) + assert.Equal(t, []string{"beijing", "hangzhou"}, tagRouter.tagRouterRule.getTagNames()) + assert.Equal(t, []string{"192.168.1.1", "192.168.1.2", "192.168.1.3", "192.168.1.4"}, tagRouter.tagRouterRule.getAddresses()) + assert.Equal(t, []string{"192.168.1.3", "192.168.1.4"}, tagRouter.tagRouterRule.getTagNameToAddresses()["hangzhou"]) + assert.Equal(t, []string{"beijing"}, tagRouter.tagRouterRule.getAddressToTagNames()["192.168.1.1"]) + tagRouter.Process(&config_center.ConfigChangeEvent{ConfigType: remoting.EventTypeDel}) + assert.Nil(t, tagRouter.tagRouterRule) +} From a31a4e211fc9696741aa22fa792a16d87b55256d Mon Sep 17 00:00:00 2001 From: watermelo <80680489@qq.com> Date: Mon, 3 Aug 2020 14:48:48 +0800 Subject: [PATCH 066/242] Add: add zk jar for tag test --- before_ut.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/before_ut.sh b/before_ut.sh index 210e9e723b..b55e424ef7 100755 --- a/before_ut.sh +++ b/before_ut.sh @@ -36,5 +36,8 @@ cp ${zkJar} cluster/router/chain/zookeeper-4unittest/contrib/fatjar mkdir -p cluster/router/condition/zookeeper-4unittest/contrib/fatjar cp ${zkJar} cluster/router/condition/zookeeper-4unittest/contrib/fatjar +mkdir -p cluster/router/tag/zookeeper-4unittest/contrib/fatjar +cp ${zkJar} cluster/router/tag/zookeeper-4unittest/contrib/fatjar + mkdir -p metadata/report/zookeeper/zookeeper-4unittest/contrib/fatjar cp ${zkJar} metadata/report/zookeeper/zookeeper-4unittest/contrib/fatjar \ No newline at end of file From 73e7bdd1402f30da2031ca91ae7977fa2199afc0 Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Mon, 3 Aug 2020 22:48:43 +0800 Subject: [PATCH 067/242] refactor config center --- config/base_config.go | 2 +- config/base_config_test.go | 19 ----------- config/config_center_config.go | 15 +++++++-- config/config_center_config_test.go | 51 +++++++++++++++++++++++++++++ config/consumer_config.go | 14 ++++---- 5 files changed, 71 insertions(+), 30 deletions(-) create mode 100644 config/config_center_config_test.go diff --git a/config/base_config.go b/config/base_config.go index e15b58f8bf..c0992006da 100644 --- a/config/base_config.go +++ b/config/base_config.go @@ -69,7 +69,7 @@ func (c *BaseConfig) GetRemoteConfig(name string) (config *RemoteConfig, ok bool return } -func (c *BaseConfig) newURL(name string, protocol string) (common.URL, error) { +func (c *BaseConfig) toURL(name string, protocol string) (common.URL, error) { rc, ok := GetBaseConfig().GetRemoteConfig(name) if !ok { diff --git a/config/base_config_test.go b/config/base_config_test.go index 6db6a8dcb8..849a9c4586 100644 --- a/config/base_config_test.go +++ b/config/base_config_test.go @@ -28,8 +28,6 @@ import ( import ( "github.com/apache/dubbo-go/common/config" - "github.com/apache/dubbo-go/common/extension" - "github.com/apache/dubbo-go/config_center" _ "github.com/apache/dubbo-go/config_center/apollo" ) @@ -282,23 +280,6 @@ func TestRefreshProvider(t *testing.T) { assert.Equal(t, "20001", father.Protocols["jsonrpc1"].Port) } -func TestStartConfigCenter(t *testing.T) { - extension.SetConfigCenterFactory("mock", func() config_center.DynamicConfigurationFactory { - return &config_center.MockDynamicConfigurationFactory{} - }) - c := &BaseConfig{ConfigCenterConfig: &ConfigCenterConfig{ - Protocol: "mock", - Address: "172.0.0.1", - Group: "dubbo", - ConfigFile: "mockDubbo.properties", - }} - err := c.startConfigCenter() - assert.NoError(t, err) - b, v := config.GetEnvInstance().Configuration().Back().Value.(*config.InmemoryConfiguration).GetProperty("dubbo.application.organization") - assert.True(t, b) - assert.Equal(t, "ikurento.com", v) -} - func TestInitializeStruct(t *testing.T) { testConsumerConfig := &ConsumerConfig{} tp := reflect.TypeOf(ConsumerConfig{}) diff --git a/config/config_center_config.go b/config/config_center_config.go index cd0612afb6..a70415004c 100644 --- a/config/config_center_config.go +++ b/config/config_center_config.go @@ -89,11 +89,20 @@ func (c *ConfigCenterConfig) GetUrlMap() url.Values { type configCenter struct { } +// toURL will compatible with baseConfig.ConfigCenterConfig.Address before 1.6.0 +// After 1.6.0 will not compatible, only baseConfig.ConfigCenterConfig.RemoteRef +func (b *configCenter) toURL(baseConfig BaseConfig) (common.URL, error) { + if len(baseConfig.ConfigCenterConfig.Address) > 0 { + return common.NewURL(baseConfig.ConfigCenterConfig.Address, + common.WithProtocol(baseConfig.ConfigCenterConfig.Protocol), common.WithParams(baseConfig.ConfigCenterConfig.GetUrlMap())) + } + return baseConfig.toURL(baseConfig.ConfigCenterConfig.RemoteRef, baseConfig.ConfigCenterConfig.Protocol) +} + // startConfigCenter will start the config center. // it will prepare the environment func (b *configCenter) startConfigCenter(baseConfig BaseConfig) error { - url, err := common.NewURL(baseConfig.ConfigCenterConfig.Address, - common.WithProtocol(baseConfig.ConfigCenterConfig.Protocol), common.WithParams(baseConfig.ConfigCenterConfig.GetUrlMap())) + url, err := b.toURL(baseConfig) if err != nil { return err } @@ -112,7 +121,7 @@ func (b *configCenter) prepareEnvironment(baseConfig BaseConfig, configCenterUrl logger.Errorf("Get dynamic configuration error , error message is %v", err) return perrors.WithStack(err) } - content, err := dynamicConfig.GetProperties(baseConfig.ConfigCenterConfig.ConfigFile, config_center.WithGroup(c.ConfigCenterConfig.Group)) + content, err := dynamicConfig.GetProperties(baseConfig.ConfigCenterConfig.ConfigFile, config_center.WithGroup(baseConfig.ConfigCenterConfig.Group)) if err != nil { logger.Errorf("Get config content in dynamic configuration error , error message is %v", err) return perrors.WithStack(err) diff --git a/config/config_center_config_test.go b/config/config_center_config_test.go new file mode 100644 index 0000000000..ae653181e9 --- /dev/null +++ b/config/config_center_config_test.go @@ -0,0 +1,51 @@ +/* + * 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 config + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common/config" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/config_center" +) + +func TestStartConfigCenter(t *testing.T) { + extension.SetConfigCenterFactory("mock", func() config_center.DynamicConfigurationFactory { + return &config_center.MockDynamicConfigurationFactory{} + }) + baseConfig := &BaseConfig{ConfigCenterConfig: &ConfigCenterConfig{ + Protocol: "mock", + Address: "172.0.0.1", + Group: "dubbo", + ConfigFile: "mockDubbo.properties", + }} + + c := &configCenter{} + err := c.startConfigCenter(*baseConfig) + assert.NoError(t, err) + b, v := config.GetEnvInstance().Configuration().Back().Value.(*config.InmemoryConfiguration).GetProperty("dubbo.application.organization") + assert.True(t, b) + assert.Equal(t, "ikurento.com", v) +} diff --git a/config/consumer_config.go b/config/consumer_config.go index d26709c978..1775312092 100644 --- a/config/consumer_config.go +++ b/config/consumer_config.go @@ -126,13 +126,6 @@ func ConsumerInit(confConFile string) error { func configCenterRefreshConsumer() error { //fresh it var err error - if consumerConfig.ConfigCenterConfig != nil { - consumerConfig.SetFatherConfig(consumerConfig) - if err = consumerConfig.startConfigCenter((*consumerConfig).BaseConfig); err != nil { - return perrors.Errorf("start config center error , error message is {%v}", perrors.WithStack(err)) - } - consumerConfig.fresh() - } if consumerConfig.Request_Timeout != "" { if consumerConfig.RequestTimeout, err = time.ParseDuration(consumerConfig.Request_Timeout); err != nil { return perrors.WithMessagef(err, "time.ParseDuration(Request_Timeout{%#v})", consumerConfig.Request_Timeout) @@ -143,5 +136,12 @@ func configCenterRefreshConsumer() error { return perrors.WithMessagef(err, "time.ParseDuration(Connect_Timeout{%#v})", consumerConfig.Connect_Timeout) } } + if consumerConfig.ConfigCenterConfig != nil { + consumerConfig.SetFatherConfig(consumerConfig) + if err = consumerConfig.startConfigCenter((*consumerConfig).BaseConfig); err != nil { + return perrors.Errorf("start config center error , error message is {%v}", perrors.WithStack(err)) + } + consumerConfig.fresh() + } return nil } From d5a491ee51c978e9ef24e6e56fe7acf45d8a5346 Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Mon, 3 Aug 2020 23:03:20 +0800 Subject: [PATCH 068/242] add case for this feature --- config/config_center_config.go | 2 +- config/config_center_config_test.go | 45 +++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/config/config_center_config.go b/config/config_center_config.go index a70415004c..e423582012 100644 --- a/config/config_center_config.go +++ b/config/config_center_config.go @@ -89,7 +89,7 @@ func (c *ConfigCenterConfig) GetUrlMap() url.Values { type configCenter struct { } -// toURL will compatible with baseConfig.ConfigCenterConfig.Address before 1.6.0 +// toURL will compatible with baseConfig.ConfigCenterConfig.Address and baseConfig.ConfigCenterConfig.RemoteRef before 1.6.0 // After 1.6.0 will not compatible, only baseConfig.ConfigCenterConfig.RemoteRef func (b *configCenter) toURL(baseConfig BaseConfig) (common.URL, error) { if len(baseConfig.ConfigCenterConfig.Address) > 0 { diff --git a/config/config_center_config_test.go b/config/config_center_config_test.go index ae653181e9..3a08e01302 100644 --- a/config/config_center_config_test.go +++ b/config/config_center_config_test.go @@ -49,3 +49,48 @@ func TestStartConfigCenter(t *testing.T) { assert.True(t, b) assert.Equal(t, "ikurento.com", v) } + +func TestStartConfigCenterWithRemoteRef(t *testing.T) { + extension.SetConfigCenterFactory("mock", func() config_center.DynamicConfigurationFactory { + return &config_center.MockDynamicConfigurationFactory{} + }) + m := make(map[string]*RemoteConfig) + m["mock"] = &RemoteConfig{Address: "172.0.0.1"} + baseConfig = &BaseConfig{ + Remotes: m, + ConfigCenterConfig: &ConfigCenterConfig{ + Protocol: "mock", + Group: "dubbo", + RemoteRef: "mock", + ConfigFile: "mockDubbo.properties", + }} + + c := &configCenter{} + err := c.startConfigCenter(*baseConfig) + assert.NoError(t, err) + b, v := config.GetEnvInstance().Configuration().Back().Value.(*config.InmemoryConfiguration).GetProperty("dubbo.application.organization") + assert.True(t, b) + assert.Equal(t, "ikurento.com", v) + + baseConfig = nil +} + +func TestStartConfigCenterWithRemoteRefError(t *testing.T) { + extension.SetConfigCenterFactory("mock", func() config_center.DynamicConfigurationFactory { + return &config_center.MockDynamicConfigurationFactory{} + }) + m := make(map[string]*RemoteConfig) + m["mock"] = &RemoteConfig{Address: "172.0.0.1"} + baseConfig := &BaseConfig{ + Remotes: m, + ConfigCenterConfig: &ConfigCenterConfig{ + Protocol: "mock", + Group: "dubbo", + RemoteRef: "mock", + ConfigFile: "mockDubbo.properties", + }} + + c := &configCenter{} + err := c.startConfigCenter(*baseConfig) + assert.Error(t, err) +} From 3e9f57a12159006bac5b848b57f2ed4dec57d58a Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Mon, 3 Aug 2020 23:08:32 +0800 Subject: [PATCH 069/242] refactor config center --- config/config_center_config.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/config/config_center_config.go b/config/config_center_config.go index e423582012..e2943dcc5c 100644 --- a/config/config_center_config.go +++ b/config/config_center_config.go @@ -96,7 +96,11 @@ func (b *configCenter) toURL(baseConfig BaseConfig) (common.URL, error) { return common.NewURL(baseConfig.ConfigCenterConfig.Address, common.WithProtocol(baseConfig.ConfigCenterConfig.Protocol), common.WithParams(baseConfig.ConfigCenterConfig.GetUrlMap())) } - return baseConfig.toURL(baseConfig.ConfigCenterConfig.RemoteRef, baseConfig.ConfigCenterConfig.Protocol) + newURL, err := baseConfig.toURL(baseConfig.ConfigCenterConfig.RemoteRef, baseConfig.ConfigCenterConfig.Protocol) + if err == nil { + newURL.SetParams(baseConfig.ConfigCenterConfig.GetUrlMap()) + } + return newURL, err } // startConfigCenter will start the config center. From 0ef77658ad08f14575e6609175628257dd3f0fa0 Mon Sep 17 00:00:00 2001 From: lzp0412 <641785844@qq.com> Date: Tue, 4 Aug 2020 10:00:14 +0800 Subject: [PATCH 070/242] unregister instance when registry destroy --- config_center/nacos/client.go | 1 - go.mod | 2 +- go.sum | 4 +-- registry/nacos/registry.go | 39 +++++++++++++++++++++++++++++ registry/nacos/service_discovery.go | 21 ++++++++++++---- 5 files changed, 58 insertions(+), 9 deletions(-) diff --git a/config_center/nacos/client.go b/config_center/nacos/client.go index 6fe5c4d7df..9a09b713fa 100644 --- a/config_center/nacos/client.go +++ b/config_center/nacos/client.go @@ -167,7 +167,6 @@ func initNacosConfigClient(nacosAddrs []string, timeout time.Duration, url commo "serverConfigs": svrConfList, "clientConfig": nacosconst.ClientConfig{ TimeoutMs: uint64(int32(timeout / time.Millisecond)), - ListenInterval: uint64(int32(timeout / time.Millisecond)), NotLoadCacheAtStart: true, LogDir: url.GetParam(constant.NACOS_LOG_DIR_KEY, logDir), CacheDir: url.GetParam(constant.NACOS_CACHE_DIR_KEY, ""), diff --git a/go.mod b/go.mod index 7a472daef7..c196273782 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ require ( github.com/mitchellh/hashstructure v1.0.0 // indirect github.com/mitchellh/mapstructure v1.2.3 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd - github.com/nacos-group/nacos-sdk-go v0.3.3-0.20200617023039-50c7537d6a5f + github.com/nacos-group/nacos-sdk-go v1.0.0 github.com/opentracing/opentracing-go v1.1.0 github.com/pierrec/lz4 v2.2.6+incompatible // indirect github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 424a11087b..aa6ecc86e2 100644 --- a/go.sum +++ b/go.sum @@ -516,8 +516,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/nacos-group/nacos-sdk-go v0.3.3-0.20200617023039-50c7537d6a5f h1:gid5/0AkHvINWK69Fgbidb3BVIXqlf1YEm7wO0NVPsw= -github.com/nacos-group/nacos-sdk-go v0.3.3-0.20200617023039-50c7537d6a5f/go.mod h1:fti1GlX/EB6RDKvzK/P7Vuibqj0JMPJHQwrcTU1tLXk= +github.com/nacos-group/nacos-sdk-go v1.0.0 h1:CufUF7DZca2ZzIrJtMMCDih1sA58BWCglArLMCZArUc= +github.com/nacos-group/nacos-sdk-go v1.0.0/go.mod h1:hlAPn3UdzlxIlSILAyOXKxjFSvDJ9oLzTJ9hLAK1KzA= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 h1:BQ1HW7hr4IVovMwWg0E0PYcyW8CzqDcVmaew9cujU4s= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= diff --git a/registry/nacos/registry.go b/registry/nacos/registry.go index 51d3e2f56a..6ed5bc0d2f 100644 --- a/registry/nacos/registry.go +++ b/registry/nacos/registry.go @@ -59,6 +59,7 @@ func init() { type nacosRegistry struct { *common.URL namingClient naming_client.INamingClient + registryUrls []common.URL } func getCategory(url common.URL) string { @@ -128,6 +129,36 @@ func (nr *nacosRegistry) Register(url common.URL) error { if !isRegistry { return perrors.New("registry [" + serviceName + "] to nacos failed") } + nr.registryUrls = append(nr.registryUrls, url) + return nil +} + +func createDeregisterParam(url common.URL, serviceName string) vo.DeregisterInstanceParam { + if len(url.Ip) == 0 { + url.Ip = localIP + } + if len(url.Port) == 0 || url.Port == "0" { + url.Port = "80" + } + port, _ := strconv.Atoi(url.Port) + return vo.DeregisterInstanceParam{ + Ip: url.Ip, + Port: uint64(port), + ServiceName: serviceName, + Ephemeral: true, + } +} + +func (nr *nacosRegistry) DeRegister(url common.URL) error { + serviceName := getServiceName(url) + param := createDeregisterParam(url, serviceName) + isDeRegistry, err := nr.namingClient.DeregisterInstance(param) + if err != nil { + return err + } + if !isDeRegistry { + return perrors.New("DeRegistry [" + serviceName + "] to nacos failed") + } return nil } @@ -193,6 +224,13 @@ func (nr *nacosRegistry) IsAvailable() bool { // nolint func (nr *nacosRegistry) Destroy() { + for _, url := range nr.registryUrls { + err := nr.DeRegister(url) + logger.Infof("DeRegister Nacos url:%+v", url) + if err != nil { + logger.Errorf("Deregister url:%+v err:%v", url, err.Error()) + } + } return } @@ -209,6 +247,7 @@ func newNacosRegistry(url *common.URL) (registry.Registry, error) { registry := &nacosRegistry{ URL: url, namingClient: client, + registryUrls: []common.URL{}, } return registry, nil } diff --git a/registry/nacos/service_discovery.go b/registry/nacos/service_discovery.go index 63d92d70fd..0e5ad8e699 100644 --- a/registry/nacos/service_discovery.go +++ b/registry/nacos/service_discovery.go @@ -60,11 +60,20 @@ type nacosServiceDiscovery struct { // namingClient is the Nacos' client namingClient naming_client.INamingClient + // cache registry instances + registryInstances []registry.ServiceInstance } // Destroy will close the service discovery. // Actually, it only marks the naming client as null and then return func (n *nacosServiceDiscovery) Destroy() error { + for _, inst := range n.registryInstances { + err := n.Unregister(inst) + logger.Infof("Unregister nacos instance:%+v", inst) + if err != nil { + logger.Errorf("Unregister nacos instance:%+v, err:%+v", inst, err) + } + } n.namingClient = nil return nil } @@ -76,6 +85,7 @@ func (n *nacosServiceDiscovery) Register(instance registry.ServiceInstance) erro if err != nil || !ok { return perrors.WithMessage(err, "Could not register the instance. "+instance.GetServiceName()) } + n.registryInstances = append(n.registryInstances, instance) return nil } @@ -118,8 +128,8 @@ func (n *nacosServiceDiscovery) GetServices() *gxset.HashSet { return res } - for _, e := range services { - res.Add(e.Name) + for _, e := range services.Doms { + res.Add(e) } return res } @@ -334,8 +344,9 @@ func newNacosServiceDiscovery(name string) (registry.ServiceDiscovery, error) { descriptor := fmt.Sprintf("nacos-service-discovery[%s]", remoteConfig.Address) return &nacosServiceDiscovery{ - group: group, - namingClient: client, - descriptor: descriptor, + group: group, + namingClient: client, + descriptor: descriptor, + registryInstances: []registry.ServiceInstance{}, }, nil } From d32cbdb2d4ea6cd79eb7526e24d13442c8e68292 Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Tue, 4 Aug 2020 23:12:21 +0800 Subject: [PATCH 071/242] fix review comment --- config/base_config.go | 6 +++--- config/config_center_config.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/base_config.go b/config/base_config.go index c0992006da..3602c17508 100644 --- a/config/base_config.go +++ b/config/base_config.go @@ -69,8 +69,8 @@ func (c *BaseConfig) GetRemoteConfig(name string) (config *RemoteConfig, ok bool return } -func (c *BaseConfig) toURL(name string, protocol string) (common.URL, error) { - rc, ok := GetBaseConfig().GetRemoteConfig(name) +func (c *BaseConfig) toConfigCenterURL() (common.URL, error) { + rc, ok := GetBaseConfig().GetRemoteConfig(baseConfig.ConfigCenterConfig.RemoteRef) if !ok { return common.URL{}, perrors.New("Could not find out the remote ref config, name: " + name) @@ -80,7 +80,7 @@ func (c *BaseConfig) toURL(name string, protocol string) (common.URL, error) { common.WithUsername(rc.Username), common.WithPassword(rc.Password), common.WithLocation(rc.Address), - common.WithProtocol(protocol), + common.WithProtocol(baseConfig.ConfigCenterConfig.Protocol), ) } diff --git a/config/config_center_config.go b/config/config_center_config.go index e2943dcc5c..514e2d9a0e 100644 --- a/config/config_center_config.go +++ b/config/config_center_config.go @@ -96,7 +96,7 @@ func (b *configCenter) toURL(baseConfig BaseConfig) (common.URL, error) { return common.NewURL(baseConfig.ConfigCenterConfig.Address, common.WithProtocol(baseConfig.ConfigCenterConfig.Protocol), common.WithParams(baseConfig.ConfigCenterConfig.GetUrlMap())) } - newURL, err := baseConfig.toURL(baseConfig.ConfigCenterConfig.RemoteRef, baseConfig.ConfigCenterConfig.Protocol) + newURL, err := baseConfig.toConfigCenterURL() if err == nil { newURL.SetParams(baseConfig.ConfigCenterConfig.GetUrlMap()) } From e0dd74ead3d66b3a11b05bcd89e1fdcfd0ac99ce Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Tue, 4 Aug 2020 23:18:45 +0800 Subject: [PATCH 072/242] fix review comment --- config/base_config.go | 16 ---------------- config/config_center_config.go | 10 +++++++++- config/remote_config.go | 10 ++++++++++ 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/config/base_config.go b/config/base_config.go index 3602c17508..22a0832731 100644 --- a/config/base_config.go +++ b/config/base_config.go @@ -29,7 +29,6 @@ import ( ) import ( - "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/config" "github.com/apache/dubbo-go/common/logger" ) @@ -69,21 +68,6 @@ func (c *BaseConfig) GetRemoteConfig(name string) (config *RemoteConfig, ok bool return } -func (c *BaseConfig) toConfigCenterURL() (common.URL, error) { - rc, ok := GetBaseConfig().GetRemoteConfig(baseConfig.ConfigCenterConfig.RemoteRef) - - if !ok { - return common.URL{}, perrors.New("Could not find out the remote ref config, name: " + name) - } - - return common.NewURL(rc.Address, - common.WithUsername(rc.Username), - common.WithPassword(rc.Password), - common.WithLocation(rc.Address), - common.WithProtocol(baseConfig.ConfigCenterConfig.Protocol), - ) -} - func getKeyPrefix(val reflect.Value) []string { var ( prefix string diff --git a/config/config_center_config.go b/config/config_center_config.go index 514e2d9a0e..e50fabd555 100644 --- a/config/config_center_config.go +++ b/config/config_center_config.go @@ -96,7 +96,15 @@ func (b *configCenter) toURL(baseConfig BaseConfig) (common.URL, error) { return common.NewURL(baseConfig.ConfigCenterConfig.Address, common.WithProtocol(baseConfig.ConfigCenterConfig.Protocol), common.WithParams(baseConfig.ConfigCenterConfig.GetUrlMap())) } - newURL, err := baseConfig.toConfigCenterURL() + + remoteRef := baseConfig.ConfigCenterConfig.RemoteRef + rc, ok := GetBaseConfig().GetRemoteConfig(remoteRef) + + if !ok { + return common.URL{}, perrors.New("Could not find out the remote ref config, name: " + remoteRef) + } + + newURL, err := rc.toURL(baseConfig.ConfigCenterConfig.Protocol) if err == nil { newURL.SetParams(baseConfig.ConfigCenterConfig.GetUrlMap()) } diff --git a/config/remote_config.go b/config/remote_config.go index 5e0330c571..618313d7dc 100644 --- a/config/remote_config.go +++ b/config/remote_config.go @@ -18,6 +18,7 @@ package config import ( + "github.com/apache/dubbo-go/common" "time" ) @@ -56,3 +57,12 @@ func (rc *RemoteConfig) GetParam(key string, def string) string { } return param } + +func (rc *RemoteConfig) toURL(protocol string) (common.URL, error) { + return common.NewURL(rc.Address, + common.WithUsername(rc.Username), + common.WithPassword(rc.Password), + common.WithLocation(rc.Address), + common.WithProtocol(protocol), + ) +} From 8e6d2ac875c2c65aaecfd58c779d1b48768330e5 Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Wed, 5 Aug 2020 00:32:26 +0800 Subject: [PATCH 073/242] fix review comment --- config/config_center_config.go | 4 ++-- config/config_center_config_test.go | 3 +-- config/remote_config.go | 5 +++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/config/config_center_config.go b/config/config_center_config.go index e50fabd555..84f307d455 100644 --- a/config/config_center_config.go +++ b/config/config_center_config.go @@ -104,7 +104,7 @@ func (b *configCenter) toURL(baseConfig BaseConfig) (common.URL, error) { return common.URL{}, perrors.New("Could not find out the remote ref config, name: " + remoteRef) } - newURL, err := rc.toURL(baseConfig.ConfigCenterConfig.Protocol) + newURL, err := rc.toURL() if err == nil { newURL.SetParams(baseConfig.ConfigCenterConfig.GetUrlMap()) } @@ -126,7 +126,7 @@ func (b *configCenter) startConfigCenter(baseConfig BaseConfig) error { } func (b *configCenter) prepareEnvironment(baseConfig BaseConfig, configCenterUrl *common.URL) error { - factory := extension.GetConfigCenterFactory(baseConfig.ConfigCenterConfig.Protocol) + factory := extension.GetConfigCenterFactory(configCenterUrl.Protocol) dynamicConfig, err := factory.GetDynamicConfiguration(configCenterUrl) config.GetEnvInstance().SetDynamicConfiguration(dynamicConfig) if err != nil { diff --git a/config/config_center_config_test.go b/config/config_center_config_test.go index 3a08e01302..05912a9fb9 100644 --- a/config/config_center_config_test.go +++ b/config/config_center_config_test.go @@ -55,11 +55,10 @@ func TestStartConfigCenterWithRemoteRef(t *testing.T) { return &config_center.MockDynamicConfigurationFactory{} }) m := make(map[string]*RemoteConfig) - m["mock"] = &RemoteConfig{Address: "172.0.0.1"} + m["mock"] = &RemoteConfig{Protocol: "mock", Address: "172.0.0.1"} baseConfig = &BaseConfig{ Remotes: m, ConfigCenterConfig: &ConfigCenterConfig{ - Protocol: "mock", Group: "dubbo", RemoteRef: "mock", ConfigFile: "mockDubbo.properties", diff --git a/config/remote_config.go b/config/remote_config.go index 618313d7dc..f1ee734aa7 100644 --- a/config/remote_config.go +++ b/config/remote_config.go @@ -31,6 +31,7 @@ import ( // so that other module, like config center, registry could reuse the config // but now, only metadata report, metadata service, service discovery use this structure type RemoteConfig struct { + Protocol string `required:"true" yaml:"protocol" json:"protocol,omitempty"` Address string `yaml:"address" json:"address,omitempty"` TimeoutStr string `default:"5s" yaml:"timeout" json:"timeout,omitempty"` Username string `yaml:"username" json:"username,omitempty" property:"username"` @@ -58,11 +59,11 @@ func (rc *RemoteConfig) GetParam(key string, def string) string { return param } -func (rc *RemoteConfig) toURL(protocol string) (common.URL, error) { +func (rc *RemoteConfig) toURL() (common.URL, error) { return common.NewURL(rc.Address, common.WithUsername(rc.Username), common.WithPassword(rc.Password), common.WithLocation(rc.Address), - common.WithProtocol(protocol), + common.WithProtocol(rc.Protocol), ) } From 7ad26fd682b9a081c2ab3fd44a0cf502ec375620 Mon Sep 17 00:00:00 2001 From: wangwx Date: Tue, 4 Aug 2020 21:07:27 +0800 Subject: [PATCH 074/242] try to fix zk problem --- common/constant/default.go | 1 + common/constant/key.go | 1 + config/registry_config.go | 2 ++ registry/zookeeper/registry.go | 2 ++ remoting/zookeeper/listener.go | 43 ++++++++++++++++++++++++++-------- 5 files changed, 39 insertions(+), 10 deletions(-) diff --git a/common/constant/default.go b/common/constant/default.go index c1c404e089..da68d7a5b7 100644 --- a/common/constant/default.go +++ b/common/constant/default.go @@ -37,6 +37,7 @@ const ( DEFAULT_RETRIES_INT = 2 DEFAULT_PROTOCOL = "dubbo" DEFAULT_REG_TIMEOUT = "10s" + DEFAULT_REG_TTL = "10m" DEFAULT_CLUSTER = "failover" DEFAULT_FAILBACK_TIMES = "3" DEFAULT_FAILBACK_TIMES_INT = 3 diff --git a/common/constant/key.go b/common/constant/key.go index cd23dd0f1a..ea9bad9d50 100644 --- a/common/constant/key.go +++ b/common/constant/key.go @@ -97,6 +97,7 @@ const ( ROLE_KEY = "registry.role" REGISTRY_DEFAULT_KEY = "registry.default" REGISTRY_TIMEOUT_KEY = "registry.timeout" + REGISTRY_TTL_KEY = "registry.ttl" ) const ( diff --git a/config/registry_config.go b/config/registry_config.go index ef527c827e..703606b836 100644 --- a/config/registry_config.go +++ b/config/registry_config.go @@ -39,6 +39,7 @@ type RegistryConfig struct { // I changed "type" to "protocol" ,the same as "protocol" field in java class RegistryConfig TimeoutStr string `yaml:"timeout" default:"5s" json:"timeout,omitempty" property:"timeout"` // unit: second Group string `yaml:"group" json:"group,omitempty" property:"group"` + TTL string `yaml:"ttl" default:"10m" json:"ttl,omitempty" property:"ttl"` // unit: minute // for registry Address string `yaml:"address" json:"address,omitempty" property:"address"` Username string `yaml:"username" json:"username,omitempty" property:"username"` @@ -118,6 +119,7 @@ func (c *RegistryConfig) getUrlMap(roleType common.RoleType) url.Values { urlMap.Set(constant.ROLE_KEY, strconv.Itoa(int(roleType))) urlMap.Set(constant.REGISTRY_KEY, c.Protocol) urlMap.Set(constant.REGISTRY_TIMEOUT_KEY, c.TimeoutStr) + urlMap.Set(constant.REGISTRY_TTL_KEY, c.TTL) for k, v := range c.Params { urlMap.Set(k, v) } diff --git a/registry/zookeeper/registry.go b/registry/zookeeper/registry.go index 8f2ac1023b..e8ee51beb7 100644 --- a/registry/zookeeper/registry.go +++ b/registry/zookeeper/registry.go @@ -243,6 +243,8 @@ func (r *zkRegistry) getListener(conf *common.URL) (*RegistryConfigurationListen var zkListener *RegistryConfigurationListener dataListener := r.dataListener + ttl := r.GetParam(constant.REGISTRY_TTL_KEY, constant.DEFAULT_REG_TTL) + conf.SetParam(constant.REGISTRY_TTL_KEY, ttl) dataListener.mutex.Lock() defer dataListener.mutex.Unlock() if r.dataListener.subscribed[conf.ServiceKey()] != nil { diff --git a/remoting/zookeeper/listener.go b/remoting/zookeeper/listener.go index 9a4874db24..97c61f2e46 100644 --- a/remoting/zookeeper/listener.go +++ b/remoting/zookeeper/listener.go @@ -37,6 +37,10 @@ import ( "github.com/apache/dubbo-go/remoting" ) +var ( + DefaultTTL = 10 * time.Minute +) + // nolint type ZkEventListener struct { client *ZookeeperClient @@ -197,10 +201,18 @@ func (l *ZkEventListener) listenDirEvent(conf *common.URL, zkPath string, listen var ( failTimes int + ttl time.Duration event chan struct{} zkEvent zk.Event ) event = make(chan struct{}, 4) + ttl = DefaultTTL + timeout, err := time.ParseDuration(conf.GetParam(constant.REGISTRY_TTL_KEY, constant.DEFAULT_REG_TTL)) + if err == nil { + ttl = timeout + } else { + logger.Warnf("wrong configuration for registry ttl, error:=%+v", err) + } defer close(event) for { // get current children for a zkPath @@ -302,18 +314,29 @@ func (l *ZkEventListener) listenDirEvent(conf *common.URL, zkPath string, listen }(dubboPath, listener) } } - select { - case zkEvent = <-childEventCh: - logger.Warnf("get a zookeeper zkEvent{type:%s, server:%s, path:%s, state:%d-%s, err:%s}", - zkEvent.Type.String(), zkEvent.Server, zkEvent.Path, zkEvent.State, StateToString(zkEvent.State), zkEvent.Err) - if zkEvent.Type != zk.EventNodeChildrenChanged { - continue + // Periodically update provider information + ticker := time.NewTicker(ttl) + WATCH: + for { + select { + case <-ticker.C: + l.handleZkNodeEvent(zkEvent.Path, children, listener) + case zkEvent = <-childEventCh: + logger.Warnf("get a zookeeper zkEvent{type:%s, server:%s, path:%s, state:%d-%s, err:%s}", + zkEvent.Type.String(), zkEvent.Server, zkEvent.Path, zkEvent.State, StateToString(zkEvent.State), zkEvent.Err) + ticker.Stop() + if zkEvent.Type != zk.EventNodeChildrenChanged { + break WATCH + } + l.handleZkNodeEvent(zkEvent.Path, children, listener) + break WATCH + case <-l.client.Done(): + logger.Warnf("client.done(), listen(path{%s}) goroutine exit now...", zkPath) + ticker.Stop() + return } - l.handleZkNodeEvent(zkEvent.Path, children, listener) - case <-l.client.Done(): - logger.Warnf("client.done(), listen(path{%s}) goroutine exit now...", zkPath) - return } + } } From 53587eccff2595d0bed261eb2449c895eefa6bfd Mon Sep 17 00:00:00 2001 From: wangwx Date: Wed, 5 Aug 2020 11:35:12 +0800 Subject: [PATCH 075/242] try to fix ut failed --- remoting/zookeeper/listener.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/remoting/zookeeper/listener.go b/remoting/zookeeper/listener.go index 97c61f2e46..6e1908bed4 100644 --- a/remoting/zookeeper/listener.go +++ b/remoting/zookeeper/listener.go @@ -207,11 +207,13 @@ func (l *ZkEventListener) listenDirEvent(conf *common.URL, zkPath string, listen ) event = make(chan struct{}, 4) ttl = DefaultTTL - timeout, err := time.ParseDuration(conf.GetParam(constant.REGISTRY_TTL_KEY, constant.DEFAULT_REG_TTL)) - if err == nil { - ttl = timeout - } else { - logger.Warnf("wrong configuration for registry ttl, error:=%+v", err) + if conf != nil { + timeout, err := time.ParseDuration(conf.GetParam(constant.REGISTRY_TTL_KEY, constant.DEFAULT_REG_TTL)) + if err == nil { + ttl = timeout + } else { + logger.Warnf("wrong configuration for registry ttl, error:=%+v", err) + } } defer close(event) for { From 7a88bab0bfc73f23c777c46931e8011abf1bf8bc Mon Sep 17 00:00:00 2001 From: wangwx Date: Wed, 5 Aug 2020 14:09:50 +0800 Subject: [PATCH 076/242] change the default value to 15m --- common/constant/default.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/constant/default.go b/common/constant/default.go index da68d7a5b7..629aa32392 100644 --- a/common/constant/default.go +++ b/common/constant/default.go @@ -37,7 +37,7 @@ const ( DEFAULT_RETRIES_INT = 2 DEFAULT_PROTOCOL = "dubbo" DEFAULT_REG_TIMEOUT = "10s" - DEFAULT_REG_TTL = "10m" + DEFAULT_REG_TTL = "15m" DEFAULT_CLUSTER = "failover" DEFAULT_FAILBACK_TIMES = "3" DEFAULT_FAILBACK_TIMES_INT = 3 From c33f4eabfb4cc8ff6b904ac5d5ebc067aad59193 Mon Sep 17 00:00:00 2001 From: wangwx Date: Wed, 5 Aug 2020 14:20:18 +0800 Subject: [PATCH 077/242] change the default value to 15min --- remoting/zookeeper/listener.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/remoting/zookeeper/listener.go b/remoting/zookeeper/listener.go index 6e1908bed4..2430b66f9c 100644 --- a/remoting/zookeeper/listener.go +++ b/remoting/zookeeper/listener.go @@ -38,7 +38,7 @@ import ( ) var ( - DefaultTTL = 10 * time.Minute + DefaultTTL = 15 * time.Minute ) // nolint From 2d8224e7108f4d55bb06225c186cec04175c8669 Mon Sep 17 00:00:00 2001 From: lzp0412 <641785844@qq.com> Date: Wed, 5 Aug 2020 20:52:35 +0800 Subject: [PATCH 078/242] fix review comment --- registry/nacos/registry.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/registry/nacos/registry.go b/registry/nacos/registry.go index 6ed5bc0d2f..411090820c 100644 --- a/registry/nacos/registry.go +++ b/registry/nacos/registry.go @@ -226,9 +226,9 @@ func (nr *nacosRegistry) IsAvailable() bool { func (nr *nacosRegistry) Destroy() { for _, url := range nr.registryUrls { err := nr.DeRegister(url) - logger.Infof("DeRegister Nacos url:%+v", url) + logger.Infof("DeRegister Nacos URL:%+v", url) if err != nil { - logger.Errorf("Deregister url:%+v err:%v", url, err.Error()) + logger.Errorf("Deregister URL:%+v err:%v", url, err.Error()) } } return From da4225ec32a533c3046c573c1ee2dddab5578d74 Mon Sep 17 00:00:00 2001 From: socttwang Date: Wed, 5 Aug 2020 20:58:34 +0800 Subject: [PATCH 079/242] Fix kubernetes ut --- remoting/kubernetes/listener_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/remoting/kubernetes/listener_test.go b/remoting/kubernetes/listener_test.go index 1f398485b2..5cfc051f5d 100644 --- a/remoting/kubernetes/listener_test.go +++ b/remoting/kubernetes/listener_test.go @@ -18,7 +18,9 @@ package kubernetes import ( + "runtime" "testing" + "time" ) import ( @@ -51,6 +53,10 @@ var changedData = ` dubbo.service.com.ikurento.user.UserProvider.cluster=failover ` +func init(){ + runtime.GOMAXPROCS(1) +} + type mockDataListener struct { eventList []remoting.Event client *Client @@ -87,6 +93,7 @@ func TestListener(t *testing.T) { listener := NewEventListener(c) dataListener := &mockDataListener{client: c, changedData: changedData, rc: make(chan remoting.Event)} listener.ListenServiceEvent("/dubbo", dataListener) + time.Sleep(1e9) for _, tc := range tests { From 79ade24cf543e2b3b2b884afba1ac52ab7066b45 Mon Sep 17 00:00:00 2001 From: socttwang Date: Wed, 5 Aug 2020 20:59:25 +0800 Subject: [PATCH 080/242] FMT k8s pkg --- remoting/kubernetes/listener_test.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/remoting/kubernetes/listener_test.go b/remoting/kubernetes/listener_test.go index 5cfc051f5d..0b05b6e6e0 100644 --- a/remoting/kubernetes/listener_test.go +++ b/remoting/kubernetes/listener_test.go @@ -18,7 +18,6 @@ package kubernetes import ( - "runtime" "testing" "time" ) @@ -53,10 +52,6 @@ var changedData = ` dubbo.service.com.ikurento.user.UserProvider.cluster=failover ` -func init(){ - runtime.GOMAXPROCS(1) -} - type mockDataListener struct { eventList []remoting.Event client *Client From 3bc2938860adc5a6d55940e4a639c8b277811e6d Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Wed, 5 Aug 2020 22:38:28 +0800 Subject: [PATCH 081/242] fix review comment --- config/remote_config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/remote_config.go b/config/remote_config.go index f1ee734aa7..a3f58b845a 100644 --- a/config/remote_config.go +++ b/config/remote_config.go @@ -18,11 +18,11 @@ package config import ( - "github.com/apache/dubbo-go/common" "time" ) import ( + "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/logger" ) From e654674dc91bd04751085f8520f8c6ba281f00ba Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 5 Aug 2020 23:06:46 +0800 Subject: [PATCH 082/242] register service instance after provider config load --- config/config_loader.go | 94 +++++++++++++++++++ registry/protocol/protocol.go | 33 ++++--- registry/registry_factory.go | 24 +++++ registry/service_discovery_factory.go | 24 +++++ .../service_discovery_registry.go | 43 --------- 5 files changed, 164 insertions(+), 54 deletions(-) create mode 100644 registry/registry_factory.go create mode 100644 registry/service_discovery_factory.go diff --git a/config/config_loader.go b/config/config_loader.go index d5f8c68c1b..9a1c8fe9cd 100644 --- a/config/config_loader.go +++ b/config/config_loader.go @@ -21,6 +21,7 @@ import ( "fmt" "log" "os" + "strconv" "sync" "time" ) @@ -35,6 +36,7 @@ import ( "github.com/apache/dubbo-go/common/extension" "github.com/apache/dubbo-go/common/logger" _ "github.com/apache/dubbo-go/common/observer/dispatcher" + "github.com/apache/dubbo-go/registry" ) var ( @@ -205,6 +207,98 @@ func loadProviderConfig() { panic(fmt.Sprintf("service %s export failed! err: %#v", key, err)) } } + registerServiceInstance() +} + +// registerServiceInstance register service instance +func registerServiceInstance(){ + url := selectMetadataServiceExportedURL() + if url == nil { + return + } + instance, err := createInstance(*url) + if err != nil { + panic(err) + } + p := extension.GetProtocol(constant.REGISTRY_KEY) + var rp registry.RegistryFactory + var ok bool + if rp, ok = p.(registry.RegistryFactory); !ok { + panic("dubbo registry protocol is invalid") + } + rs := rp.GetRegistries() + for _, r := range rs { + var sdr registry.ServiceDiscoveryFactory + if sdr, ok = r.(registry.ServiceDiscoveryFactory); !ok { + continue + } + err := sdr.GetServiceDiscovery().Register(instance) + if err != nil { + panic(err) + } + } +} + +// createInstance +func createInstance(url common.URL) (registry.ServiceInstance, error) { + appConfig := GetApplicationConfig() + port, err := strconv.ParseInt(url.Port, 10, 32) + if err != nil { + return nil, perrors.WithMessage(err, "invalid port: "+url.Port) + } + + host := url.Ip + if len(host) == 0 { + host, err = gxnet.GetLocalIP() + if err != nil { + return nil, perrors.WithMessage(err, "could not get the local Ip") + } + } + + // usually we will add more metadata + metadata := make(map[string]string, 8) + metadata[constant.METADATA_STORAGE_TYPE_PROPERTY_NAME] = appConfig.MetadataType + + return ®istry.DefaultServiceInstance{ + ServiceName: appConfig.Name, + Host: host, + Port: int(port), + Id: host + constant.KEY_SEPARATOR + url.Port, + Enable: true, + Healthy: true, + Metadata: metadata, + }, nil +} + +// selectMetadataServiceExportedURL get already be exported url +func selectMetadataServiceExportedURL() *common.URL { + var selectedUrl common.URL + metaDataService, err := extension.GetMetadataService(GetApplicationConfig().MetadataType) + if err != nil { + panic(err) + } + list, err := metaDataService.GetExportedURLs(constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE) + if err != nil { + panic(err) + } + if len(list) == 0 { + return nil + } + for _, urlStr := range list { + url, err := common.NewURL(urlStr.(string)) + if err != nil { + logger.Errorf("url format error {%v}", url) + continue + } + // rest first + if url.Protocol == "rest" { + selectedUrl = url + break + } else { + selectedUrl = url + } + } + return &selectedUrl } func initRouter() { diff --git a/registry/protocol/protocol.go b/registry/protocol/protocol.go index 4c669b2cee..963a3cba95 100644 --- a/registry/protocol/protocol.go +++ b/registry/protocol/protocol.go @@ -43,7 +43,7 @@ import ( ) var ( - regProtocol *registryProtocol + regProtocol *RegistryProtocol once sync.Once reserveParams = []string{ "application", "codec", "exchanger", "serialization", "cluster", "connections", "deprecated", "group", @@ -52,7 +52,7 @@ var ( } ) -type registryProtocol struct { +type RegistryProtocol struct { invokers []protocol.Invoker // Registry Map registries *sync.Map @@ -74,8 +74,8 @@ func getCacheKey(url *common.URL) string { return url.CloneExceptParams(delKeys).String() } -func newRegistryProtocol() *registryProtocol { - return ®istryProtocol{ +func newRegistryProtocol() *RegistryProtocol { + return &RegistryProtocol{ registries: &sync.Map{}, bounds: &sync.Map{}, } @@ -111,14 +111,25 @@ func filterHideKey(url *common.URL) *common.URL { return url.CloneExceptParams(removeSet) } -func (proto *registryProtocol) initConfigurationListeners() { +func (proto *RegistryProtocol) initConfigurationListeners() { proto.overrideListeners = &sync.Map{} proto.serviceConfigurationListeners = &sync.Map{} proto.providerConfigurationListener = newProviderConfigurationListener(proto.overrideListeners) } +func (proto *RegistryProtocol) GetRegistries() []registry.Registry{ + var rs []registry.Registry + proto.registries.Range(func(_, v interface{}) bool { + if r, ok := v.(registry.Registry); ok { + rs = append(rs, r) + } + return true + }) + return rs +} + // Refer provider service from registry center -func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker { +func (proto *RegistryProtocol) Refer(url common.URL) protocol.Invoker { var registryUrl = url var serviceUrl = registryUrl.SubURL if registryUrl.Protocol == constant.REGISTRY_PROTOCOL { @@ -158,7 +169,7 @@ func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker { } // Export provider service to registry center -func (proto *registryProtocol) Export(invoker protocol.Invoker) protocol.Exporter { +func (proto *RegistryProtocol) Export(invoker protocol.Invoker) protocol.Exporter { proto.once.Do(func() { proto.initConfigurationListeners() }) @@ -207,7 +218,7 @@ func (proto *registryProtocol) Export(invoker protocol.Invoker) protocol.Exporte } -func (proto *registryProtocol) reExport(invoker protocol.Invoker, newUrl *common.URL) { +func (proto *RegistryProtocol) reExport(invoker protocol.Invoker, newUrl *common.URL) { url := getProviderUrl(invoker) key := getCacheKey(url) if oldExporter, loaded := proto.bounds.Load(key); loaded { @@ -223,11 +234,11 @@ func (proto *registryProtocol) reExport(invoker protocol.Invoker, newUrl *common type overrideSubscribeListener struct { url *common.URL originInvoker protocol.Invoker - protocol *registryProtocol + protocol *RegistryProtocol configurator config_center.Configurator } -func newOverrideSubscribeListener(overriderUrl *common.URL, invoker protocol.Invoker, proto *registryProtocol) *overrideSubscribeListener { +func newOverrideSubscribeListener(overriderUrl *common.URL, invoker protocol.Invoker, proto *RegistryProtocol) *overrideSubscribeListener { return &overrideSubscribeListener{url: overriderUrl, originInvoker: invoker, protocol: proto} } @@ -329,7 +340,7 @@ func getSubscribedOverrideUrl(providerUrl *common.URL) *common.URL { } // Destroy registry protocol -func (proto *registryProtocol) Destroy() { +func (proto *RegistryProtocol) Destroy() { for _, ivk := range proto.invokers { ivk.Destroy() } diff --git a/registry/registry_factory.go b/registry/registry_factory.go new file mode 100644 index 0000000000..58fbe39553 --- /dev/null +++ b/registry/registry_factory.go @@ -0,0 +1,24 @@ +/* + * 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 registry + +// RegistryFactory +type RegistryFactory interface { + // GetRegistries get registries + GetRegistries() []Registry +} diff --git a/registry/service_discovery_factory.go b/registry/service_discovery_factory.go new file mode 100644 index 0000000000..6382403a45 --- /dev/null +++ b/registry/service_discovery_factory.go @@ -0,0 +1,24 @@ +/* + * 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 registry + +// ServiceDiscoveryFactory +type ServiceDiscoveryFactory interface { + // GetServiceDiscovery get service discovery + GetServiceDiscovery() ServiceDiscovery +} diff --git a/registry/servicediscovery/service_discovery_registry.go b/registry/servicediscovery/service_discovery_registry.go index 061d832b03..cdb586c137 100644 --- a/registry/servicediscovery/service_discovery_registry.go +++ b/registry/servicediscovery/service_discovery_registry.go @@ -28,7 +28,6 @@ import ( import ( cm "github.com/Workiva/go-datastructures/common" gxset "github.com/dubbogo/gost/container/set" - gxnet "github.com/dubbogo/gost/net" perrors "github.com/pkg/errors" "go.uber.org/atomic" ) @@ -176,18 +175,6 @@ func (s *serviceDiscoveryRegistry) Register(url common.URL) error { logger.Warnf("The URL[%s] has been registry!", url.String()) } - // we try to register this instance. Dubbo do this in org.apache.dubbo.config.bootstrap.DubboBootstrap - // But we don't want to design a similar bootstrap class. - ins, err := createInstance(url) - if err != nil { - return perrors.WithMessage(err, "could not create servcie instance, please check your service url") - } - - err = s.serviceDiscovery.Register(ins) - if err != nil { - return perrors.WithMessage(err, "register the service failed") - } - err = s.metaDataService.PublishServiceDefinition(url) if err != nil { return perrors.WithMessage(err, "publish the service definition failed. ") @@ -198,36 +185,6 @@ func (s *serviceDiscoveryRegistry) Register(url common.URL) error { url.Protocol) } -func createInstance(url common.URL) (registry.ServiceInstance, error) { - appConfig := config.GetApplicationConfig() - port, err := strconv.ParseInt(url.Port, 10, 32) - if err != nil { - return nil, perrors.WithMessage(err, "invalid port: "+url.Port) - } - - host := url.Ip - if len(host) == 0 { - host, err = gxnet.GetLocalIP() - if err != nil { - return nil, perrors.WithMessage(err, "could not get the local Ip") - } - } - - // usually we will add more metadata - metadata := make(map[string]string, 8) - metadata[constant.METADATA_STORAGE_TYPE_PROPERTY_NAME] = appConfig.MetadataType - - return ®istry.DefaultServiceInstance{ - ServiceName: appConfig.Name, - Host: host, - Port: int(port), - Id: host + constant.KEY_SEPARATOR + url.Port, - Enable: true, - Healthy: true, - Metadata: metadata, - }, nil -} - func shouldRegister(url common.URL) bool { side := url.GetParam(constant.SIDE_KEY, "") if side == constant.PROVIDER_PROTOCOL { From dc075daadfa0507c45c48ecc3405f034eb389f97 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 5 Aug 2020 23:16:19 +0800 Subject: [PATCH 083/242] modify name --- config/config_loader.go | 4 ++-- registry/protocol/protocol.go | 26 +++++++++++++------------- registry/service_discovery_factory.go | 4 ++-- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/config/config_loader.go b/config/config_loader.go index 9a1c8fe9cd..98553fc316 100644 --- a/config/config_loader.go +++ b/config/config_loader.go @@ -228,8 +228,8 @@ func registerServiceInstance(){ } rs := rp.GetRegistries() for _, r := range rs { - var sdr registry.ServiceDiscoveryFactory - if sdr, ok = r.(registry.ServiceDiscoveryFactory); !ok { + var sdr registry.ServiceDiscoveryHolder + if sdr, ok = r.(registry.ServiceDiscoveryHolder); !ok { continue } err := sdr.GetServiceDiscovery().Register(instance) diff --git a/registry/protocol/protocol.go b/registry/protocol/protocol.go index 963a3cba95..f7189a9954 100644 --- a/registry/protocol/protocol.go +++ b/registry/protocol/protocol.go @@ -43,7 +43,7 @@ import ( ) var ( - regProtocol *RegistryProtocol + regProtocol *registryProtocol once sync.Once reserveParams = []string{ "application", "codec", "exchanger", "serialization", "cluster", "connections", "deprecated", "group", @@ -52,7 +52,7 @@ var ( } ) -type RegistryProtocol struct { +type registryProtocol struct { invokers []protocol.Invoker // Registry Map registries *sync.Map @@ -74,8 +74,8 @@ func getCacheKey(url *common.URL) string { return url.CloneExceptParams(delKeys).String() } -func newRegistryProtocol() *RegistryProtocol { - return &RegistryProtocol{ +func newRegistryProtocol() *registryProtocol { + return ®istryProtocol{ registries: &sync.Map{}, bounds: &sync.Map{}, } @@ -111,13 +111,13 @@ func filterHideKey(url *common.URL) *common.URL { return url.CloneExceptParams(removeSet) } -func (proto *RegistryProtocol) initConfigurationListeners() { +func (proto *registryProtocol) initConfigurationListeners() { proto.overrideListeners = &sync.Map{} proto.serviceConfigurationListeners = &sync.Map{} proto.providerConfigurationListener = newProviderConfigurationListener(proto.overrideListeners) } -func (proto *RegistryProtocol) GetRegistries() []registry.Registry{ +func (proto *registryProtocol) GetRegistries() []registry.Registry{ var rs []registry.Registry proto.registries.Range(func(_, v interface{}) bool { if r, ok := v.(registry.Registry); ok { @@ -129,7 +129,7 @@ func (proto *RegistryProtocol) GetRegistries() []registry.Registry{ } // Refer provider service from registry center -func (proto *RegistryProtocol) Refer(url common.URL) protocol.Invoker { +func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker { var registryUrl = url var serviceUrl = registryUrl.SubURL if registryUrl.Protocol == constant.REGISTRY_PROTOCOL { @@ -169,7 +169,7 @@ func (proto *RegistryProtocol) Refer(url common.URL) protocol.Invoker { } // Export provider service to registry center -func (proto *RegistryProtocol) Export(invoker protocol.Invoker) protocol.Exporter { +func (proto *registryProtocol) Export(invoker protocol.Invoker) protocol.Exporter { proto.once.Do(func() { proto.initConfigurationListeners() }) @@ -218,7 +218,7 @@ func (proto *RegistryProtocol) Export(invoker protocol.Invoker) protocol.Exporte } -func (proto *RegistryProtocol) reExport(invoker protocol.Invoker, newUrl *common.URL) { +func (proto *registryProtocol) reExport(invoker protocol.Invoker, newUrl *common.URL) { url := getProviderUrl(invoker) key := getCacheKey(url) if oldExporter, loaded := proto.bounds.Load(key); loaded { @@ -234,11 +234,11 @@ func (proto *RegistryProtocol) reExport(invoker protocol.Invoker, newUrl *common type overrideSubscribeListener struct { url *common.URL originInvoker protocol.Invoker - protocol *RegistryProtocol + protocol *registryProtocol configurator config_center.Configurator } -func newOverrideSubscribeListener(overriderUrl *common.URL, invoker protocol.Invoker, proto *RegistryProtocol) *overrideSubscribeListener { +func newOverrideSubscribeListener(overriderUrl *common.URL, invoker protocol.Invoker, proto *registryProtocol) *overrideSubscribeListener { return &overrideSubscribeListener{url: overriderUrl, originInvoker: invoker, protocol: proto} } @@ -340,7 +340,7 @@ func getSubscribedOverrideUrl(providerUrl *common.URL) *common.URL { } // Destroy registry protocol -func (proto *RegistryProtocol) Destroy() { +func (proto *registryProtocol) Destroy() { for _, ivk := range proto.invokers { ivk.Destroy() } @@ -384,7 +384,7 @@ func setProviderUrl(regURL *common.URL, providerURL *common.URL) { regURL.SubURL = providerURL } -// GetProtocol return the singleton RegistryProtocol +// GetProtocol return the singleton registryProtocol func GetProtocol() protocol.Protocol { once.Do(func() { regProtocol = newRegistryProtocol() diff --git a/registry/service_discovery_factory.go b/registry/service_discovery_factory.go index 6382403a45..86c38d0cf3 100644 --- a/registry/service_discovery_factory.go +++ b/registry/service_discovery_factory.go @@ -17,8 +17,8 @@ package registry -// ServiceDiscoveryFactory -type ServiceDiscoveryFactory interface { +// ServiceDiscoveryHolder +type ServiceDiscoveryHolder interface { // GetServiceDiscovery get service discovery GetServiceDiscovery() ServiceDiscovery } From eb919e19d82afec1d28c660791e0f341166b725c Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Thu, 6 Aug 2020 00:41:03 +0800 Subject: [PATCH 084/242] fix review comment --- config/config_center_config.go | 6 +++--- config/config_center_config_test.go | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/config/config_center_config.go b/config/config_center_config.go index 84f307d455..b0a0090ce7 100644 --- a/config/config_center_config.go +++ b/config/config_center_config.go @@ -118,21 +118,21 @@ func (b *configCenter) startConfigCenter(baseConfig BaseConfig) error { if err != nil { return err } - if b.prepareEnvironment(baseConfig, &url) != nil { + if err = b.prepareEnvironment(baseConfig, &url); err != nil { return perrors.WithMessagef(err, "start config center error!") } // c.fresh() - return err + return nil } func (b *configCenter) prepareEnvironment(baseConfig BaseConfig, configCenterUrl *common.URL) error { factory := extension.GetConfigCenterFactory(configCenterUrl.Protocol) dynamicConfig, err := factory.GetDynamicConfiguration(configCenterUrl) - config.GetEnvInstance().SetDynamicConfiguration(dynamicConfig) if err != nil { logger.Errorf("Get dynamic configuration error , error message is %v", err) return perrors.WithStack(err) } + config.GetEnvInstance().SetDynamicConfiguration(dynamicConfig) content, err := dynamicConfig.GetProperties(baseConfig.ConfigCenterConfig.ConfigFile, config_center.WithGroup(baseConfig.ConfigCenterConfig.Group)) if err != nil { logger.Errorf("Get config content in dynamic configuration error , error message is %v", err) diff --git a/config/config_center_config_test.go b/config/config_center_config_test.go index 05912a9fb9..0f43d01655 100644 --- a/config/config_center_config_test.go +++ b/config/config_center_config_test.go @@ -70,8 +70,6 @@ func TestStartConfigCenterWithRemoteRef(t *testing.T) { b, v := config.GetEnvInstance().Configuration().Back().Value.(*config.InmemoryConfiguration).GetProperty("dubbo.application.organization") assert.True(t, b) assert.Equal(t, "ikurento.com", v) - - baseConfig = nil } func TestStartConfigCenterWithRemoteRefError(t *testing.T) { From 9b4745f43ffdb8ece35573569c5c36d59e45fd37 Mon Sep 17 00:00:00 2001 From: wangwx Date: Thu, 6 Aug 2020 09:42:41 +0800 Subject: [PATCH 085/242] fix comments --- remoting/zookeeper/listener.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/remoting/zookeeper/listener.go b/remoting/zookeeper/listener.go index 2430b66f9c..4f50c18ab6 100644 --- a/remoting/zookeeper/listener.go +++ b/remoting/zookeeper/listener.go @@ -38,7 +38,7 @@ import ( ) var ( - DefaultTTL = 15 * time.Minute + defaultTTL = 15 * time.Minute ) // nolint @@ -206,13 +206,13 @@ func (l *ZkEventListener) listenDirEvent(conf *common.URL, zkPath string, listen zkEvent zk.Event ) event = make(chan struct{}, 4) - ttl = DefaultTTL + ttl = defaultTTL if conf != nil { timeout, err := time.ParseDuration(conf.GetParam(constant.REGISTRY_TTL_KEY, constant.DEFAULT_REG_TTL)) if err == nil { ttl = timeout } else { - logger.Warnf("wrong configuration for registry ttl, error:=%+v", err) + logger.Warnf("wrong configuration for registry ttl, error:=%+v, using default value %v instead", err, defaultTTL) } } defer close(event) From bcffde9b63a2f52f0c4f2954d80703351d42f64b Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Thu, 6 Aug 2020 12:35:02 +0800 Subject: [PATCH 086/242] fix travis --- config/remote_config.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/config/remote_config.go b/config/remote_config.go index a3f58b845a..55380dd5a0 100644 --- a/config/remote_config.go +++ b/config/remote_config.go @@ -21,6 +21,10 @@ import ( "time" ) +import ( + perrors "github.com/pkg/errors" +) + import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/logger" @@ -31,7 +35,7 @@ import ( // so that other module, like config center, registry could reuse the config // but now, only metadata report, metadata service, service discovery use this structure type RemoteConfig struct { - Protocol string `required:"true" yaml:"protocol" json:"protocol,omitempty"` + Protocol string `yaml:"protocol" json:"protocol,omitempty"` Address string `yaml:"address" json:"address,omitempty"` TimeoutStr string `default:"5s" yaml:"timeout" json:"timeout,omitempty"` Username string `yaml:"username" json:"username,omitempty" property:"username"` @@ -60,6 +64,9 @@ func (rc *RemoteConfig) GetParam(key string, def string) string { } func (rc *RemoteConfig) toURL() (common.URL, error) { + if len(rc.Protocol) == 0 { + return common.URL{}, perrors.Errorf("Must provide protocol in RemoteConfig.") + } return common.NewURL(rc.Address, common.WithUsername(rc.Username), common.WithPassword(rc.Password), From 603ffc19b81dc56d6001fb15e898690630bddb54 Mon Sep 17 00:00:00 2001 From: "Xin.Zh" Date: Thu, 6 Aug 2020 13:19:51 +0800 Subject: [PATCH 087/242] Revert "Ftr: dynamic tag router" --- before_ut.sh | 3 - cluster/router/condition/app_router_test.go | 2 +- cluster/router/condition/listenable_router.go | 2 +- cluster/router/tag/file.go | 2 +- cluster/router/tag/router_rule.go | 71 ---- cluster/router/tag/router_rule_test.go | 55 +-- cluster/router/tag/tag.go | 39 -- cluster/router/tag/tag_router.go | 333 +----------------- cluster/router/tag/tag_router_test.go | 245 +------------ common/constant/key.go | 3 - 10 files changed, 22 insertions(+), 733 deletions(-) delete mode 100644 cluster/router/tag/tag.go diff --git a/before_ut.sh b/before_ut.sh index b55e424ef7..210e9e723b 100755 --- a/before_ut.sh +++ b/before_ut.sh @@ -36,8 +36,5 @@ cp ${zkJar} cluster/router/chain/zookeeper-4unittest/contrib/fatjar mkdir -p cluster/router/condition/zookeeper-4unittest/contrib/fatjar cp ${zkJar} cluster/router/condition/zookeeper-4unittest/contrib/fatjar -mkdir -p cluster/router/tag/zookeeper-4unittest/contrib/fatjar -cp ${zkJar} cluster/router/tag/zookeeper-4unittest/contrib/fatjar - mkdir -p metadata/report/zookeeper/zookeeper-4unittest/contrib/fatjar cp ${zkJar} metadata/report/zookeeper/zookeeper-4unittest/contrib/fatjar \ No newline at end of file diff --git a/cluster/router/condition/app_router_test.go b/cluster/router/condition/app_router_test.go index ea18604964..8b38f2dd61 100644 --- a/cluster/router/condition/app_router_test.go +++ b/cluster/router/condition/app_router_test.go @@ -24,6 +24,7 @@ import ( ) import ( + _ "github.com/apache/dubbo-go/config_center/zookeeper" "github.com/stretchr/testify/assert" ) @@ -33,7 +34,6 @@ import ( "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" "github.com/apache/dubbo-go/config_center" - _ "github.com/apache/dubbo-go/config_center/zookeeper" "github.com/apache/dubbo-go/remoting" "github.com/apache/dubbo-go/remoting/zookeeper" ) diff --git a/cluster/router/condition/listenable_router.go b/cluster/router/condition/listenable_router.go index 7f4f14a8e4..4ccc19e955 100644 --- a/cluster/router/condition/listenable_router.go +++ b/cluster/router/condition/listenable_router.go @@ -85,7 +85,7 @@ func newListenableRouter(url *common.URL, ruleKey string) (*AppRouter, error) { return l, nil } -// Process Process config change event, generate routers and set them to the listenableRouter instance +// Process Process config change event , generate routers and set them to the listenableRouter instance func (l *listenableRouter) Process(event *config_center.ConfigChangeEvent) { logger.Infof("Notification of condition rule, change type is:[%s] , raw rule is:[%v]", event.ConfigType, event.Value) if remoting.EventTypeDel == event.ConfigType { diff --git a/cluster/router/tag/file.go b/cluster/router/tag/file.go index 433abcb72e..8144c83203 100644 --- a/cluster/router/tag/file.go +++ b/cluster/router/tag/file.go @@ -42,7 +42,7 @@ type FileTagRouter struct { force bool } -// NewFileTagRouter Create file tag router instance with content (from config file) +// NewFileTagRouter Create file tag router instance with content ( from config file) func NewFileTagRouter(content []byte) (*FileTagRouter, error) { fileRouter := &FileTagRouter{} rule, err := getRule(string(content)) diff --git a/cluster/router/tag/router_rule.go b/cluster/router/tag/router_rule.go index 5fb7ab151c..926446dcb2 100644 --- a/cluster/router/tag/router_rule.go +++ b/cluster/router/tag/router_rule.go @@ -22,27 +22,9 @@ import ( "github.com/apache/dubbo-go/common/yaml" ) -/** - * %YAML1.2 - * --- - * force: true - * runtime: false - * enabled: true - * priority: 1 - * key: demo-provider - * tags: - * - name: tag1 - * addresses: [ip1, ip2] - * - name: tag2 - * addresses: [ip3, ip4] - * ... - */ // RouterRule RouterRule config read from config file or config center type RouterRule struct { router.BaseRouterRule `yaml:",inline""` - Tags []Tag - addressToTagNames map[string][]string - tagNameToAddresses map[string][]string } func getRule(rawRule string) (*RouterRule, error) { @@ -52,58 +34,5 @@ func getRule(rawRule string) (*RouterRule, error) { return r, err } r.RawRule = rawRule - r.init() return r, nil } - -func (t *RouterRule) init() { - t.addressToTagNames = make(map[string][]string, 8) - t.tagNameToAddresses = make(map[string][]string, 8) - for _, tag := range t.Tags { - for _, address := range tag.Addresses { - t.addressToTagNames[address] = append(t.addressToTagNames[address], tag.Name) - } - t.tagNameToAddresses[tag.Name] = tag.Addresses - } -} - -func (t *RouterRule) getAddresses() []string { - var result = make([]string, 0, 8*len(t.Tags)) - for _, tag := range t.Tags { - result = append(result, tag.Addresses...) - } - return result -} - -func (t *RouterRule) getTagNames() []string { - var result = make([]string, 0, len(t.Tags)) - for _, tag := range t.Tags { - result = append(result, tag.Name) - } - return result -} - -func (t *RouterRule) hasTag(tag string) bool { - for _, t := range t.Tags { - if tag == t.Name { - return true - } - } - return false -} - -func (t *RouterRule) getAddressToTagNames() map[string][]string { - return t.addressToTagNames -} - -func (t *RouterRule) getTagNameToAddresses() map[string][]string { - return t.tagNameToAddresses -} - -func (t *RouterRule) getTags() []Tag { - return t.Tags -} - -func (t *RouterRule) setTags(tags []Tag) { - t.Tags = tags -} diff --git a/cluster/router/tag/router_rule_test.go b/cluster/router/tag/router_rule_test.go index 4e0f5b729e..2df65193f9 100644 --- a/cluster/router/tag/router_rule_test.go +++ b/cluster/router/tag/router_rule_test.go @@ -22,56 +22,19 @@ import ( ) import ( - "github.com/stretchr/testify/suite" + "github.com/stretchr/testify/assert" ) -type RuleTestSuite struct { - suite.Suite - rule *RouterRule -} - -func (suite *RuleTestSuite) SetupTest() { - var err error +func TestGetRule(t *testing.T) { yml := ` scope: application +runtime: true force: true -runtime: false -enabled: true -priority: 1 -key: demo-provider -tags: - - name: tag1 - addresses: [ip1, ip2] - - name: tag2 - addresses: [ip3, ip4] ` - suite.rule, err = getRule(yml) - suite.Nil(err) -} - -func (suite *RuleTestSuite) TestGetRule() { - var err error - suite.Equal(true, suite.rule.Force) - suite.Equal(false, suite.rule.Runtime) - suite.Equal("application", suite.rule.Scope) - suite.Equal(1, suite.rule.Priority) - suite.Equal("demo-provider", suite.rule.Key) - suite.Nil(err) -} - -func (suite *RuleTestSuite) TestGetTagNames() { - suite.Equal([]string{"tag1", "tag2"}, suite.rule.getTagNames()) -} - -func (suite *RuleTestSuite) TestGetAddresses() { - suite.Equal([]string{"ip1", "ip2", "ip3", "ip4"}, suite.rule.getAddresses()) -} - -func (suite *RuleTestSuite) TestHasTag() { - suite.Equal(true, suite.rule.hasTag("tag1")) - suite.Equal(false, suite.rule.hasTag("tag404")) -} - -func TestRuleTestSuite(t *testing.T) { - suite.Run(t, new(RuleTestSuite)) + rule, e := getRule(yml) + assert.Nil(t, e) + assert.NotNil(t, rule) + assert.Equal(t, true, rule.Force) + assert.Equal(t, true, rule.Runtime) + assert.Equal(t, "application", rule.Scope) } diff --git a/cluster/router/tag/tag.go b/cluster/router/tag/tag.go deleted file mode 100644 index 73d10b5db4..0000000000 --- a/cluster/router/tag/tag.go +++ /dev/null @@ -1,39 +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 tag - -type Tag struct { - Name string - Addresses []string -} - -func (t *Tag) getName() string { - return t.Name -} - -func (t *Tag) setName(name string) { - t.Name = name -} - -func (t *Tag) getAddresses() []string { - return t.Addresses -} - -func (t *Tag) setAddresses(addresses []string) { - t.Addresses = addresses -} diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index ece950ebc0..e1376fd96a 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -18,10 +18,7 @@ package tag import ( - "errors" - "net" "strconv" - "strings" ) import ( @@ -30,21 +27,15 @@ import ( import ( "github.com/apache/dubbo-go/common" - "github.com/apache/dubbo-go/common/config" "github.com/apache/dubbo-go/common/constant" - "github.com/apache/dubbo-go/common/logger" - "github.com/apache/dubbo-go/config_center" "github.com/apache/dubbo-go/protocol" - "github.com/apache/dubbo-go/remoting" ) // tagRouter defines url, enable and the priority type tagRouter struct { - url *common.URL - tagRouterRule *RouterRule - enabled bool - priority int64 - application string + url *common.URL + enabled bool + priority int64 } // NewTagRouter returns a tagRouter instance if url is not nil @@ -64,15 +55,6 @@ func (c *tagRouter) isEnabled() bool { return c.enabled } -func (c *tagRouter) SetApplication(app string) { - c.application = app -} - -func (c *tagRouter) tagRouterRuleCopy() RouterRule { - routerRule := *c.tagRouterRule - return routerRule -} - // Route gets a list of invoker func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { if !c.isEnabled() { @@ -81,152 +63,7 @@ func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocati if len(invokers) == 0 { return invokers } - if c.tagRouterRule == nil || !c.tagRouterRule.Valid || !c.tagRouterRule.Enabled { - return filterUsingStaticTag(invokers, url, invocation) - } - // since the rule can be changed by config center, we should copy one to use. - tagRouterRuleCopy := c.tagRouterRuleCopy() - tag, ok := invocation.Attachments()[constant.Tagkey] - if !ok { - tag = url.GetParam(constant.Tagkey, "") - } - var ( - result []protocol.Invoker - addresses []string - ) - // if we are requesting for a Provider with a specific tag - if len(tag) > 0 { - addresses, _ = tagRouterRuleCopy.getTagNameToAddresses()[tag] - // filter by dynamic tag group first - if len(addresses) > 0 { - filterAddressMatches := func(invoker protocol.Invoker) bool { - url := invoker.GetUrl() - if len(addresses) > 0 && checkAddressMatch(addresses, url.Ip, url.Port) { - return true - } - return false - } - result = filterInvoker(invokers, filterAddressMatches) - if len(result) > 0 || tagRouterRuleCopy.Force { - return result - } - } else { - // dynamic tag group doesn't have any item about the requested app OR it's null after filtered by - // dynamic tag group but force=false. check static tag - filter := func(invoker protocol.Invoker) bool { - if invoker.GetUrl().GetParam(constant.Tagkey, "") == tag { - return true - } - return false - } - result = filterInvoker(invokers, filter) - } - // If there's no tagged providers that can match the current tagged request. force.tag is set by default - // to false, which means it will invoke any providers without a tag unless it's explicitly disallowed. - if len(result) > 0 || isForceUseTag(url, invocation) { - return result - } else { - // FAILOVER: return all Providers without any tags. - filterAddressNotMatches := func(invoker protocol.Invoker) bool { - url := invoker.GetUrl() - if len(addresses) == 0 || !checkAddressMatch(tagRouterRuleCopy.getAddresses(), url.Ip, url.Port) { - return true - } - return false - } - filterTagIsEmpty := func(invoker protocol.Invoker) bool { - if invoker.GetUrl().GetParam(constant.Tagkey, "") == "" { - return true - } - return false - } - return filterInvoker(invokers, filterAddressNotMatches, filterTagIsEmpty) - } - } else { - // return all addresses in dynamic tag group. - addresses = tagRouterRuleCopy.getAddresses() - if len(addresses) > 0 { - filterAddressNotMatches := func(invoker protocol.Invoker) bool { - url := invoker.GetUrl() - if len(addresses) == 0 || !checkAddressMatch(addresses, url.Ip, url.Port) { - return true - } - return false - } - result = filterInvoker(invokers, filterAddressNotMatches) - // 1. all addresses are in dynamic tag group, return empty list. - if len(result) == 0 { - return result - } - } - // 2. if there are some addresses that are not in any dynamic tag group, continue to filter using the - // static tag group. - filter := func(invoker protocol.Invoker) bool { - localTag := invoker.GetUrl().GetParam(constant.Tagkey, "") - return localTag == "" || !(tagRouterRuleCopy.hasTag(localTag)) - } - return filterInvoker(result, filter) - } -} - -func (c *tagRouter) Process(event *config_center.ConfigChangeEvent) { - logger.Infof("Notification of tag rule, change type is:[%s] , raw rule is:[%v]", event.ConfigType, event.Value) - if remoting.EventTypeDel == event.ConfigType { - c.tagRouterRule = nil - return - } else { - content, ok := event.Value.(string) - if !ok { - logger.Errorf("Convert event content fail,raw content:[%s] ", event.Value) - return - } - - routerRule, err := getRule(content) - if err != nil { - logger.Errorf("Parse dynamic tag router rule fail,error:[%s] ", err) - return - } - c.tagRouterRule = routerRule - return - } -} - -func (c *tagRouter) Notify(invokers []protocol.Invoker) { - if len(invokers) == 0 { - return - } - invoker := invokers[0] - url := invoker.GetUrl() - providerApplication := url.GetParam(constant.RemoteApplicationKey, "") - if providerApplication == "" { - logger.Error("TagRouter must getConfig from or subscribe to a specific application, but the application " + - "in this TagRouter is not specified.") - return - } - dynamicConfiguration := config.GetEnvInstance().GetDynamicConfiguration() - if dynamicConfiguration == nil { - logger.Error("Get dynamicConfiguration fail, dynamicConfiguration is nil, init config center plugin please") - return - } - - if providerApplication != c.application { - dynamicConfiguration.RemoveListener(c.application+constant.TagRouterRuleSuffix, c) - } - - routerKey := providerApplication + constant.TagRouterRuleSuffix - dynamicConfiguration.AddListener(routerKey, c) - //get rule - rule, err := dynamicConfiguration.GetRule(routerKey, config_center.WithGroup(config_center.DEFAULT_GROUP)) - if len(rule) == 0 || err != nil { - logger.Errorf("Get rule fail, config rule{%s}, error{%v}", rule, err) - return - } - if rule != "" { - c.Process(&config_center.ConfigChangeEvent{ - Key: routerKey, - Value: rule, - ConfigType: remoting.EventTypeUpdate}) - } + return filterUsingStaticTag(invokers, url, invocation) } // URL gets the url of tagRouter @@ -239,7 +76,7 @@ func (c *tagRouter) Priority() int64 { return c.priority } -// filterUsingStaticTag gets a list of invoker using static tag, If there's no dynamic tag rule being set, use static tag in URL +// filterUsingStaticTag gets a list of invoker using static tag func filterUsingStaticTag(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { if tag, ok := invocation.Attachments()[constant.Tagkey]; ok { result := make([]protocol.Invoker, 0, 8) @@ -263,163 +100,3 @@ func isForceUseTag(url *common.URL, invocation protocol.Invocation) bool { } return false } - -type filter func(protocol.Invoker) bool - -func filterInvoker(invokers []protocol.Invoker, filters ...filter) []protocol.Invoker { - var res []protocol.Invoker -OUTER: - for _, invoker := range invokers { - for _, filter := range filters { - if !filter(invoker) { - continue OUTER - } - } - res = append(res, invoker) - } - return res -} - -// TODO: need move to dubbogo/gost -func checkAddressMatch(addresses []string, host, port string) bool { - for _, address := range addresses { - if matchIp(address, host, port) { - return true - } - if address == net.JoinHostPort(constant.ANYHOST_VALUE, port) { - return true - } - } - return false -} - -func matchIp(pattern, host, port string) bool { - // if the pattern is subnet format, it will not be allowed to config port param in pattern. - if strings.Contains(pattern, "/") { - _, subnet, _ := net.ParseCIDR(pattern) - if subnet != nil && subnet.Contains(net.ParseIP(host)) { - return true - } - return false - } - return matchIpRange(pattern, host, port) -} - -func matchIpRange(pattern, host, port string) bool { - if pattern == "" || host == "" { - logger.Error("Illegal Argument pattern or hostName. Pattern:" + pattern + ", Host:" + host) - return false - } - - pattern = strings.TrimSpace(pattern) - if "*.*.*.*" == pattern || "*" == pattern { - return true - } - - isIpv4 := true - ip4 := net.ParseIP(host).To4() - - if ip4 == nil { - isIpv4 = false - } - - hostAndPort := getPatternHostAndPort(pattern, isIpv4) - if hostAndPort[1] != "" && hostAndPort[1] != port { - return false - } - - pattern = hostAndPort[0] - // TODO 常量化 - splitCharacter := "." - if !isIpv4 { - splitCharacter = ":" - } - - mask := strings.Split(pattern, splitCharacter) - // check format of pattern - if err := checkHostPattern(pattern, mask, isIpv4); err != nil { - logger.Error(err) - return false - } - - if pattern == host { - return true - } - - // short name condition - if !ipPatternContains(pattern) { - return pattern == host - } - - ipAddress := strings.Split(host, splitCharacter) - for i := 0; i < len(mask); i++ { - if "*" == mask[i] || mask[i] == ipAddress[i] { - continue - } else if strings.Contains(mask[i], "-") { - rangeNumStrs := strings.Split(mask[i], "-") - if len(rangeNumStrs) != 2 { - logger.Error("There is wrong format of ip Address: " + mask[i]) - return false - } - min := getNumOfIpSegment(rangeNumStrs[0], isIpv4) - max := getNumOfIpSegment(rangeNumStrs[1], isIpv4) - ip := getNumOfIpSegment(ipAddress[i], isIpv4) - if ip < min || ip > max { - return false - } - } else if "0" == ipAddress[i] && "0" == mask[i] || "00" == mask[i] || "000" == mask[i] || "0000" == mask[i] { - continue - } else if mask[i] != ipAddress[i] { - return false - } - } - return true -} - -func ipPatternContains(pattern string) bool { - return strings.Contains(pattern, "*") || strings.Contains(pattern, "-") -} - -func checkHostPattern(pattern string, mask []string, isIpv4 bool) error { - if !isIpv4 { - if len(mask) != 8 && ipPatternContains(pattern) { - return errors.New("If you config ip expression that contains '*' or '-', please fill qualified ip pattern like 234e:0:4567:0:0:0:3d:*. ") - } - if len(mask) != 8 && !strings.Contains(pattern, "::") { - return errors.New("The host is ipv6, but the pattern is not ipv6 pattern : " + pattern) - } - } else { - if len(mask) != 4 { - return errors.New("The host is ipv4, but the pattern is not ipv4 pattern : " + pattern) - } - } - return nil -} - -func getPatternHostAndPort(pattern string, isIpv4 bool) []string { - result := make([]string, 2) - if strings.HasPrefix(pattern, "[") && strings.Contains(pattern, "]:") { - end := strings.Index(pattern, "]:") - result[0] = pattern[1:end] - result[1] = pattern[end+2:] - } else if strings.HasPrefix(pattern, "[") && strings.HasSuffix(pattern, "]") { - result[0] = pattern[1 : len(pattern)-1] - result[1] = "" - } else if isIpv4 && strings.Contains(pattern, ":") { - end := strings.Index(pattern, ":") - result[0] = pattern[:end] - result[1] = pattern[end+1:] - } else { - result[0] = pattern - } - return result -} - -func getNumOfIpSegment(ipSegment string, isIpv4 bool) int { - if isIpv4 { - ipSeg, _ := strconv.Atoi(ipSegment) - return ipSeg - } - ipSeg, _ := strconv.ParseInt(ipSegment, 0, 16) - return int(ipSeg) -} diff --git a/cluster/router/tag/tag_router_test.go b/cluster/router/tag/tag_router_test.go index e5ddc2890c..000b3ec672 100644 --- a/cluster/router/tag/tag_router_test.go +++ b/cluster/router/tag/tag_router_test.go @@ -19,43 +19,25 @@ package tag import ( "context" - "fmt" - "github.com/stretchr/testify/suite" "testing" - "time" ) import ( - "github.com/dubbogo/go-zookeeper/zk" "github.com/stretchr/testify/assert" ) import ( "github.com/apache/dubbo-go/common" - "github.com/apache/dubbo-go/common/config" - "github.com/apache/dubbo-go/common/constant" - "github.com/apache/dubbo-go/common/extension" - "github.com/apache/dubbo-go/config_center" - _ "github.com/apache/dubbo-go/config_center/zookeeper" "github.com/apache/dubbo-go/protocol" "github.com/apache/dubbo-go/protocol/invocation" - "github.com/apache/dubbo-go/remoting" - "github.com/apache/dubbo-go/remoting/zookeeper" ) const ( - tagRouterTestHangZhouUrl = "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=hangzhou&remote.application=test-tag" - tagRouterTestShangHaiUrl = "dubbo://127.0.0.1:20002/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=shanghai&remote.application=test-tag" - tagRouterTestBeijingUrl = "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=beijing&remote.application=test-tag" - tagRouterTestEnabledBeijingUrl = "dubbo://127.0.0.1:20004/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=false&dubbo.tag=beijing&remote.application=test-tag" - tagRouterTestUserConsumer = "dubbo://127.0.0.1:20005/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&remote.application=test-tag" - tagRouterTestUserConsumerTag = "dubbo://127.0.0.1:20000/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&dubbo.force.tag=true&remote.application=test-tag" - - tagRouterTestDynamicIpv4Provider1 = "dubbo://127.0.0.1:20001/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&remote.application=test-tag" - tagRouterTestDynamicIpv4Provider2 = "dubbo://127.0.0.1:20002/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&remote.application=test-tag" - tagRouterTestDynamicIpv4Provider3 = "dubbo://127.0.0.1:20003/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&remote.application=test-tag" - tagRouterTestDynamicIpv4Provider4 = "dubbo://127.0.0.1:20004/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&remote.application=test-tag&dubbo.tag=tag4" - tagRouterTestDynamicIpv4Provider5 = "dubbo://127.0.0.1:20005/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&remote.application=test-tag&dubbo.tag=tag5" + tagRouterTestHangZhouUrl = "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=hangzhou" + tagRouterTestShangHaiUrl = "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=shanghai" + tagRouterTestBeijingUrl = "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=beijing" + tagRouterTestUserConsumer = "dubbo://127.0.0.1:20000/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true" + tagRouterTestUserConsumerTag = "dubbo://127.0.0.1:20000/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&dubbo.force.tag=true" tagRouterTestDubboTag = "dubbo.tag" tagRouterTestDubboForceTag = "dubbo.force.tag" @@ -63,15 +45,6 @@ const ( tagRouterTestGuangZhou = "guangzhou" tagRouterTestFalse = "false" tagRouterTestTrue = "true" - - routerPath = "/dubbo/config/dubbo/test-tag.tag-router" - routerLocalIP = "127.0.0.1" - routerZk = "zookeeper" -) - -var ( - zkFormat = "zookeeper://%s:%d" - conditionFormat = "condition://%s/com.foo.BarService" ) // MockInvoker is only mock the Invoker to support test tagRouter @@ -187,211 +160,3 @@ func TestTagRouterRouteNoForce(t *testing.T) { invRst2 := tagRouter.Route(invokers, &u1, inv) assert.Equal(t, 3, len(invRst2)) } - -func TestFilterInvoker(t *testing.T) { - u2, e2 := common.NewURL(tagRouterTestHangZhouUrl) - u3, e3 := common.NewURL(tagRouterTestShangHaiUrl) - u4, e4 := common.NewURL(tagRouterTestBeijingUrl) - u5, e5 := common.NewURL(tagRouterTestEnabledBeijingUrl) - assert.Nil(t, e2) - assert.Nil(t, e3) - assert.Nil(t, e4) - assert.Nil(t, e5) - inv2 := NewMockInvoker(u2) - inv3 := NewMockInvoker(u3) - inv4 := NewMockInvoker(u4) - inv5 := NewMockInvoker(u5) - var invokers []protocol.Invoker - invokers = append(invokers, inv2, inv3, inv4, inv5) - filterTag := func(invoker protocol.Invoker) bool { - if invoker.GetUrl().GetParam(constant.Tagkey, "") == "beijing" { - return true - } - return false - } - res := filterInvoker(invokers, filterTag) - assert.Equal(t, []protocol.Invoker{inv4, inv5}, res) - flag := true - filterEnabled := func(invoker protocol.Invoker) bool { - if invoker.GetUrl().GetParamBool(constant.RouterEnabled, false) == flag { - return true - } - return false - } - res2 := filterInvoker(invokers, filterTag, filterEnabled) - assert.Equal(t, []protocol.Invoker{inv4}, res2) -} - -type DynamicTagRouter struct { - suite.Suite - rule *RouterRule - - route *tagRouter - zkClient *zookeeper.ZookeeperClient - testCluster *zk.TestCluster - invokers []protocol.Invoker - url *common.URL -} - -func TestDynamicTagRouter(t *testing.T) { - dtg := &DynamicTagRouter{} - u1, _ := common.NewURL(tagRouterTestDynamicIpv4Provider1) - u2, _ := common.NewURL(tagRouterTestDynamicIpv4Provider2) - u3, _ := common.NewURL(tagRouterTestDynamicIpv4Provider3) - u4, _ := common.NewURL(tagRouterTestDynamicIpv4Provider4) - u5, _ := common.NewURL(tagRouterTestDynamicIpv4Provider5) - inv1 := NewMockInvoker(u1) - inv2 := NewMockInvoker(u2) - inv3 := NewMockInvoker(u3) - inv4 := NewMockInvoker(u4) - inv5 := NewMockInvoker(u5) - dtg.invokers = append(dtg.invokers, inv1, inv2, inv3, inv4, inv5) - suite.Run(t, dtg) -} - -func (suite *DynamicTagRouter) SetupTest() { - var err error - testYML := `enabled: true -scope: application -force: true -runtime: false -valid: true -priority: 1 -key: demo-provider -tags: - - name: tag1 - addresses: ["127.0.0.1:20001"] - - name: tag2 - addresses: ["127.0.0.1:20002"] - - name: tag3 - addresses: ["127.0.0.1:20003", "127.0.0.1:20004"] -` - ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second) - suite.NoError(err) - err = z.Create(routerPath) - suite.NoError(err) - - suite.zkClient = z - suite.testCluster = ts - - _, err = z.Conn.Set(routerPath, []byte(testYML), 0) - suite.NoError(err) - - zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, routerLocalIP, suite.testCluster.Servers[0].Port)) - configuration, err := extension.GetConfigCenterFactory(routerZk).GetDynamicConfiguration(&zkUrl) - config.GetEnvInstance().SetDynamicConfiguration(configuration) - - suite.Nil(err) - suite.NotNil(configuration) - - url, e1 := common.NewURL(tagRouterTestUserConsumerTag) - suite.Nil(e1) - - tagRouter, err := NewTagRouter(&url) - suite.Nil(err) - suite.NotNil(tagRouter) - suite.route = tagRouter - suite.url = &url -} - -func (suite *DynamicTagRouter) TearDownTest() { - suite.zkClient.Close() - suite.testCluster.Stop() -} - -func (suite *DynamicTagRouter) TestDynamicTagRouterSetByIPv4() { - invokers := suite.invokers - suite.route.Notify(invokers) - suite.NotNil(suite.route.tagRouterRule) - - consumer := &invocation.RPCInvocation{} - consumer.SetAttachments(tagRouterTestDubboTag, "tag1") - targetInvokers := suite.route.Route(invokers, suite.url, consumer) - suite.Equal(1, len(targetInvokers)) - suite.Equal(targetInvokers[0], suite.invokers[0]) - - consumer.SetAttachments(tagRouterTestDubboTag, "tag3") - targetInvokers = suite.route.Route(invokers, suite.url, consumer) - suite.Equal(2, len(targetInvokers)) - suite.Equal(targetInvokers, []protocol.Invoker{suite.invokers[2], suite.invokers[3]}) -} - -func (suite *DynamicTagRouter) TestDynamicTagRouterStaticTag() { - invokers := suite.invokers - consumer := &invocation.RPCInvocation{} - consumer.SetAttachments(tagRouterTestDubboTag, "tag4") - targetInvokers := suite.route.Route(invokers, suite.url, consumer) - suite.Equal(1, len(targetInvokers)) - suite.Equal(targetInvokers[0], suite.invokers[3]) -} - -// Teas no tag and return a address are not in dynamic tag group -func (suite *DynamicTagRouter) TestDynamicTagRouterByNoTagAndAddressMatch() { - invokers := suite.invokers - suite.route.Notify(invokers) - suite.NotNil(suite.route.tagRouterRule) - consumer := &invocation.RPCInvocation{} - targetInvokers := suite.route.Route(invokers, suite.url, consumer) - suite.Equal(1, len(targetInvokers)) - suite.Equal(targetInvokers[0], suite.invokers[4]) - // test if there are some addresses that are not in any dynamic tag group, continue to filter using the static tag group. - consumer.SetAttachments(tagRouterTestDubboTag, "tag5") - targetInvokers = suite.route.Route(invokers, suite.url, consumer) - suite.Equal(1, len(targetInvokers)) - suite.Equal(targetInvokers[0], suite.invokers[4]) -} - -func (suite *DynamicTagRouter) TestTODO() { - testYML := `enabled: true -scope: application -force: true -runtime: false -valid: true -priority: 1 -key: demo-provider -tags: - - name: tag1 - addresses: ["127.0.0.1:20001"] - - name: tag2 - addresses: ["127.0.0.1:20002"] - - name: tag3 - addresses: ["127.0.0.1:20003", "127.0.0.1:20004"] -` - _, err := suite.zkClient.Conn.Set(routerPath, []byte(testYML), 1) - suite.NoError(err) - - zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, routerLocalIP, suite.testCluster.Servers[0].Port)) - configuration, err := extension.GetConfigCenterFactory(routerZk).GetDynamicConfiguration(&zkUrl) - config.GetEnvInstance().SetDynamicConfiguration(configuration) -} - -func TestProcess(t *testing.T) { - u1, err := common.NewURL(tagRouterTestUserConsumerTag) - assert.Nil(t, err) - tagRouter, e := NewTagRouter(&u1) - assert.Nil(t, e) - assert.NotNil(t, tagRouter) - - testYML := ` -scope: application -force: true -runtime: false -enabled: true -valid: true -priority: 1 -key: demo-provider -tags: - - name: beijing - addresses: [192.168.1.1, 192.168.1.2] - - name: hangzhou - addresses: [192.168.1.3, 192.168.1.4] -` - tagRouter.Process(&config_center.ConfigChangeEvent{Value: testYML, ConfigType: remoting.EventTypeAdd}) - assert.NotNil(t, tagRouter.tagRouterRule) - assert.Equal(t, []string{"beijing", "hangzhou"}, tagRouter.tagRouterRule.getTagNames()) - assert.Equal(t, []string{"192.168.1.1", "192.168.1.2", "192.168.1.3", "192.168.1.4"}, tagRouter.tagRouterRule.getAddresses()) - assert.Equal(t, []string{"192.168.1.3", "192.168.1.4"}, tagRouter.tagRouterRule.getTagNameToAddresses()["hangzhou"]) - assert.Equal(t, []string{"beijing"}, tagRouter.tagRouterRule.getAddressToTagNames()["192.168.1.1"]) - tagRouter.Process(&config_center.ConfigChangeEvent{ConfigType: remoting.EventTypeDel}) - assert.Nil(t, tagRouter.tagRouterRule) -} diff --git a/common/constant/key.go b/common/constant/key.go index 72072ddb15..ea9bad9d50 100644 --- a/common/constant/key.go +++ b/common/constant/key.go @@ -188,9 +188,6 @@ const ( HealthCheckRouterName = "health_check" // TagRouterName Specify the name of TagRouter TagRouterName = "tag" - // TagRouterRuleSuffix Specify tag router suffix - TagRouterRuleSuffix = ".tag-router" - RemoteApplicationKey = "remote.application" // ConditionRouterRuleSuffix Specify condition router suffix ConditionRouterRuleSuffix = ".condition-router" From 6300a24d968ff979fb4f3a17eb2292a7ef29ac12 Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Thu, 6 Aug 2020 13:45:24 +0800 Subject: [PATCH 088/242] fix travis --- config/config_center_config.go | 2 +- config/config_center_config_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/config_center_config.go b/config/config_center_config.go index b0a0090ce7..0fc4007940 100644 --- a/config/config_center_config.go +++ b/config/config_center_config.go @@ -98,7 +98,7 @@ func (b *configCenter) toURL(baseConfig BaseConfig) (common.URL, error) { } remoteRef := baseConfig.ConfigCenterConfig.RemoteRef - rc, ok := GetBaseConfig().GetRemoteConfig(remoteRef) + rc, ok := baseConfig.GetRemoteConfig(remoteRef) if !ok { return common.URL{}, perrors.New("Could not find out the remote ref config, name: " + remoteRef) diff --git a/config/config_center_config_test.go b/config/config_center_config_test.go index 0f43d01655..2299167bb6 100644 --- a/config/config_center_config_test.go +++ b/config/config_center_config_test.go @@ -56,7 +56,7 @@ func TestStartConfigCenterWithRemoteRef(t *testing.T) { }) m := make(map[string]*RemoteConfig) m["mock"] = &RemoteConfig{Protocol: "mock", Address: "172.0.0.1"} - baseConfig = &BaseConfig{ + baseConfig := &BaseConfig{ Remotes: m, ConfigCenterConfig: &ConfigCenterConfig{ Group: "dubbo", From bd7c5ca298c78db4c112014ed0949d791c7f8d38 Mon Sep 17 00:00:00 2001 From: "xg.gao" Date: Fri, 7 Aug 2020 09:42:01 +0800 Subject: [PATCH 089/242] stack level --- common/logger/log.yml | 3 +-- registry/nacos/service_discovery_test.go | 4 +--- test/integrate/dubbo/go-client/log.yml | 3 +-- test/integrate/dubbo/go-server/log.yml | 3 +-- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/common/logger/log.yml b/common/logger/log.yml index 59fa4279ad..21f97bcbc4 100644 --- a/common/logger/log.yml +++ b/common/logger/log.yml @@ -1,6 +1,5 @@ - level: "debug" -development: true +development: false disableCaller: false disableStacktrace: false sampling: diff --git a/registry/nacos/service_discovery_test.go b/registry/nacos/service_discovery_test.go index 24412999c5..c9dfb2bfcd 100644 --- a/registry/nacos/service_discovery_test.go +++ b/registry/nacos/service_discovery_test.go @@ -139,9 +139,7 @@ func TestNacosServiceDiscovery_CRUD(t *testing.T) { err = serviceDiscovery.Update(instance) assert.Nil(t, err) - //sometimes nacos may be failed to push update of instance, - //so it need 10s to pull, we sleep 10 second to make sure instance has been update - time.Sleep(11 * time.Second) + time.Sleep(5 * time.Second) pageMap := serviceDiscovery.GetRequestInstances([]string{serviceName}, 0, 1) assert.Equal(t, 1, len(pageMap)) diff --git a/test/integrate/dubbo/go-client/log.yml b/test/integrate/dubbo/go-client/log.yml index 59fa4279ad..21f97bcbc4 100644 --- a/test/integrate/dubbo/go-client/log.yml +++ b/test/integrate/dubbo/go-client/log.yml @@ -1,6 +1,5 @@ - level: "debug" -development: true +development: false disableCaller: false disableStacktrace: false sampling: diff --git a/test/integrate/dubbo/go-server/log.yml b/test/integrate/dubbo/go-server/log.yml index 59fa4279ad..21f97bcbc4 100644 --- a/test/integrate/dubbo/go-server/log.yml +++ b/test/integrate/dubbo/go-server/log.yml @@ -1,6 +1,5 @@ - level: "debug" -development: true +development: false disableCaller: false disableStacktrace: false sampling: From 95e863e570667b29d9c85e5f544abff1b183729a Mon Sep 17 00:00:00 2001 From: william feng <> Date: Fri, 7 Aug 2020 16:37:26 +0800 Subject: [PATCH 090/242] add default priority 0 for condition url which neither application nor service level router --- cluster/router/condition/router.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cluster/router/condition/router.go b/cluster/router/condition/router.go index 8940805061..6be3a4fc0a 100644 --- a/cluster/router/condition/router.go +++ b/cluster/router/condition/router.go @@ -120,8 +120,10 @@ func NewConditionRouter(url *common.URL) (*ConditionRouter, error) { var defaultPriority int64 if url.GetParam(constant.APPLICATION_KEY, "") != "" { defaultPriority = 150 - } else { + } else if url.GetParam(constant.INTERFACE_KEY, "") != "" { defaultPriority = 140 + } else { + defaultPriority = 0 } router.priority = url.GetParamInt(constant.RouterPriority, defaultPriority) router.Force = url.GetParamBool(constant.RouterForce, false) From 01910a00960fe6f1166d5fd67593791615e4fa1e Mon Sep 17 00:00:00 2001 From: william feng <> Date: Fri, 7 Aug 2020 16:45:36 +0800 Subject: [PATCH 091/242] reduce cognitive in base_directory.go --- cluster/directory/base_directory.go | 13 +++++-------- cluster/router/condition/router.go | 4 +--- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/cluster/directory/base_directory.go b/cluster/directory/base_directory.go index 634cee4263..7e1563a831 100644 --- a/cluster/directory/base_directory.go +++ b/cluster/directory/base_directory.go @@ -116,14 +116,11 @@ func (dir *BaseDirectory) isProperRouter(url *common.URL) bool { if serviceKey == "" { serviceKey = dir.GetUrl().SubURL.ServiceKey() } - if len(app) > 0 { - if app != dir.GetUrl().GetParam(constant.APPLICATION_KEY, "") { - return false - } - } else { - if url.ServiceKey() != serviceKey { - return false - } + if len(app) > 0 && app != dir.GetUrl().GetParam(constant.APPLICATION_KEY, "") { + return false + } + if url.ServiceKey() != serviceKey { + return false } return true } diff --git a/cluster/router/condition/router.go b/cluster/router/condition/router.go index 6be3a4fc0a..800293da6c 100644 --- a/cluster/router/condition/router.go +++ b/cluster/router/condition/router.go @@ -117,13 +117,11 @@ func NewConditionRouter(url *common.URL) (*ConditionRouter, error) { } router.url = url - var defaultPriority int64 + var defaultPriority int64 = 0 if url.GetParam(constant.APPLICATION_KEY, "") != "" { defaultPriority = 150 } else if url.GetParam(constant.INTERFACE_KEY, "") != "" { defaultPriority = 140 - } else { - defaultPriority = 0 } router.priority = url.GetParamInt(constant.RouterPriority, defaultPriority) router.Force = url.GetParamBool(constant.RouterForce, false) From 4c250c2b311e16677273223766761edf43f2193f Mon Sep 17 00:00:00 2001 From: tiecheng <18768171164@163.com> Date: Fri, 7 Aug 2020 17:39:14 +0800 Subject: [PATCH 092/242] add cluster interceptor --- cluster/cluster_impl/base_cluster_invoker.go | 16 ++++++++ cluster/cluster_impl/failover_cluster.go | 9 +---- cluster/cluster_impl/zone_aware_cluster.go | 10 +---- .../zone_aware_cluster_invoker.go | 37 ++++++++++++++++--- .../zone_aware_cluster_invoker_test.go | 15 ++++---- cluster/cluster_interceptor.go | 36 ++++++++++++++++++ common/constant/cluster.go | 24 ++++++++++++ common/constant/key.go | 3 +- config/config_loader_test.go | 6 +-- config/reference_config.go | 21 +++++------ config/reference_config_test.go | 8 ++-- config/registry_config.go | 7 +++- protocol/invocation.go | 2 + 13 files changed, 145 insertions(+), 49 deletions(-) create mode 100644 cluster/cluster_interceptor.go create mode 100644 common/constant/cluster.go diff --git a/cluster/cluster_impl/base_cluster_invoker.go b/cluster/cluster_impl/base_cluster_invoker.go index bbdfa715d7..b826a6b9ab 100644 --- a/cluster/cluster_impl/base_cluster_invoker.go +++ b/cluster/cluster_impl/base_cluster_invoker.go @@ -18,6 +18,7 @@ package cluster_impl import ( + "context" gxnet "github.com/dubbogo/gost/net" perrors "github.com/pkg/errors" "go.uber.org/atomic" @@ -36,6 +37,7 @@ type baseClusterInvoker struct { availablecheck bool destroyed *atomic.Bool stickyInvoker protocol.Invoker + interceptor cluster.ClusterInterceptor } func newBaseClusterInvoker(directory cluster.Directory) baseClusterInvoker { @@ -146,6 +148,20 @@ func (invoker *baseClusterInvoker) doSelectInvoker(lb cluster.LoadBalance, invoc return selectedInvoker } +func (invoker *baseClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { + if invoker.interceptor != nil { + invoker.interceptor.BeforeInvoker(ctx, invocation) + + result := invoker.interceptor.DoInvoke(ctx, invocation) + + invoker.interceptor.AfterInvoker(ctx, invocation) + + return result + } + + return nil +} + func isInvoked(selectedInvoker protocol.Invoker, invoked []protocol.Invoker) bool { for _, i := range invoked { if i == selectedInvoker { diff --git a/cluster/cluster_impl/failover_cluster.go b/cluster/cluster_impl/failover_cluster.go index 6ef0fbf951..4c09fd16d3 100644 --- a/cluster/cluster_impl/failover_cluster.go +++ b/cluster/cluster_impl/failover_cluster.go @@ -19,16 +19,15 @@ package cluster_impl import ( "github.com/apache/dubbo-go/cluster" + "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" "github.com/apache/dubbo-go/protocol" ) type failoverCluster struct{} -const name = "failover" - func init() { - extension.SetCluster(name, NewFailoverCluster) + extension.SetCluster(constant.FAILOVER_CLUSTER_NAME, NewFailoverCluster) } // NewFailoverCluster returns a failover cluster instance @@ -44,7 +43,3 @@ func NewFailoverCluster() cluster.Cluster { func (cluster *failoverCluster) Join(directory cluster.Directory) protocol.Invoker { return newFailoverClusterInvoker(directory) } - -func GetFailoverName() string { - return name -} diff --git a/cluster/cluster_impl/zone_aware_cluster.go b/cluster/cluster_impl/zone_aware_cluster.go index 2e19be4762..7439db2d37 100644 --- a/cluster/cluster_impl/zone_aware_cluster.go +++ b/cluster/cluster_impl/zone_aware_cluster.go @@ -19,16 +19,15 @@ package cluster_impl import ( "github.com/apache/dubbo-go/cluster" + "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" "github.com/apache/dubbo-go/protocol" ) type zoneAwareCluster struct{} -const zoneAware = "zoneAware" - func init() { - extension.SetCluster(zoneAware, NewZoneAwareCluster) + extension.SetCluster(constant.ZONEAWARE_CLUSTER_NAME, NewZoneAwareCluster) } // NewZoneAwareCluster returns a zoneaware cluster instance. @@ -43,8 +42,3 @@ func NewZoneAwareCluster() cluster.Cluster { func (cluster *zoneAwareCluster) Join(directory cluster.Directory) protocol.Invoker { return newZoneAwareClusterInvoker(directory) } - -// GetZoneAwareName get cluster name -func GetZoneAwareName() string { - return zoneAware -} diff --git a/cluster/cluster_impl/zone_aware_cluster_invoker.go b/cluster/cluster_impl/zone_aware_cluster_invoker.go index dd4d319b40..0f52b0442c 100644 --- a/cluster/cluster_impl/zone_aware_cluster_invoker.go +++ b/cluster/cluster_impl/zone_aware_cluster_invoker.go @@ -40,13 +40,16 @@ type zoneAwareClusterInvoker struct { } func newZoneAwareClusterInvoker(directory cluster.Directory) protocol.Invoker { - return &zoneAwareClusterInvoker{ + invoke := &zoneAwareClusterInvoker{ baseClusterInvoker: newBaseClusterInvoker(directory), } + // add self to interceptor + invoke.interceptor = invoke + return invoke } // nolint -func (invoker *zoneAwareClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { +func (invoker *zoneAwareClusterInvoker) DoInvoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { invokers := invoker.directory.List(invocation) err := invoker.checkInvokers(invokers, invocation) @@ -63,16 +66,16 @@ func (invoker *zoneAwareClusterInvoker) Invoke(ctx context.Context, invocation p } // providers in the registry with the same zone - zone := invocation.AttachmentsByKey(constant.REGISTRY_ZONE, "") + key := constant.REGISTRY_KEY + "." + constant.ZONE_KEY + zone := invocation.AttachmentsByKey(key, "") if "" != zone { for _, invoker := range invokers { - key := constant.REGISTRY_KEY + "." + constant.ZONE_KEY if invoker.IsAvailable() && matchParam(zone, key, "", invoker) { return invoker.Invoke(ctx, invocation) } } - force := invocation.AttachmentsByKey(constant.REGISTRY_ZONE_FORCE, "") + force := invocation.AttachmentsByKey(constant.REGISTRY_KEY+"."+constant.ZONE_FORCE_KEY, "") if "true" == force { return &protocol.RPCResult{ Err: fmt.Errorf("no registry instance in zone or "+ @@ -101,6 +104,30 @@ func (invoker *zoneAwareClusterInvoker) Invoke(ctx context.Context, invocation p } } +func (invoker *zoneAwareClusterInvoker) BeforeInvoker(ctx context.Context, invocation protocol.Invocation) { + key := constant.REGISTRY_KEY + "." + constant.ZONE_FORCE_KEY + force := ctx.Value(key) + + if force != nil { + switch value := force.(type) { + case bool: + if value { + invocation.SetAttachments(key, "true") + } + case string: + if "true" == value { + invocation.SetAttachments(key, "true") + } + default: + // ignore + } + } +} + +func (invoker *zoneAwareClusterInvoker) AfterInvoker(ctx context.Context, invocation protocol.Invocation) { + +} + func matchParam(target, key, def string, invoker protocol.Invoker) bool { return target == invoker.GetUrl().GetParam(key, def) } diff --git a/cluster/cluster_impl/zone_aware_cluster_invoker_test.go b/cluster/cluster_impl/zone_aware_cluster_invoker_test.go index 4ac865972a..cd201a42c7 100644 --- a/cluster/cluster_impl/zone_aware_cluster_invoker_test.go +++ b/cluster/cluster_impl/zone_aware_cluster_invoker_test.go @@ -37,7 +37,7 @@ import ( "github.com/apache/dubbo-go/protocol/mock" ) -func Test_ZoneWareInvokerWithPreferredSuccess(t *testing.T) { +func TestZoneWareInvokerWithPreferredSuccess(t *testing.T) { ctrl := gomock.NewController(t) // In Go versions 1.14+, if you pass a *testing.T // into gomock.NewController(t) you no longer need to call ctrl.Finish(). @@ -78,7 +78,7 @@ func Test_ZoneWareInvokerWithPreferredSuccess(t *testing.T) { assert.Equal(t, mockResult, result) } -func Test_ZoneWareInvokerWithWeightSuccess(t *testing.T) { +func TestZoneWareInvokerWithWeightSuccess(t *testing.T) { ctrl := gomock.NewController(t) // In Go versions 1.14+, if you pass a *testing.T // into gomock.NewController(t) you no longer need to call ctrl.Finish(). @@ -134,7 +134,7 @@ func Test_ZoneWareInvokerWithWeightSuccess(t *testing.T) { w1, w1Count, w2, w2Count) } -func Test_ZoneWareInvokerWithZoneSuccess(t *testing.T) { +func TestZoneWareInvokerWithZoneSuccess(t *testing.T) { var zoneArray = []string{"hangzhou", "shanghai"} ctrl := gomock.NewController(t) @@ -167,14 +167,14 @@ func Test_ZoneWareInvokerWithZoneSuccess(t *testing.T) { inv := &invocation.RPCInvocation{} // zone hangzhou hz := zoneArray[0] - inv.SetAttachments(constant.REGISTRY_ZONE, hz) + inv.SetAttachments(constant.REGISTRY_KEY+"."+constant.ZONE_KEY, hz) result := clusterInvoker.Invoke(context.Background(), inv) assert.Equal(t, hz, result.Attachment(constant.ZONE_KEY, "")) } -func Test_ZoneWareInvokerWithZoneForceFail(t *testing.T) { +func TestZoneWareInvokerWithZoneForceFail(t *testing.T) { ctrl := gomock.NewController(t) // In Go versions 1.14+, if you pass a *testing.T // into gomock.NewController(t) you no longer need to call ctrl.Finish(). @@ -196,8 +196,9 @@ func Test_ZoneWareInvokerWithZoneForceFail(t *testing.T) { inv := &invocation.RPCInvocation{} // zone hangzhou - inv.SetAttachments(constant.REGISTRY_ZONE, "hangzhou") - inv.SetAttachments(constant.REGISTRY_ZONE_FORCE, "true") + inv.SetAttachments(constant.REGISTRY_KEY+"."+constant.ZONE_KEY, "hangzhou") + // zone force + inv.SetAttachments(constant.REGISTRY_KEY+"."+constant.ZONE_FORCE_KEY, "true") result := clusterInvoker.Invoke(context.Background(), inv) diff --git a/cluster/cluster_interceptor.go b/cluster/cluster_interceptor.go new file mode 100644 index 0000000000..d78dc15eda --- /dev/null +++ b/cluster/cluster_interceptor.go @@ -0,0 +1,36 @@ +/* + * 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 cluster + +import ( + "context" + "github.com/apache/dubbo-go/protocol" +) + +// ClusterInterceptor +// Extension - ClusterInterceptor +type ClusterInterceptor interface { + // Before DoInvoke method + BeforeInvoker(ctx context.Context, invocation protocol.Invocation) + + // After DoInvoke method + AfterInvoker(ctx context.Context, invocation protocol.Invocation) + + // Corresponding cluster invoke + DoInvoke(ctx context.Context, invocation protocol.Invocation) protocol.Result +} diff --git a/common/constant/cluster.go b/common/constant/cluster.go new file mode 100644 index 0000000000..6894f3595e --- /dev/null +++ b/common/constant/cluster.go @@ -0,0 +1,24 @@ +/* + * 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 constant + +// nolint +const ( + FAILOVER_CLUSTER_NAME = "failover" + ZONEAWARE_CLUSTER_NAME = "zoneAware" +) diff --git a/common/constant/key.go b/common/constant/key.go index f7189b7c25..5c4b0ad669 100644 --- a/common/constant/key.go +++ b/common/constant/key.go @@ -100,8 +100,7 @@ const ( REGISTRY_LABEL_KEY = "label" PREFERRED_KEY = "preferred" ZONE_KEY = "zone" - REGISTRY_ZONE = "registry_zone" - REGISTRY_ZONE_FORCE = "registry_zone_force" + ZONE_FORCE_KEY = "zone.force" REGISTRY_TTL_KEY = "registry.ttl" ) diff --git a/config/config_loader_test.go b/config/config_loader_test.go index 103a978b25..a219b9f465 100644 --- a/config/config_loader_test.go +++ b/config/config_loader_test.go @@ -72,7 +72,7 @@ func TestLoad(t *testing.T) { SetProviderService(ms) extension.SetProtocol("registry", GetProtocol) - extension.SetCluster(cluster_impl.GetZoneAwareName(), cluster_impl.NewZoneAwareCluster) + extension.SetCluster(constant.ZONEAWARE_CLUSTER_NAME, cluster_impl.NewZoneAwareCluster) extension.SetProxyFactory("default", proxy_factory.NewDefaultProxyFactory) Load() @@ -101,7 +101,7 @@ func TestLoadWithSingleReg(t *testing.T) { SetProviderService(ms) extension.SetProtocol("registry", GetProtocol) - extension.SetCluster(cluster_impl.GetZoneAwareName(), cluster_impl.NewZoneAwareCluster) + extension.SetCluster(constant.ZONEAWARE_CLUSTER_NAME, cluster_impl.NewZoneAwareCluster) extension.SetProxyFactory("default", proxy_factory.NewDefaultProxyFactory) Load() @@ -130,7 +130,7 @@ func TestWithNoRegLoad(t *testing.T) { SetProviderService(ms) extension.SetProtocol("registry", GetProtocol) - extension.SetCluster(cluster_impl.GetZoneAwareName(), cluster_impl.NewZoneAwareCluster) + extension.SetCluster(constant.ZONEAWARE_CLUSTER_NAME, cluster_impl.NewZoneAwareCluster) extension.SetProxyFactory("default", proxy_factory.NewDefaultProxyFactory) Load() diff --git a/config/reference_config.go b/config/reference_config.go index 1ce60d3677..bbc875192c 100644 --- a/config/reference_config.go +++ b/config/reference_config.go @@ -26,7 +26,6 @@ import ( ) import ( - "github.com/apache/dubbo-go/cluster/cluster_impl" "github.com/creasty/defaults" gxstrings "github.com/dubbogo/gost/strings" ) @@ -148,26 +147,26 @@ func (c *ReferenceConfig) Refer(_ interface{}) { } // TODO(decouple from directory, config should not depend on directory module) + var hitClu string if regUrl != nil { // for multi-subscription scenario, use 'zone-aware' policy by default - cluster := extension.GetCluster(cluster_impl.GetZoneAwareName()) - // The invoker wrap sequence would be: - // ZoneAwareClusterInvoker(StaticDirectory) -> - // FailoverClusterInvoker(RegistryDirectory, routing happens here) -> Invoker - c.invoker = cluster.Join(directory.NewStaticDirectory(invokers)) + hitClu = constant.ZONEAWARE_CLUSTER_NAME } else { // not a registry url, must be direct invoke. - clu := cluster_impl.GetFailoverName() + hitClu = constant.FAILOVER_CLUSTER_NAME if len(invokers) > 0 { u := invokers[0].GetUrl() if nil != &u { - clu = u.GetParam(constant.CLUSTER_KEY, cluster_impl.GetZoneAwareName()) + hitClu = u.GetParam(constant.CLUSTER_KEY, constant.ZONEAWARE_CLUSTER_NAME) } } - - cluster := extension.GetCluster(clu) - c.invoker = cluster.Join(directory.NewStaticDirectory(invokers)) } + + cluster := extension.GetCluster(hitClu) + // If 'zone-aware' policy select, the invoker wrap sequence would be: + // ZoneAwareClusterInvoker(StaticDirectory) -> + // FailoverClusterInvoker(RegistryDirectory, routing happens here) -> Invoker + c.invoker = cluster.Join(directory.NewStaticDirectory(invokers)) } // create proxy diff --git a/config/reference_config_test.go b/config/reference_config_test.go index 1e31d7d445..e457801596 100644 --- a/config/reference_config_test.go +++ b/config/reference_config_test.go @@ -190,7 +190,7 @@ func doInitConsumerWithSingleRegistry() { func TestReferMultiReg(t *testing.T) { doInitConsumer() extension.SetProtocol("registry", GetProtocol) - extension.SetCluster(cluster_impl.GetZoneAwareName(), cluster_impl.NewZoneAwareCluster) + extension.SetCluster(constant.ZONEAWARE_CLUSTER_NAME, cluster_impl.NewZoneAwareCluster) for _, reference := range consumerConfig.References { reference.Refer(nil) @@ -203,7 +203,7 @@ func TestReferMultiReg(t *testing.T) { func TestRefer(t *testing.T) { doInitConsumer() extension.SetProtocol("registry", GetProtocol) - extension.SetCluster(cluster_impl.GetZoneAwareName(), cluster_impl.NewZoneAwareCluster) + extension.SetCluster(constant.ZONEAWARE_CLUSTER_NAME, cluster_impl.NewZoneAwareCluster) for _, reference := range consumerConfig.References { reference.Refer(nil) @@ -217,7 +217,7 @@ func TestRefer(t *testing.T) { func TestReferAsync(t *testing.T) { doInitConsumerAsync() extension.SetProtocol("registry", GetProtocol) - extension.SetCluster(cluster_impl.GetZoneAwareName(), cluster_impl.NewZoneAwareCluster) + extension.SetCluster(constant.ZONEAWARE_CLUSTER_NAME, cluster_impl.NewZoneAwareCluster) for _, reference := range consumerConfig.References { reference.Refer(nil) @@ -275,7 +275,7 @@ func TestReferMultiP2PWithReg(t *testing.T) { func TestImplement(t *testing.T) { doInitConsumer() extension.SetProtocol("registry", GetProtocol) - extension.SetCluster(cluster_impl.GetZoneAwareName(), cluster_impl.NewZoneAwareCluster) + extension.SetCluster(constant.ZONEAWARE_CLUSTER_NAME, cluster_impl.NewZoneAwareCluster) for _, reference := range consumerConfig.References { reference.Refer(nil) reference.Implement(&MockService{}) diff --git a/config/registry_config.go b/config/registry_config.go index 2adae5d037..89566c428e 100644 --- a/config/registry_config.go +++ b/config/registry_config.go @@ -46,9 +46,11 @@ type RegistryConfig struct { Password string `yaml:"password" json:"password,omitempty" property:"password"` Simplified bool `yaml:"simplified" json:"simplified,omitempty" property:"simplified"` // Always use this registry first if set to true, useful when subscribe to multiple registries - Preferred bool `yaml:"preferred" json:"params,omitempty" property:"preferred"` + Preferred bool `yaml:"preferred" json:"preferred,omitempty" property:"preferred"` // The region where the registry belongs, usually used to isolate traffics - Zone string `yaml:"zone" json:"params,omitempty" property:"zone"` + Zone string `yaml:"zone" json:"zone,omitempty" property:"zone"` + //// Force must user the region, property zone is specified. + //ZoneForce bool `yaml:"zoneForce" json:"zoneForce,omitempty" property:"zoneForce"` // Affects traffic distribution among registries, // useful when subscribe to multiple registries Take effect only when no preferred registry is specified. Weight int64 `yaml:"weight" json:"params,omitempty" property:"weight"` @@ -130,6 +132,7 @@ func (c *RegistryConfig) getUrlMap(roleType common.RoleType) url.Values { urlMap.Set(constant.REGISTRY_KEY+"."+constant.REGISTRY_LABEL_KEY, strconv.FormatBool(true)) urlMap.Set(constant.REGISTRY_KEY+"."+constant.PREFERRED_KEY, strconv.FormatBool(c.Preferred)) urlMap.Set(constant.REGISTRY_KEY+"."+constant.ZONE_KEY, c.Zone) + //urlMap.Set(constant.REGISTRY_KEY+"."+constant.ZONE_FORCE_KEY, strconv.FormatBool(c.ZoneForce)) urlMap.Set(constant.REGISTRY_KEY+"."+constant.WEIGHT_KEY, strconv.FormatInt(c.Weight, 10)) urlMap.Set(constant.REGISTRY_TTL_KEY, c.TTL) for k, v := range c.Params { diff --git a/protocol/invocation.go b/protocol/invocation.go index ba5949794c..296ec0540c 100644 --- a/protocol/invocation.go +++ b/protocol/invocation.go @@ -41,6 +41,8 @@ type Invocation interface { Attributes() map[string]interface{} // AttributeByKey gets attribute by key , if nil then return default value AttributeByKey(string, interface{}) interface{} + // SetAttachments sets attribute by @key and @value. + SetAttachments(key string, value string) // Invoker gets the invoker in current context. Invoker() Invoker } From 2b19ca559046c6bd9ebd0422656359bd099981e3 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Fri, 7 Aug 2020 20:22:00 +0800 Subject: [PATCH 093/242] a quick prototype for bitmap router chain --- cluster/directory/static_directory.go | 3 +- cluster/router/{chan.go => chain.go} | 9 +- cluster/router/chain/chain.go | 190 +++++++++++++++++- cluster/router/condition/listenable_router.go | 81 +++++++- cluster/router/condition/router.go | 66 ++++-- .../router/healthcheck/health_check_route.go | 38 +++- cluster/router/router.go | 35 +++- cluster/router/tag/tag_router.go | 55 +++-- cluster/router/utils/bitmap_util.go | 37 ++++ go.mod | 4 +- go.sum | 28 +-- registry/directory/directory.go | 11 +- 12 files changed, 482 insertions(+), 75 deletions(-) rename cluster/router/{chan.go => chain.go} (81%) create mode 100644 cluster/router/utils/bitmap_util.go diff --git a/cluster/directory/static_directory.go b/cluster/directory/static_directory.go index 87f5135649..c5b0637bd7 100644 --- a/cluster/directory/static_directory.go +++ b/cluster/directory/static_directory.go @@ -69,7 +69,7 @@ func (dir *staticDirectory) List(invocation protocol.Invocation) []protocol.Invo return invokers } dirUrl := dir.GetUrl() - return routerChain.Route(invokers, &dirUrl, invocation) + return routerChain.Route(&dirUrl, invocation) } // Destroy Destroy @@ -92,6 +92,7 @@ func (dir *staticDirectory) BuildRouterChain(invokers []protocol.Invoker) error if e != nil { return e } + routerChain.SetInvokers(dir.invokers) dir.SetRouterChain(routerChain) return nil } diff --git a/cluster/router/chan.go b/cluster/router/chain.go similarity index 81% rename from cluster/router/chan.go rename to cluster/router/chain.go index 6904e1734a..3614d0a5a3 100644 --- a/cluster/router/chan.go +++ b/cluster/router/chain.go @@ -17,9 +17,16 @@ package router +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/protocol" +) + // Chain type Chain interface { - router + Route(*common.URL, protocol.Invocation) []protocol.Invoker + // Refresh invokers + SetInvokers([]protocol.Invoker) // AddRouters Add routers AddRouters([]PriorityRouter) } diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go index 97d20ac5fc..b94beb65ed 100644 --- a/cluster/router/chain/chain.go +++ b/cluster/router/chain/chain.go @@ -18,9 +18,13 @@ package chain import ( + "github.com/RoaringBitmap/roaring" + "github.com/apache/dubbo-go/common/constant" "math" "sort" "sync" + "sync/atomic" + "time" ) import ( @@ -35,6 +39,12 @@ import ( "github.com/apache/dubbo-go/protocol" ) +const ( + timeInterval = 5 * time.Second + timeThreshold = 2 * time.Second + countThreshold = 5 +) + // RouterChain Router chain type RouterChain struct { // Full list of addresses from registry, classified by method name. @@ -46,23 +56,44 @@ type RouterChain struct { builtinRouters []router.PriorityRouter mutex sync.RWMutex + url common.URL - url common.URL + count int64 + last time.Time + ch chan struct{} + cache atomic.Value } // Route Loop routers in RouterChain and call Route method to determine the target invokers list. -func (c *RouterChain) Route(invoker []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { - finalInvokers := invoker +func (c *RouterChain) Route(url *common.URL, invocation protocol.Invocation) []protocol.Invoker { + rs := c.copyRouters() + + v := c.cache.Load() + if v == nil { + return c.invokers + } + + cache := v.(*router.AddrCache) + bitmap := cache.Bitmap + for _, r := range rs { + bitmap = r.Route(bitmap, cache, url, invocation) + } + + indexes := bitmap.ToArray() + finalInvokers := make([]protocol.Invoker, len(indexes)) + for _, index := range indexes { + finalInvokers = append(finalInvokers, cache.Invokers[index]) + } + return finalInvokers +} + +func (c *RouterChain) copyRouters() []router.PriorityRouter { l := len(c.routers) rs := make([]router.PriorityRouter, l, int(math.Ceil(float64(l)*1.2))) c.mutex.RLock() copy(rs, c.routers) c.mutex.RUnlock() - - for _, r := range rs { - finalInvokers = r.Route(finalInvokers, url, invocation) - } - return finalInvokers + return rs } // AddRouters Add routers to router chain @@ -79,6 +110,76 @@ func (c *RouterChain) AddRouters(routers []router.PriorityRouter) { c.routers = newRouters } +func (c *RouterChain) SetInvokers(invokers []protocol.Invoker) { + c.invokers = invokers + + c.count++ + now := time.Now() + if c.count >= countThreshold && now.Sub(c.last) >= timeThreshold { + c.last = now + c.count = 0 + go func() { + c.ch <- struct{}{} + }() + } +} + +func (c *RouterChain) loop() { + for { + select { + case <-time.Tick(timeInterval): + logger.Debugf("start to build address cache since time interval %d second ticks", int(timeInterval.Seconds())) + c.buildCache() + case <-c.ch: + logger.Debugf("start to build address cache since at least %d times of address notified within %d seconds", + countThreshold, int(timeThreshold.Seconds())) + c.buildCache() + } + } +} + +func (c *RouterChain) buildCache() { + if c.invokers == nil || len(c.invokers) == 0 { + return + } + + // FIXME: should lock here, it is fine with dirty read if no panic happens I believe. + invokers := make([]protocol.Invoker, len(c.invokers)) + copy(invokers, c.invokers) + cache := &router.AddrCache{ + Invokers: invokers, + Bitmap: ToBitmap(invokers), + AddrPool: make(map[string]router.RouterAddrPool), + AddrMeta: make(map[string]router.AddrMetadata), + } + + var origin *router.AddrCache + v := c.cache.Load() + if v != nil { + origin = v.(*router.AddrCache) + } + + var mutex sync.Mutex + var wg sync.WaitGroup + + for _, r := range c.copyRouters() { + if p, ok := r.(router.Poolable); ok { + wg.Add(1) + go func(p router.Poolable) { + pool, info := poolRouter(p, origin, invokers) + mutex.Lock() + cache.AddrPool[p.Name()] = pool + cache.AddrMeta[p.Name()] = info + mutex.Unlock() + wg.Done() + }(p) + } + } + wg.Wait() + + c.cache.Store(cache) +} + // URL Return URL in RouterChain func (c *RouterChain) URL() common.URL { return c.url @@ -109,14 +210,87 @@ func NewRouterChain(url *common.URL) (*RouterChain, error) { chain := &RouterChain{ builtinRouters: routers, routers: newRouters, + last: time.Now(), + ch: make(chan struct{}), } if url != nil { chain.url = *url } + go chain.loop() return chain, nil } +func poolRouter(p router.Poolable, origin *router.AddrCache, invokers []protocol.Invoker) (router.RouterAddrPool, router.AddrMetadata) { + name := p.Name() + if isCacheMiss(origin, name) || p.ShouldRePool() || IsDiff(origin.Invokers, invokers) { + logger.Debugf("build address cache for router %q", name) + return p.Pool(invokers) + } else { + logger.Debugf("reuse existing address cache for router %q", name) + return origin.AddrPool[name], origin.AddrMeta[name] + } +} + +func isCacheMiss(cache *router.AddrCache, key string) bool { + if cache == nil || cache.AddrPool == nil || cache.Invokers == nil || cache.AddrPool[key] == nil { + return true + } + return false +} + +func IsDiff(left []protocol.Invoker, right []protocol.Invoker) bool { + if len(right) != len(left) { + return true + } + + for _, r := range right { + found := false + for _, l := range left { + if IsEquals(l.GetUrl(), r.GetUrl()) { + found = true + break + } + } + if !found { + return true + } + } + return false +} + +func IsEquals(left common.URL, right common.URL) bool { + if left.Ip != right.Ip || left.Port != right.Port { + return false + } + + leftMap := left.ToMap() + delete(leftMap, constant.TIMESTAMP_KEY) + delete(leftMap, constant.REMOTE_TIMESTAMP_KEY) + rightMap := right.ToMap() + delete(rightMap, constant.TIMESTAMP_KEY) + delete(rightMap, constant.REMOTE_TIMESTAMP_KEY) + + if len(leftMap) != len(rightMap) { + return false + } + + for lk, lv := range leftMap { + if rv, ok := rightMap[lk]; !ok { + return false + } else if lv != rv { + return false + } + } + return true +} + +func ToBitmap(invokers []protocol.Invoker) *roaring.Bitmap { + bitmap := roaring.NewBitmap() + bitmap.AddRange(0, uint64(len(invokers))) + return bitmap +} + // sortRouter Sort router instance by priority with stable algorithm func sortRouter(routers []router.PriorityRouter) { sort.Stable(byPriority(routers)) diff --git a/cluster/router/condition/listenable_router.go b/cluster/router/condition/listenable_router.go index 4ccc19e955..b6651d2519 100644 --- a/cluster/router/condition/listenable_router.go +++ b/cluster/router/condition/listenable_router.go @@ -19,6 +19,9 @@ package condition import ( "fmt" + "github.com/RoaringBitmap/roaring" + "github.com/apache/dubbo-go/cluster/router" + "sync" ) import ( @@ -40,13 +43,24 @@ const ( listenableRouterDefaultPriority = ^int64(0) ) +type conditionRouterSnapshot struct { + routers []*ConditionRouter +} + +func (s *conditionRouterSnapshot) Source() string { + return "listenable-router" +} + // listenableRouter Abstract router which listens to dynamic configuration type listenableRouter struct { conditionRouters []*ConditionRouter routerRule *RouterRule url *common.URL + routerKey string + changed bool force bool priority int64 + mutex sync.RWMutex } // RouterRule Get RouterRule instance from listenableRouter @@ -64,6 +78,9 @@ func newListenableRouter(url *common.URL, ruleKey string) (*AppRouter, error) { l.priority = listenableRouterDefaultPriority routerKey := ruleKey + constant.ConditionRouterRuleSuffix + l.routerKey = routerKey + l.changed = true + //add listener dynamicConfiguration := config.GetEnvInstance().GetDynamicConfiguration() if dynamicConfiguration == nil { @@ -90,6 +107,8 @@ func (l *listenableRouter) Process(event *config_center.ConfigChangeEvent) { logger.Infof("Notification of condition rule, change type is:[%s] , raw rule is:[%v]", event.ConfigType, event.Value) if remoting.EventTypeDel == event.ConfigType { l.routerRule = nil + l.mutex.Lock() + l.mutex.Unlock() if l.conditionRouters != nil { l.conditionRouters = l.conditionRouters[:0] } @@ -114,6 +133,9 @@ func (l *listenableRouter) generateConditions(rule *RouterRule) { if rule == nil || !rule.Valid { return } + + l.mutex.Lock() + defer l.mutex.Unlock() l.conditionRouters = make([]*ConditionRouter, 0, len(rule.Conditions)) l.routerRule = rule for _, c := range rule.Conditions { @@ -126,20 +148,71 @@ func (l *listenableRouter) generateConditions(rule *RouterRule) { router.enabled = rule.Enabled l.conditionRouters = append(l.conditionRouters, router) } + l.changed = true } // Route Determine the target invokers list. -func (l *listenableRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { - if len(invokers) == 0 || len(l.conditionRouters) == 0 { +func (l *listenableRouter) Route(invokers *roaring.Bitmap, cache *router.AddrCache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap { + meta := cache.FindAddrMeta(l) + conditionRouters := meta.(*conditionRouterSnapshot).routers + + if invokers.IsEmpty() || len(conditionRouters) == 0 { return invokers } + //We will check enabled status inside each router. - for _, r := range l.conditionRouters { - invokers = r.Route(invokers, url, invocation) + newCache := l.convertCache(cache, conditionRouters) + for _, r := range conditionRouters { + invokers = r.Route(invokers, newCache, url, invocation) } return invokers } +func (l *listenableRouter) convertCache(cache *router.AddrCache, conditionRouters []*ConditionRouter) *router.AddrCache { + pool := cache.FindAddrPool(l) + pools := make(map[string]router.RouterAddrPool) + for _, r := range conditionRouters { + rb := pool[r.Name()] + rp := make(router.RouterAddrPool) + rp["matched"] = rb + pools[r.Name()] = rp + } + + newCache := &router.AddrCache{ + Invokers: cache.Invokers, + Bitmap: cache.Bitmap, + AddrPool: pools, + } + return newCache +} + +func (l *listenableRouter) Pool(invokers []protocol.Invoker) (router.RouterAddrPool, router.AddrMetadata) { + l.mutex.RLock() + routers := make([]*ConditionRouter, len(l.conditionRouters)) + copy(routers, l.conditionRouters) + l.mutex.RUnlock() + + rb := make(router.RouterAddrPool) + for _, r := range routers { + pool, _ := r.Pool(invokers) + rb[r.Name()] = pool["matched"] + } + return rb, &conditionRouterSnapshot{routers} +} + +func (l *listenableRouter) ShouldRePool() bool { + if l.changed { + l.changed = false + return true + } else { + return false + } +} + +func (l *listenableRouter) Name() string { + return l.routerKey +} + // Priority Return Priority in listenable router func (l *listenableRouter) Priority() int64 { return l.priority diff --git a/cluster/router/condition/router.go b/cluster/router/condition/router.go index 40a251573f..5f62e9698f 100644 --- a/cluster/router/condition/router.go +++ b/cluster/router/condition/router.go @@ -18,6 +18,9 @@ package condition import ( + "github.com/RoaringBitmap/roaring" + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/cluster/router/utils" "regexp" "strings" ) @@ -51,6 +54,8 @@ type ConditionRouter struct { priority int64 Force bool enabled bool + rule string + isNew bool WhenCondition map[string]MatchPair ThenCondition map[string]MatchPair } @@ -96,6 +101,8 @@ func NewConditionRouterWithRule(rule string) (*ConditionRouter, error) { } return &ConditionRouter{ Pattern: pattern, + rule: rule, + isNew: true, WhenCondition: when, ThenCondition: then, }, nil @@ -142,29 +149,38 @@ func (c *ConditionRouter) Enabled() bool { } // Route Determine the target invokers list. -func (c *ConditionRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { +func (c *ConditionRouter) Route(invokers *roaring.Bitmap, cache *router.AddrCache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap { if !c.Enabled() { return invokers } - if len(invokers) == 0 { + + if invokers.IsEmpty() { return invokers } + isMatchWhen := c.MatchWhen(url, invocation) if !isMatchWhen { return invokers } - var result []protocol.Invoker + if len(c.ThenCondition) == 0 { - return result - } - for _, invoker := range invokers { - invokerUrl := invoker.GetUrl() - isMatchThen := c.MatchThen(&invokerUrl, url) - if isMatchThen { - result = append(result, invoker) - } + return router.EmptyAddr } - if len(result) > 0 { + + addrPool := cache.FindAddrPool(c) + result := utils.JoinIfNotEqual(addrPool["matched"], invokers) + + //result = roaring.NewBitmap() + //for iter := invokers.Iterator(); iter.HasNext(); { + // index := iter.Next() + // invokerUrl := cache.Invokers[index].GetUrl() + // isMatchThen := c.MatchThen(&invokerUrl, url) + // if isMatchThen { + // result.Add(index) + // } + //} + + if !result.IsEmpty() { return result } else if c.Force { rule, _ := url.GetParamAndDecoded(constant.RULE_KEY) @@ -175,6 +191,32 @@ func (c *ConditionRouter) Route(invokers []protocol.Invoker, url *common.URL, in return invokers } +func (c *ConditionRouter) Pool(invokers []protocol.Invoker) (router.RouterAddrPool, router.AddrMetadata) { + rb := make(router.RouterAddrPool) + rb["matched"] = roaring.NewBitmap() + param := invokers[0].GetUrl() + for i, invoker := range invokers { + invokerUrl := invoker.GetUrl() + if c.MatchThen(&invokerUrl, ¶m) { + rb["matched"].AddInt(i) + } + } + return rb, nil +} + +func (c *ConditionRouter) ShouldRePool() bool { + if c.isNew { + c.isNew = false + return true + } else { + return false + } +} + +func (c *ConditionRouter) Name() string { + return c.rule +} + func parseRule(rule string) (map[string]MatchPair, error) { condition := make(map[string]MatchPair, 16) if len(rule) == 0 { diff --git a/cluster/router/healthcheck/health_check_route.go b/cluster/router/healthcheck/health_check_route.go index ee42e47e3b..1199ff07ed 100644 --- a/cluster/router/healthcheck/health_check_route.go +++ b/cluster/router/healthcheck/health_check_route.go @@ -18,7 +18,9 @@ package healthcheck import ( + "github.com/RoaringBitmap/roaring" "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/cluster/router/utils" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" @@ -28,6 +30,7 @@ import ( const ( HEALTH_ROUTE_ENABLED_KEY = "health.route.enabled" + healthy = "healthy" ) // HealthCheckRouter provides a health-first routing mechanism through HealthChecker @@ -51,25 +54,42 @@ func NewHealthCheckRouter(url *common.URL) (router.PriorityRouter, error) { } // Route gets a list of healthy invoker -func (r *HealthCheckRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { +func (r *HealthCheckRouter) Route(invokers *roaring.Bitmap, cache *router.AddrCache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap { if !r.enabled { return invokers } - healthyInvokers := make([]protocol.Invoker, 0, len(invokers)) + + addrPool := cache.FindAddrPool(r) // Add healthy invoker to the list - for _, invoker := range invokers { - if r.checker.IsHealthy(invoker) { - healthyInvokers = append(healthyInvokers, invoker) - } - } - // If all Invoke are considered unhealthy, downgrade to all inovker - if len(healthyInvokers) == 0 { + healthyInvokers := utils.JoinIfNotEqual(addrPool[healthy], invokers) + // If all Invoke are considered unhealthy, downgrade to all invoker + if healthyInvokers.IsEmpty() { logger.Warnf(" Now all invokers are unhealthy, so downgraded to all! Service: [%s]", url.ServiceKey()) return invokers } return healthyInvokers } +func (r *HealthCheckRouter) Pool(invokers []protocol.Invoker) (router.RouterAddrPool, router.AddrMetadata) { + rb := make(router.RouterAddrPool) + rb[healthy] = roaring.NewBitmap() + for i, invoker := range invokers { + if r.checker.IsHealthy(invoker) { + rb[healthy].Add(uint32(i)) + } + } + + return rb, nil +} + +func (r *HealthCheckRouter) ShouldRePool() bool { + return true +} + +func (r *HealthCheckRouter) Name() string { + return "health-check-router" +} + // Priority func (r *HealthCheckRouter) Priority() int64 { return 0 diff --git a/cluster/router/router.go b/cluster/router/router.go index 1d1f79d277..998b7d8b02 100644 --- a/cluster/router/router.go +++ b/cluster/router/router.go @@ -18,6 +18,7 @@ package router import ( + "github.com/RoaringBitmap/roaring" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/protocol" ) @@ -38,7 +39,8 @@ type FilePriorityRouterFactory interface { // Router type router interface { // Route Determine the target invokers list. - Route([]protocol.Invoker, *common.URL, protocol.Invocation) []protocol.Invoker + Route(*roaring.Bitmap, *AddrCache, *common.URL, protocol.Invocation) *roaring.Bitmap + // URL Return URL in router URL() common.URL } @@ -50,3 +52,34 @@ type PriorityRouter interface { // 0 to ^int(0) is better Priority() int64 } + +type Poolable interface { + Pool([]protocol.Invoker) (RouterAddrPool, AddrMetadata) + ShouldRePool() bool + Name() string +} + +type AddrMetadata interface { + Source() string +} + +type RouterAddrPool map[string]*roaring.Bitmap + +// AddrCache caches all addresses relevant info for a snapshot of received invokers, the calculation logic is +// different from router to router. +type AddrCache struct { + Invokers []protocol.Invoker // invokers snapshot + Bitmap *roaring.Bitmap // bitmap for invokers + AddrPool map[string]RouterAddrPool // address pool from the invokers for one particular router + AddrMeta map[string]AddrMetadata // address meta info collected from the invokers for one particular router +} + +func (c *AddrCache) FindAddrPool(p Poolable) RouterAddrPool { + return c.AddrPool[p.Name()] +} + +func (c *AddrCache) FindAddrMeta(p Poolable) AddrMetadata { + return c.AddrMeta[p.Name()] +} + +var EmptyAddr = roaring.NewBitmap() diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index 87da418943..c2d8a0f808 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -18,6 +18,9 @@ package tag import ( + "github.com/RoaringBitmap/roaring" + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/cluster/router/utils" "strconv" ) @@ -52,14 +55,25 @@ func (c *tagRouter) isEnabled() bool { return c.enabled } -func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { - if !c.isEnabled() { +func (c *tagRouter) Route(invokers *roaring.Bitmap, cache *router.AddrCache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap { + if !c.isEnabled() || invokers.IsEmpty() { return invokers } - if len(invokers) == 0 { + + tag := findStaticTag(invocation) + if tag == "" { return invokers } - return filterUsingStaticTag(invokers, url, invocation) + + addrPool := cache.FindAddrPool(c) + if target, ok := addrPool[tag]; ok { + ret := utils.JoinIfNotEqual(target, invokers) + if ret.IsEmpty() && !isForceUseTag(url, invocation) { + return invokers + } + return ret + } + return invokers } func (c *tagRouter) URL() common.URL { @@ -70,20 +84,31 @@ func (c *tagRouter) Priority() int64 { return c.priority } -func filterUsingStaticTag(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { - if tag, ok := invocation.Attachments()[constant.Tagkey]; ok { - result := make([]protocol.Invoker, 0, 8) - for _, v := range invokers { - if v.GetUrl().GetParam(constant.Tagkey, "") == tag { - result = append(result, v) +func (c *tagRouter) Pool(invokers []protocol.Invoker) (router.RouterAddrPool, router.AddrMetadata) { + rb := make(router.RouterAddrPool) + for i, invoker := range invokers { + url := invoker.GetUrl() + tag := url.GetParam(constant.Tagkey, "") + if tag != "" { + if _, ok := rb[tag]; !ok { + rb[tag] = roaring.NewBitmap() } + rb[tag].AddInt(i) } - if len(result) == 0 && !isForceUseTag(url, invocation) { - return invokers - } - return result } - return invokers + return rb, nil +} + +func (c *tagRouter) ShouldRePool() bool { + return false +} + +func (c *tagRouter) Name() string { + return "tag-router" +} + +func findStaticTag(invocation protocol.Invocation) string { + return invocation.Attachments()[constant.Tagkey] } func isForceUseTag(url *common.URL, invocation protocol.Invocation) bool { diff --git a/cluster/router/utils/bitmap_util.go b/cluster/router/utils/bitmap_util.go new file mode 100644 index 0000000000..b51cff96a2 --- /dev/null +++ b/cluster/router/utils/bitmap_util.go @@ -0,0 +1,37 @@ +package utils + +import ( + "github.com/RoaringBitmap/roaring" + "github.com/apache/dubbo-go/protocol" +) + +func JoinIfNotEqual(left *roaring.Bitmap, right *roaring.Bitmap) *roaring.Bitmap { + if !left.Equals(right) { + left = left.Clone() + left.And(right) + } + return left +} + +func FallbackIfJoinToEmpty(left *roaring.Bitmap, right *roaring.Bitmap) *roaring.Bitmap { + ret := JoinIfNotEqual(left, right) + if ret == nil || ret.IsEmpty() { + return right + } else { + return ret + } +} + +func ToIndex(invokers []protocol.Invoker) []int { + var ret []int + for i := range invokers { + ret = append(ret, i) + } + return ret +} + +func ToBitmap(invokers []protocol.Invoker) *roaring.Bitmap { + bitmap := roaring.NewBitmap() + bitmap.AddRange(0, uint64(len(invokers))) + return bitmap +} diff --git a/go.mod b/go.mod index e82a04b279..5fb907fdab 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,7 @@ module github.com/apache/dubbo-go require ( github.com/Workiva/go-datastructures v1.0.50 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 - github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5 // indirect github.com/apache/dubbo-go-hessian2 v1.6.1 - github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect github.com/coreos/bbolt v1.3.3 // indirect github.com/coreos/etcd v3.3.13+incompatible github.com/coreos/go-semver v0.3.0 // indirect @@ -22,6 +20,7 @@ require ( github.com/golang/mock v1.3.1 github.com/golang/protobuf v1.3.2 github.com/google/btree v1.0.0 // indirect + github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.9.5 // indirect @@ -56,6 +55,7 @@ require ( k8s.io/apimachinery v0.0.0-20190223001710-c182ff3b9841 k8s.io/client-go v8.0.0+incompatible k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a // indirect + github.com/RoaringBitmap/roaring v0.4.23 ) go 1.13 diff --git a/go.sum b/go.sum index 8c8c4ef132..56e2c3210e 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,7 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX github.com/Azure/go-autorest v10.7.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v10.15.3+incompatible h1:nhKI/bvazIs3C3TFGoSqKY6hZ8f5od5mb5/UcS6HVIY= github.com/Azure/go-autorest v10.15.3+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DataDog/datadog-go v2.2.0+incompatible h1:V5BKkxACZLjzHjSgBbr2gvLA2Ae49yhc6CSY7MLy5k4= github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= @@ -35,16 +36,10 @@ github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vaj github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190802083043-4cd0c391755e/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ= github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA= github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA= github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk= github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk= -github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= -github.com/apache/dubbo-go-hessian2 v1.5.0 h1:fzulDG5G7nX0ccgKdiN9XipJ7tZ4WXKgmk4stdlDS6s= -github.com/apache/dubbo-go-hessian2 v1.5.0/go.mod h1:VwEnsOMidkM1usya2uPfGpSLO9XUF//WQcWn3y+jFz8= -github.com/apache/dubbo-go-hessian2 v1.6.1-0.20200623062814-707fde850279 h1:1g3IJdaUjXWs++NA9Ail8+r6WgrkfhjS6hD/YXvRzjk= -github.com/apache/dubbo-go-hessian2 v1.6.1-0.20200623062814-707fde850279/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= github.com/apache/dubbo-go-hessian2 v1.6.1 h1:mFKeCZzaCkk4mMOyP+LQ85GHbRyqKT7858KS21JQYA4= github.com/apache/dubbo-go-hessian2 v1.6.1/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= @@ -58,7 +53,6 @@ github.com/asaskevich/govalidator v0.0.0-20180319081651-7d2e70ef918f h1:/8NcnxL6 github.com/asaskevich/govalidator v0.0.0-20180319081651-7d2e70ef918f/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.15.24 h1:xLAdTA/ore6xdPAljzZRed7IGqQgC+nY+ERS5vaj4Ro= github.com/aws/aws-sdk-go v1.15.24/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= -github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -113,13 +107,10 @@ github.com/docker/go-connections v0.3.0 h1:3lOnM9cSzgGwx8VfK/NGOW5fLQ0GjIlCkaktF github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dubbogo/getty v1.3.5 h1:xJxdDj9jm7wlrRSsVZSk2TDNxJbbac5GpxV0QpjO+Tw= -github.com/dubbogo/getty v1.3.5/go.mod h1:T55vN8Q6tZjf2AQZiGmkujneD3LfqYbv2b3QjacwYOY= github.com/dubbogo/getty v1.3.7 h1:xlkYD2/AH34iGteuLMsGjLl2PwBVrbIhHjf3tlUsv1M= github.com/dubbogo/getty v1.3.7/go.mod h1:XWO4+wAaMqgnBN9Ykv2YxxOAkGxymg6LGO9RK+EiCDY= github.com/dubbogo/go-zookeeper v1.0.1 h1:irLzvOsDOTNsN8Sv9tvYYxVu6DCQfLtziZQtUHmZgz8= github.com/dubbogo/go-zookeeper v1.0.1/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c= -github.com/dubbogo/gost v1.5.1/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= github.com/dubbogo/gost v1.9.0 h1:UT+dWwvLyJiDotxJERO75jB3Yxgsdy10KztR5ycxRAk= github.com/dubbogo/gost v1.9.0/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74 h1:2MIhn2R6oXQbgW5yHfS+d6YqyMfXiu2L55rFZC4UD/M= @@ -217,8 +208,8 @@ github.com/gophercloud/gophercloud v0.0.0-20180828235145-f29afc2cceca/go.mod h1: github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw= +github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI= @@ -402,8 +393,6 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nacos-group/nacos-sdk-go v0.3.2 h1:q+ukmIImL6u0zBtbceMZl2frgeAc45QT6cIrTZZz50c= -github.com/nacos-group/nacos-sdk-go v0.3.2/go.mod h1:4TdsN7eZnnVCDlOlBa61b0gsRnvNJI74m9+2+OKZkcw= github.com/nacos-group/nacos-sdk-go v0.3.3-0.20200617023039-50c7537d6a5f h1:gid5/0AkHvINWK69Fgbidb3BVIXqlf1YEm7wO0NVPsw= github.com/nacos-group/nacos-sdk-go v0.3.3-0.20200617023039-50c7537d6a5f/go.mod h1:fti1GlX/EB6RDKvzK/P7Vuibqj0JMPJHQwrcTU1tLXk= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 h1:BQ1HW7hr4IVovMwWg0E0PYcyW8CzqDcVmaew9cujU4s= @@ -474,7 +463,6 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1Tj9sZEYsmarJEQfMVYpd/Vyy/A8dqE= github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= @@ -531,17 +519,12 @@ github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8 h1:k8TV7Gz7cpWpOw/dz7 github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8/go.mod h1:S1cAa98KMFv4Sa8SbJ6ZtvOmf0VlgH0QJ1gXI0lBfBY= go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg= go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -553,6 +536,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -614,6 +598,7 @@ golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.0.0-20180829000535-087779f1d2c9 h1:z1TeLUmxf9ws9KLICfmX+KGXTs+rjm+aGWzfsv7MZ9w= @@ -658,6 +643,7 @@ gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= istio.io/gogo-genproto v0.0.0-20190124151557-6d926a6e6feb/go.mod h1:eIDJ6jNk/IeJz6ODSksHl5Aiczy5JUq6vFhJWI5OtiI= k8s.io/api v0.0.0-20180806132203-61b11ee65332/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= diff --git a/registry/directory/directory.go b/registry/directory/directory.go index 2fbf9410f7..bdeacfe169 100644 --- a/registry/directory/directory.go +++ b/registry/directory/directory.go @@ -18,6 +18,7 @@ package directory import ( + "github.com/apache/dubbo-go/cluster/router/chain" "sync" ) @@ -75,6 +76,13 @@ func NewRegistryDirectory(url *common.URL, registry registry.Registry) (cluster. serviceType: url.SubURL.Service(), registry: registry, } + + if routerChain, err := chain.NewRouterChain(url.SubURL); err == nil { + dir.BaseDirectory.SetRouterChain(routerChain) + } else { + logger.Warnf("fail to create router chain with url: %s, err is: %v", url.SubURL, err) + } + dir.consumerConfigurationListener = newConsumerConfigurationListener(dir) go dir.subscribe(url.SubURL) @@ -145,6 +153,7 @@ func (dir *RegistryDirectory) refreshInvokers(res *registry.ServiceEvent) { newInvokers := dir.toGroupInvokers() dir.listenerLock.Lock() dir.cacheInvokers = newInvokers + dir.RouterChain().SetInvokers(newInvokers) dir.listenerLock.Unlock() // After dir.cacheInvokers is updated,destroy the oldInvoker // Ensure that no request will enter the oldInvoker @@ -251,7 +260,7 @@ func (dir *RegistryDirectory) List(invocation protocol.Invocation) []protocol.In if routerChain == nil { return invokers } - return routerChain.Route(invokers, dir.cacheOriginUrl, invocation) + return routerChain.Route(dir.cacheOriginUrl, invocation) } // IsAvailable whether the directory is available From 03bde755952c756b6d5e1875186ab5c4f875e67c Mon Sep 17 00:00:00 2001 From: tiecheng <18768171164@163.com> Date: Fri, 7 Aug 2020 23:06:14 +0800 Subject: [PATCH 094/242] import split --- cluster/cluster_impl/base_cluster_invoker.go | 3 ++- cluster/cluster_interceptor.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cluster/cluster_impl/base_cluster_invoker.go b/cluster/cluster_impl/base_cluster_invoker.go index b826a6b9ab..6ab134b28d 100644 --- a/cluster/cluster_impl/base_cluster_invoker.go +++ b/cluster/cluster_impl/base_cluster_invoker.go @@ -17,8 +17,9 @@ package cluster_impl +import "context" + import ( - "context" gxnet "github.com/dubbogo/gost/net" perrors "github.com/pkg/errors" "go.uber.org/atomic" diff --git a/cluster/cluster_interceptor.go b/cluster/cluster_interceptor.go index d78dc15eda..8cebc40798 100644 --- a/cluster/cluster_interceptor.go +++ b/cluster/cluster_interceptor.go @@ -17,8 +17,9 @@ package cluster +import "context" + import ( - "context" "github.com/apache/dubbo-go/protocol" ) From ddb250415e83904a6f1a8ffdb14aac4c7574bc09 Mon Sep 17 00:00:00 2001 From: watermelo <80680489@qq.com> Date: Sat, 8 Aug 2020 02:32:17 +0800 Subject: [PATCH 095/242] Add: add setInvoker function for router chain --- cluster/router/chain/chain.go | 13 +- cluster/router/chain/chain_test.go | 3 +- cluster/router/router.go | 11 ++ cluster/router/tag/router_rule.go | 16 ++- cluster/router/tag/tag_router.go | 173 ++++++++++++-------------- cluster/router/tag/tag_router_test.go | 29 +---- 6 files changed, 116 insertions(+), 129 deletions(-) diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go index 97d20ac5fc..1fd6fb5e24 100644 --- a/cluster/router/chain/chain.go +++ b/cluster/router/chain/chain.go @@ -51,8 +51,8 @@ type RouterChain struct { } // Route Loop routers in RouterChain and call Route method to determine the target invokers list. -func (c *RouterChain) Route(invoker []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { - finalInvokers := invoker +func (c *RouterChain) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { + finalInvokers := invokers l := len(c.routers) rs := make([]router.PriorityRouter, l, int(math.Ceil(float64(l)*1.2))) c.mutex.RLock() @@ -65,6 +65,15 @@ func (c *RouterChain) Route(invoker []protocol.Invoker, url *common.URL, invocat return finalInvokers } +// Notify router chain of the initial addresses from registry at the first time. Notify whenever addresses in registry change. +func (c *RouterChain) SetInvokers(invokers []protocol.Invoker) { + for _, r := range c.routers { + if notifyRouter, ok := r.(router.NotifyRouter); ok { + notifyRouter.Notify(invokers) + } + } +} + // AddRouters Add routers to router chain // New a array add builtinRouters which is not sorted in RouterChain and routers // Sort the array diff --git a/cluster/router/chain/chain_test.go b/cluster/router/chain/chain_test.go index c1f723525f..17a7fa1ae7 100644 --- a/cluster/router/chain/chain_test.go +++ b/cluster/router/chain/chain_test.go @@ -169,6 +169,8 @@ func TestRouterChainRoute(t *testing.T) { dubboURL, _ := common.NewURL(fmt.Sprintf(dubboForamt, test1234IP, port20000)) invokers = append(invokers, protocol.NewBaseInvoker(dubboURL)) + chain.SetInvokers(invokers) + targetURL, _ := common.NewURL(fmt.Sprintf(consumerFormat, test1111IP)) inv := &invocation.RPCInvocation{} finalInvokers := chain.Route(invokers, &targetURL, inv) @@ -228,7 +230,6 @@ func TestRouterChainRouteNoRoute(t *testing.T) { url := getConditionRouteUrl(applicationKey) assert.NotNil(t, url) - invokers := []protocol.Invoker{} dubboURL, _ := common.NewURL(fmt.Sprintf(dubboForamt, test1234IP, port20000)) invokers = append(invokers, protocol.NewBaseInvoker(dubboURL)) diff --git a/cluster/router/router.go b/cluster/router/router.go index 1d1f79d277..7291224edb 100644 --- a/cluster/router/router.go +++ b/cluster/router/router.go @@ -50,3 +50,14 @@ type PriorityRouter interface { // 0 to ^int(0) is better Priority() int64 } + +// NotifyRouter notify router use the invoker list. Invoker list may change from time to time. This method gives the router a +// chance to prepare before {@link Router#route(List, URL, Invocation)} gets called. +type NotifyRouter interface { + router + // Notify notify whenever addresses in registry change + Notify([]protocol.Invoker) + // Priority Return Priority in router + // 0 to ^int(0) is better + Priority() int64 +} diff --git a/cluster/router/tag/router_rule.go b/cluster/router/tag/router_rule.go index 5fb7ab151c..5ca9281a2d 100644 --- a/cluster/router/tag/router_rule.go +++ b/cluster/router/tag/router_rule.go @@ -56,9 +56,10 @@ func getRule(rawRule string) (*RouterRule, error) { return r, nil } +// init use for flatten yaml tags data to @addressToTagNames and @tagNameToAddresses func (t *RouterRule) init() { - t.addressToTagNames = make(map[string][]string, 8) - t.tagNameToAddresses = make(map[string][]string, 8) + t.addressToTagNames = make(map[string][]string, 2*len(t.Tags)) + t.tagNameToAddresses = make(map[string][]string, len(t.Tags)) for _, tag := range t.Tags { for _, address := range tag.Addresses { t.addressToTagNames[address] = append(t.addressToTagNames[address], tag.Name) @@ -67,14 +68,16 @@ func (t *RouterRule) init() { } } +// getAddresses gets all tag addresses func (t *RouterRule) getAddresses() []string { - var result = make([]string, 0, 8*len(t.Tags)) + var result = make([]string, 0, 2*len(t.Tags)) for _, tag := range t.Tags { result = append(result, tag.Addresses...) } return result } +// getTagNames gets all tag names func (t *RouterRule) getTagNames() []string { var result = make([]string, 0, len(t.Tags)) for _, tag := range t.Tags { @@ -84,12 +87,7 @@ func (t *RouterRule) getTagNames() []string { } func (t *RouterRule) hasTag(tag string) bool { - for _, t := range t.Tags { - if tag == t.Name { - return true - } - } - return false + return len(t.tagNameToAddresses[tag]) > 0 } func (t *RouterRule) getAddressToTagNames() map[string][]string { diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index ece950ebc0..07ca8f1136 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -75,98 +75,51 @@ func (c *tagRouter) tagRouterRuleCopy() RouterRule { // Route gets a list of invoker func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { - if !c.isEnabled() { - return invokers - } - if len(invokers) == 0 { + var ( + result []protocol.Invoker + addresses []string + ) + if !c.isEnabled() || len(invokers) == 0 { return invokers } + + // Use static tags if dynamic tags are not set or invalid if c.tagRouterRule == nil || !c.tagRouterRule.Valid || !c.tagRouterRule.Enabled { return filterUsingStaticTag(invokers, url, invocation) } + // since the rule can be changed by config center, we should copy one to use. tagRouterRuleCopy := c.tagRouterRuleCopy() tag, ok := invocation.Attachments()[constant.Tagkey] if !ok { tag = url.GetParam(constant.Tagkey, "") } - var ( - result []protocol.Invoker - addresses []string - ) + // if we are requesting for a Provider with a specific tag if len(tag) > 0 { - addresses, _ = tagRouterRuleCopy.getTagNameToAddresses()[tag] - // filter by dynamic tag group first - if len(addresses) > 0 { - filterAddressMatches := func(invoker protocol.Invoker) bool { - url := invoker.GetUrl() - if len(addresses) > 0 && checkAddressMatch(addresses, url.Ip, url.Port) { - return true - } - return false - } - result = filterInvoker(invokers, filterAddressMatches) - if len(result) > 0 || tagRouterRuleCopy.Force { - return result - } - } else { - // dynamic tag group doesn't have any item about the requested app OR it's null after filtered by - // dynamic tag group but force=false. check static tag - filter := func(invoker protocol.Invoker) bool { - if invoker.GetUrl().GetParam(constant.Tagkey, "") == tag { - return true - } - return false - } - result = filterInvoker(invokers, filter) + return filterInvokersWithTag(invokers, url, invocation, tagRouterRuleCopy, tag) + } + + // return all addresses in dynamic tag group. + addresses = tagRouterRuleCopy.getAddresses() + if len(addresses) > 0 { + filterAddressNotMatches := func(invoker protocol.Invoker) bool { + url := invoker.GetUrl() + return len(addresses) == 0 || !checkAddressMatch(addresses, url.Ip, url.Port) } - // If there's no tagged providers that can match the current tagged request. force.tag is set by default - // to false, which means it will invoke any providers without a tag unless it's explicitly disallowed. - if len(result) > 0 || isForceUseTag(url, invocation) { + result = filterInvoker(invokers, filterAddressNotMatches) + // 1. all addresses are in dynamic tag group, return empty list. + if len(result) == 0 { return result - } else { - // FAILOVER: return all Providers without any tags. - filterAddressNotMatches := func(invoker protocol.Invoker) bool { - url := invoker.GetUrl() - if len(addresses) == 0 || !checkAddressMatch(tagRouterRuleCopy.getAddresses(), url.Ip, url.Port) { - return true - } - return false - } - filterTagIsEmpty := func(invoker protocol.Invoker) bool { - if invoker.GetUrl().GetParam(constant.Tagkey, "") == "" { - return true - } - return false - } - return filterInvoker(invokers, filterAddressNotMatches, filterTagIsEmpty) - } - } else { - // return all addresses in dynamic tag group. - addresses = tagRouterRuleCopy.getAddresses() - if len(addresses) > 0 { - filterAddressNotMatches := func(invoker protocol.Invoker) bool { - url := invoker.GetUrl() - if len(addresses) == 0 || !checkAddressMatch(addresses, url.Ip, url.Port) { - return true - } - return false - } - result = filterInvoker(invokers, filterAddressNotMatches) - // 1. all addresses are in dynamic tag group, return empty list. - if len(result) == 0 { - return result - } - } - // 2. if there are some addresses that are not in any dynamic tag group, continue to filter using the - // static tag group. - filter := func(invoker protocol.Invoker) bool { - localTag := invoker.GetUrl().GetParam(constant.Tagkey, "") - return localTag == "" || !(tagRouterRuleCopy.hasTag(localTag)) } - return filterInvoker(result, filter) } + // 2. if there are some addresses that are not in any dynamic tag group, continue to filter using the + // static tag group. + filter := func(invoker protocol.Invoker) bool { + localTag := invoker.GetUrl().GetParam(constant.Tagkey, "") + return localTag == "" || !(tagRouterRuleCopy.hasTag(localTag)) + } + return filterInvoker(result, filter) } func (c *tagRouter) Process(event *config_center.ConfigChangeEvent) { @@ -174,21 +127,20 @@ func (c *tagRouter) Process(event *config_center.ConfigChangeEvent) { if remoting.EventTypeDel == event.ConfigType { c.tagRouterRule = nil return - } else { - content, ok := event.Value.(string) - if !ok { - logger.Errorf("Convert event content fail,raw content:[%s] ", event.Value) - return - } + } + content, ok := event.Value.(string) + if !ok { + logger.Errorf("Convert event content fail,raw content:[%s] ", event.Value) + return + } - routerRule, err := getRule(content) - if err != nil { - logger.Errorf("Parse dynamic tag router rule fail,error:[%s] ", err) - return - } - c.tagRouterRule = routerRule + routerRule, err := getRule(content) + if err != nil { + logger.Errorf("Parse dynamic tag router rule fail,error:[%s] ", err) return } + c.tagRouterRule = routerRule + return } func (c *tagRouter) Notify(invokers []protocol.Invoker) { @@ -221,7 +173,7 @@ func (c *tagRouter) Notify(invokers []protocol.Invoker) { logger.Errorf("Get rule fail, config rule{%s}, error{%v}", rule, err) return } - if rule != "" { + if len(rule) > 0 { c.Process(&config_center.ConfigChangeEvent{ Key: routerKey, Value: rule, @@ -256,6 +208,48 @@ func filterUsingStaticTag(invokers []protocol.Invoker, url *common.URL, invocati return invokers } +// filterInvokersWithTag gets a list of invoker using dynamic route with tag +func filterInvokersWithTag(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation, tagRouterRule RouterRule, tag string) []protocol.Invoker { + var ( + result []protocol.Invoker + addresses []string + ) + addresses, _ = tagRouterRule.getTagNameToAddresses()[tag] + // filter by dynamic tag group first + if len(addresses) > 0 { + filterAddressMatches := func(invoker protocol.Invoker) bool { + url := invoker.GetUrl() + return len(addresses) > 0 && checkAddressMatch(addresses, url.Ip, url.Port) + } + result = filterInvoker(invokers, filterAddressMatches) + if len(result) > 0 || tagRouterRule.Force { + return result + } + } else { + // dynamic tag group doesn't have any item about the requested app OR it's null after filtered by + // dynamic tag group but force=false. check static tag + filter := func(invoker protocol.Invoker) bool { + return invoker.GetUrl().GetParam(constant.Tagkey, "") == tag + } + result = filterInvoker(invokers, filter) + } + // If there's no tagged providers that can match the current tagged request. force.tag is set by default + // to false, which means it will invoke any providers without a tag unless it's explicitly disallowed. + if len(result) > 0 || isForceUseTag(url, invocation) { + return result + } else { + // FAILOVER: return all Providers without any tags. + filterAddressNotMatches := func(invoker protocol.Invoker) bool { + url := invoker.GetUrl() + return len(addresses) == 0 || !checkAddressMatch(tagRouterRule.getAddresses(), url.Ip, url.Port) + } + filterTagIsEmpty := func(invoker protocol.Invoker) bool { + return invoker.GetUrl().GetParam(constant.Tagkey, "") == "" + } + return filterInvoker(invokers, filterAddressNotMatches, filterTagIsEmpty) + } +} + // isForceUseTag returns whether force use tag func isForceUseTag(url *common.URL, invocation protocol.Invocation) bool { if b, e := strconv.ParseBool(invocation.AttachmentsByKey(constant.ForceUseTag, url.GetParam(constant.ForceUseTag, "false"))); e == nil { @@ -280,7 +274,6 @@ OUTER: return res } -// TODO: need move to dubbogo/gost func checkAddressMatch(addresses []string, host, port string) bool { for _, address := range addresses { if matchIp(address, host, port) { @@ -293,6 +286,7 @@ func checkAddressMatch(addresses []string, host, port string) bool { return false } +// TODO: Already moved to dubbogo/gost, after gost by merged the follows codes will be deleted. func matchIp(pattern, host, port string) bool { // if the pattern is subnet format, it will not be allowed to config port param in pattern. if strings.Contains(pattern, "/") { @@ -329,7 +323,6 @@ func matchIpRange(pattern, host, port string) bool { } pattern = hostAndPort[0] - // TODO 常量化 splitCharacter := "." if !isIpv4 { splitCharacter = ":" diff --git a/cluster/router/tag/tag_router_test.go b/cluster/router/tag/tag_router_test.go index e5ddc2890c..5ea28c3799 100644 --- a/cluster/router/tag/tag_router_test.go +++ b/cluster/router/tag/tag_router_test.go @@ -20,7 +20,6 @@ package tag import ( "context" "fmt" - "github.com/stretchr/testify/suite" "testing" "time" ) @@ -28,6 +27,7 @@ import ( import ( "github.com/dubbogo/go-zookeeper/zk" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" ) import ( @@ -70,8 +70,7 @@ const ( ) var ( - zkFormat = "zookeeper://%s:%d" - conditionFormat = "condition://%s/com.foo.BarService" + zkFormat = "zookeeper://%s:%d" ) // MockInvoker is only mock the Invoker to support test tagRouter @@ -341,30 +340,6 @@ func (suite *DynamicTagRouter) TestDynamicTagRouterByNoTagAndAddressMatch() { suite.Equal(targetInvokers[0], suite.invokers[4]) } -func (suite *DynamicTagRouter) TestTODO() { - testYML := `enabled: true -scope: application -force: true -runtime: false -valid: true -priority: 1 -key: demo-provider -tags: - - name: tag1 - addresses: ["127.0.0.1:20001"] - - name: tag2 - addresses: ["127.0.0.1:20002"] - - name: tag3 - addresses: ["127.0.0.1:20003", "127.0.0.1:20004"] -` - _, err := suite.zkClient.Conn.Set(routerPath, []byte(testYML), 1) - suite.NoError(err) - - zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, routerLocalIP, suite.testCluster.Servers[0].Port)) - configuration, err := extension.GetConfigCenterFactory(routerZk).GetDynamicConfiguration(&zkUrl) - config.GetEnvInstance().SetDynamicConfiguration(configuration) -} - func TestProcess(t *testing.T) { u1, err := common.NewURL(tagRouterTestUserConsumerTag) assert.Nil(t, err) From cf75539ece0f6aa56fa8694efe6c0e411bbba03f Mon Sep 17 00:00:00 2001 From: "xg.gao" Date: Sat, 8 Aug 2020 15:53:00 +0800 Subject: [PATCH 096/242] replace bzr to git --- go.mod | 2 ++ go.sum | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index c196273782..197f2a3012 100644 --- a/go.mod +++ b/go.mod @@ -67,3 +67,5 @@ require ( ) go 1.13 + +replace launchpad.net/gocheck => github.com/go-check/check v0.0.0-20140225173054-eb6ee6f84d0a diff --git a/go.sum b/go.sum index aa6ecc86e2..6df5fc5248 100644 --- a/go.sum +++ b/go.sum @@ -184,6 +184,7 @@ github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ER github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= github.com/go-asn1-ber/asn1-ber v1.3.1 h1:gvPdv/Hr++TRFCl0UbPFHC54P9N9jgsRPnmnr419Uck= github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-check/check v0.0.0-20140225173054-eb6ee6f84d0a/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-co-op/gocron v0.1.1 h1:OfDmkqkCguFtFMsm6Eaayci3DADLa8pXvdmOlPU/JcU= github.com/go-co-op/gocron v0.1.1/go.mod h1:Y9PWlYqDChf2Nbgg7kfS+ZsXHDTZbMZYPEQ0MILqH+M= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= @@ -898,7 +899,6 @@ k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLy k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/utils v0.0.0-20190801114015-581e00157fb1 h1:+ySTxfHnfzZb9ys375PXNlLhkJPLKgHajBU0N62BDvE= k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= From 96a368819bac04072cd315179381e550fcdb8ee5 Mon Sep 17 00:00:00 2001 From: william feng <> Date: Sat, 8 Aug 2020 16:57:20 +0800 Subject: [PATCH 097/242] fix empty application configure always return true in base_directory setup --- cluster/directory/base_directory.go | 10 ++-- cluster/directory/base_directory_test.go | 58 +++++++++++++++++++++--- 2 files changed, 56 insertions(+), 12 deletions(-) diff --git a/cluster/directory/base_directory.go b/cluster/directory/base_directory.go index 7e1563a831..651c90ff68 100644 --- a/cluster/directory/base_directory.go +++ b/cluster/directory/base_directory.go @@ -116,13 +116,13 @@ func (dir *BaseDirectory) isProperRouter(url *common.URL) bool { if serviceKey == "" { serviceKey = dir.GetUrl().SubURL.ServiceKey() } - if len(app) > 0 && app != dir.GetUrl().GetParam(constant.APPLICATION_KEY, "") { - return false + if len(app) > 0 && app == dir.GetUrl().GetParam(constant.APPLICATION_KEY, "") { + return true } - if url.ServiceKey() != serviceKey { - return false + if url.ServiceKey() == serviceKey { + return true } - return true + return false } // Destroy Destroy diff --git a/cluster/directory/base_directory_test.go b/cluster/directory/base_directory_test.go index f60eddad79..a2b62dfa00 100644 --- a/cluster/directory/base_directory_test.go +++ b/cluster/directory/base_directory_test.go @@ -37,7 +37,7 @@ import ( var ( url, _ = common.NewURL( fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT)) - anyUrl, _ = common.NewURL(fmt.Sprintf("condition://%s/com.foo.BarService", constant.ANYHOST_VALUE)) + anyURL, _ = common.NewURL(fmt.Sprintf("condition://%s/com.foo.BarService", constant.ANYHOST_VALUE)) ) func TestNewBaseDirectory(t *testing.T) { @@ -57,7 +57,7 @@ func TestBuildRouterChain(t *testing.T) { localIP, _ := gxnet.GetLocalIP() rule := base64.URLEncoding.EncodeToString([]byte("true => " + " host = " + localIP)) - routeURL := getRouteURL(rule) + routeURL := getRouteURL(rule, anyURL) routeURL.AddParam(constant.INTERFACE_KEY, "mock-app") routerURLs := make([]*common.URL, 0) routerURLs = append(routerURLs, routeURL) @@ -67,9 +67,53 @@ func TestBuildRouterChain(t *testing.T) { assert.NotNil(t, chain) } -func getRouteURL(rule string) *common.URL { - anyUrl.AddParam("rule", rule) - anyUrl.AddParam("force", "true") - anyUrl.AddParam(constant.ROUTER_KEY, "router") - return &anyUrl +func getRouteURL(rule string, u common.URL) *common.URL { + ru := u + ru.AddParam("rule", rule) + ru.AddParam("force", "true") + ru.AddParam(constant.ROUTER_KEY, "router") + return &ru +} + +func TestIsProperRouter(t *testing.T) { + regURL := url + regURL.AddParam(constant.APPLICATION_KEY, "mock-app") + d := NewBaseDirectory(®URL) + localIP, _ := gxnet.GetLocalIP() + rule := base64.URLEncoding.EncodeToString([]byte("true => " + " host = " + localIP)) + routeURL := getRouteURL(rule, anyURL) + routeURL.AddParam(constant.APPLICATION_KEY, "mock-app") + rst := d.isProperRouter(routeURL) + assert.True(t, rst) + + regURL.AddParam(constant.APPLICATION_KEY, "") + regURL.AddParam(constant.INTERFACE_KEY, "com.foo.BarService") + d = NewBaseDirectory(®URL) + routeURL = getRouteURL(rule, anyURL) + routeURL.AddParam(constant.INTERFACE_KEY, "com.foo.BarService") + rst = d.isProperRouter(routeURL) + assert.True(t, rst) + + regURL.AddParam(constant.APPLICATION_KEY, "") + regURL.AddParam(constant.INTERFACE_KEY, "") + d = NewBaseDirectory(®URL) + routeURL = getRouteURL(rule, anyURL) + rst = d.isProperRouter(routeURL) + assert.True(t, rst) + + regURL.SetParam(constant.APPLICATION_KEY, "") + regURL.SetParam(constant.INTERFACE_KEY, "") + d = NewBaseDirectory(®URL) + routeURL = getRouteURL(rule, anyURL) + routeURL.AddParam(constant.APPLICATION_KEY, "mock-service") + rst = d.isProperRouter(routeURL) + assert.False(t, rst) + + regURL.SetParam(constant.APPLICATION_KEY, "") + regURL.SetParam(constant.INTERFACE_KEY, "") + d = NewBaseDirectory(®URL) + routeURL = getRouteURL(rule, anyURL) + routeURL.AddParam(constant.INTERFACE_KEY, "mock-service") + rst = d.isProperRouter(routeURL) + assert.False(t, rst) } From 5f02d5372ee87df8904e5b55925923fbf656b8a2 Mon Sep 17 00:00:00 2001 From: william feng <> Date: Sat, 8 Aug 2020 17:03:34 +0800 Subject: [PATCH 098/242] added comment to file.go --- cluster/router/condition/file.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cluster/router/condition/file.go b/cluster/router/condition/file.go index d4293c9809..996db7443f 100644 --- a/cluster/router/condition/file.go +++ b/cluster/router/condition/file.go @@ -95,6 +95,12 @@ func (f *FileConditionRouter) URL() common.URL { return f.url } +// The input value must follow [{group}/]{service}[:{version}] pattern +// the returning strings are representing group, service, version respectively. +// input: mock-group/mock-service:1.0.0 ==> "mock-group", "mock-service", "1.0.0" +// input: mock-group/mock-service ==> "mock-group", "mock-service", "" +// input: mock-service:1.0.0 ==> "", "mock-service", "1.0.0" +// For more samples, please refer to unit test. func parseServiceRouterKey(key string) (string, string, string, error) { if len(strings.TrimSpace(key)) == 0 { return "", "", "", nil From 87a52c305b02691a0dd39d7db4a14a297985613c Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Sun, 9 Aug 2020 02:02:26 +0800 Subject: [PATCH 099/242] make condition router no-poolable and introduce consumer url in RegistryDirectory --- cluster/router/chain/chain.go | 4 +- cluster/router/chain/chain_test.go | 12 +- cluster/router/condition/factory_test.go | 109 ++++++++++-------- cluster/router/condition/listenable_router.go | 81 +------------ cluster/router/condition/router.go | 57 ++------- .../healthcheck/health_check_route_test.go | 52 +++++---- cluster/router/tag/file.go | 9 +- cluster/router/tag/tag_router.go | 17 +-- cluster/router/tag/tag_router_test.go | 43 ++++--- go.sum | 11 ++ registry/directory/directory.go | 31 ++++- 11 files changed, 198 insertions(+), 228 deletions(-) diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go index b94beb65ed..1ca7d479d1 100644 --- a/cluster/router/chain/chain.go +++ b/cluster/router/chain/chain.go @@ -81,8 +81,8 @@ func (c *RouterChain) Route(url *common.URL, invocation protocol.Invocation) []p indexes := bitmap.ToArray() finalInvokers := make([]protocol.Invoker, len(indexes)) - for _, index := range indexes { - finalInvokers = append(finalInvokers, cache.Invokers[index]) + for i, index := range indexes { + finalInvokers[i] = cache.Invokers[index] } return finalInvokers } diff --git a/cluster/router/chain/chain_test.go b/cluster/router/chain/chain_test.go index c1f723525f..8890c293ed 100644 --- a/cluster/router/chain/chain_test.go +++ b/cluster/router/chain/chain_test.go @@ -168,10 +168,12 @@ func TestRouterChainRoute(t *testing.T) { invokers := []protocol.Invoker{} dubboURL, _ := common.NewURL(fmt.Sprintf(dubboForamt, test1234IP, port20000)) invokers = append(invokers, protocol.NewBaseInvoker(dubboURL)) + chain.SetInvokers(invokers) + chain.buildCache() targetURL, _ := common.NewURL(fmt.Sprintf(consumerFormat, test1111IP)) inv := &invocation.RPCInvocation{} - finalInvokers := chain.Route(invokers, &targetURL, inv) + finalInvokers := chain.Route(&targetURL, inv) assert.Equal(t, 1, len(finalInvokers)) } @@ -205,10 +207,12 @@ conditions: invokers := []protocol.Invoker{} dubboURL, _ := common.NewURL(fmt.Sprintf(dubboForamt, test1234IP, port20000)) invokers = append(invokers, protocol.NewBaseInvoker(dubboURL)) + chain.SetInvokers(invokers) + chain.buildCache() targetURL, _ := common.NewURL(fmt.Sprintf(consumerFormat, test1111IP)) inv := &invocation.RPCInvocation{} - finalInvokers := chain.Route(invokers, &targetURL, inv) + finalInvokers := chain.Route(&targetURL, inv) assert.Equal(t, 0, len(finalInvokers)) } @@ -232,10 +236,12 @@ func TestRouterChainRouteNoRoute(t *testing.T) { invokers := []protocol.Invoker{} dubboURL, _ := common.NewURL(fmt.Sprintf(dubboForamt, test1234IP, port20000)) invokers = append(invokers, protocol.NewBaseInvoker(dubboURL)) + chain.SetInvokers(invokers) + chain.buildCache() targetURL, _ := common.NewURL(fmt.Sprintf(consumerFormat, test1111IP)) inv := &invocation.RPCInvocation{} - finalInvokers := chain.Route(invokers, &targetURL, inv) + finalInvokers := chain.Route(&targetURL, inv) assert.Equal(t, 0, len(finalInvokers)) } diff --git a/cluster/router/condition/factory_test.go b/cluster/router/condition/factory_test.go index 0f61b39fc7..91aeb8d886 100644 --- a/cluster/router/condition/factory_test.go +++ b/cluster/router/condition/factory_test.go @@ -32,6 +32,8 @@ import ( ) import ( + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/cluster/router/utils" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" @@ -180,30 +182,30 @@ func TestRoute_matchFilter(t *testing.T) { router5, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule5)) router6, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule6)) cUrl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) - fileredInvokers1 := router1.Route(invokers, &cUrl, &invocation.RPCInvocation{}) - fileredInvokers2 := router2.Route(invokers, &cUrl, &invocation.RPCInvocation{}) - fileredInvokers3 := router3.Route(invokers, &cUrl, &invocation.RPCInvocation{}) - fileredInvokers4 := router4.Route(invokers, &cUrl, &invocation.RPCInvocation{}) - fileredInvokers5 := router5.Route(invokers, &cUrl, &invocation.RPCInvocation{}) - fileredInvokers6 := router6.Route(invokers, &cUrl, &invocation.RPCInvocation{}) - assert.Equal(t, 1, len(fileredInvokers1)) - assert.Equal(t, 0, len(fileredInvokers2)) - assert.Equal(t, 0, len(fileredInvokers3)) - assert.Equal(t, 1, len(fileredInvokers4)) - assert.Equal(t, 2, len(fileredInvokers5)) - assert.Equal(t, 1, len(fileredInvokers6)) + ret1 := router1.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), &cUrl, &invocation.RPCInvocation{}) + ret2 := router2.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), &cUrl, &invocation.RPCInvocation{}) + ret3 := router3.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), &cUrl, &invocation.RPCInvocation{}) + ret4 := router4.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), &cUrl, &invocation.RPCInvocation{}) + ret5 := router5.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), &cUrl, &invocation.RPCInvocation{}) + ret6 := router6.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), &cUrl, &invocation.RPCInvocation{}) + assert.Equal(t, 1, len(ret1.ToArray())) + assert.Equal(t, 0, len(ret2.ToArray())) + assert.Equal(t, 0, len(ret3.ToArray())) + assert.Equal(t, 1, len(ret4.ToArray())) + assert.Equal(t, 2, len(ret5.ToArray())) + assert.Equal(t, 1, len(ret6.ToArray())) } func TestRoute_methodRoute(t *testing.T) { inv := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("getFoo"), invocation.WithParameterTypes([]reflect.Type{}), invocation.WithArguments([]interface{}{})) rule := base64.URLEncoding.EncodeToString([]byte("host !=4.4.4.* & host = 2.2.2.2,1.1.1.1,3.3.3.3 => host = 1.2.3.4")) - router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) + r, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) url, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService?methods=setFoo,getFoo,findFoo") - matchWhen := router.(*ConditionRouter).MatchWhen(&url, inv) + matchWhen := r.(*ConditionRouter).MatchWhen(&url, inv) assert.Equal(t, true, matchWhen) url1, _ := common.NewURL(fmt.Sprintf(factoryConsumerMethodFormat, factory1111Ip)) - matchWhen = router.(*ConditionRouter).MatchWhen(&url1, inv) + matchWhen = r.(*ConditionRouter).MatchWhen(&url1, inv) assert.Equal(t, true, matchWhen) url2, _ := common.NewURL(fmt.Sprintf(factoryConsumerMethodFormat, factory1111Ip)) rule2 := base64.URLEncoding.EncodeToString([]byte("methods=getFoo & host!=1.1.1.1 => host = 1.2.3.4")) @@ -225,9 +227,9 @@ func TestRoute_ReturnFalse(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => false")) curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) - router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) - assert.Equal(t, 0, len(fileredInvokers)) + r, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) + ret := r.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), &curl, inv) + assert.Equal(t, 0, len(ret.ToArray())) } func TestRoute_ReturnEmpty(t *testing.T) { @@ -237,9 +239,9 @@ func TestRoute_ReturnEmpty(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => ")) curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) - router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) - assert.Equal(t, 0, len(fileredInvokers)) + r, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) + ret := r.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), &curl, inv) + assert.Equal(t, 0, len(ret.ToArray())) } func TestRoute_ReturnAll(t *testing.T) { @@ -253,9 +255,9 @@ func TestRoute_ReturnAll(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = " + localIP)) curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) - router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) - assert.Equal(t, invokers, fileredInvokers) + r, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) + ret := r.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), &curl, inv) + assert.Equal(t, len(invokers), len(ret.ToArray())) } func TestRoute_HostFilter(t *testing.T) { @@ -270,11 +272,11 @@ func TestRoute_HostFilter(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = " + localIP)) curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) - router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) - assert.Equal(t, 2, len(fileredInvokers)) - assert.Equal(t, invoker2, fileredInvokers[0]) - assert.Equal(t, invoker3, fileredInvokers[1]) + r, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) + ret := r.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), &curl, inv) + assert.Equal(t, 2, len(ret.ToArray())) + assert.Equal(t, invoker2, invokers[ret.ToArray()[0]]) + assert.Equal(t, invoker3, invokers[ret.ToArray()[1]]) } func TestRoute_Empty_HostFilter(t *testing.T) { @@ -289,11 +291,11 @@ func TestRoute_Empty_HostFilter(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte(" => " + " host = " + localIP)) curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) - router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) - assert.Equal(t, 2, len(fileredInvokers)) - assert.Equal(t, invoker2, fileredInvokers[0]) - assert.Equal(t, invoker3, fileredInvokers[1]) + r, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) + ret := r.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), &curl, inv) + assert.Equal(t, 2, len(ret.ToArray())) + assert.Equal(t, invoker2, invokers[ret.ToArray()[0]]) + assert.Equal(t, invoker3, invokers[ret.ToArray()[1]]) } func TestRoute_False_HostFilter(t *testing.T) { @@ -308,11 +310,11 @@ func TestRoute_False_HostFilter(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("true => " + " host = " + localIP)) curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) - router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) - assert.Equal(t, 2, len(fileredInvokers)) - assert.Equal(t, invoker2, fileredInvokers[0]) - assert.Equal(t, invoker3, fileredInvokers[1]) + r, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) + ret := r.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), &curl, inv) + assert.Equal(t, 2, len(ret.ToArray())) + assert.Equal(t, invoker2, invokers[ret.ToArray()[0]]) + assert.Equal(t, invoker3, invokers[ret.ToArray()[1]]) } func TestRoute_Placeholder(t *testing.T) { @@ -327,11 +329,11 @@ func TestRoute_Placeholder(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = $host")) curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) - router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) - assert.Equal(t, 2, len(fileredInvokers)) - assert.Equal(t, invoker2, fileredInvokers[0]) - assert.Equal(t, invoker3, fileredInvokers[1]) + r, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) + ret := r.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), &curl, inv) + assert.Equal(t, 2, len(ret.ToArray())) + assert.Equal(t, invoker2, invokers[ret.ToArray()[0]]) + assert.Equal(t, invoker3, invokers[ret.ToArray()[1]]) } func TestRoute_NoForce(t *testing.T) { @@ -346,9 +348,9 @@ func TestRoute_NoForce(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte(fmt.Sprintf(factoryHostIp1234Format, localIP))) curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) - router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrlWithNoForce(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) - assert.Equal(t, invokers, fileredInvokers) + r, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrlWithNoForce(rule)) + ret := r.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), &curl, inv) + assert.Equal(t, len(invokers), len(ret.ToArray())) } func TestRoute_Force(t *testing.T) { @@ -363,9 +365,9 @@ func TestRoute_Force(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte(fmt.Sprintf(factoryHostIp1234Format, localIP))) curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) - router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrlWithForce(rule, "true")) - fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) - assert.Equal(t, 0, len(fileredInvokers)) + r, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrlWithForce(rule, "true")) + fileredInvokers := r.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), &curl, inv) + assert.Equal(t, 0, len(fileredInvokers.ToArray())) } func TestNewConditionRouterFactory(t *testing.T) { @@ -377,3 +379,10 @@ func TestNewAppRouterFactory(t *testing.T) { factory := newAppRouterFactory() assert.NotNil(t, factory) } + +func setUpAddrCache(addrs []protocol.Invoker) *router.AddrCache { + cache := &router.AddrCache{ + Invokers: addrs, + } + return cache +} diff --git a/cluster/router/condition/listenable_router.go b/cluster/router/condition/listenable_router.go index b6651d2519..86f128bf4d 100644 --- a/cluster/router/condition/listenable_router.go +++ b/cluster/router/condition/listenable_router.go @@ -19,16 +19,15 @@ package condition import ( "fmt" - "github.com/RoaringBitmap/roaring" - "github.com/apache/dubbo-go/cluster/router" - "sync" ) import ( + "github.com/RoaringBitmap/roaring" perrors "github.com/pkg/errors" ) import ( + "github.com/apache/dubbo-go/cluster/router" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/config" "github.com/apache/dubbo-go/common/constant" @@ -43,24 +42,13 @@ const ( listenableRouterDefaultPriority = ^int64(0) ) -type conditionRouterSnapshot struct { - routers []*ConditionRouter -} - -func (s *conditionRouterSnapshot) Source() string { - return "listenable-router" -} - // listenableRouter Abstract router which listens to dynamic configuration type listenableRouter struct { conditionRouters []*ConditionRouter routerRule *RouterRule url *common.URL - routerKey string - changed bool force bool priority int64 - mutex sync.RWMutex } // RouterRule Get RouterRule instance from listenableRouter @@ -78,9 +66,6 @@ func newListenableRouter(url *common.URL, ruleKey string) (*AppRouter, error) { l.priority = listenableRouterDefaultPriority routerKey := ruleKey + constant.ConditionRouterRuleSuffix - l.routerKey = routerKey - l.changed = true - //add listener dynamicConfiguration := config.GetEnvInstance().GetDynamicConfiguration() if dynamicConfiguration == nil { @@ -107,8 +92,6 @@ func (l *listenableRouter) Process(event *config_center.ConfigChangeEvent) { logger.Infof("Notification of condition rule, change type is:[%s] , raw rule is:[%v]", event.ConfigType, event.Value) if remoting.EventTypeDel == event.ConfigType { l.routerRule = nil - l.mutex.Lock() - l.mutex.Unlock() if l.conditionRouters != nil { l.conditionRouters = l.conditionRouters[:0] } @@ -133,9 +116,6 @@ func (l *listenableRouter) generateConditions(rule *RouterRule) { if rule == nil || !rule.Valid { return } - - l.mutex.Lock() - defer l.mutex.Unlock() l.conditionRouters = make([]*ConditionRouter, 0, len(rule.Conditions)) l.routerRule = rule for _, c := range rule.Conditions { @@ -148,71 +128,20 @@ func (l *listenableRouter) generateConditions(rule *RouterRule) { router.enabled = rule.Enabled l.conditionRouters = append(l.conditionRouters, router) } - l.changed = true } // Route Determine the target invokers list. func (l *listenableRouter) Route(invokers *roaring.Bitmap, cache *router.AddrCache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap { - meta := cache.FindAddrMeta(l) - conditionRouters := meta.(*conditionRouterSnapshot).routers - - if invokers.IsEmpty() || len(conditionRouters) == 0 { + if invokers.IsEmpty() || len(l.conditionRouters) == 0 { return invokers } - //We will check enabled status inside each router. - newCache := l.convertCache(cache, conditionRouters) - for _, r := range conditionRouters { - invokers = r.Route(invokers, newCache, url, invocation) + for _, r := range l.conditionRouters { + invokers = r.Route(invokers, cache, url, invocation) } return invokers } -func (l *listenableRouter) convertCache(cache *router.AddrCache, conditionRouters []*ConditionRouter) *router.AddrCache { - pool := cache.FindAddrPool(l) - pools := make(map[string]router.RouterAddrPool) - for _, r := range conditionRouters { - rb := pool[r.Name()] - rp := make(router.RouterAddrPool) - rp["matched"] = rb - pools[r.Name()] = rp - } - - newCache := &router.AddrCache{ - Invokers: cache.Invokers, - Bitmap: cache.Bitmap, - AddrPool: pools, - } - return newCache -} - -func (l *listenableRouter) Pool(invokers []protocol.Invoker) (router.RouterAddrPool, router.AddrMetadata) { - l.mutex.RLock() - routers := make([]*ConditionRouter, len(l.conditionRouters)) - copy(routers, l.conditionRouters) - l.mutex.RUnlock() - - rb := make(router.RouterAddrPool) - for _, r := range routers { - pool, _ := r.Pool(invokers) - rb[r.Name()] = pool["matched"] - } - return rb, &conditionRouterSnapshot{routers} -} - -func (l *listenableRouter) ShouldRePool() bool { - if l.changed { - l.changed = false - return true - } else { - return false - } -} - -func (l *listenableRouter) Name() string { - return l.routerKey -} - // Priority Return Priority in listenable router func (l *listenableRouter) Priority() int64 { return l.priority diff --git a/cluster/router/condition/router.go b/cluster/router/condition/router.go index 5f62e9698f..cd544477a1 100644 --- a/cluster/router/condition/router.go +++ b/cluster/router/condition/router.go @@ -18,18 +18,17 @@ package condition import ( - "github.com/RoaringBitmap/roaring" - "github.com/apache/dubbo-go/cluster/router" - "github.com/apache/dubbo-go/cluster/router/utils" "regexp" "strings" ) import ( + "github.com/RoaringBitmap/roaring" perrors "github.com/pkg/errors" ) import ( + "github.com/apache/dubbo-go/cluster/router" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" @@ -54,8 +53,6 @@ type ConditionRouter struct { priority int64 Force bool enabled bool - rule string - isNew bool WhenCondition map[string]MatchPair ThenCondition map[string]MatchPair } @@ -101,8 +98,6 @@ func NewConditionRouterWithRule(rule string) (*ConditionRouter, error) { } return &ConditionRouter{ Pattern: pattern, - rule: rule, - isNew: true, WhenCondition: when, ThenCondition: then, }, nil @@ -167,18 +162,15 @@ func (c *ConditionRouter) Route(invokers *roaring.Bitmap, cache *router.AddrCach return router.EmptyAddr } - addrPool := cache.FindAddrPool(c) - result := utils.JoinIfNotEqual(addrPool["matched"], invokers) - - //result = roaring.NewBitmap() - //for iter := invokers.Iterator(); iter.HasNext(); { - // index := iter.Next() - // invokerUrl := cache.Invokers[index].GetUrl() - // isMatchThen := c.MatchThen(&invokerUrl, url) - // if isMatchThen { - // result.Add(index) - // } - //} + result := roaring.NewBitmap() + for iter := invokers.Iterator(); iter.HasNext(); { + index := iter.Next() + invokerUrl := cache.Invokers[index].GetUrl() + isMatchThen := c.MatchThen(&invokerUrl, url) + if isMatchThen { + result.Add(index) + } + } if !result.IsEmpty() { return result @@ -188,33 +180,8 @@ func (c *ConditionRouter) Route(invokers *roaring.Bitmap, cache *router.AddrCach logger.Warnf("The route result is empty and force execute. consumer: %s, service: %s, router: %s", localIP, url.Service(), rule) return result } - return invokers -} - -func (c *ConditionRouter) Pool(invokers []protocol.Invoker) (router.RouterAddrPool, router.AddrMetadata) { - rb := make(router.RouterAddrPool) - rb["matched"] = roaring.NewBitmap() - param := invokers[0].GetUrl() - for i, invoker := range invokers { - invokerUrl := invoker.GetUrl() - if c.MatchThen(&invokerUrl, ¶m) { - rb["matched"].AddInt(i) - } - } - return rb, nil -} - -func (c *ConditionRouter) ShouldRePool() bool { - if c.isNew { - c.isNew = false - return true - } else { - return false - } -} -func (c *ConditionRouter) Name() string { - return c.rule + return invokers } func parseRule(rule string) (map[string]MatchPair, error) { diff --git a/cluster/router/healthcheck/health_check_route_test.go b/cluster/router/healthcheck/health_check_route_test.go index d5862fb884..ddd68ec403 100644 --- a/cluster/router/healthcheck/health_check_route_test.go +++ b/cluster/router/healthcheck/health_check_route_test.go @@ -19,6 +19,8 @@ package healthcheck import ( "fmt" + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/cluster/router/utils" "math" "testing" "time" @@ -59,25 +61,25 @@ func TestHealthCheckRouterRoute(t *testing.T) { invoker3 := NewMockInvoker(url3) invokers = append(invokers, invoker1, invoker2, invoker3) inv := invocation.NewRPCInvocation(healthCheckRouteMethodNameTest, nil, nil) - res := hcr.Route(invokers, &consumerURL, inv) + res := hcr.Route(utils.ToBitmap(invokers), setUpAddrCache(hcr.(*HealthCheckRouter), invokers), &consumerURL, inv) // now all invokers are healthy - assert.True(t, len(res) == len(invokers)) + assert.True(t, len(res.ToArray()) == len(invokers)) for i := 0; i < 10; i++ { request(url1, healthCheckRouteMethodNameTest, 0, false, false) } - res = hcr.Route(invokers, &consumerURL, inv) + res = hcr.Route(utils.ToBitmap(invokers), setUpAddrCache(hcr.(*HealthCheckRouter), invokers), &consumerURL, inv) // invokers1 is unhealthy now - assert.True(t, len(res) == 2 && !contains(res, invoker1)) + assert.True(t, len(res.ToArray()) == 2 && !res.Contains(0)) for i := 0; i < 10; i++ { request(url1, healthCheckRouteMethodNameTest, 0, false, false) request(url2, healthCheckRouteMethodNameTest, 0, false, false) } - res = hcr.Route(invokers, &consumerURL, inv) + res = hcr.Route(utils.ToBitmap(invokers), setUpAddrCache(hcr.(*HealthCheckRouter), invokers), &consumerURL, inv) // only invokers3 is healthy now - assert.True(t, len(res) == 1 && !contains(res, invoker1) && !contains(res, invoker2)) + assert.True(t, len(res.ToArray()) == 1 && !res.Contains(0) && !res.Contains(1)) for i := 0; i < 10; i++ { request(url1, healthCheckRouteMethodNameTest, 0, false, false) @@ -85,37 +87,28 @@ func TestHealthCheckRouterRoute(t *testing.T) { request(url3, healthCheckRouteMethodNameTest, 0, false, false) } - res = hcr.Route(invokers, &consumerURL, inv) + res = hcr.Route(utils.ToBitmap(invokers), setUpAddrCache(hcr.(*HealthCheckRouter), invokers), &consumerURL, inv) // now all invokers are unhealthy, so downgraded to all - assert.True(t, len(res) == 3) + assert.True(t, len(res.ToArray()) == 3) // reset the invoker1 successive failed count, so invoker1 go to healthy request(url1, healthCheckRouteMethodNameTest, 0, false, true) - res = hcr.Route(invokers, &consumerURL, inv) - assert.True(t, contains(res, invoker1)) + res = hcr.Route(utils.ToBitmap(invokers), setUpAddrCache(hcr.(*HealthCheckRouter), invokers), &consumerURL, inv) + assert.True(t, res.Contains(0)) for i := 0; i < 6; i++ { request(url1, healthCheckRouteMethodNameTest, 0, false, false) } // now all invokers are unhealthy, so downgraded to all again - res = hcr.Route(invokers, &consumerURL, inv) - assert.True(t, len(res) == 3) + res = hcr.Route(utils.ToBitmap(invokers), setUpAddrCache(hcr.(*HealthCheckRouter), invokers), &consumerURL, inv) + assert.True(t, len(res.ToArray()) == 3) time.Sleep(time.Second * 2) // invoker1 go to healthy again after 2s - res = hcr.Route(invokers, &consumerURL, inv) - assert.True(t, contains(res, invoker1)) + res = hcr.Route(utils.ToBitmap(invokers), setUpAddrCache(hcr.(*HealthCheckRouter), invokers), &consumerURL, inv) + assert.True(t, res.Contains(0)) } -func contains(invokers []protocol.Invoker, invoker protocol.Invoker) bool { - for _, e := range invokers { - if e == invoker { - return true - } - } - return false -} - func TestNewHealthCheckRouter(t *testing.T) { defer protocol.CleanAllStatus() url, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1010IP)) @@ -143,3 +136,16 @@ func TestNewHealthCheckRouter(t *testing.T) { assert.Equal(t, dhc.requestSuccessiveFailureThreshold, int32(10)) assert.Equal(t, dhc.circuitTrippedTimeoutFactor, int32(500)) } + +func setUpAddrCache(r router.Poolable, addrs []protocol.Invoker) *router.AddrCache { + pool, info := r.Pool(addrs) + cache := &router.AddrCache{ + Invokers: addrs, + AddrPool: make(map[string]router.RouterAddrPool), + AddrMeta: make(map[string]router.AddrMetadata), + } + + cache.AddrMeta[r.Name()] = info + cache.AddrPool[r.Name()] = pool + return cache +} diff --git a/cluster/router/tag/file.go b/cluster/router/tag/file.go index 8144c83203..4f0f8517bd 100644 --- a/cluster/router/tag/file.go +++ b/cluster/router/tag/file.go @@ -18,6 +18,8 @@ package tag import ( + "github.com/RoaringBitmap/roaring" + "github.com/apache/dubbo-go/cluster/router" "net/url" "strconv" "sync" @@ -74,9 +76,10 @@ func (f *FileTagRouter) Priority() int64 { return f.router.priority } -func (f *FileTagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { - if len(invokers) == 0 { +func (f *FileTagRouter) Route(invokers *roaring.Bitmap, cache *router.AddrCache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap { + if invokers.IsEmpty() { return invokers } - return f.Route(invokers, url, invocation) + // FIXME: I believe this is incorrect. + return f.Route(invokers, cache, url, invocation) } diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index c2d8a0f808..6b359b869d 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -65,15 +65,16 @@ func (c *tagRouter) Route(invokers *roaring.Bitmap, cache *router.AddrCache, url return invokers } - addrPool := cache.FindAddrPool(c) - if target, ok := addrPool[tag]; ok { - ret := utils.JoinIfNotEqual(target, invokers) - if ret.IsEmpty() && !isForceUseTag(url, invocation) { - return invokers - } - return ret + ret := router.EmptyAddr + if target, ok := cache.FindAddrPool(c)[tag]; ok { + ret = utils.JoinIfNotEqual(target, invokers) } - return invokers + + if ret.IsEmpty() && !isForceUseTag(url, invocation) { + return invokers + } + + return ret } func (c *tagRouter) URL() common.URL { diff --git a/cluster/router/tag/tag_router_test.go b/cluster/router/tag/tag_router_test.go index 000b3ec672..465f550492 100644 --- a/cluster/router/tag/tag_router_test.go +++ b/cluster/router/tag/tag_router_test.go @@ -19,6 +19,8 @@ package tag import ( "context" + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/cluster/router/utils" "testing" ) @@ -116,17 +118,17 @@ func TestTagRouterRouteForce(t *testing.T) { invokers = append(invokers, inv2, inv3, inv4) inv := &invocation.RPCInvocation{} inv.SetAttachments(tagRouterTestDubboTag, tagRouterTestHangZhou) - invRst1 := tagRouter.Route(invokers, &u1, inv) - assert.Equal(t, 1, len(invRst1)) - assert.Equal(t, tagRouterTestHangZhou, invRst1[0].GetUrl().GetParam(tagRouterTestDubboTag, "")) + invRst1 := tagRouter.Route(utils.ToBitmap(invokers), setUpAddrCache(tagRouter, invokers), &u1, inv) + assert.Equal(t, 1, len(invRst1.ToArray())) + assert.Equal(t, tagRouterTestHangZhou, invokers[invRst1.ToArray()[0]].GetUrl().GetParam(tagRouterTestDubboTag, "")) inv.SetAttachments(tagRouterTestDubboTag, tagRouterTestGuangZhou) - invRst2 := tagRouter.Route(invokers, &u1, inv) - assert.Equal(t, 0, len(invRst2)) + invRst2 := tagRouter.Route(utils.ToBitmap(invokers), setUpAddrCache(tagRouter, invokers), &u1, inv) + assert.Equal(t, 0, len(invRst2.ToArray())) inv.SetAttachments(tagRouterTestDubboForceTag, tagRouterTestFalse) inv.SetAttachments(tagRouterTestDubboTag, tagRouterTestGuangZhou) - invRst3 := tagRouter.Route(invokers, &u1, inv) - assert.Equal(t, 3, len(invRst3)) + invRst3 := tagRouter.Route(utils.ToBitmap(invokers), setUpAddrCache(tagRouter, invokers), &u1, inv) + assert.Equal(t, 3, len(invRst3.ToArray())) } func TestTagRouterRouteNoForce(t *testing.T) { @@ -148,15 +150,28 @@ func TestTagRouterRouteNoForce(t *testing.T) { invokers = append(invokers, inv2, inv3, inv4) inv := &invocation.RPCInvocation{} inv.SetAttachments(tagRouterTestDubboTag, tagRouterTestHangZhou) - invRst := tagRouter.Route(invokers, &u1, inv) - assert.Equal(t, 1, len(invRst)) - assert.Equal(t, tagRouterTestHangZhou, invRst[0].GetUrl().GetParam(tagRouterTestDubboTag, "")) + invRst := tagRouter.Route(utils.ToBitmap(invokers), setUpAddrCache(tagRouter, invokers), &u1, inv) + assert.Equal(t, 1, len(invRst.ToArray())) + assert.Equal(t, tagRouterTestHangZhou, invokers[invRst.ToArray()[0]].GetUrl().GetParam(tagRouterTestDubboTag, "")) inv.SetAttachments(tagRouterTestDubboTag, tagRouterTestGuangZhou) inv.SetAttachments(tagRouterTestDubboForceTag, tagRouterTestTrue) - invRst1 := tagRouter.Route(invokers, &u1, inv) - assert.Equal(t, 0, len(invRst1)) + invRst1 := tagRouter.Route(utils.ToBitmap(invokers), setUpAddrCache(tagRouter, invokers), &u1, inv) + assert.Equal(t, 0, len(invRst1.ToArray())) inv.SetAttachments(tagRouterTestDubboForceTag, tagRouterTestFalse) - invRst2 := tagRouter.Route(invokers, &u1, inv) - assert.Equal(t, 3, len(invRst2)) + invRst2 := tagRouter.Route(utils.ToBitmap(invokers), setUpAddrCache(tagRouter, invokers), &u1, inv) + assert.Equal(t, 3, len(invRst2.ToArray())) +} + +func setUpAddrCache(r router.Poolable, addrs []protocol.Invoker) *router.AddrCache { + pool, info := r.Pool(addrs) + cache := &router.AddrCache{ + Invokers: addrs, + AddrPool: make(map[string]router.RouterAddrPool), + AddrMeta: make(map[string]router.AddrMetadata), + } + + cache.AddrMeta[r.Name()] = info + cache.AddrPool[r.Name()] = pool + return cache } diff --git a/go.sum b/go.sum index 56e2c3210e..74d640b196 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,8 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEV github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/RoaringBitmap/roaring v0.4.23 h1:gpyfd12QohbqhFO4NVDUdoPOCXsyahYRQhINmlHxKeo= +github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= github.com/SAP/go-hdb v0.12.0 h1:5hBQZ2jjyZ268qjDmoDZJuCyLzR6oRLI60eYzmTW9m4= github.com/SAP/go-hdb v0.12.0/go.mod h1:etBT+FAi1t5k3K3tf5vQTnosgYmhDkRi8jEnQqCnxF0= github.com/SermoDigital/jose v0.0.0-20180104203859-803625baeddc h1:LkkwnbY+S8WmwkWq1SVyRWMH9nYWO1P5XN3OD1tts/w= @@ -136,6 +138,9 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 h1:Ujru1hufTHVb++eG6OuNDKMxZnGIvF6o/u8q/8h2+I4= +github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= +github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/go-co-op/gocron v0.1.1 h1:OfDmkqkCguFtFMsm6Eaayci3DADLa8pXvdmOlPU/JcU= github.com/go-co-op/gocron v0.1.1/go.mod h1:Y9PWlYqDChf2Nbgg7kfS+ZsXHDTZbMZYPEQ0MILqH+M= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= @@ -391,6 +396,7 @@ github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lN github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nacos-group/nacos-sdk-go v0.3.3-0.20200617023039-50c7537d6a5f h1:gid5/0AkHvINWK69Fgbidb3BVIXqlf1YEm7wO0NVPsw= @@ -429,6 +435,8 @@ github.com/patrickmn/go-cache v0.0.0-20180527043350-9f6ff22cfff8 h1:BR6MM54q4W9p github.com/patrickmn/go-cache v0.0.0-20180527043350-9f6ff22cfff8/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= +github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -505,6 +513,8 @@ github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIU github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9/go.mod h1:RHkNRtSLfOK7qBTHaeSX1D6BNpI3qw7NTxsmNr4RvN8= github.com/tevid/gohamcrest v1.1.1 h1:ou+xSqlIw1xfGTg1uq1nif/htZ2S3EzRqLm2BP+tYU0= github.com/tevid/gohamcrest v1.1.1/go.mod h1:3UvtWlqm8j5JbwYZh80D/PVBt0mJ1eJiYgZMibh0H/k= +github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU= +github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 h1:kF/7m/ZU+0D4Jj5eZ41Zm3IH/J8OElK1Qtd7tVKAwLk= @@ -513,6 +523,7 @@ github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/vmware/govmomi v0.18.0 h1:f7QxSmP7meCtoAmiKZogvVbLInT+CZx6Px6K5rYsJZo= github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= +github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8 h1:k8TV7Gz7cpWpOw/dz71fx8cCZdWoPuckHJ/wkJl+meg= diff --git a/registry/directory/directory.go b/registry/directory/directory.go index bdeacfe169..0c59d5e677 100644 --- a/registry/directory/directory.go +++ b/registry/directory/directory.go @@ -18,11 +18,14 @@ package directory import ( - "github.com/apache/dubbo-go/cluster/router/chain" + "fmt" + "net/url" + "os" "sync" ) import ( + gxnet "github.com/dubbogo/gost/net" perrors "github.com/pkg/errors" "go.uber.org/atomic" ) @@ -30,6 +33,7 @@ import ( import ( "github.com/apache/dubbo-go/cluster" "github.com/apache/dubbo-go/cluster/directory" + "github.com/apache/dubbo-go/cluster/router/chain" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" @@ -77,7 +81,9 @@ func NewRegistryDirectory(url *common.URL, registry registry.Registry) (cluster. registry: registry, } - if routerChain, err := chain.NewRouterChain(url.SubURL); err == nil { + dir.cacheOriginUrl = dir.getConsumerUrl(url.SubURL) + + if routerChain, err := chain.NewRouterChain(dir.cacheOriginUrl); err == nil { dir.BaseDirectory.SetRouterChain(routerChain) } else { logger.Warnf("fail to create router chain with url: %s, err is: %v", url.SubURL, err) @@ -223,9 +229,8 @@ func (dir *RegistryDirectory) cacheInvoker(url *common.URL) protocol.Invoker { if url == nil && dir.cacheOriginUrl != nil { url = dir.cacheOriginUrl - } else { - dir.cacheOriginUrl = url } + if url == nil { logger.Error("URL is nil ,pls check if service url is subscribe successfully!") return nil @@ -296,6 +301,24 @@ func (dir *RegistryDirectory) overrideUrl(targetUrl *common.URL) { doOverrideUrl(dir.referenceConfigurationListener.Configurators(), targetUrl) } +func (dir *RegistryDirectory) getConsumerUrl(c *common.URL) *common.URL { + processID := fmt.Sprintf("%d", os.Getpid()) + localIP, _ := gxnet.GetLocalIP() + + params := url.Values{} + c.RangeParams(func(key, value string) bool { + params.Add(key, value) + return true + }) + + params.Add("pid", processID) + params.Add("ip", localIP) + params.Add("protocol", c.Protocol) + + return common.NewURLWithOptions(common.WithProtocol("consumer"), common.WithIp(localIP), common.WithPath(c.Path), + common.WithParams(params)) +} + func doOverrideUrl(configurators []config_center.Configurator, targetUrl *common.URL) { for _, v := range configurators { v.Configure(targetUrl) From 04e0b4ef31e8b25a75df59f708ddee61bc1c98de Mon Sep 17 00:00:00 2001 From: lihaowei Date: Sun, 9 Aug 2020 10:58:05 +0800 Subject: [PATCH 100/242] Correct words and Format codes --- README.md | 2 +- cluster/loadbalance/consistent_hash.go | 2 +- cluster/router/condition/router.go | 4 ++-- cluster/router/healthcheck/default_health_check.go | 10 +++++----- filter/filter.go | 2 +- protocol/dubbo/client.go | 2 +- protocol/dubbo/codec.go | 2 +- protocol/grpc/server.go | 2 +- protocol/invocation/rpcinvocation.go | 4 ++-- protocol/result.go | 2 +- registry/etcdv3/service_discovery.go | 2 +- registry/nacos/listener.go | 4 ++-- registry/registry.go | 2 +- remoting/etcdv3/client.go | 2 +- remoting/kubernetes/registry_controller.go | 2 +- remoting/zookeeper/client.go | 2 +- 16 files changed, 23 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index a1c09fc3ca..7df7e7973a 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Both extension module and layered project architecture is according to Apache Du ![dubbo go extend](./doc/pic/arch/dubbo-go-ext.png) -If you wanna know more about dubbo-go, please visit this reference [Project Architeture design](https://github.com/apache/dubbo-go/wiki/dubbo-go-V1.0-design) +If you wanna know more about dubbo-go, please visit this reference [Project Architecture design](https://github.com/apache/dubbo-go/wiki/dubbo-go-V1.0-design) ## Feature list ## diff --git a/cluster/loadbalance/consistent_hash.go b/cluster/loadbalance/consistent_hash.go index 84fbb268c7..36d93d224e 100644 --- a/cluster/loadbalance/consistent_hash.go +++ b/cluster/loadbalance/consistent_hash.go @@ -42,7 +42,7 @@ const ( ConsistentHash = "consistenthash" // HashNodes ... HashNodes = "hash.nodes" - // HashArguments key of hash arguments in url + // HashArguments key of hash arguments in url HashArguments = "hash.arguments" ) diff --git a/cluster/router/condition/router.go b/cluster/router/condition/router.go index 40a251573f..11e56cc8a5 100644 --- a/cluster/router/condition/router.go +++ b/cluster/router/condition/router.go @@ -288,7 +288,7 @@ func matchCondition(pairs map[string]MatchPair, url *common.URL, param *common.U return result } -// MatchPair Match key pair , condition process +// MatchPair Match key pair, condition process type MatchPair struct { Matches *gxset.HashSet Mismatches *gxset.HashSet @@ -314,7 +314,7 @@ func (pair MatchPair) isMatch(value string, param *common.URL) bool { return true } if !pair.Mismatches.Empty() && !pair.Matches.Empty() { - //when both mismatches and matches contain the same value, then using mismatches first + // when both mismatches and matches contain the same value, then using mismatches first for mismatch := range pair.Mismatches.Items { if isMatchGlobalPattern(mismatch.(string), value, param) { return false diff --git a/cluster/router/healthcheck/default_health_check.go b/cluster/router/healthcheck/default_health_check.go index a26f86ddac..c693b86ecd 100644 --- a/cluster/router/healthcheck/default_health_check.go +++ b/cluster/router/healthcheck/default_health_check.go @@ -49,7 +49,7 @@ type DefaultHealthChecker struct { // and the current active request func (c *DefaultHealthChecker) IsHealthy(invoker protocol.Invoker) bool { urlStatus := protocol.GetURLStatus(invoker.GetUrl()) - if c.isCircuitBreakerTripped(urlStatus) || urlStatus.GetActive() > c.GetOutStandingRequestConutLimit() { + if c.isCircuitBreakerTripped(urlStatus) || urlStatus.GetActive() > c.GetOutStandingRequestCountLimit() { logger.Debugf("Invoker [%s] is currently in circuitbreaker tripped state", invoker.GetUrl().Key()) return false } @@ -92,18 +92,18 @@ func (c *DefaultHealthChecker) getCircuitBreakerSleepWindowTime(status *protocol return int64(sleepWindow) } -// GetOutStandingRequestConutLimit return the requestSuccessiveFailureThreshold bound to this DefaultHealthChecker +// GetRequestSuccessiveFailureThreshold return the requestSuccessiveFailureThreshold bound to this DefaultHealthChecker func (c *DefaultHealthChecker) GetRequestSuccessiveFailureThreshold() int32 { return c.requestSuccessiveFailureThreshold } -// GetOutStandingRequestConutLimit return the circuitTrippedTimeoutFactor bound to this DefaultHealthChecker +// GetCircuitTrippedTimeoutFactor return the circuitTrippedTimeoutFactor bound to this DefaultHealthChecker func (c *DefaultHealthChecker) GetCircuitTrippedTimeoutFactor() int32 { return c.circuitTrippedTimeoutFactor } -// GetOutStandingRequestConutLimit return the outStandingRequestConutLimit bound to this DefaultHealthChecker -func (c *DefaultHealthChecker) GetOutStandingRequestConutLimit() int32 { +// GetOutStandingRequestCountLimit return the outStandingRequestConutLimit bound to this DefaultHealthChecker +func (c *DefaultHealthChecker) GetOutStandingRequestCountLimit() int32 { return c.outStandingRequestConutLimit } diff --git a/filter/filter.go b/filter/filter.go index d20ca72c34..804bf3b9df 100644 --- a/filter/filter.go +++ b/filter/filter.go @@ -27,7 +27,7 @@ import ( // Filter interface defines the functions of a filter // Extension - Filter type Filter interface { - // Invoke is the core function of a filter, it determins the process of the filter + // Invoke is the core function of a filter, it determines the process of the filter Invoke(context.Context, protocol.Invoker, protocol.Invocation) protocol.Result // OnResponse updates the results from Invoke and then returns the modified results. OnResponse(context.Context, protocol.Result, protocol.Invoker, protocol.Invocation) protocol.Result diff --git a/protocol/dubbo/client.go b/protocol/dubbo/client.go index 6d1b771bf4..54752ee5fa 100644 --- a/protocol/dubbo/client.go +++ b/protocol/dubbo/client.go @@ -193,7 +193,7 @@ type Response struct { atta map[string]string } -// NewResponse create a new Response. +// NewResponse create a new Response. func NewResponse(reply interface{}, atta map[string]string) *Response { return &Response{ reply: reply, diff --git a/protocol/dubbo/codec.go b/protocol/dubbo/codec.go index 1f7d107544..ef71e93040 100644 --- a/protocol/dubbo/codec.go +++ b/protocol/dubbo/codec.go @@ -82,7 +82,7 @@ func (p *DubboPackage) Marshal() (*bytes.Buffer, error) { return bytes.NewBuffer(pkg), nil } -// Unmarshal dncode hessian package. +// Unmarshal decode hessian package. func (p *DubboPackage) Unmarshal(buf *bytes.Buffer, opts ...interface{}) error { // fix issue https://github.com/apache/dubbo-go/issues/380 bufLen := buf.Len() diff --git a/protocol/grpc/server.go b/protocol/grpc/server.go index 4017b65dd5..2b7b1adddf 100644 --- a/protocol/grpc/server.go +++ b/protocol/grpc/server.go @@ -69,7 +69,7 @@ func (s *Server) Start(url common.URL) { panic(err) } - // if global trace instance was set , then server tracer instance can be get. If not , will return Nooptracer + // if global trace instance was set, then server tracer instance can be get. If not , will return Nooptracer tracer := opentracing.GlobalTracer() server := grpc.NewServer( grpc.UnaryInterceptor(otgrpc.OpenTracingServerInterceptor(tracer))) diff --git a/protocol/invocation/rpcinvocation.go b/protocol/invocation/rpcinvocation.go index b8b5b50970..74511bb4a6 100644 --- a/protocol/invocation/rpcinvocation.go +++ b/protocol/invocation/rpcinvocation.go @@ -27,7 +27,7 @@ import ( ) // /////////////////////////// -// Invocation Impletment of RPC +// Invocation Implement of RPC // /////////////////////////// // todo: is it necessary to separate fields of consumer(provider) from RPCInvocation @@ -103,7 +103,7 @@ func (r *RPCInvocation) Attachments() map[string]string { return r.attachments } -// AttachmentsByKey gets RPC attachment by key , if nil then return default value. +// AttachmentsByKey gets RPC attachment by key, if nil then return default value. func (r *RPCInvocation) AttachmentsByKey(key string, defaultValue string) string { r.lock.RLock() defer r.lock.RUnlock() diff --git a/protocol/result.go b/protocol/result.go index 2e7a6e492a..2a33be612f 100644 --- a/protocol/result.go +++ b/protocol/result.go @@ -38,7 +38,7 @@ type Result interface { } ///////////////////////////// -// Result Impletment of RPC +// Result Implement of RPC ///////////////////////////// // RPCResult is default RPC result. diff --git a/registry/etcdv3/service_discovery.go b/registry/etcdv3/service_discovery.go index 10396049fb..4bef2ef4d1 100644 --- a/registry/etcdv3/service_discovery.go +++ b/registry/etcdv3/service_discovery.go @@ -71,7 +71,7 @@ func (e *etcdV3ServiceDiscovery) String() string { return e.descriptor } -// Destory service discovery +// Destroy service discovery func (e *etcdV3ServiceDiscovery) Destroy() error { if e.client != nil { e.client.Close() diff --git a/registry/nacos/listener.go b/registry/nacos/listener.go index 36f733df5a..4282bf86be 100644 --- a/registry/nacos/listener.go +++ b/registry/nacos/listener.go @@ -132,7 +132,7 @@ func (nl *nacosListener) Callback(services []model.SubscribeService, err error) instance := generateInstance(services[i]) newInstanceMap[host] = instance if old, ok := nl.instanceMap[host]; !ok { - //instance is not exsit in cache,add it to cache + //instance is not exist in cache,add it to cache addInstances = append(addInstances, instance) } else { //instance is not different from cache,update it to cache @@ -144,7 +144,7 @@ func (nl *nacosListener) Callback(services []model.SubscribeService, err error) for host, inst := range nl.instanceMap { if _, ok := newInstanceMap[host]; !ok { - //cache instance is not exsit in new instance list, remove it from cache + //cache instance is not exist in new instance list, remove it from cache delInstances = append(delInstances, inst) } } diff --git a/registry/registry.go b/registry/registry.go index bb09ead7ef..5e77eab186 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -46,7 +46,7 @@ type Registry interface { //Deprecated! //subscribe(event.URL) (Listener, error) - //Will relace mode1 in dubbogo version v1.1.0 + //Will replace mode1 in dubbogo version v1.1.0 //mode2 : callback mode, subscribe with notify(notify listener). Subscribe(*common.URL, NotifyListener) error diff --git a/remoting/etcdv3/client.go b/remoting/etcdv3/client.go index 7632a7cd04..89c3c5d91c 100644 --- a/remoting/etcdv3/client.go +++ b/remoting/etcdv3/client.go @@ -35,7 +35,7 @@ import ( ) const ( - // ConnDelay connection dalay + // ConnDelay connection delay ConnDelay = 3 // MaxFailTimes max failure times MaxFailTimes = 15 diff --git a/remoting/kubernetes/registry_controller.go b/remoting/kubernetes/registry_controller.go index f93a00a6f2..20be0d72ec 100644 --- a/remoting/kubernetes/registry_controller.go +++ b/remoting/kubernetes/registry_controller.go @@ -567,7 +567,7 @@ func (c *dubboRegistryController) addAnnotationForCurrentPod(k string, v string) c.lock.Lock() defer c.lock.Unlock() - // 1. accord old pod && (k, v) assemble new pod dubbo annotion v + // 1. accord old pod && (k, v) assemble new pod dubbo annotation v // 2. get patch data // 3. PATCH the pod currentPod, err := c.readCurrentPod() diff --git a/remoting/zookeeper/client.go b/remoting/zookeeper/client.go index 4ca34a6aec..186f9261a9 100644 --- a/remoting/zookeeper/client.go +++ b/remoting/zookeeper/client.go @@ -190,7 +190,7 @@ func NewZookeeperClient(name string, zkAddrs []string, timeout time.Duration) (* return z, nil } -// WithTestCluster sets test cluser for zk client +// WithTestCluster sets test cluster for zk client func WithTestCluster(ts *zk.TestCluster) Option { return func(opt *Options) { opt.ts = ts From c56ceb038cd113b8b06b0d00601513e8893c8277 Mon Sep 17 00:00:00 2001 From: lihaowei Date: Sun, 9 Aug 2020 11:02:32 +0800 Subject: [PATCH 101/242] Format codes --- registry/nacos/listener.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/registry/nacos/listener.go b/registry/nacos/listener.go index 4282bf86be..6699007362 100644 --- a/registry/nacos/listener.go +++ b/registry/nacos/listener.go @@ -132,10 +132,10 @@ func (nl *nacosListener) Callback(services []model.SubscribeService, err error) instance := generateInstance(services[i]) newInstanceMap[host] = instance if old, ok := nl.instanceMap[host]; !ok { - //instance is not exist in cache,add it to cache + // instance is not exist in cache, add it to cache addInstances = append(addInstances, instance) } else { - //instance is not different from cache,update it to cache + // instance is not different from cache, update it to cache if !reflect.DeepEqual(old, instance) { updateInstances = append(updateInstances, instance) } @@ -144,7 +144,7 @@ func (nl *nacosListener) Callback(services []model.SubscribeService, err error) for host, inst := range nl.instanceMap { if _, ok := newInstanceMap[host]; !ok { - //cache instance is not exist in new instance list, remove it from cache + // cache instance is not exist in new instance list, remove it from cache delInstances = append(delInstances, inst) } } From 4dfc8aac0561224a8299cc4b8131d65732155348 Mon Sep 17 00:00:00 2001 From: tiecheng <18768171164@163.com> Date: Sun, 9 Aug 2020 13:55:13 +0800 Subject: [PATCH 102/242] change format --- cluster/cluster_impl/base_cluster_invoker.go | 4 +++- cluster/cluster_interceptor.go | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cluster/cluster_impl/base_cluster_invoker.go b/cluster/cluster_impl/base_cluster_invoker.go index 6ab134b28d..ced5b15cb9 100644 --- a/cluster/cluster_impl/base_cluster_invoker.go +++ b/cluster/cluster_impl/base_cluster_invoker.go @@ -17,7 +17,9 @@ package cluster_impl -import "context" +import ( + "context" +) import ( gxnet "github.com/dubbogo/gost/net" diff --git a/cluster/cluster_interceptor.go b/cluster/cluster_interceptor.go index 8cebc40798..a627e81365 100644 --- a/cluster/cluster_interceptor.go +++ b/cluster/cluster_interceptor.go @@ -17,7 +17,9 @@ package cluster -import "context" +import ( + "context" +) import ( "github.com/apache/dubbo-go/protocol" From 49ebaba592a37e73ee4b54172009f5e66fd2caf9 Mon Sep 17 00:00:00 2001 From: aliiohs Date: Sun, 2 Aug 2020 19:53:40 +0800 Subject: [PATCH 103/242] add tls support --- common/constant/key.go | 1 + config/protocol_config.go | 7 ++++--- config/service_config.go | 1 + config/ssl_config.go | 43 +++++++++++++++++++++++++++++++++++++++ go.mod | 4 +++- protocol/dubbo/client.go | 1 + protocol/dubbo/pool.go | 31 ++++++++++++++++++++-------- protocol/dubbo/server.go | 16 ++++++++++++--- 8 files changed, 88 insertions(+), 16 deletions(-) create mode 100644 config/ssl_config.go diff --git a/common/constant/key.go b/common/constant/key.go index 0aa6912e4b..97262cead2 100644 --- a/common/constant/key.go +++ b/common/constant/key.go @@ -46,6 +46,7 @@ const ( DUBBO_KEY = "dubbo" RELEASE_KEY = "release" ANYHOST_KEY = "anyhost" + SSL_ENABLED_KEY = "ssl-enabled" ) const ( diff --git a/config/protocol_config.go b/config/protocol_config.go index cee5b7aa75..3d9e1185d6 100644 --- a/config/protocol_config.go +++ b/config/protocol_config.go @@ -27,9 +27,10 @@ import ( // ProtocolConfig is protocol configuration type ProtocolConfig struct { - Name string `required:"true" yaml:"name" json:"name,omitempty" property:"name"` - Ip string `required:"true" yaml:"ip" json:"ip,omitempty" property:"ip"` - Port string `required:"true" yaml:"port" json:"port,omitempty" property:"port"` + Name string `required:"true" yaml:"name" json:"name,omitempty" property:"name"` + Ip string `required:"true" yaml:"ip" json:"ip,omitempty" property:"ip"` + Port string `required:"true" yaml:"port" json:"port,omitempty" property:"port"` + SslEnabled bool `required:"false" yaml:"sslEnabled" json:"sslEnabled,omitempty" property:"sslEnabled"` } // nolint diff --git a/config/service_config.go b/config/service_config.go index 4351eea7c9..728915381a 100644 --- a/config/service_config.go +++ b/config/service_config.go @@ -188,6 +188,7 @@ func (c *ServiceConfig) Export() error { common.WithPort(port), common.WithParams(urlMap), common.WithParamsValue(constant.BEAN_NAME_KEY, c.id), + common.WithParamsValue(constant.SSL_ENABLED_KEY, strconv.FormatBool(proto.SslEnabled)), common.WithMethods(strings.Split(methods, ",")), common.WithToken(c.Token), ) diff --git a/config/ssl_config.go b/config/ssl_config.go new file mode 100644 index 0000000000..019f83d3cb --- /dev/null +++ b/config/ssl_config.go @@ -0,0 +1,43 @@ +/* + * 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 config + +import ( + "github.com/dubbogo/getty" +) + +var ( + serverTlsConfigBuilder getty.TlsConfigBuilder + clientTlsConfigBuilder getty.TlsConfigBuilder +) + +func GetServerTlsConfigBuilder() getty.TlsConfigBuilder { + return serverTlsConfigBuilder +} + +func GetClientTlsConfigBuilder() getty.TlsConfigBuilder { + return clientTlsConfigBuilder +} + +func SetServerTlsConfigBuilder(configBuilder getty.TlsConfigBuilder) { + serverTlsConfigBuilder = configBuilder +} + +func SetClientTlsConfigBuilder(configBuilder getty.TlsConfigBuilder) { + clientTlsConfigBuilder = configBuilder +} diff --git a/go.mod b/go.mod index c196273782..af6882adff 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,7 @@ module github.com/apache/dubbo-go +go 1.14 + require ( cloud.google.com/go v0.39.0 // indirect github.com/Microsoft/go-winio v0.4.13 // indirect @@ -66,4 +68,4 @@ require ( k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a // indirect ) -go 1.13 +replace github.com/dubbogo/getty v1.2.2 => github.com/aliiohs/getty v1.1.1-0.20200802094147-169328c4ff38 diff --git a/protocol/dubbo/client.go b/protocol/dubbo/client.go index 6d1b771bf4..d73d481ac9 100644 --- a/protocol/dubbo/client.go +++ b/protocol/dubbo/client.go @@ -229,6 +229,7 @@ func (c *Client) call(ct CallType, request *Request, response *Response, callbac p.Service.Version = request.svcUrl.GetParam(constant.VERSION_KEY, "") p.Service.Group = request.svcUrl.GetParam(constant.GROUP_KEY, "") p.Service.Method = request.method + c.pool.sslEnabled = request.svcUrl.GetParamBool(constant.SSL_ENABLED_KEY, false) p.Service.Timeout = c.opts.RequestTimeout var timeout = request.svcUrl.GetParam(strings.Join([]string{constant.METHOD_KEYS, request.method + constant.RETRIES_KEY}, "."), "") diff --git a/protocol/dubbo/pool.go b/protocol/dubbo/pool.go index c9f5e34fad..cb1960a756 100644 --- a/protocol/dubbo/pool.go +++ b/protocol/dubbo/pool.go @@ -33,6 +33,7 @@ import ( import ( "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config" ) type gettyRPCClient struct { @@ -53,15 +54,26 @@ var ( ) func newGettyRPCClientConn(pool *gettyRPCClientPool, protocol, addr string) (*gettyRPCClient, error) { - c := &gettyRPCClient{ - protocol: protocol, - addr: addr, - pool: pool, - gettyClient: getty.NewTCPClient( + var gettyClient getty.Client + if pool.sslEnabled { + gettyClient = getty.NewTCPClient( + getty.WithServerAddress(addr), + getty.WithConnectionNumber((int)(pool.rpcClient.conf.ConnectionNum)), + getty.WithReconnectInterval(pool.rpcClient.conf.ReconnectInterval), + getty.WithClientTlsConfigBuilder(config.GetClientTlsConfigBuilder()), + ) + } else { + gettyClient = getty.NewTCPClient( getty.WithServerAddress(addr), getty.WithConnectionNumber((int)(pool.rpcClient.conf.ConnectionNum)), getty.WithReconnectInterval(pool.rpcClient.conf.ReconnectInterval), - ), + ) + } + c := &gettyRPCClient{ + protocol: protocol, + addr: addr, + pool: pool, + gettyClient: gettyClient, } go c.gettyClient.RunEventLoop(c.newSession) idx := 1 @@ -288,9 +300,10 @@ func (c *gettyRPCClient) close() error { } type gettyRPCClientPool struct { - rpcClient *Client - size int // size of []*gettyRPCClient - ttl int64 // ttl of every gettyRPCClient, it is checked when getConn + rpcClient *Client + size int // size of []*gettyRPCClient + ttl int64 // ttl of every gettyRPCClient, it is checked when getConn + sslEnabled bool sync.Mutex conns []*gettyRPCClient diff --git a/protocol/dubbo/server.go b/protocol/dubbo/server.go index 8de353a0b3..bef7b3f680 100644 --- a/protocol/dubbo/server.go +++ b/protocol/dubbo/server.go @@ -30,6 +30,7 @@ import ( import ( "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/config" ) @@ -163,9 +164,18 @@ func (s *Server) Start(url common.URL) { ) addr = url.Location - tcpServer = getty.NewTCPServer( - getty.WithLocalAddress(addr), - ) + if url.GetParamBool(constant.SSL_ENABLED_KEY, false) { + tcpServer = getty.NewTCPServer( + getty.WithLocalAddress(addr), + getty.WithServerSslEnabled(url.GetParamBool(constant.SSL_ENABLED_KEY, false)), + getty.WithServerTlsConfigBuilder(config.GetServerTlsConfigBuilder()), + ) + + } else { + tcpServer = getty.NewTCPServer( + getty.WithLocalAddress(addr), + ) + } tcpServer.RunEventLoop(s.newSession) logger.Debugf("s bind addr{%s} ok!", addr) s.tcpServer = tcpServer From ecdf8ccdbe752f989f13439de040b28ac4e4b510 Mon Sep 17 00:00:00 2001 From: lihaowei Date: Sun, 9 Aug 2020 14:12:22 +0800 Subject: [PATCH 104/242] add nolint --- protocol/dubbo/client.go | 2 +- protocol/dubbo/codec.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/protocol/dubbo/client.go b/protocol/dubbo/client.go index 54752ee5fa..253427f301 100644 --- a/protocol/dubbo/client.go +++ b/protocol/dubbo/client.go @@ -193,7 +193,7 @@ type Response struct { atta map[string]string } -// NewResponse create a new Response. +// nolint func NewResponse(reply interface{}, atta map[string]string) *Response { return &Response{ reply: reply, diff --git a/protocol/dubbo/codec.go b/protocol/dubbo/codec.go index ef71e93040..3878984fd0 100644 --- a/protocol/dubbo/codec.go +++ b/protocol/dubbo/codec.go @@ -82,7 +82,7 @@ func (p *DubboPackage) Marshal() (*bytes.Buffer, error) { return bytes.NewBuffer(pkg), nil } -// Unmarshal decode hessian package. +// Unmarshal decodes hessian package. func (p *DubboPackage) Unmarshal(buf *bytes.Buffer, opts ...interface{}) error { // fix issue https://github.com/apache/dubbo-go/issues/380 bufLen := buf.Len() From 443c1b8643a328b0c0e4a18c90785c232b055266 Mon Sep 17 00:00:00 2001 From: aliiohs Date: Sun, 9 Aug 2020 15:48:01 +0800 Subject: [PATCH 105/242] change module version --- go.mod | 6 ++---- go.sum | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index af6882adff..cb13fd2a20 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect github.com/creasty/defaults v1.3.0 github.com/docker/go-connections v0.4.0 // indirect - github.com/dubbogo/getty v1.3.7 + github.com/dubbogo/getty v1.3.8 github.com/dubbogo/go-zookeeper v1.0.1 github.com/dubbogo/gost v1.9.0 github.com/elazarl/go-bindata-assetfs v1.0.0 // indirect @@ -66,6 +66,4 @@ require ( k8s.io/apimachinery v0.16.9 k8s.io/client-go v0.16.9 k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a // indirect -) - -replace github.com/dubbogo/getty v1.2.2 => github.com/aliiohs/getty v1.1.1-0.20200802094147-169328c4ff38 +) \ No newline at end of file diff --git a/go.sum b/go.sum index aa6ecc86e2..399d26b2d5 100644 --- a/go.sum +++ b/go.sum @@ -145,6 +145,7 @@ github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5Xh github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/dubbogo/getty v1.3.7 h1:xlkYD2/AH34iGteuLMsGjLl2PwBVrbIhHjf3tlUsv1M= github.com/dubbogo/getty v1.3.7/go.mod h1:XWO4+wAaMqgnBN9Ykv2YxxOAkGxymg6LGO9RK+EiCDY= +github.com/dubbogo/getty v1.3.8/go.mod h1:JIN5lNZznGnBuoYjlEe/yjNmq7eSjKaawHdnBAdtiEo= github.com/dubbogo/go-zookeeper v1.0.1 h1:irLzvOsDOTNsN8Sv9tvYYxVu6DCQfLtziZQtUHmZgz8= github.com/dubbogo/go-zookeeper v1.0.1/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c= github.com/dubbogo/gost v1.9.0 h1:UT+dWwvLyJiDotxJERO75jB3Yxgsdy10KztR5ycxRAk= From bef03b44c8afbf70fc0aab3e07c696075caa742b Mon Sep 17 00:00:00 2001 From: aliiohs Date: Sun, 9 Aug 2020 15:49:48 +0800 Subject: [PATCH 106/242] upgrade getty version --- go.mod | 5 +--- go.sum | 89 +--------------------------------------------------------- 2 files changed, 2 insertions(+), 92 deletions(-) diff --git a/go.mod b/go.mod index cb13fd2a20..f3cc4183b3 100644 --- a/go.mod +++ b/go.mod @@ -38,8 +38,6 @@ require ( github.com/hashicorp/vault/api v1.0.5-0.20191108163347-bdd38fca2cff // indirect github.com/hashicorp/vault/sdk v0.1.14-0.20191112033314-390e96e22eb2 github.com/jinzhu/copier v0.0.0-20190625015134-976e0346caa8 - github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect - github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b // indirect github.com/magiconair/properties v1.8.1 github.com/mitchellh/hashstructure v1.0.0 // indirect github.com/mitchellh/mapstructure v1.2.3 @@ -60,10 +58,9 @@ require ( go.uber.org/zap v1.15.0 google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64 // indirect google.golang.org/grpc v1.23.0 - gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce // indirect gopkg.in/yaml.v2 v2.2.8 k8s.io/api v0.16.9 k8s.io/apimachinery v0.16.9 k8s.io/client-go v0.16.9 k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a // indirect -) \ No newline at end of file +) diff --git a/go.sum b/go.sum index 399d26b2d5..fc7015759d 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ -cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.39.0 h1:UgQP9na6OTfp4dsAiz/eFpFA1C6tPdH5wiRdi19tuMw= cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts= @@ -37,16 +35,13 @@ github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VY github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/DataDog/datadog-go v2.2.0+incompatible h1:V5BKkxACZLjzHjSgBbr2gvLA2Ae49yhc6CSY7MLy5k4= github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/Microsoft/go-winio v0.4.3 h1:M3NHMuPgMSUPdE5epwNUHlRPSVzHs8HpRTrVXhR0myo= github.com/Microsoft/go-winio v0.4.3/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.13 h1:Hmi80lzZuI/CaYmlJp/b+FjZdRZhKu9c2mDVqKlLWVs= github.com/Microsoft/go-winio v0.4.13/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/NYTimes/gziphandler v1.0.1 h1:iLrQrdwjDd52kHDA5op2UBJFjmOb9g+7scBan4RN8F0= github.com/NYTimes/gziphandler v1.0.1/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= @@ -64,8 +59,6 @@ github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA= -github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA= -github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk= github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk= github.com/apache/dubbo-go-hessian2 v1.6.1 h1:mFKeCZzaCkk4mMOyP+LQ85GHbRyqKT7858KS21JQYA4= github.com/apache/dubbo-go-hessian2 v1.6.1/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= @@ -73,11 +66,9 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM= github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= github.com/armon/go-metrics v0.3.0 h1:B7AQgHi8QSEi4uHu7Sbsga+IJDU+CENgjxoo81vDUqU= github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26FmByPp2eC2qla6E1Tw+scpcg/to= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= @@ -138,20 +129,17 @@ github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TR github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/docker/go-connections v0.3.0 h1:3lOnM9cSzgGwx8VfK/NGOW5fLQ0GjIlCkaktF+n1M6o= github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/dubbogo/getty v1.3.7 h1:xlkYD2/AH34iGteuLMsGjLl2PwBVrbIhHjf3tlUsv1M= -github.com/dubbogo/getty v1.3.7/go.mod h1:XWO4+wAaMqgnBN9Ykv2YxxOAkGxymg6LGO9RK+EiCDY= +github.com/dubbogo/getty v1.3.8 h1:D9VQLlO4df0H+k8lEW+Re1hGymxzQjvvEKOzGNI35sI= github.com/dubbogo/getty v1.3.8/go.mod h1:JIN5lNZznGnBuoYjlEe/yjNmq7eSjKaawHdnBAdtiEo= github.com/dubbogo/go-zookeeper v1.0.1 h1:irLzvOsDOTNsN8Sv9tvYYxVu6DCQfLtziZQtUHmZgz8= github.com/dubbogo/go-zookeeper v1.0.1/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c= github.com/dubbogo/gost v1.9.0 h1:UT+dWwvLyJiDotxJERO75jB3Yxgsdy10KztR5ycxRAk= github.com/dubbogo/gost v1.9.0/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0 h1:ZoRgc53qJCfSLimXqJDrmBhnt5GChDsExMCK7t48o0Y= github.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk= github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= @@ -168,31 +156,25 @@ github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6 github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 h1:Ghm4eQYC0nEPnSJdVkTrXpu9KtoVCSo1hg7mtI7G9KU= github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/frankban/quicktest v1.4.1 h1:Wv2VwvNn73pAdFIVUQRXYDFp31lXKbqblIXo/Q5GPSg= github.com/frankban/quicktest v1.4.1/go.mod h1:36zfPVQyHxymz4cH7wlDmVwDrJuljRB60qkgn7rorfQ= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew= github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= -github.com/go-asn1-ber/asn1-ber v1.3.1 h1:gvPdv/Hr++TRFCl0UbPFHC54P9N9jgsRPnmnr419Uck= github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-co-op/gocron v0.1.1 h1:OfDmkqkCguFtFMsm6Eaayci3DADLa8pXvdmOlPU/JcU= github.com/go-co-op/gocron v0.1.1/go.mod h1:Y9PWlYqDChf2Nbgg7kfS+ZsXHDTZbMZYPEQ0MILqH+M= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-ldap/ldap v3.0.2+incompatible h1:kD5HQcAzlQ7yrhfn+h+MSABeAy/jAJhvIJ/QDllP44g= github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= -github.com/go-ldap/ldap/v3 v3.1.3 h1:RIgdpHXJpsUqUK5WXwKyVsESrGFqo5BRWPk3RR4/ogQ= github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -213,7 +195,6 @@ github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3a github.com/gogo/googleapis v1.1.0 h1:kFkMAZBNAn4j7K0GiZr8cRYzejq68VbheufiV3YuyFI= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -239,11 +220,9 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0= github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -255,9 +234,7 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2 h1:AtvtonGEH/fZK0XPNNBdB6swgy7Iudfx88wzyIpwqJ8= github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2/go.mod h1:DavVbd41y+b7ukKDmlnPR4nGYmkWXR6vHUkjQNiHPBs= -github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4 h1:hU4mGcQI4DaAYW+IbTun+2qEZVFxK0ySjQLTbS0VQKc= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= @@ -269,9 +246,7 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 h1:z53tR0945TRRQO/fLEVPI6SMv7ZflF0TEaTAoU7tOzg= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -292,7 +267,6 @@ github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/U github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-bexpr v0.1.2 h1:ijMXI4qERbzxbCnkxmfUtwMyjrrk3y+Vt0MxojNCbBs= github.com/hashicorp/go-bexpr v0.1.2/go.mod h1:ANbpTX1oAql27TZkKVeW8p1w8NTdnyzPe/0qqPCKohU= -github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de h1:XDCSythtg8aWSRSO29uwhgh7b127fWr+m5SemqjSUL8= github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de/go.mod h1:xIwEieBHERyEvaeKF/TcHh1Hu+lxPM+n2vT1+g9I4m4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= @@ -303,12 +277,10 @@ github.com/hashicorp/go-discover v0.0.0-20200501174627-ad1e96bde088 h1:jBvElOiln github.com/hashicorp/go-discover v0.0.0-20200501174627-ad1e96bde088/go.mod h1:vZu6Opqf49xX5lsFAu7iFNewkcVF1sn/wyapZh5ytlg= github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v0.9.1 h1:9PZfAcVEvez4yhLH2TBU64/h/z4xlFI80cWXRrxuKuM= github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.12.0 h1:d4QkX8FRTYaKaCZBoXYY8zJX2BXjWxurN/GA2tkrmZM= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.1.0 h1:vN9wG1D6KG6YHRTWr8512cxGOVgTMEfgEdSj/hr8MPc= github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -317,33 +289,26 @@ github.com/hashicorp/go-memdb v1.0.3/go.mod h1:LWQ8R70vPrS4OEY9k28D2z8/Zzyu34NVz github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= -github.com/hashicorp/go-plugin v1.0.1 h1:4OtAfUGbnKC6yS48p0CtMX2oFYtzFZVv6rok3cRWgnE= github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= -github.com/hashicorp/go-raftchunking v0.6.1 h1:moEnaG3gcwsWNyIBJoD5PCByE+Ewkqxh6N05CT+MbwA= github.com/hashicorp/go-raftchunking v0.6.1/go.mod h1:cGlg3JtDy7qy6c/3Bu660Mic1JF+7lWqIwCFSb08fX0= github.com/hashicorp/go-raftchunking v0.6.3-0.20191002164813-7e9e8525653a h1:FmnBDwGwlTgugDGbVxwV8UavqSMACbGrUpfc98yFLR4= github.com/hashicorp/go-raftchunking v0.6.3-0.20191002164813-7e9e8525653a/go.mod h1:xbXnmKqX9/+RhPkJ4zrEx4738HacP72aaUPlT2RZ4sU= -github.com/hashicorp/go-retryablehttp v0.5.3 h1:QlWt0KvWT0lq8MFppF9tsJGF+ynG7ztc2KIPhzRGk7s= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-retryablehttp v0.5.4 h1:1BZvpawXoJCWX6pNtow9+rpEj+3itIlutiqnntI6jOE= github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.6.2 h1:bHM2aVXwBtBJWxHtkSrWuI4umABCUczs52eiUS9nSiw= github.com/hashicorp/go-retryablehttp v0.6.2/go.mod h1:gEx6HMUGxYYhJScX7W1Il64m6cc2C1mDaW3NQ9sY1FY= github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2-0.20191001231223-f32f5fe8d6a8/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= @@ -351,9 +316,7 @@ github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= @@ -361,7 +324,6 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hil v0.0.0-20160711231837-1e86c6b523c5 h1:uk280DXEbQiCOZgCOI3elFSeNxf8YIZiNsbr2pQLYD0= github.com/hashicorp/hil v0.0.0-20160711231837-1e86c6b523c5/go.mod h1:KHvg/R2/dPtaePb16oW4qIyzkMxXOL38xjRN64adsts= -github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.1 h1:XFSOubp8KWB+Jd2PDyaX5xUd5bhSP/+pTDZVDMzZJM8= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= @@ -370,7 +332,6 @@ github.com/hashicorp/memberlist v0.2.2 h1:5+RffWKwqJ71YPu9mWsF7ZOscZmwfasdA8kbdC github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/net-rpc-msgpackrpc v0.0.0-20151116020338-a14192a58a69 h1:lc3c72qGlIMDqQpQH82Y4vaglRMMFdJbziYWriR4UcE= github.com/hashicorp/net-rpc-msgpackrpc v0.0.0-20151116020338-a14192a58a69/go.mod h1:/z+jUGRBlwVpUZfjute9jWaF6/HuhjuFQuL1YXzVD1Q= -github.com/hashicorp/raft v1.1.1 h1:HJr7UE1x/JrJSc9Oy6aDBHtNHUUBHjcQjTgvUVihoZs= github.com/hashicorp/raft v1.1.1/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= github.com/hashicorp/raft v1.1.2-0.20191002163536-9c6bd3e3eb17/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= github.com/hashicorp/raft v1.1.2 h1:oxEL5DDeurYxLd3UbcY/hccgSPhLLpiBZ1YxtWEq59c= @@ -380,7 +341,6 @@ github.com/hashicorp/raft-boltdb v0.0.0-20171010151810-6e5ba93211ea/go.mod h1:pN github.com/hashicorp/serf v0.9.0/go.mod h1:YL0HO+FifKOW2u1ke99DGVu1zhcpZzNwrLIqBC7vbYU= github.com/hashicorp/serf v0.9.2 h1:yJoyfZXo4Pk2p/M/viW+YLibBFiIbKoP79gu7kDAFP0= github.com/hashicorp/serf v0.9.2/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= -github.com/hashicorp/vault/api v1.0.4 h1:j08Or/wryXT4AcHj1oCbMd7IijXcKzYUGw59LGu9onU= github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= github.com/hashicorp/vault/api v1.0.5-0.20191108163347-bdd38fca2cff h1:cl94LQIrs/mNbh3ny1R8lM1gtYcUBa7HnGtOCi35SlQ= github.com/hashicorp/vault/api v1.0.5-0.20191108163347-bdd38fca2cff/go.mod h1:Uf8LaHyrYsgVgHzO2tMZKhqRGlL3UJ6XaSwW2EA1Iqo= @@ -400,7 +360,6 @@ github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= -github.com/jackc/pgx v3.3.0+incompatible h1:Wa90/+qsITBAPkAZjiByeIGHFcj3Ztu+VzrrIpHjL90= github.com/jackc/pgx v3.3.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da h1:FjHUJJ7oBW4G/9j1KzlHaXL09LyMVM9rupS39lncbXk= github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= @@ -412,7 +371,6 @@ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5i github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62 h1:JHCT6xuyPUrbbgAPE/3dqlvUKzRHMNuTBKKUb6OeR/k= github.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA= github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f h1:ENpDacvnr8faw5ugQmEF1QYk+f/Y9lXFvuYmRxykago= github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f/go.mod h1:KDSfL7qe5ZfQqvlDMkVjCztbmcpp/c8M77vhQP8ZPvk= @@ -424,22 +382,14 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/juju/errors v0.0.0-20190930114154-d42613fe1ab9 h1:hJix6idebFclqlfZCHE7EUX7uqLCyb70nHNHH1XKGBg= -github.com/juju/errors v0.0.0-20190930114154-d42613fe1ab9/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= -github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI= -github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= -github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b h1:Rrp0ByJXEjhREMPGTt3aWYjoIsUGCbt21ekbeJcTWv0= -github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -452,7 +402,6 @@ github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f h1:sgU github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f/go.mod h1:UGmTpUd3rjbtfIpwAPrcfmGf/Z1HS95TATB+m57TPB8= github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 h1:Bvq8AziQ5jFF4BHGAEDSqwPW1NJS3XshxbRCxtjFAZc= github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042/go.mod h1:TPpsiPUEh0zFL1Snz4crhMlBe60PYxRHr5oFF3rRYg0= -github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/linode/linodego v0.7.1 h1:4WZmMpSA2NRwlPZcc0+4Gyn7rr99Evk9bnr0B3gXRKE= github.com/linode/linodego v0.7.1/go.mod h1:ga11n3ivecUrPCHN0rANxKmfWBJVkOXfLMZinAbj2sY= @@ -460,12 +409,10 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= @@ -475,11 +422,9 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0 h1:tEElEatulEHDeedTxwckzyYMA5c86fbmNIUL1hBIiTg= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= @@ -488,17 +433,14 @@ github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFW github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.0 h1:/x0XQ6h+3U3nAyk1yx+bHPURrKa9sVVvYbuqZ7pIAtI= github.com/mitchellh/go-testing-interface v1.14.0/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452 h1:hOY53G+kBFhbYFpRVxHl5eS7laP6B1+Cq+Z9Dry1iMU= github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= github.com/mitchellh/hashstructure v1.0.0 h1:ZkRJX1CyOoTkar7p/mLS5TZU4nJ1Rn/F8u9dGS02Q3Y= github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.2.3 h1:f/MjBEBDLttYCGfRaKBbKSRVF5aV2O6fnBpzknuE3jU= github.com/mitchellh/mapstructure v1.2.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -521,12 +463,10 @@ github.com/nacos-group/nacos-sdk-go v1.0.0 h1:CufUF7DZca2ZzIrJtMMCDih1sA58BWCglA github.com/nacos-group/nacos-sdk-go v1.0.0/go.mod h1:hlAPn3UdzlxIlSILAyOXKxjFSvDJ9oLzTJ9hLAK1KzA= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 h1:BQ1HW7hr4IVovMwWg0E0PYcyW8CzqDcVmaew9cujU4s= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk= -github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20180130162743-b8a9be070da4/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= @@ -545,21 +485,17 @@ github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144T github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.2.6+incompatible h1:6aCX4/YZ9v8q69hTyiR7dNLnTA3fgtKHVVW5BCd5Znw= github.com/pierrec/lz4 v2.2.6+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= @@ -587,7 +523,6 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rboyer/safeio v0.2.1 h1:05xhhdRNAdS3apYm7JRjOqngf4xruaW959jmRxGDuSU= github.com/rboyer/safeio v0.2.1/go.mod h1:Cq/cEPK+YXFn622lsQ0K4KsPZSPtaptHHEldsy7Fmig= github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 h1:Wdi9nwnhFNAlseAOekn6B5G/+GMtks9UKbvRU/CMM/o= github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03/go.mod h1:gRAiPF5C5Nd0eyyRdqIu9qTiFSoZzpTq727b5B8fkkU= @@ -596,20 +531,16 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rs/zerolog v1.4.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/columnize v2.1.0+incompatible h1:j1Wcmh8OrK4Q7GXY+V7SVSY8nUWQxHW5TkBe7YUl+2s= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= -github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= -github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/conswriter v0.0.0-20180208195008-f5ae3917a627/go.mod h1:7zjs06qF79/FKAJpBvFx3P8Ww4UTIMAe+lpNXDHziac= github.com/sean-/pager v0.0.0-20180208200047-666be9bf53b5/go.mod h1:BeybITEsBEg6qbIiqJ6/Bqeq25bCLbL7YFmpaFfJDuM= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shirou/gopsutil v0.0.0-20181107111621-48177ef5f880 h1:1Ge4j/3uB2rxzPWD3TC+daeCw+w91z8UCUL/7WH5gn8= github.com/shirou/gopsutil v0.0.0-20181107111621-48177ef5f880/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v2.19.9+incompatible h1:IrPVlK4nfwW10DF7pW+7YJKws9NkgNzWozwwWv9FsgY= github.com/shirou/gopsutil v2.19.9+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= @@ -617,7 +548,6 @@ github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+D github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= @@ -639,14 +569,12 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -700,7 +628,6 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= @@ -733,13 +660,11 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= @@ -748,7 +673,6 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -773,7 +697,6 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190508220229-2d0786266e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -782,7 +705,6 @@ golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -817,13 +739,11 @@ google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEt google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0 h1:9sdfJOzWlkqPltHAuzT2Cp+yrBeY1KRVYgms8soxMwM= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.0 h1:Tfd7cKwKbFRsI8RMAD3oqqw7JPFRrvFlOsfbgVkjOOw= google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -831,7 +751,6 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190530194941-fb225487d101 h1:wuGevabY6r+ivPNagjUXGGxF+GqgMd+dBhjsxW4q9u4= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64 h1:iKtrH9Y8mcbADOP0YFaEMth7OfuHY9xHOwNj4znpM1A= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= @@ -845,10 +764,8 @@ google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -860,11 +777,8 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU= -gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y= gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= @@ -872,7 +786,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From c62ec2e223a3031e49b0356b781ab98c70afce88 Mon Sep 17 00:00:00 2001 From: aliiohs Date: Sun, 9 Aug 2020 15:57:24 +0800 Subject: [PATCH 107/242] fix getty const --- config/consumer_config.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/config/consumer_config.go b/config/consumer_config.go index 1775312092..4f3de6c11f 100644 --- a/config/consumer_config.go +++ b/config/consumer_config.go @@ -24,7 +24,6 @@ import ( import ( "github.com/creasty/defaults" - "github.com/dubbogo/getty" perrors "github.com/pkg/errors" ) @@ -34,6 +33,10 @@ import ( "github.com/apache/dubbo-go/common/yaml" ) +const ( + maxWheelTimeSpan = 900e9 // 900s, 15 minute +) + ///////////////////////// // consumerConfig ///////////////////////// @@ -107,9 +110,9 @@ func ConsumerInit(confConFile string) error { if consumerConfig.RequestTimeout, err = time.ParseDuration(consumerConfig.Request_Timeout); err != nil { return perrors.WithMessagef(err, "time.ParseDuration(Request_Timeout{%#v})", consumerConfig.Request_Timeout) } - if consumerConfig.RequestTimeout >= time.Duration(getty.MaxWheelTimeSpan) { + if consumerConfig.RequestTimeout >= time.Duration(maxWheelTimeSpan) { return perrors.WithMessagef(err, "request_timeout %s should be less than %s", - consumerConfig.Request_Timeout, time.Duration(getty.MaxWheelTimeSpan)) + consumerConfig.Request_Timeout, time.Duration(maxWheelTimeSpan)) } } if consumerConfig.Connect_Timeout != "" { From b75fcea6acf7252f361e41750b739d8513ccd2b8 Mon Sep 17 00:00:00 2001 From: aliiohs Date: Sun, 9 Aug 2020 16:07:59 +0800 Subject: [PATCH 108/242] fix getty const --- config/consumer_config.go | 6 +++--- protocol/dubbo/config.go | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/config/consumer_config.go b/config/consumer_config.go index 4f3de6c11f..9d283eeca7 100644 --- a/config/consumer_config.go +++ b/config/consumer_config.go @@ -34,7 +34,7 @@ import ( ) const ( - maxWheelTimeSpan = 900e9 // 900s, 15 minute + MaxWheelTimeSpan = 900e9 // 900s, 15 minute ) ///////////////////////// @@ -110,9 +110,9 @@ func ConsumerInit(confConFile string) error { if consumerConfig.RequestTimeout, err = time.ParseDuration(consumerConfig.Request_Timeout); err != nil { return perrors.WithMessagef(err, "time.ParseDuration(Request_Timeout{%#v})", consumerConfig.Request_Timeout) } - if consumerConfig.RequestTimeout >= time.Duration(maxWheelTimeSpan) { + if consumerConfig.RequestTimeout >= time.Duration(MaxWheelTimeSpan) { return perrors.WithMessagef(err, "request_timeout %s should be less than %s", - consumerConfig.Request_Timeout, time.Duration(maxWheelTimeSpan)) + consumerConfig.Request_Timeout, time.Duration(MaxWheelTimeSpan)) } } if consumerConfig.Connect_Timeout != "" { diff --git a/protocol/dubbo/config.go b/protocol/dubbo/config.go index 635d12109a..3280c12309 100644 --- a/protocol/dubbo/config.go +++ b/protocol/dubbo/config.go @@ -18,11 +18,11 @@ package dubbo import ( + "github.com/apache/dubbo-go/config" "time" ) import ( - "github.com/dubbogo/getty" perrors "github.com/pkg/errors" ) @@ -178,9 +178,9 @@ func (c *ClientConfig) CheckValidity() error { return perrors.WithMessagef(err, "time.ParseDuration(HeartbeatPeroid{%#v})", c.HeartbeatPeriod) } - if c.heartbeatPeriod >= time.Duration(getty.MaxWheelTimeSpan) { + if c.heartbeatPeriod >= time.Duration(config.MaxWheelTimeSpan) { return perrors.WithMessagef(err, "heartbeat_period %s should be less than %s", - c.HeartbeatPeriod, time.Duration(getty.MaxWheelTimeSpan)) + c.HeartbeatPeriod, time.Duration(config.MaxWheelTimeSpan)) } if c.sessionTimeout, err = time.ParseDuration(c.SessionTimeout); err != nil { @@ -198,9 +198,9 @@ func (c *ServerConfig) CheckValidity() error { return perrors.WithMessagef(err, "time.ParseDuration(SessionTimeout{%#v})", c.SessionTimeout) } - if c.sessionTimeout >= time.Duration(getty.MaxWheelTimeSpan) { + if c.sessionTimeout >= time.Duration(config.MaxWheelTimeSpan) { return perrors.WithMessagef(err, "session_timeout %s should be less than %s", - c.SessionTimeout, time.Duration(getty.MaxWheelTimeSpan)) + c.SessionTimeout, time.Duration(config.MaxWheelTimeSpan)) } return perrors.WithStack(c.GettySessionParam.CheckValidity()) From 8e85afdf48849223cbc1ea86f63ae2d58a41b40a Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 31 Mar 2020 19:58:13 +0800 Subject: [PATCH 109/242] optimized code --- .../rest/server/server_impl/go_restful_server.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/protocol/rest/server/server_impl/go_restful_server.go b/protocol/rest/server/server_impl/go_restful_server.go index 69f36a5c80..812699b3b6 100644 --- a/protocol/rest/server/server_impl/go_restful_server.go +++ b/protocol/rest/server/server_impl/go_restful_server.go @@ -81,29 +81,28 @@ func (grs *GoRestfulServer) Start(url common.URL) { } func (grs *GoRestfulServer) Deploy(invoker protocol.Invoker, restMethodConfig map[string]*config.RestMethodConfig) { - svc := common.ServiceMap.GetService(invoker.GetUrl().Protocol, strings.TrimPrefix(invoker.GetUrl().Path, "/")) for methodName, config := range restMethodConfig { - // get method - method := svc.Method()[methodName] - argsTypes := method.ArgsType() - replyType := method.ReplyType() ws := new(restful.WebService) ws.Path(config.Path). Produces(strings.Split(config.Produces, ",")...). Consumes(strings.Split(config.Consumes, ",")...). - Route(ws.Method(config.MethodType).To(getFunc(methodName, invoker, argsTypes, replyType, config))) + Route(ws.Method(config.MethodType).To(getFunc(methodName, invoker, config))) grs.container.Add(ws) } } -func getFunc(methodName string, invoker protocol.Invoker, argsTypes []reflect.Type, - replyType reflect.Type, config *config.RestMethodConfig) func(req *restful.Request, resp *restful.Response) { +func getFunc(methodName string, invoker protocol.Invoker, config *config.RestMethodConfig) func(req *restful.Request, resp *restful.Response) { return func(req *restful.Request, resp *restful.Response) { var ( err error args []interface{} ) + svc := common.ServiceMap.GetService(invoker.GetUrl().Protocol, strings.TrimPrefix(invoker.GetUrl().Path, "/")) + // get method + method := svc.Method()[methodName] + argsTypes := method.ArgsType() + replyType := method.ReplyType() if (len(argsTypes) == 1 || len(argsTypes) == 2 && replyType == nil) && argsTypes[0].String() == "[]interface {}" { args = getArgsInterfaceFromRequest(req, config) From 13369bf9a72e16ef657ccf6b27d269367dc37776 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 31 Mar 2020 23:10:40 +0800 Subject: [PATCH 110/242] use adapter model to modify restServer --- .../rest/client/client_impl/resty_client.go | 2 +- protocol/rest/client/rest_client.go | 4 +- protocol/rest/rest_invoker.go | 3 +- protocol/rest/rest_protocol.go | 4 +- protocol/rest/server/rest_server.go | 261 +++++++++++++++- .../server/server_impl/go_restful_server.go | 287 +++++------------- 6 files changed, 335 insertions(+), 226 deletions(-) diff --git a/protocol/rest/client/client_impl/resty_client.go b/protocol/rest/client/client_impl/resty_client.go index aa6c23137d..af9637ea91 100644 --- a/protocol/rest/client/client_impl/resty_client.go +++ b/protocol/rest/client/client_impl/resty_client.go @@ -65,7 +65,7 @@ func NewRestyClient(restOption *client.RestOptions) client.RestClient { } } -func (rc *RestyClient) Do(restRequest *client.RestRequest, res interface{}) error { +func (rc *RestyClient) Do(restRequest *client.RestClientRequest, res interface{}) error { r, err := rc.client.R(). SetHeader("Content-Type", restRequest.Consumes). SetHeader("Accept", restRequest.Produces). diff --git a/protocol/rest/client/rest_client.go b/protocol/rest/client/rest_client.go index 7d020abc81..3acccb53ae 100644 --- a/protocol/rest/client/rest_client.go +++ b/protocol/rest/client/rest_client.go @@ -26,7 +26,7 @@ type RestOptions struct { ConnectTimeout time.Duration } -type RestRequest struct { +type RestClientRequest struct { Location string Path string Produces string @@ -39,5 +39,5 @@ type RestRequest struct { } type RestClient interface { - Do(request *RestRequest, res interface{}) error + Do(request *RestClientRequest, res interface{}) error } diff --git a/protocol/rest/rest_invoker.go b/protocol/rest/rest_invoker.go index 0c82035ac5..c8e3feaff6 100644 --- a/protocol/rest/rest_invoker.go +++ b/protocol/rest/rest_invoker.go @@ -78,8 +78,7 @@ func (ri *RestInvoker) Invoke(ctx context.Context, invocation protocol.Invocatio if len(inv.Arguments()) > methodConfig.Body && methodConfig.Body >= 0 { body = inv.Arguments()[methodConfig.Body] } - - req := &client.RestRequest{ + req := &client.RestClientRequest{ Location: ri.GetUrl().Location, Produces: methodConfig.Produces, Consumes: methodConfig.Consumes, diff --git a/protocol/rest/rest_protocol.go b/protocol/rest/rest_protocol.go index 47ecb6093b..e15eeb39d7 100644 --- a/protocol/rest/rest_protocol.go +++ b/protocol/rest/rest_protocol.go @@ -75,7 +75,9 @@ func (rp *RestProtocol) Export(invoker protocol.Invoker) protocol.Exporter { } rp.SetExporterMap(serviceKey, exporter) restServer := rp.getServer(url, restServiceConfig.Server) - restServer.Deploy(invoker, restServiceConfig.RestMethodConfigsMap) + for _, methodConfig := range restServiceConfig.RestMethodConfigsMap { + restServer.Deploy(methodConfig, server.GetRouteFunc(invoker, methodConfig)) + } return exporter } diff --git a/protocol/rest/server/rest_server.go b/protocol/rest/server/rest_server.go index c10c98a7b6..b7eb555625 100644 --- a/protocol/rest/server/rest_server.go +++ b/protocol/rest/server/rest_server.go @@ -17,15 +17,270 @@ package server +import ( + "context" + "net/http" + "reflect" + "strconv" + "strings" +) + +import ( + perrors "github.com/pkg/errors" +) + import ( "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/protocol" - "github.com/apache/dubbo-go/protocol/rest/config" + "github.com/apache/dubbo-go/protocol/invocation" + rest_config "github.com/apache/dubbo-go/protocol/rest/config" ) type RestServer interface { Start(url common.URL) - Deploy(invoker protocol.Invoker, restMethodConfig map[string]*config.RestMethodConfig) - UnDeploy(restMethodConfig map[string]*config.RestMethodConfig) + Deploy(restMethodConfig *rest_config.RestMethodConfig, routeFunc func(request RestServerRequest, response RestServerResponse)) + UnDeploy(restMethodConfig *rest_config.RestMethodConfig) Destroy() } + +// RestServerRequest interface +type RestServerRequest interface { + PathParameter(name string) string + PathParameters() map[string]string + QueryParameter(name string) string + QueryParameters(name string) []string + BodyParameter(name string) (string, error) + HeaderParameter(name string) string + ReadEntity(entityPointer interface{}) error +} + +// RestServerResponse interface +type RestServerResponse interface { + WriteError(httpStatus int, err error) (writeErr error) + WriteEntity(value interface{}) error +} + +func GetRouteFunc(invoker protocol.Invoker, methodConfig *rest_config.RestMethodConfig) func(req RestServerRequest, resp RestServerResponse) { + return func(req RestServerRequest, resp RestServerResponse) { + var ( + err error + args []interface{} + ) + svc := common.ServiceMap.GetService(invoker.GetUrl().Protocol, strings.TrimPrefix(invoker.GetUrl().Path, "/")) + // get method + method := svc.Method()[methodConfig.MethodName] + argsTypes := method.ArgsType() + replyType := method.ReplyType() + if (len(argsTypes) == 1 || len(argsTypes) == 2 && replyType == nil) && + argsTypes[0].String() == "[]interface {}" { + args = getArgsInterfaceFromRequest(req, methodConfig) + } else { + args = getArgsFromRequest(req, argsTypes, methodConfig) + } + result := invoker.Invoke(context.Background(), invocation.NewRPCInvocation(methodConfig.MethodName, args, make(map[string]string))) + if result.Error() != nil { + err = resp.WriteError(http.StatusInternalServerError, result.Error()) + if err != nil { + logger.Errorf("[Go Restful] WriteError error:%v", err) + } + return + } + err = resp.WriteEntity(result.Result()) + if err != nil { + logger.Errorf("[Go Restful] WriteEntity error:%v", err) + } + } +} + +// when service function like GetUser(req []interface{}, rsp *User) error +// use this method to get arguments +func getArgsInterfaceFromRequest(req RestServerRequest, methodConfig *rest_config.RestMethodConfig) []interface{} { + argsMap := make(map[int]interface{}, 8) + maxKey := 0 + for k, v := range methodConfig.PathParamsMap { + if maxKey < k { + maxKey = k + } + argsMap[k] = req.PathParameter(v) + } + for k, v := range methodConfig.QueryParamsMap { + if maxKey < k { + maxKey = k + } + params := req.QueryParameters(v) + if len(params) == 1 { + argsMap[k] = params[0] + } else { + argsMap[k] = params + } + } + for k, v := range methodConfig.HeadersMap { + if maxKey < k { + maxKey = k + } + argsMap[k] = req.HeaderParameter(v) + } + if methodConfig.Body >= 0 { + if maxKey < methodConfig.Body { + maxKey = methodConfig.Body + } + m := make(map[string]interface{}) + // TODO read as a slice + if err := req.ReadEntity(&m); err != nil { + logger.Warnf("[Go restful] Read body entity as map[string]interface{} error:%v", perrors.WithStack(err)) + } else { + argsMap[methodConfig.Body] = m + } + } + args := make([]interface{}, maxKey+1) + for k, v := range argsMap { + if k >= 0 { + args[k] = v + } + } + return args +} + +// get arguments from server.RestServerRequest +func getArgsFromRequest(req RestServerRequest, argsTypes []reflect.Type, methodConfig *rest_config.RestMethodConfig) []interface{} { + argsLength := len(argsTypes) + args := make([]interface{}, argsLength) + for i, t := range argsTypes { + args[i] = reflect.Zero(t).Interface() + } + assembleArgsFromPathParams(methodConfig, argsLength, argsTypes, req, args) + assembleArgsFromQueryParams(methodConfig, argsLength, argsTypes, req, args) + assembleArgsFromBody(methodConfig, argsTypes, req, args) + assembleArgsFromHeaders(methodConfig, req, argsLength, argsTypes, args) + return args +} + +// assemble arguments from headers +func assembleArgsFromHeaders(methodConfig *rest_config.RestMethodConfig, req RestServerRequest, argsLength int, argsTypes []reflect.Type, args []interface{}) { + for k, v := range methodConfig.HeadersMap { + param := req.HeaderParameter(v) + if k < 0 || k >= argsLength { + logger.Errorf("[Go restful] Header param parse error, the args:%v doesn't exist", k) + continue + } + t := argsTypes[k] + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + if t.Kind() == reflect.String { + args[k] = param + } else { + logger.Errorf("[Go restful] Header param parse error, the args:%v of type isn't string", k) + } + } +} + +// assemble arguments from body +func assembleArgsFromBody(methodConfig *rest_config.RestMethodConfig, argsTypes []reflect.Type, req RestServerRequest, args []interface{}) { + if methodConfig.Body >= 0 && methodConfig.Body < len(argsTypes) { + t := argsTypes[methodConfig.Body] + kind := t.Kind() + if kind == reflect.Ptr { + t = t.Elem() + } + var ni interface{} + if t.String() == "[]interface {}" { + ni = make([]map[string]interface{}, 0) + } else if t.String() == "interface {}" { + ni = make(map[string]interface{}) + } else { + n := reflect.New(t) + if n.CanInterface() { + ni = n.Interface() + } + } + if err := req.ReadEntity(&ni); err != nil { + logger.Errorf("[Go restful] Read body entity error:%v", err) + } else { + args[methodConfig.Body] = ni + } + } +} + +// assemble arguments from query params +func assembleArgsFromQueryParams(methodConfig *rest_config.RestMethodConfig, argsLength int, argsTypes []reflect.Type, req RestServerRequest, args []interface{}) { + var ( + err error + param interface{} + i64 int64 + ) + for k, v := range methodConfig.QueryParamsMap { + if k < 0 || k >= argsLength { + logger.Errorf("[Go restful] Query param parse error, the args:%v doesn't exist", k) + continue + } + t := argsTypes[k] + kind := t.Kind() + if kind == reflect.Ptr { + t = t.Elem() + } + if kind == reflect.Slice { + param = req.QueryParameters(v) + } else if kind == reflect.String { + param = req.QueryParameter(v) + } else if kind == reflect.Int { + param, err = strconv.Atoi(req.QueryParameter(v)) + } else if kind == reflect.Int32 { + i64, err = strconv.ParseInt(req.QueryParameter(v), 10, 32) + if err == nil { + param = int32(i64) + } + } else if kind == reflect.Int64 { + param, err = strconv.ParseInt(req.QueryParameter(v), 10, 64) + } else { + logger.Errorf("[Go restful] Query param parse error, the args:%v of type isn't int or string or slice", k) + continue + } + if err != nil { + logger.Errorf("[Go restful] Query param parse error, error is %v", err) + continue + } + args[k] = param + } +} + +// assemble arguments from path params +func assembleArgsFromPathParams(methodConfig *rest_config.RestMethodConfig, argsLength int, argsTypes []reflect.Type, req RestServerRequest, args []interface{}) { + var ( + err error + param interface{} + i64 int64 + ) + for k, v := range methodConfig.PathParamsMap { + if k < 0 || k >= argsLength { + logger.Errorf("[Go restful] Path param parse error, the args:%v doesn't exist", k) + continue + } + t := argsTypes[k] + kind := t.Kind() + if kind == reflect.Ptr { + t = t.Elem() + } + if kind == reflect.Int { + param, err = strconv.Atoi(req.PathParameter(v)) + } else if kind == reflect.Int32 { + i64, err = strconv.ParseInt(req.PathParameter(v), 10, 32) + if err == nil { + param = int32(i64) + } + } else if kind == reflect.Int64 { + param, err = strconv.ParseInt(req.PathParameter(v), 10, 64) + } else if kind == reflect.String { + param = req.PathParameter(v) + } else { + logger.Warnf("[Go restful] Path param parse error, the args:%v of type isn't int or string", k) + continue + } + if err != nil { + logger.Errorf("[Go restful] Path param parse error, error is %v", err) + continue + } + args[k] = param + } +} diff --git a/protocol/rest/server/server_impl/go_restful_server.go b/protocol/rest/server/server_impl/go_restful_server.go index 812699b3b6..3c81fe0bfd 100644 --- a/protocol/rest/server/server_impl/go_restful_server.go +++ b/protocol/rest/server/server_impl/go_restful_server.go @@ -22,8 +22,6 @@ import ( "fmt" "net" "net/http" - "reflect" - "strconv" "strings" "time" ) @@ -38,8 +36,6 @@ import ( "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" "github.com/apache/dubbo-go/common/logger" - "github.com/apache/dubbo-go/protocol" - "github.com/apache/dubbo-go/protocol/invocation" "github.com/apache/dubbo-go/protocol/rest/config" "github.com/apache/dubbo-go/protocol/rest/server" ) @@ -80,57 +76,27 @@ func (grs *GoRestfulServer) Start(url common.URL) { }() } -func (grs *GoRestfulServer) Deploy(invoker protocol.Invoker, restMethodConfig map[string]*config.RestMethodConfig) { - for methodName, config := range restMethodConfig { - ws := new(restful.WebService) - ws.Path(config.Path). - Produces(strings.Split(config.Produces, ",")...). - Consumes(strings.Split(config.Consumes, ",")...). - Route(ws.Method(config.MethodType).To(getFunc(methodName, invoker, config))) - grs.container.Add(ws) +func (grs *GoRestfulServer) Deploy(restMethodConfig *config.RestMethodConfig, routeFunc func(request server.RestServerRequest, response server.RestServerResponse)) { + ws := new(restful.WebService) + rf := func(req *restful.Request, resp *restful.Response) { + respAdapter := NewGoRestfulResponseAdapter(resp) + reqAdapter := NewGoRestfulRequestAdapter(req) + routeFunc(reqAdapter, respAdapter) } + ws.Path(restMethodConfig.Path). + Produces(strings.Split(restMethodConfig.Produces, ",")...). + Consumes(strings.Split(restMethodConfig.Consumes, ",")...). + Route(ws.Method(restMethodConfig.MethodType).To(rf)) + grs.container.Add(ws) } -func getFunc(methodName string, invoker protocol.Invoker, config *config.RestMethodConfig) func(req *restful.Request, resp *restful.Response) { - return func(req *restful.Request, resp *restful.Response) { - var ( - err error - args []interface{} - ) - svc := common.ServiceMap.GetService(invoker.GetUrl().Protocol, strings.TrimPrefix(invoker.GetUrl().Path, "/")) - // get method - method := svc.Method()[methodName] - argsTypes := method.ArgsType() - replyType := method.ReplyType() - if (len(argsTypes) == 1 || len(argsTypes) == 2 && replyType == nil) && - argsTypes[0].String() == "[]interface {}" { - args = getArgsInterfaceFromRequest(req, config) - } else { - args = getArgsFromRequest(req, argsTypes, config) - } - result := invoker.Invoke(context.Background(), invocation.NewRPCInvocation(methodName, args, make(map[string]string))) - if result.Error() != nil { - err = resp.WriteError(http.StatusInternalServerError, result.Error()) - if err != nil { - logger.Errorf("[Go Restful] WriteError error:%v", err) - } - return - } - err = resp.WriteEntity(result.Result()) - if err != nil { - logger.Error("[Go Restful] WriteEntity error:%v", err) - } - } -} -func (grs *GoRestfulServer) UnDeploy(restMethodConfig map[string]*config.RestMethodConfig) { - for _, config := range restMethodConfig { - ws := new(restful.WebService) - ws.Path(config.Path) - err := grs.container.Remove(ws) - if err != nil { - logger.Warnf("[Go restful] Remove web service error:%v", err) - } +func (grs *GoRestfulServer) UnDeploy(restMethodConfig *config.RestMethodConfig) { + ws := new(restful.WebService) + ws.Path(restMethodConfig.Path) + err := grs.container.Remove(ws) + if err != nil { + logger.Warnf("[Go restful] Remove web service error:%v", err) } } @@ -143,179 +109,66 @@ func (grs *GoRestfulServer) Destroy() { logger.Infof("[Go Restful] Server exiting") } -func getArgsInterfaceFromRequest(req *restful.Request, config *config.RestMethodConfig) []interface{} { - argsMap := make(map[int]interface{}, 8) - maxKey := 0 - for k, v := range config.PathParamsMap { - if maxKey < k { - maxKey = k - } - argsMap[k] = req.PathParameter(v) - } - for k, v := range config.QueryParamsMap { - if maxKey < k { - maxKey = k - } - params := req.QueryParameters(v) - if len(params) == 1 { - argsMap[k] = params[0] - } else { - argsMap[k] = params - } - } - for k, v := range config.HeadersMap { - if maxKey < k { - maxKey = k - } - argsMap[k] = req.HeaderParameter(v) - } - if config.Body >= 0 { - if maxKey < config.Body { - maxKey = config.Body - } - m := make(map[string]interface{}) - // TODO read as a slice - if err := req.ReadEntity(&m); err != nil { - logger.Warnf("[Go restful] Read body entity as map[string]interface{} error:%v", perrors.WithStack(err)) - } else { - argsMap[config.Body] = m - } - } - args := make([]interface{}, maxKey+1) - for k, v := range argsMap { - if k >= 0 { - args[k] = v - } - } - return args +func GetNewGoRestfulServer() server.RestServer { + return NewGoRestfulServer() } -func getArgsFromRequest(req *restful.Request, argsTypes []reflect.Type, config *config.RestMethodConfig) []interface{} { - argsLength := len(argsTypes) - args := make([]interface{}, argsLength) - for i, t := range argsTypes { - args[i] = reflect.Zero(t).Interface() - } - var ( - err error - param interface{} - i64 int64 - ) - for k, v := range config.PathParamsMap { - if k < 0 || k >= argsLength { - logger.Errorf("[Go restful] Path param parse error, the args:%v doesn't exist", k) - continue - } - t := argsTypes[k] - kind := t.Kind() - if kind == reflect.Ptr { - t = t.Elem() - } - if kind == reflect.Int { - param, err = strconv.Atoi(req.PathParameter(v)) - } else if kind == reflect.Int32 { - i64, err = strconv.ParseInt(req.PathParameter(v), 10, 32) - if err == nil { - param = int32(i64) - } - } else if kind == reflect.Int64 { - param, err = strconv.ParseInt(req.PathParameter(v), 10, 64) - } else if kind == reflect.String { - param = req.PathParameter(v) - } else { - logger.Warnf("[Go restful] Path param parse error, the args:%v of type isn't int or string", k) - continue - } - if err != nil { - logger.Errorf("[Go restful] Path param parse error, error is %v", err) - continue - } - args[k] = param - } - for k, v := range config.QueryParamsMap { - if k < 0 || k >= argsLength { - logger.Errorf("[Go restful] Query param parse error, the args:%v doesn't exist", k) - continue - } - t := argsTypes[k] - kind := t.Kind() - if kind == reflect.Ptr { - t = t.Elem() - } - if kind == reflect.Slice { - param = req.QueryParameters(v) - } else if kind == reflect.String { - param = req.QueryParameter(v) - } else if kind == reflect.Int { - param, err = strconv.Atoi(req.QueryParameter(v)) - } else if kind == reflect.Int32 { - i64, err = strconv.ParseInt(req.QueryParameter(v), 10, 32) - if err == nil { - param = int32(i64) - } - } else if kind == reflect.Int64 { - param, err = strconv.ParseInt(req.QueryParameter(v), 10, 64) - } else { - logger.Errorf("[Go restful] Query param parse error, the args:%v of type isn't int or string or slice", k) - continue - } - if err != nil { - logger.Errorf("[Go restful] Query param parse error, error is %v", err) - continue - } - args[k] = param - } +// Let user addFilter +// addFilter should before config.Load() +func AddGoRestfulServerFilter(filterFuc restful.FilterFunction) { + filterSlice = append(filterSlice, filterFuc) +} - if config.Body >= 0 && config.Body < len(argsTypes) { - t := argsTypes[config.Body] - kind := t.Kind() - if kind == reflect.Ptr { - t = t.Elem() - } - var ni interface{} - if t.String() == "[]interface {}" { - ni = make([]map[string]interface{}, 0) - } else if t.String() == "interface {}" { - ni = make(map[string]interface{}) - } else { - n := reflect.New(t) - if n.CanInterface() { - ni = n.Interface() - } - } - if err := req.ReadEntity(&ni); err != nil { - logger.Errorf("[Go restful] Read body entity error:%v", err) - } else { - args[config.Body] = ni - } - } +// Go Restful Request adapt to RestServerRequest +type GoRestfulRequestAdapter struct { + rawRequest *restful.Request +} - for k, v := range config.HeadersMap { - param := req.HeaderParameter(v) - if k < 0 || k >= argsLength { - logger.Errorf("[Go restful] Header param parse error, the args:%v doesn't exist", k) - continue - } - t := argsTypes[k] - if t.Kind() == reflect.Ptr { - t = t.Elem() - } - if t.Kind() == reflect.String { - args[k] = param - } else { - logger.Errorf("[Go restful] Header param parse error, the args:%v of type isn't string", k) - } - } +func (gra *GoRestfulRequestAdapter) PathParameter(name string) string { + return gra.rawRequest.PathParameter(name) +} - return args +func (gra *GoRestfulRequestAdapter) PathParameters() map[string]string { + return gra.rawRequest.PathParameters() } -func GetNewGoRestfulServer() server.RestServer { - return NewGoRestfulServer() +func (gra *GoRestfulRequestAdapter) QueryParameter(name string) string { + return gra.rawRequest.QueryParameter(name) } -// Let user addFilter -// addFilter should before config.Load() -func AddGoRestfulServerFilter(filterFuc restful.FilterFunction) { - filterSlice = append(filterSlice, filterFuc) +func (gra *GoRestfulRequestAdapter) QueryParameters(name string) []string { + return gra.rawRequest.QueryParameters(name) +} + +func (gra *GoRestfulRequestAdapter) BodyParameter(name string) (string, error) { + return gra.rawRequest.BodyParameter(name) +} + +func (gra *GoRestfulRequestAdapter) HeaderParameter(name string) string { + return gra.rawRequest.HeaderParameter(name) +} + +func (gra *GoRestfulRequestAdapter) ReadEntity(entityPointer interface{}) error { + return gra.rawRequest.ReadEntity(entityPointer) +} + +func NewGoRestfulRequestAdapter(rawRequest *restful.Request) server.RestServerRequest { + return &GoRestfulRequestAdapter{rawRequest: rawRequest} +} + +// Go Restful Request adapt to RestClientRequest +type GoRestfulResponseAdapter struct { + rawResponse *restful.Response +} + +func (grsa *GoRestfulResponseAdapter) WriteError(httpStatus int, err error) (writeErr error) { + return grsa.rawResponse.WriteError(httpStatus, err) +} + +func (grsa *GoRestfulResponseAdapter) WriteEntity(value interface{}) error { + return grsa.rawResponse.WriteEntity(value) +} + +func NewGoRestfulResponseAdapter(rawResponse *restful.Response) server.RestServerResponse { + return &GoRestfulResponseAdapter{rawResponse: rawResponse} } From 9a9b3a7e264ad8811ba2e9f21987e526e92118d9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 1 Apr 2020 01:40:18 +0800 Subject: [PATCH 111/242] remove adapter --- .../server/server_impl/go_restful_server.go | 58 +------------------ 1 file changed, 1 insertion(+), 57 deletions(-) diff --git a/protocol/rest/server/server_impl/go_restful_server.go b/protocol/rest/server/server_impl/go_restful_server.go index 3c81fe0bfd..81043c8a72 100644 --- a/protocol/rest/server/server_impl/go_restful_server.go +++ b/protocol/rest/server/server_impl/go_restful_server.go @@ -79,9 +79,7 @@ func (grs *GoRestfulServer) Start(url common.URL) { func (grs *GoRestfulServer) Deploy(restMethodConfig *config.RestMethodConfig, routeFunc func(request server.RestServerRequest, response server.RestServerResponse)) { ws := new(restful.WebService) rf := func(req *restful.Request, resp *restful.Response) { - respAdapter := NewGoRestfulResponseAdapter(resp) - reqAdapter := NewGoRestfulRequestAdapter(req) - routeFunc(reqAdapter, respAdapter) + routeFunc(req, resp) } ws.Path(restMethodConfig.Path). Produces(strings.Split(restMethodConfig.Produces, ",")...). @@ -118,57 +116,3 @@ func GetNewGoRestfulServer() server.RestServer { func AddGoRestfulServerFilter(filterFuc restful.FilterFunction) { filterSlice = append(filterSlice, filterFuc) } - -// Go Restful Request adapt to RestServerRequest -type GoRestfulRequestAdapter struct { - rawRequest *restful.Request -} - -func (gra *GoRestfulRequestAdapter) PathParameter(name string) string { - return gra.rawRequest.PathParameter(name) -} - -func (gra *GoRestfulRequestAdapter) PathParameters() map[string]string { - return gra.rawRequest.PathParameters() -} - -func (gra *GoRestfulRequestAdapter) QueryParameter(name string) string { - return gra.rawRequest.QueryParameter(name) -} - -func (gra *GoRestfulRequestAdapter) QueryParameters(name string) []string { - return gra.rawRequest.QueryParameters(name) -} - -func (gra *GoRestfulRequestAdapter) BodyParameter(name string) (string, error) { - return gra.rawRequest.BodyParameter(name) -} - -func (gra *GoRestfulRequestAdapter) HeaderParameter(name string) string { - return gra.rawRequest.HeaderParameter(name) -} - -func (gra *GoRestfulRequestAdapter) ReadEntity(entityPointer interface{}) error { - return gra.rawRequest.ReadEntity(entityPointer) -} - -func NewGoRestfulRequestAdapter(rawRequest *restful.Request) server.RestServerRequest { - return &GoRestfulRequestAdapter{rawRequest: rawRequest} -} - -// Go Restful Request adapt to RestClientRequest -type GoRestfulResponseAdapter struct { - rawResponse *restful.Response -} - -func (grsa *GoRestfulResponseAdapter) WriteError(httpStatus int, err error) (writeErr error) { - return grsa.rawResponse.WriteError(httpStatus, err) -} - -func (grsa *GoRestfulResponseAdapter) WriteEntity(value interface{}) error { - return grsa.rawResponse.WriteEntity(value) -} - -func NewGoRestfulResponseAdapter(rawResponse *restful.Response) server.RestServerResponse { - return &GoRestfulResponseAdapter{rawResponse: rawResponse} -} From bcb01004337eb1a3baf4390f905f649e4477ad85 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 1 Apr 2020 19:05:58 +0800 Subject: [PATCH 112/242] optimize header transmit in RestClient and RestServer --- .../rest/client/client_impl/resty_client.go | 11 +++-- protocol/rest/client/rest_client.go | 5 +-- protocol/rest/rest_invoker.go | 23 +++++++--- protocol/rest/server/rest_server.go | 4 ++ .../server/server_impl/go_restful_server.go | 44 ++++++++++++++++++- 5 files changed, 72 insertions(+), 15 deletions(-) diff --git a/protocol/rest/client/client_impl/resty_client.go b/protocol/rest/client/client_impl/resty_client.go index af9637ea91..9e0c80ccd2 100644 --- a/protocol/rest/client/client_impl/resty_client.go +++ b/protocol/rest/client/client_impl/resty_client.go @@ -66,20 +66,19 @@ func NewRestyClient(restOption *client.RestOptions) client.RestClient { } func (rc *RestyClient) Do(restRequest *client.RestClientRequest, res interface{}) error { - r, err := rc.client.R(). - SetHeader("Content-Type", restRequest.Consumes). - SetHeader("Accept", restRequest.Produces). + req := rc.client.R() + req.Header = restRequest.Header + resp, err := req. SetPathParams(restRequest.PathParams). SetQueryParams(restRequest.QueryParams). - SetHeaders(restRequest.Headers). SetBody(restRequest.Body). SetResult(res). Execute(restRequest.Method, "http://"+path.Join(restRequest.Location, restRequest.Path)) if err != nil { return perrors.WithStack(err) } - if r.IsError() { - return perrors.New(r.String()) + if resp.IsError() { + return perrors.New(resp.String()) } return nil } diff --git a/protocol/rest/client/rest_client.go b/protocol/rest/client/rest_client.go index 3acccb53ae..5be4bb3be3 100644 --- a/protocol/rest/client/rest_client.go +++ b/protocol/rest/client/rest_client.go @@ -18,6 +18,7 @@ package client import ( + "net/http" "time" ) @@ -27,15 +28,13 @@ type RestOptions struct { } type RestClientRequest struct { + Header http.Header Location string Path string - Produces string - Consumes string Method string PathParams map[string]string QueryParams map[string]string Body interface{} - Headers map[string]string } type RestClient interface { diff --git a/protocol/rest/rest_invoker.go b/protocol/rest/rest_invoker.go index c8e3feaff6..121d1217ef 100644 --- a/protocol/rest/rest_invoker.go +++ b/protocol/rest/rest_invoker.go @@ -20,6 +20,7 @@ package rest import ( "context" "fmt" + "net/http" ) import ( @@ -56,7 +57,7 @@ func (ri *RestInvoker) Invoke(ctx context.Context, invocation protocol.Invocatio body interface{} pathParams map[string]string queryParams map[string]string - headers map[string]string + header http.Header err error ) if methodConfig == nil { @@ -71,7 +72,7 @@ func (ri *RestInvoker) Invoke(ctx context.Context, invocation protocol.Invocatio result.Err = err return &result } - if headers, err = restStringMapTransform(methodConfig.HeadersMap, inv.Arguments()); err != nil { + if header, err = getRestHttpHeader(methodConfig, inv.Arguments()); err != nil { result.Err = err return &result } @@ -80,14 +81,12 @@ func (ri *RestInvoker) Invoke(ctx context.Context, invocation protocol.Invocatio } req := &client.RestClientRequest{ Location: ri.GetUrl().Location, - Produces: methodConfig.Produces, - Consumes: methodConfig.Consumes, Method: methodConfig.MethodType, Path: methodConfig.Path, PathParams: pathParams, QueryParams: queryParams, Body: body, - Headers: headers, + Header: header, } result.Err = ri.client.Do(req, inv.Reply()) if result.Err == nil { @@ -106,3 +105,17 @@ func restStringMapTransform(paramsMap map[int]string, args []interface{}) (map[s } return resMap, nil } + +func getRestHttpHeader(methodConfig *config.RestMethodConfig, args []interface{}) (http.Header, error) { + header := http.Header{} + headersMap := methodConfig.HeadersMap + header.Set("Content-Type", methodConfig.Consumes) + header.Set("Accept", methodConfig.Produces) + for k, v := range headersMap { + if k >= len(args) || k < 0 { + return nil, perrors.Errorf("[Rest Invoke] Index %v is out of bundle", k) + } + header.Set(v, fmt.Sprint(args[k])) + } + return header, nil +} diff --git a/protocol/rest/server/rest_server.go b/protocol/rest/server/rest_server.go index b7eb555625..7fb0560ff2 100644 --- a/protocol/rest/server/rest_server.go +++ b/protocol/rest/server/rest_server.go @@ -46,6 +46,7 @@ type RestServer interface { // RestServerRequest interface type RestServerRequest interface { + RawRequest() *http.Request PathParameter(name string) string PathParameters() map[string]string QueryParameter(name string) string @@ -57,6 +58,9 @@ type RestServerRequest interface { // RestServerResponse interface type RestServerResponse interface { + Header() http.Header + Write([]byte) (int, error) + WriteHeader(statusCode int) WriteError(httpStatus int, err error) (writeErr error) WriteEntity(value interface{}) error } diff --git a/protocol/rest/server/server_impl/go_restful_server.go b/protocol/rest/server/server_impl/go_restful_server.go index 81043c8a72..9163d3aed7 100644 --- a/protocol/rest/server/server_impl/go_restful_server.go +++ b/protocol/rest/server/server_impl/go_restful_server.go @@ -79,7 +79,7 @@ func (grs *GoRestfulServer) Start(url common.URL) { func (grs *GoRestfulServer) Deploy(restMethodConfig *config.RestMethodConfig, routeFunc func(request server.RestServerRequest, response server.RestServerResponse)) { ws := new(restful.WebService) rf := func(req *restful.Request, resp *restful.Response) { - routeFunc(req, resp) + routeFunc(NewGoRestfulRequestAdapter(req), resp) } ws.Path(restMethodConfig.Path). Produces(strings.Split(restMethodConfig.Produces, ",")...). @@ -116,3 +116,45 @@ func GetNewGoRestfulServer() server.RestServer { func AddGoRestfulServerFilter(filterFuc restful.FilterFunction) { filterSlice = append(filterSlice, filterFuc) } + +// Adapter about RestServerRequest +type GoRestfulRequestAdapter struct { + server.RestServerRequest + request *restful.Request +} + +func NewGoRestfulRequestAdapter(request *restful.Request) *GoRestfulRequestAdapter { + return &GoRestfulRequestAdapter{request: request} +} + +func (grra *GoRestfulRequestAdapter) RawRequest() *http.Request { + return grra.request.Request +} + +func (grra *GoRestfulRequestAdapter) PathParameter(name string) string { + return grra.request.PathParameter(name) +} + +func (grra *GoRestfulRequestAdapter) PathParameters() map[string]string { + return grra.request.PathParameters() +} + +func (grra *GoRestfulRequestAdapter) QueryParameter(name string) string { + return grra.request.QueryParameter(name) +} + +func (grra *GoRestfulRequestAdapter) QueryParameters(name string) []string { + return grra.request.QueryParameters(name) +} + +func (grra *GoRestfulRequestAdapter) BodyParameter(name string) (string, error) { + return grra.request.BodyParameter(name) +} + +func (grra *GoRestfulRequestAdapter) HeaderParameter(name string) string { + return grra.request.HeaderParameter(name) +} + +func (grra *GoRestfulRequestAdapter) ReadEntity(entityPointer interface{}) error { + return grra.request.ReadEntity(entityPointer) +} From f2949db91e69f536316a435eaa9433e539947bbe Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 1 Apr 2020 20:04:31 +0800 Subject: [PATCH 113/242] add some comments --- .../rest/client/client_impl/resty_client.go | 1 + protocol/rest/client/rest_client.go | 3 +++ protocol/rest/server/rest_server.go | 20 +++++++++++--- .../server/server_impl/go_restful_server.go | 27 ++++++++++++++----- 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/protocol/rest/client/client_impl/resty_client.go b/protocol/rest/client/client_impl/resty_client.go index 9e0c80ccd2..bfd74455bc 100644 --- a/protocol/rest/client/client_impl/resty_client.go +++ b/protocol/rest/client/client_impl/resty_client.go @@ -40,6 +40,7 @@ func init() { extension.SetRestClient(constant.DEFAULT_REST_CLIENT, NewRestyClient) } +// A rest client implement by Resty type RestyClient struct { client *resty.Client } diff --git a/protocol/rest/client/rest_client.go b/protocol/rest/client/rest_client.go index 5be4bb3be3..47d17c6943 100644 --- a/protocol/rest/client/rest_client.go +++ b/protocol/rest/client/rest_client.go @@ -22,11 +22,13 @@ import ( "time" ) +// Some rest options type RestOptions struct { RequestTimeout time.Duration ConnectTimeout time.Duration } +// Client request type RestClientRequest struct { Header http.Header Location string @@ -37,6 +39,7 @@ type RestClientRequest struct { Body interface{} } +// User can implement this client interface to send request type RestClient interface { Do(request *RestClientRequest, res interface{}) error } diff --git a/protocol/rest/server/rest_server.go b/protocol/rest/server/rest_server.go index 7fb0560ff2..60f0dab942 100644 --- a/protocol/rest/server/rest_server.go +++ b/protocol/rest/server/rest_server.go @@ -38,33 +38,47 @@ import ( ) type RestServer interface { + // start rest server Start(url common.URL) + // deploy a http api Deploy(restMethodConfig *rest_config.RestMethodConfig, routeFunc func(request RestServerRequest, response RestServerResponse)) + // unDeploy a http api UnDeploy(restMethodConfig *rest_config.RestMethodConfig) + // destroy rest server Destroy() } // RestServerRequest interface type RestServerRequest interface { + // Get the Ptr of http.Request RawRequest() *http.Request + // Get the path parameter by name PathParameter(name string) string + // Get the map of the path parameters PathParameters() map[string]string + // Get the query parameter by name QueryParameter(name string) string + // Get the map of query parameters QueryParameters(name string) []string + // Get the body parameter of name BodyParameter(name string) (string, error) + // Get the header parameter of name HeaderParameter(name string) string + // ReadEntity checks the Accept header and reads the content into the entityPointer. ReadEntity(entityPointer interface{}) error } // RestServerResponse interface type RestServerResponse interface { - Header() http.Header - Write([]byte) (int, error) - WriteHeader(statusCode int) + http.ResponseWriter + // WriteError writes the http status and the error string on the response. err can be nil. + // Return an error if writing was not succesful. WriteError(httpStatus int, err error) (writeErr error) + // WriteEntity marshals the value using the representation denoted by the Accept Header. WriteEntity(value interface{}) error } +// A route function will be invoked by http server func GetRouteFunc(invoker protocol.Invoker, methodConfig *rest_config.RestMethodConfig) func(req RestServerRequest, resp RestServerResponse) { return func(req RestServerRequest, resp RestServerResponse) { var ( diff --git a/protocol/rest/server/server_impl/go_restful_server.go b/protocol/rest/server/server_impl/go_restful_server.go index 9163d3aed7..00b644f208 100644 --- a/protocol/rest/server/server_impl/go_restful_server.go +++ b/protocol/rest/server/server_impl/go_restful_server.go @@ -41,20 +41,24 @@ import ( ) func init() { - extension.SetRestServer(constant.DEFAULT_REST_SERVER, GetNewGoRestfulServer) + extension.SetRestServer(constant.DEFAULT_REST_SERVER, NewGoRestfulServer) } var filterSlice []restful.FilterFunction +// A rest server implement by go-restful type GoRestfulServer struct { srv *http.Server container *restful.Container } -func NewGoRestfulServer() *GoRestfulServer { +// A constructor of GoRestfulServer +func NewGoRestfulServer() server.RestServer { return &GoRestfulServer{} } +// Start go-restful server +// It will add all go-restful filters func (grs *GoRestfulServer) Start(url common.URL) { grs.container = restful.NewContainer() for _, filter := range filterSlice { @@ -76,6 +80,8 @@ func (grs *GoRestfulServer) Start(url common.URL) { }() } +// Publish a http api in go-restful server +// The routeFunc should be invoked when the server receive a request func (grs *GoRestfulServer) Deploy(restMethodConfig *config.RestMethodConfig, routeFunc func(request server.RestServerRequest, response server.RestServerResponse)) { ws := new(restful.WebService) rf := func(req *restful.Request, resp *restful.Response) { @@ -89,6 +95,7 @@ func (grs *GoRestfulServer) Deploy(restMethodConfig *config.RestMethodConfig, ro } +// Delete a http api in go-restful server func (grs *GoRestfulServer) UnDeploy(restMethodConfig *config.RestMethodConfig) { ws := new(restful.WebService) ws.Path(restMethodConfig.Path) @@ -98,6 +105,7 @@ func (grs *GoRestfulServer) UnDeploy(restMethodConfig *config.RestMethodConfig) } } +// Destroy the go-restful server func (grs *GoRestfulServer) Destroy() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -107,11 +115,7 @@ func (grs *GoRestfulServer) Destroy() { logger.Infof("[Go Restful] Server exiting") } -func GetNewGoRestfulServer() server.RestServer { - return NewGoRestfulServer() -} - -// Let user addFilter +// Let user add the http server of go-restful // addFilter should before config.Load() func AddGoRestfulServerFilter(filterFuc restful.FilterFunction) { filterSlice = append(filterSlice, filterFuc) @@ -123,38 +127,47 @@ type GoRestfulRequestAdapter struct { request *restful.Request } +// A constructor of GoRestfulRequestAdapter func NewGoRestfulRequestAdapter(request *restful.Request) *GoRestfulRequestAdapter { return &GoRestfulRequestAdapter{request: request} } +// A adapter function of server.RestServerRequest's RawRequest func (grra *GoRestfulRequestAdapter) RawRequest() *http.Request { return grra.request.Request } +// A adapter function of server.RestServerRequest's PathParameter func (grra *GoRestfulRequestAdapter) PathParameter(name string) string { return grra.request.PathParameter(name) } +// A adapter function of server.RestServerRequest's QueryParameter func (grra *GoRestfulRequestAdapter) PathParameters() map[string]string { return grra.request.PathParameters() } +// A adapter function of server.RestServerRequest's QueryParameters func (grra *GoRestfulRequestAdapter) QueryParameter(name string) string { return grra.request.QueryParameter(name) } +// A adapter function of server.RestServerRequest's QueryParameters func (grra *GoRestfulRequestAdapter) QueryParameters(name string) []string { return grra.request.QueryParameters(name) } +// A adapter function of server.RestServerRequest's BodyParameter func (grra *GoRestfulRequestAdapter) BodyParameter(name string) (string, error) { return grra.request.BodyParameter(name) } +// A adapter function of server.RestServerRequest's HeaderParameter func (grra *GoRestfulRequestAdapter) HeaderParameter(name string) string { return grra.request.HeaderParameter(name) } +// A adapter func of server.RestServerRequest's ReadEntity func (grra *GoRestfulRequestAdapter) ReadEntity(entityPointer interface{}) error { return grra.request.ReadEntity(entityPointer) } From 79bd56cfefefb2431c36ab4ffecd81e37f3e13c7 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 1 Apr 2020 23:38:08 +0800 Subject: [PATCH 114/242] modify some comments and when parsing parameters occurred error, return error immediately --- .../rest/client/client_impl/resty_client.go | 4 +- protocol/rest/client/rest_client.go | 6 +- protocol/rest/server/rest_server.go | 114 ++++++++++-------- 3 files changed, 71 insertions(+), 53 deletions(-) diff --git a/protocol/rest/client/client_impl/resty_client.go b/protocol/rest/client/client_impl/resty_client.go index bfd74455bc..b60f50a5a7 100644 --- a/protocol/rest/client/client_impl/resty_client.go +++ b/protocol/rest/client/client_impl/resty_client.go @@ -40,11 +40,12 @@ func init() { extension.SetRestClient(constant.DEFAULT_REST_CLIENT, NewRestyClient) } -// A rest client implement by Resty +// RestyClient a rest client implement by Resty type RestyClient struct { client *resty.Client } +// NewRestyClient a constructor of RestyClient func NewRestyClient(restOption *client.RestOptions) client.RestClient { client := resty.New() client.SetTransport( @@ -66,6 +67,7 @@ func NewRestyClient(restOption *client.RestOptions) client.RestClient { } } +// Do send request by RestyClient func (rc *RestyClient) Do(restRequest *client.RestClientRequest, res interface{}) error { req := rc.client.R() req.Header = restRequest.Header diff --git a/protocol/rest/client/rest_client.go b/protocol/rest/client/rest_client.go index 47d17c6943..d63c5e0bd0 100644 --- a/protocol/rest/client/rest_client.go +++ b/protocol/rest/client/rest_client.go @@ -22,13 +22,13 @@ import ( "time" ) -// Some rest options +// RestOptions type RestOptions struct { RequestTimeout time.Duration ConnectTimeout time.Duration } -// Client request +// RestClientRequest type RestClientRequest struct { Header http.Header Location string @@ -39,7 +39,7 @@ type RestClientRequest struct { Body interface{} } -// User can implement this client interface to send request +// RestClient user can implement this client interface to send request type RestClient interface { Do(request *RestClientRequest, res interface{}) error } diff --git a/protocol/rest/server/rest_server.go b/protocol/rest/server/rest_server.go index 60f0dab942..60cac9afbb 100644 --- a/protocol/rest/server/rest_server.go +++ b/protocol/rest/server/rest_server.go @@ -19,6 +19,7 @@ package server import ( "context" + "errors" "net/http" "reflect" "strconv" @@ -37,6 +38,8 @@ import ( rest_config "github.com/apache/dubbo-go/protocol/rest/config" ) +const parseParameterErrorStr = "An error occurred while parsing parameters on the server" + type RestServer interface { // start rest server Start(url common.URL) @@ -50,19 +53,19 @@ type RestServer interface { // RestServerRequest interface type RestServerRequest interface { - // Get the Ptr of http.Request + // RawRequest get the Ptr of http.Request RawRequest() *http.Request - // Get the path parameter by name + // PathParameter get the path parameter by name PathParameter(name string) string - // Get the map of the path parameters + // PathParameters get the map of the path parameters PathParameters() map[string]string - // Get the query parameter by name + // QueryParameter get the query parameter by name QueryParameter(name string) string - // Get the map of query parameters + // QueryParameters get the map of query parameters QueryParameters(name string) []string - // Get the body parameter of name + // BodyParameter get the body parameter of name BodyParameter(name string) (string, error) - // Get the header parameter of name + // HeaderParameter get the header parameter of name HeaderParameter(name string) string // ReadEntity checks the Accept header and reads the content into the entityPointer. ReadEntity(entityPointer interface{}) error @@ -72,12 +75,13 @@ type RestServerRequest interface { type RestServerResponse interface { http.ResponseWriter // WriteError writes the http status and the error string on the response. err can be nil. - // Return an error if writing was not succesful. + // Return an error if writing was not successful. WriteError(httpStatus int, err error) (writeErr error) // WriteEntity marshals the value using the representation denoted by the Accept Header. WriteEntity(value interface{}) error } +// GetRouteFunc // A route function will be invoked by http server func GetRouteFunc(invoker protocol.Invoker, methodConfig *rest_config.RestMethodConfig) func(req RestServerRequest, resp RestServerResponse) { return func(req RestServerRequest, resp RestServerResponse) { @@ -92,9 +96,16 @@ func GetRouteFunc(invoker protocol.Invoker, methodConfig *rest_config.RestMethod replyType := method.ReplyType() if (len(argsTypes) == 1 || len(argsTypes) == 2 && replyType == nil) && argsTypes[0].String() == "[]interface {}" { - args = getArgsInterfaceFromRequest(req, methodConfig) + args, err = getArgsInterfaceFromRequest(req, methodConfig) } else { - args = getArgsFromRequest(req, argsTypes, methodConfig) + args, err = getArgsFromRequest(req, argsTypes, methodConfig) + } + if err != nil { + logger.Errorf("[Go Restful] parsing parameters error:%v", err) + err = resp.WriteError(http.StatusInternalServerError, errors.New(parseParameterErrorStr)) + if err != nil { + logger.Errorf("[Go Restful] WriteErrorString error:%v", err) + } } result := invoker.Invoke(context.Background(), invocation.NewRPCInvocation(methodConfig.MethodName, args, make(map[string]string))) if result.Error() != nil { @@ -111,9 +122,9 @@ func GetRouteFunc(invoker protocol.Invoker, methodConfig *rest_config.RestMethod } } -// when service function like GetUser(req []interface{}, rsp *User) error +// getArgsInterfaceFromRequest when service function like GetUser(req []interface{}, rsp *User) error // use this method to get arguments -func getArgsInterfaceFromRequest(req RestServerRequest, methodConfig *rest_config.RestMethodConfig) []interface{} { +func getArgsInterfaceFromRequest(req RestServerRequest, methodConfig *rest_config.RestMethodConfig) ([]interface{}, error) { argsMap := make(map[int]interface{}, 8) maxKey := 0 for k, v := range methodConfig.PathParamsMap { @@ -145,10 +156,10 @@ func getArgsInterfaceFromRequest(req RestServerRequest, methodConfig *rest_confi } m := make(map[string]interface{}) // TODO read as a slice - if err := req.ReadEntity(&m); err != nil { - logger.Warnf("[Go restful] Read body entity as map[string]interface{} error:%v", perrors.WithStack(err)) - } else { + if err := req.ReadEntity(&m); err == nil { argsMap[methodConfig.Body] = m + } else { + return nil, perrors.Errorf("[Go restful] Read body entity as map[string]interface{} error:%v", err) } } args := make([]interface{}, maxKey+1) @@ -157,30 +168,37 @@ func getArgsInterfaceFromRequest(req RestServerRequest, methodConfig *rest_confi args[k] = v } } - return args + return args, nil } -// get arguments from server.RestServerRequest -func getArgsFromRequest(req RestServerRequest, argsTypes []reflect.Type, methodConfig *rest_config.RestMethodConfig) []interface{} { +// getArgsFromRequest get arguments from server.RestServerRequest +func getArgsFromRequest(req RestServerRequest, argsTypes []reflect.Type, methodConfig *rest_config.RestMethodConfig) ([]interface{}, error) { argsLength := len(argsTypes) args := make([]interface{}, argsLength) for i, t := range argsTypes { args[i] = reflect.Zero(t).Interface() } - assembleArgsFromPathParams(methodConfig, argsLength, argsTypes, req, args) - assembleArgsFromQueryParams(methodConfig, argsLength, argsTypes, req, args) - assembleArgsFromBody(methodConfig, argsTypes, req, args) - assembleArgsFromHeaders(methodConfig, req, argsLength, argsTypes, args) - return args + if err := assembleArgsFromPathParams(methodConfig, argsLength, argsTypes, req, args); err != nil { + return nil, err + } + if err := assembleArgsFromQueryParams(methodConfig, argsLength, argsTypes, req, args); err != nil { + return nil, err + } + if err := assembleArgsFromBody(methodConfig, argsTypes, req, args); err != nil { + return nil, err + } + if err := assembleArgsFromHeaders(methodConfig, req, argsLength, argsTypes, args); err != nil { + return nil, err + } + return args, nil } -// assemble arguments from headers -func assembleArgsFromHeaders(methodConfig *rest_config.RestMethodConfig, req RestServerRequest, argsLength int, argsTypes []reflect.Type, args []interface{}) { +// assembleArgsFromHeaders assemble arguments from headers +func assembleArgsFromHeaders(methodConfig *rest_config.RestMethodConfig, req RestServerRequest, argsLength int, argsTypes []reflect.Type, args []interface{}) error { for k, v := range methodConfig.HeadersMap { param := req.HeaderParameter(v) if k < 0 || k >= argsLength { - logger.Errorf("[Go restful] Header param parse error, the args:%v doesn't exist", k) - continue + return perrors.Errorf("[Go restful] Header param parse error, the args:%v doesn't exist", k) } t := argsTypes[k] if t.Kind() == reflect.Ptr { @@ -189,13 +207,14 @@ func assembleArgsFromHeaders(methodConfig *rest_config.RestMethodConfig, req Res if t.Kind() == reflect.String { args[k] = param } else { - logger.Errorf("[Go restful] Header param parse error, the args:%v of type isn't string", k) + return perrors.Errorf("[Go restful] Header param parse error, the args:%v of type isn't string", k) } } + return nil } -// assemble arguments from body -func assembleArgsFromBody(methodConfig *rest_config.RestMethodConfig, argsTypes []reflect.Type, req RestServerRequest, args []interface{}) { +// assembleArgsFromBody assemble arguments from body +func assembleArgsFromBody(methodConfig *rest_config.RestMethodConfig, argsTypes []reflect.Type, req RestServerRequest, args []interface{}) error { if methodConfig.Body >= 0 && methodConfig.Body < len(argsTypes) { t := argsTypes[methodConfig.Body] kind := t.Kind() @@ -213,16 +232,17 @@ func assembleArgsFromBody(methodConfig *rest_config.RestMethodConfig, argsTypes ni = n.Interface() } } - if err := req.ReadEntity(&ni); err != nil { - logger.Errorf("[Go restful] Read body entity error:%v", err) - } else { + if err := req.ReadEntity(&ni); err == nil { args[methodConfig.Body] = ni + } else { + return perrors.Errorf("[Go restful] Read body entity error, error is %v", perrors.WithStack(err)) } } + return nil } -// assemble arguments from query params -func assembleArgsFromQueryParams(methodConfig *rest_config.RestMethodConfig, argsLength int, argsTypes []reflect.Type, req RestServerRequest, args []interface{}) { +// assembleArgsFromQueryParams assemble arguments from query params +func assembleArgsFromQueryParams(methodConfig *rest_config.RestMethodConfig, argsLength int, argsTypes []reflect.Type, req RestServerRequest, args []interface{}) error { var ( err error param interface{} @@ -230,8 +250,7 @@ func assembleArgsFromQueryParams(methodConfig *rest_config.RestMethodConfig, arg ) for k, v := range methodConfig.QueryParamsMap { if k < 0 || k >= argsLength { - logger.Errorf("[Go restful] Query param parse error, the args:%v doesn't exist", k) - continue + return perrors.Errorf("[Go restful] Query param parse error, the args:%v doesn't exist", k) } t := argsTypes[k] kind := t.Kind() @@ -252,19 +271,18 @@ func assembleArgsFromQueryParams(methodConfig *rest_config.RestMethodConfig, arg } else if kind == reflect.Int64 { param, err = strconv.ParseInt(req.QueryParameter(v), 10, 64) } else { - logger.Errorf("[Go restful] Query param parse error, the args:%v of type isn't int or string or slice", k) - continue + return perrors.Errorf("[Go restful] Query param parse error, the args:%v of type isn't int or string or slice", k) } if err != nil { - logger.Errorf("[Go restful] Query param parse error, error is %v", err) - continue + return perrors.Errorf("[Go restful] Query param parse error, error:%v", perrors.WithStack(err)) } args[k] = param } + return nil } -// assemble arguments from path params -func assembleArgsFromPathParams(methodConfig *rest_config.RestMethodConfig, argsLength int, argsTypes []reflect.Type, req RestServerRequest, args []interface{}) { +// assembleArgsFromPathParams assemble arguments from path params +func assembleArgsFromPathParams(methodConfig *rest_config.RestMethodConfig, argsLength int, argsTypes []reflect.Type, req RestServerRequest, args []interface{}) error { var ( err error param interface{} @@ -272,8 +290,7 @@ func assembleArgsFromPathParams(methodConfig *rest_config.RestMethodConfig, args ) for k, v := range methodConfig.PathParamsMap { if k < 0 || k >= argsLength { - logger.Errorf("[Go restful] Path param parse error, the args:%v doesn't exist", k) - continue + return perrors.Errorf("[Go restful] Path param parse error, the args:%v doesn't exist", k) } t := argsTypes[k] kind := t.Kind() @@ -292,13 +309,12 @@ func assembleArgsFromPathParams(methodConfig *rest_config.RestMethodConfig, args } else if kind == reflect.String { param = req.PathParameter(v) } else { - logger.Warnf("[Go restful] Path param parse error, the args:%v of type isn't int or string", k) - continue + return perrors.Errorf("[Go restful] Path param parse error, the args:%v of type isn't int or string", k) } if err != nil { - logger.Errorf("[Go restful] Path param parse error, error is %v", err) - continue + return perrors.Errorf("[Go restful] Path param parse error, error is %v", perrors.WithStack(err)) } args[k] = param } + return nil } From 6540c8907da701dd1d7d4cf84a4de3dd6208ebbd Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 1 Apr 2020 23:40:10 +0800 Subject: [PATCH 115/242] modify comments --- protocol/rest/server/rest_server.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/protocol/rest/server/rest_server.go b/protocol/rest/server/rest_server.go index 60cac9afbb..8bc32e87b0 100644 --- a/protocol/rest/server/rest_server.go +++ b/protocol/rest/server/rest_server.go @@ -40,14 +40,15 @@ import ( const parseParameterErrorStr = "An error occurred while parsing parameters on the server" +// RestServer user can implement this server interface type RestServer interface { - // start rest server + // Start rest server Start(url common.URL) - // deploy a http api + // Deploy a http api Deploy(restMethodConfig *rest_config.RestMethodConfig, routeFunc func(request RestServerRequest, response RestServerResponse)) - // unDeploy a http api + // UnDeploy a http api UnDeploy(restMethodConfig *rest_config.RestMethodConfig) - // destroy rest server + // Destroy rest server Destroy() } From 4736cdcdf005405c9c4d5c07979a6733c454053d Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 1 Apr 2020 23:43:15 +0800 Subject: [PATCH 116/242] remove new function --- protocol/rest/server/server_impl/go_restful_server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/rest/server/server_impl/go_restful_server.go b/protocol/rest/server/server_impl/go_restful_server.go index 00b644f208..667d58c16d 100644 --- a/protocol/rest/server/server_impl/go_restful_server.go +++ b/protocol/rest/server/server_impl/go_restful_server.go @@ -83,7 +83,7 @@ func (grs *GoRestfulServer) Start(url common.URL) { // Publish a http api in go-restful server // The routeFunc should be invoked when the server receive a request func (grs *GoRestfulServer) Deploy(restMethodConfig *config.RestMethodConfig, routeFunc func(request server.RestServerRequest, response server.RestServerResponse)) { - ws := new(restful.WebService) + ws := &restful.WebService{} rf := func(req *restful.Request, resp *restful.Response) { routeFunc(NewGoRestfulRequestAdapter(req), resp) } From f580ed0b8a080ceed6cff930b2bedbe826df8d0c Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 1 Apr 2020 23:45:21 +0800 Subject: [PATCH 117/242] modify some comments --- .../server/server_impl/go_restful_server.go | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/protocol/rest/server/server_impl/go_restful_server.go b/protocol/rest/server/server_impl/go_restful_server.go index 667d58c16d..7f5e3538c8 100644 --- a/protocol/rest/server/server_impl/go_restful_server.go +++ b/protocol/rest/server/server_impl/go_restful_server.go @@ -115,59 +115,59 @@ func (grs *GoRestfulServer) Destroy() { logger.Infof("[Go Restful] Server exiting") } -// Let user add the http server of go-restful +// AddGoRestfulServerFilter let user add the http server of go-restful // addFilter should before config.Load() func AddGoRestfulServerFilter(filterFuc restful.FilterFunction) { filterSlice = append(filterSlice, filterFuc) } -// Adapter about RestServerRequest +// GoRestfulRequestAdapter a adapter struct about RestServerRequest type GoRestfulRequestAdapter struct { server.RestServerRequest request *restful.Request } -// A constructor of GoRestfulRequestAdapter +// NewGoRestfulRequestAdapter a constructor of GoRestfulRequestAdapter func NewGoRestfulRequestAdapter(request *restful.Request) *GoRestfulRequestAdapter { return &GoRestfulRequestAdapter{request: request} } -// A adapter function of server.RestServerRequest's RawRequest +// RawRequest a adapter function of server.RestServerRequest's RawRequest func (grra *GoRestfulRequestAdapter) RawRequest() *http.Request { return grra.request.Request } -// A adapter function of server.RestServerRequest's PathParameter +// PathParameter a adapter function of server.RestServerRequest's PathParameter func (grra *GoRestfulRequestAdapter) PathParameter(name string) string { return grra.request.PathParameter(name) } -// A adapter function of server.RestServerRequest's QueryParameter +// PathParameters a adapter function of server.RestServerRequest's QueryParameter func (grra *GoRestfulRequestAdapter) PathParameters() map[string]string { return grra.request.PathParameters() } -// A adapter function of server.RestServerRequest's QueryParameters +// QueryParameter a adapter function of server.RestServerRequest's QueryParameters func (grra *GoRestfulRequestAdapter) QueryParameter(name string) string { return grra.request.QueryParameter(name) } -// A adapter function of server.RestServerRequest's QueryParameters +// QueryParameters a adapter function of server.RestServerRequest's QueryParameters func (grra *GoRestfulRequestAdapter) QueryParameters(name string) []string { return grra.request.QueryParameters(name) } -// A adapter function of server.RestServerRequest's BodyParameter +// BodyParameter a adapter function of server.RestServerRequest's BodyParameter func (grra *GoRestfulRequestAdapter) BodyParameter(name string) (string, error) { return grra.request.BodyParameter(name) } -// A adapter function of server.RestServerRequest's HeaderParameter +// HeaderParameter a adapter function of server.RestServerRequest's HeaderParameter func (grra *GoRestfulRequestAdapter) HeaderParameter(name string) string { return grra.request.HeaderParameter(name) } -// A adapter func of server.RestServerRequest's ReadEntity +// ReadEntity a adapter func of server.RestServerRequest's ReadEntity func (grra *GoRestfulRequestAdapter) ReadEntity(entityPointer interface{}) error { return grra.request.ReadEntity(entityPointer) } From d469144cecfd283bbaf85721f4412585ac8b7f77 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 2 Apr 2020 00:17:07 +0800 Subject: [PATCH 118/242] modify some comments and modify some logger's message --- protocol/rest/server/rest_server.go | 17 ++++++++++------- .../server/server_impl/go_restful_server.go | 4 ++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/protocol/rest/server/rest_server.go b/protocol/rest/server/rest_server.go index 8bc32e87b0..60a75bc279 100644 --- a/protocol/rest/server/rest_server.go +++ b/protocol/rest/server/rest_server.go @@ -95,6 +95,9 @@ func GetRouteFunc(invoker protocol.Invoker, methodConfig *rest_config.RestMethod method := svc.Method()[methodConfig.MethodName] argsTypes := method.ArgsType() replyType := method.ReplyType() + // two ways to prepare arguments + // if method like this 'func1(req []interface{}, rsp *User) error' + // we don't have arguments type if (len(argsTypes) == 1 || len(argsTypes) == 2 && replyType == nil) && argsTypes[0].String() == "[]interface {}" { args, err = getArgsInterfaceFromRequest(req, methodConfig) @@ -102,7 +105,7 @@ func GetRouteFunc(invoker protocol.Invoker, methodConfig *rest_config.RestMethod args, err = getArgsFromRequest(req, argsTypes, methodConfig) } if err != nil { - logger.Errorf("[Go Restful] parsing parameters error:%v", err) + logger.Errorf("[Go Restful] parsing http parameters error:%v", err) err = resp.WriteError(http.StatusInternalServerError, errors.New(parseParameterErrorStr)) if err != nil { logger.Errorf("[Go Restful] WriteErrorString error:%v", err) @@ -199,7 +202,7 @@ func assembleArgsFromHeaders(methodConfig *rest_config.RestMethodConfig, req Res for k, v := range methodConfig.HeadersMap { param := req.HeaderParameter(v) if k < 0 || k >= argsLength { - return perrors.Errorf("[Go restful] Header param parse error, the args:%v doesn't exist", k) + return perrors.Errorf("[Go restful] Header param parse error, the index %v args of method:%v doesn't exist", k, methodConfig.MethodName) } t := argsTypes[k] if t.Kind() == reflect.Ptr { @@ -208,7 +211,7 @@ func assembleArgsFromHeaders(methodConfig *rest_config.RestMethodConfig, req Res if t.Kind() == reflect.String { args[k] = param } else { - return perrors.Errorf("[Go restful] Header param parse error, the args:%v of type isn't string", k) + return perrors.Errorf("[Go restful] Header param parse error, the index %v args's type isn't string", k) } } return nil @@ -251,7 +254,7 @@ func assembleArgsFromQueryParams(methodConfig *rest_config.RestMethodConfig, arg ) for k, v := range methodConfig.QueryParamsMap { if k < 0 || k >= argsLength { - return perrors.Errorf("[Go restful] Query param parse error, the args:%v doesn't exist", k) + return perrors.Errorf("[Go restful] Query param parse error, the index %v args of method:%v doesn't exist", k, methodConfig.MethodName) } t := argsTypes[k] kind := t.Kind() @@ -272,7 +275,7 @@ func assembleArgsFromQueryParams(methodConfig *rest_config.RestMethodConfig, arg } else if kind == reflect.Int64 { param, err = strconv.ParseInt(req.QueryParameter(v), 10, 64) } else { - return perrors.Errorf("[Go restful] Query param parse error, the args:%v of type isn't int or string or slice", k) + return perrors.Errorf("[Go restful] Query param parse error, the index %v args's type isn't int or string or slice", k) } if err != nil { return perrors.Errorf("[Go restful] Query param parse error, error:%v", perrors.WithStack(err)) @@ -291,7 +294,7 @@ func assembleArgsFromPathParams(methodConfig *rest_config.RestMethodConfig, args ) for k, v := range methodConfig.PathParamsMap { if k < 0 || k >= argsLength { - return perrors.Errorf("[Go restful] Path param parse error, the args:%v doesn't exist", k) + return perrors.Errorf("[Go restful] Path param parse error, the index %v args of method:%v doesn't exist", k, methodConfig.MethodName) } t := argsTypes[k] kind := t.Kind() @@ -310,7 +313,7 @@ func assembleArgsFromPathParams(methodConfig *rest_config.RestMethodConfig, args } else if kind == reflect.String { param = req.PathParameter(v) } else { - return perrors.Errorf("[Go restful] Path param parse error, the args:%v of type isn't int or string", k) + return perrors.Errorf("[Go restful] Path param parse error, the index %v args's type isn't int or string", k) } if err != nil { return perrors.Errorf("[Go restful] Path param parse error, error is %v", perrors.WithStack(err)) diff --git a/protocol/rest/server/server_impl/go_restful_server.go b/protocol/rest/server/server_impl/go_restful_server.go index 7f5e3538c8..c7d971fcaa 100644 --- a/protocol/rest/server/server_impl/go_restful_server.go +++ b/protocol/rest/server/server_impl/go_restful_server.go @@ -46,13 +46,13 @@ func init() { var filterSlice []restful.FilterFunction -// A rest server implement by go-restful +// GoRestfulServer a rest server implement by go-restful type GoRestfulServer struct { srv *http.Server container *restful.Container } -// A constructor of GoRestfulServer +// NewGoRestfulServer a constructor of GoRestfulServer func NewGoRestfulServer() server.RestServer { return &GoRestfulServer{} } From ca2538a7e8c374678fda25b13f892eaee9112803 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 4 Apr 2020 18:33:01 +0800 Subject: [PATCH 119/242] optimized code --- protocol/rest/server/rest_server.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/protocol/rest/server/rest_server.go b/protocol/rest/server/rest_server.go index 60a75bc279..fbd6fb7ad9 100644 --- a/protocol/rest/server/rest_server.go +++ b/protocol/rest/server/rest_server.go @@ -160,11 +160,10 @@ func getArgsInterfaceFromRequest(req RestServerRequest, methodConfig *rest_confi } m := make(map[string]interface{}) // TODO read as a slice - if err := req.ReadEntity(&m); err == nil { - argsMap[methodConfig.Body] = m - } else { + if err := req.ReadEntity(&m); err != nil { return nil, perrors.Errorf("[Go restful] Read body entity as map[string]interface{} error:%v", err) } + argsMap[methodConfig.Body] = m } args := make([]interface{}, maxKey+1) for k, v := range argsMap { @@ -236,11 +235,10 @@ func assembleArgsFromBody(methodConfig *rest_config.RestMethodConfig, argsTypes ni = n.Interface() } } - if err := req.ReadEntity(&ni); err == nil { - args[methodConfig.Body] = ni - } else { + if err := req.ReadEntity(&ni); err != nil { return perrors.Errorf("[Go restful] Read body entity error, error is %v", perrors.WithStack(err)) } + args[methodConfig.Body] = ni } return nil } From a7174c3922ca3deb2a8c5515ea5468efd7aaac21 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 28 Jul 2020 21:49:59 +0800 Subject: [PATCH 120/242] let go_restful_server support same url and different methodType --- .../server/server_impl/go_restful_server.go | 24 ++++---- .../server_impl/go_restful_server_test.go | 57 +++++++++++++++++++ 2 files changed, 70 insertions(+), 11 deletions(-) create mode 100644 protocol/rest/server/server_impl/go_restful_server_test.go diff --git a/protocol/rest/server/server_impl/go_restful_server.go b/protocol/rest/server/server_impl/go_restful_server.go index c7d971fcaa..6fb9ee8daa 100644 --- a/protocol/rest/server/server_impl/go_restful_server.go +++ b/protocol/rest/server/server_impl/go_restful_server.go @@ -48,8 +48,8 @@ var filterSlice []restful.FilterFunction // GoRestfulServer a rest server implement by go-restful type GoRestfulServer struct { - srv *http.Server - container *restful.Container + srv *http.Server + ws *restful.WebService } // NewGoRestfulServer a constructor of GoRestfulServer @@ -60,13 +60,17 @@ func NewGoRestfulServer() server.RestServer { // Start go-restful server // It will add all go-restful filters func (grs *GoRestfulServer) Start(url common.URL) { - grs.container = restful.NewContainer() + container := restful.NewContainer() for _, filter := range filterSlice { - grs.container.Filter(filter) + container.Filter(filter) } grs.srv = &http.Server{ - Handler: grs.container, + Handler: container, } + grs.ws = &restful.WebService{} + grs.ws.Path("/") + grs.ws.SetDynamicRoutes(true) + container.Add(grs.ws) ln, err := net.Listen("tcp", url.Location) if err != nil { panic(perrors.New(fmt.Sprintf("Restful Server start error:%v", err))) @@ -83,23 +87,21 @@ func (grs *GoRestfulServer) Start(url common.URL) { // Publish a http api in go-restful server // The routeFunc should be invoked when the server receive a request func (grs *GoRestfulServer) Deploy(restMethodConfig *config.RestMethodConfig, routeFunc func(request server.RestServerRequest, response server.RestServerResponse)) { - ws := &restful.WebService{} + rf := func(req *restful.Request, resp *restful.Response) { routeFunc(NewGoRestfulRequestAdapter(req), resp) } - ws.Path(restMethodConfig.Path). + grs.ws.Route(grs.ws.Method(restMethodConfig.MethodType). Produces(strings.Split(restMethodConfig.Produces, ",")...). Consumes(strings.Split(restMethodConfig.Consumes, ",")...). - Route(ws.Method(restMethodConfig.MethodType).To(rf)) - grs.container.Add(ws) - + Path(restMethodConfig.Path).To(rf)) } // Delete a http api in go-restful server func (grs *GoRestfulServer) UnDeploy(restMethodConfig *config.RestMethodConfig) { ws := new(restful.WebService) ws.Path(restMethodConfig.Path) - err := grs.container.Remove(ws) + err := grs.ws.RemoveRoute(restMethodConfig.Path, restMethodConfig.MethodType) if err != nil { logger.Warnf("[Go restful] Remove web service error:%v", err) } diff --git a/protocol/rest/server/server_impl/go_restful_server_test.go b/protocol/rest/server/server_impl/go_restful_server_test.go new file mode 100644 index 0000000000..29a9ef89f7 --- /dev/null +++ b/protocol/rest/server/server_impl/go_restful_server_test.go @@ -0,0 +1,57 @@ +/* + * 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 server_impl + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/protocol/rest/config" + "github.com/apache/dubbo-go/protocol/rest/server" +) + +func TestGoRestfulServerDeploySameUrl(t *testing.T) { + grs := NewGoRestfulServer() + url, err := common.NewURL("http://127.0.0.1:43121") + assert.NoError(t, err) + grs.Start(url) + rmc := &config.RestMethodConfig{ + Produces: "*/*", + Consumes: "*/*", + MethodType: "POST", + Path: "/test", + } + f := func(request server.RestServerRequest, response server.RestServerResponse) {} + grs.Deploy(rmc, f) + rmc1 := &config.RestMethodConfig{ + Produces: "*/*", + Consumes: "*/*", + MethodType: "GET", + Path: "/test", + } + grs.Deploy(rmc1, f) + grs.UnDeploy(rmc) + grs.UnDeploy(rmc1) + grs.Destroy() +} From ea865671c530c7fd3a2a5bae4c75af177bba8684 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 30 Jul 2020 19:27:45 +0800 Subject: [PATCH 121/242] format code --- .../server/server_impl/go_restful_server_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/protocol/rest/server/server_impl/go_restful_server_test.go b/protocol/rest/server/server_impl/go_restful_server_test.go index 29a9ef89f7..b1e66063bf 100644 --- a/protocol/rest/server/server_impl/go_restful_server_test.go +++ b/protocol/rest/server/server_impl/go_restful_server_test.go @@ -37,18 +37,18 @@ func TestGoRestfulServerDeploySameUrl(t *testing.T) { assert.NoError(t, err) grs.Start(url) rmc := &config.RestMethodConfig{ - Produces: "*/*", - Consumes: "*/*", - MethodType: "POST", - Path: "/test", + Produces: "*/*", + Consumes: "*/*", + MethodType: "POST", + Path: "/test", } f := func(request server.RestServerRequest, response server.RestServerResponse) {} grs.Deploy(rmc, f) rmc1 := &config.RestMethodConfig{ - Produces: "*/*", - Consumes: "*/*", - MethodType: "GET", - Path: "/test", + Produces: "*/*", + Consumes: "*/*", + MethodType: "GET", + Path: "/test", } grs.Deploy(rmc1, f) grs.UnDeploy(rmc) From af3176f96beecefef50fcf00afc6bbef84aee257 Mon Sep 17 00:00:00 2001 From: lzp0412 <641785844@qq.com> Date: Sun, 9 Aug 2020 19:57:21 +0800 Subject: [PATCH 122/242] fix nacos unit test failed --- registry/nacos/service_discovery_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/nacos/service_discovery_test.go b/registry/nacos/service_discovery_test.go index 24412999c5..5b80d9fb6f 100644 --- a/registry/nacos/service_discovery_test.go +++ b/registry/nacos/service_discovery_test.go @@ -158,7 +158,7 @@ func TestNacosServiceDiscovery_CRUD(t *testing.T) { assert.Nil(t, err) // test AddListener - err = serviceDiscovery.AddListener(®istry.ServiceInstancesChangedListener{}) + err = serviceDiscovery.AddListener(®istry.ServiceInstancesChangedListener{ServiceName: serviceName}) assert.Nil(t, err) } From 9652ae6c0bc17955ed250e79af62d757f5c4b60c Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Sun, 9 Aug 2020 22:37:38 +0800 Subject: [PATCH 123/242] update getty pkg --- go.mod | 8 ++++---- go.sum | 8 ++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 038d238795..8895ffbdc2 100644 --- a/go.mod +++ b/go.mod @@ -8,9 +8,9 @@ require ( github.com/coreos/etcd v3.3.13+incompatible github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect github.com/creasty/defaults v1.3.0 - github.com/dubbogo/getty v1.3.3 + github.com/dubbogo/getty v1.3.8 github.com/dubbogo/go-zookeeper v1.0.0 - github.com/dubbogo/gost v1.5.2 + github.com/dubbogo/gost v1.9.0 github.com/emicklei/go-restful/v3 v3.0.0 github.com/go-resty/resty/v2 v2.1.0 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect @@ -33,8 +33,8 @@ require ( github.com/zouyx/agollo/v3 v3.4.4 go.etcd.io/bbolt v1.3.3 // indirect go.etcd.io/etcd v3.3.13+incompatible - go.uber.org/atomic v1.4.0 - go.uber.org/zap v1.10.0 + go.uber.org/atomic v1.6.0 + go.uber.org/zap v1.15.0 google.golang.org/grpc v1.22.1 gopkg.in/yaml.v2 v2.2.4 k8s.io/api v0.0.0-20190325185214-7544f9db76f6 diff --git a/go.sum b/go.sum index ce9d4e1f34..03b5572fbc 100644 --- a/go.sum +++ b/go.sum @@ -128,11 +128,13 @@ github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dubbogo/getty v1.3.3 h1:8m4zZBqFHO+NmhH7rMPlFuuYRVjcPD7cUhumevqMZZs= github.com/dubbogo/getty v1.3.3/go.mod h1:U92BDyJ6sW9Jpohr2Vlz8w2uUbIbNZ3d+6rJvFTSPp0= +github.com/dubbogo/getty v1.3.8/go.mod h1:JIN5lNZznGnBuoYjlEe/yjNmq7eSjKaawHdnBAdtiEo= github.com/dubbogo/go-zookeeper v1.0.0 h1:RsYdlGwhDW+iKXM3eIIcvt34P2swLdmQfuIJxsHlGoM= github.com/dubbogo/go-zookeeper v1.0.0/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c= github.com/dubbogo/gost v1.5.1/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= github.com/dubbogo/gost v1.5.2 h1:ri/03971hdpnn3QeCU+4UZgnRNGDXLDGDucR/iozZm8= github.com/dubbogo/gost v1.5.2/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= +github.com/dubbogo/gost v1.9.0/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74 h1:2MIhn2R6oXQbgW5yHfS+d6YqyMfXiu2L55rFZC4UD/M= github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74/go.mod h1:UqXY1lYT/ERa4OEAywUqdok1T4RCRdArkhic1Opuavo= github.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0 h1:ZoRgc53qJCfSLimXqJDrmBhnt5GChDsExMCK7t48o0Y= @@ -566,10 +568,14 @@ go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -673,6 +679,8 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.0.0-20180829000535-087779f1d2c9 h1:z1TeLUmxf9ws9KLICfmX+KGXTs+rjm+aGWzfsv7MZ9w= From 457dc0c610ae27cd0c384a0c6df8554f0ad57d91 Mon Sep 17 00:00:00 2001 From: aliiohs Date: Sun, 2 Aug 2020 19:53:40 +0800 Subject: [PATCH 124/242] add tls support --- common/constant/key.go | 1 + config/protocol_config.go | 7 ++++--- config/service_config.go | 1 + config/ssl_config.go | 43 +++++++++++++++++++++++++++++++++++++++ go.mod | 4 +++- protocol/dubbo/client.go | 1 + protocol/dubbo/pool.go | 31 ++++++++++++++++++++-------- protocol/dubbo/server.go | 16 ++++++++++++--- 8 files changed, 88 insertions(+), 16 deletions(-) create mode 100644 config/ssl_config.go diff --git a/common/constant/key.go b/common/constant/key.go index 0aa6912e4b..97262cead2 100644 --- a/common/constant/key.go +++ b/common/constant/key.go @@ -46,6 +46,7 @@ const ( DUBBO_KEY = "dubbo" RELEASE_KEY = "release" ANYHOST_KEY = "anyhost" + SSL_ENABLED_KEY = "ssl-enabled" ) const ( diff --git a/config/protocol_config.go b/config/protocol_config.go index cee5b7aa75..3d9e1185d6 100644 --- a/config/protocol_config.go +++ b/config/protocol_config.go @@ -27,9 +27,10 @@ import ( // ProtocolConfig is protocol configuration type ProtocolConfig struct { - Name string `required:"true" yaml:"name" json:"name,omitempty" property:"name"` - Ip string `required:"true" yaml:"ip" json:"ip,omitempty" property:"ip"` - Port string `required:"true" yaml:"port" json:"port,omitempty" property:"port"` + Name string `required:"true" yaml:"name" json:"name,omitempty" property:"name"` + Ip string `required:"true" yaml:"ip" json:"ip,omitempty" property:"ip"` + Port string `required:"true" yaml:"port" json:"port,omitempty" property:"port"` + SslEnabled bool `required:"false" yaml:"sslEnabled" json:"sslEnabled,omitempty" property:"sslEnabled"` } // nolint diff --git a/config/service_config.go b/config/service_config.go index 4351eea7c9..728915381a 100644 --- a/config/service_config.go +++ b/config/service_config.go @@ -188,6 +188,7 @@ func (c *ServiceConfig) Export() error { common.WithPort(port), common.WithParams(urlMap), common.WithParamsValue(constant.BEAN_NAME_KEY, c.id), + common.WithParamsValue(constant.SSL_ENABLED_KEY, strconv.FormatBool(proto.SslEnabled)), common.WithMethods(strings.Split(methods, ",")), common.WithToken(c.Token), ) diff --git a/config/ssl_config.go b/config/ssl_config.go new file mode 100644 index 0000000000..019f83d3cb --- /dev/null +++ b/config/ssl_config.go @@ -0,0 +1,43 @@ +/* + * 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 config + +import ( + "github.com/dubbogo/getty" +) + +var ( + serverTlsConfigBuilder getty.TlsConfigBuilder + clientTlsConfigBuilder getty.TlsConfigBuilder +) + +func GetServerTlsConfigBuilder() getty.TlsConfigBuilder { + return serverTlsConfigBuilder +} + +func GetClientTlsConfigBuilder() getty.TlsConfigBuilder { + return clientTlsConfigBuilder +} + +func SetServerTlsConfigBuilder(configBuilder getty.TlsConfigBuilder) { + serverTlsConfigBuilder = configBuilder +} + +func SetClientTlsConfigBuilder(configBuilder getty.TlsConfigBuilder) { + clientTlsConfigBuilder = configBuilder +} diff --git a/go.mod b/go.mod index c196273782..af6882adff 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,7 @@ module github.com/apache/dubbo-go +go 1.14 + require ( cloud.google.com/go v0.39.0 // indirect github.com/Microsoft/go-winio v0.4.13 // indirect @@ -66,4 +68,4 @@ require ( k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a // indirect ) -go 1.13 +replace github.com/dubbogo/getty v1.2.2 => github.com/aliiohs/getty v1.1.1-0.20200802094147-169328c4ff38 diff --git a/protocol/dubbo/client.go b/protocol/dubbo/client.go index 6d1b771bf4..d73d481ac9 100644 --- a/protocol/dubbo/client.go +++ b/protocol/dubbo/client.go @@ -229,6 +229,7 @@ func (c *Client) call(ct CallType, request *Request, response *Response, callbac p.Service.Version = request.svcUrl.GetParam(constant.VERSION_KEY, "") p.Service.Group = request.svcUrl.GetParam(constant.GROUP_KEY, "") p.Service.Method = request.method + c.pool.sslEnabled = request.svcUrl.GetParamBool(constant.SSL_ENABLED_KEY, false) p.Service.Timeout = c.opts.RequestTimeout var timeout = request.svcUrl.GetParam(strings.Join([]string{constant.METHOD_KEYS, request.method + constant.RETRIES_KEY}, "."), "") diff --git a/protocol/dubbo/pool.go b/protocol/dubbo/pool.go index c9f5e34fad..cb1960a756 100644 --- a/protocol/dubbo/pool.go +++ b/protocol/dubbo/pool.go @@ -33,6 +33,7 @@ import ( import ( "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config" ) type gettyRPCClient struct { @@ -53,15 +54,26 @@ var ( ) func newGettyRPCClientConn(pool *gettyRPCClientPool, protocol, addr string) (*gettyRPCClient, error) { - c := &gettyRPCClient{ - protocol: protocol, - addr: addr, - pool: pool, - gettyClient: getty.NewTCPClient( + var gettyClient getty.Client + if pool.sslEnabled { + gettyClient = getty.NewTCPClient( + getty.WithServerAddress(addr), + getty.WithConnectionNumber((int)(pool.rpcClient.conf.ConnectionNum)), + getty.WithReconnectInterval(pool.rpcClient.conf.ReconnectInterval), + getty.WithClientTlsConfigBuilder(config.GetClientTlsConfigBuilder()), + ) + } else { + gettyClient = getty.NewTCPClient( getty.WithServerAddress(addr), getty.WithConnectionNumber((int)(pool.rpcClient.conf.ConnectionNum)), getty.WithReconnectInterval(pool.rpcClient.conf.ReconnectInterval), - ), + ) + } + c := &gettyRPCClient{ + protocol: protocol, + addr: addr, + pool: pool, + gettyClient: gettyClient, } go c.gettyClient.RunEventLoop(c.newSession) idx := 1 @@ -288,9 +300,10 @@ func (c *gettyRPCClient) close() error { } type gettyRPCClientPool struct { - rpcClient *Client - size int // size of []*gettyRPCClient - ttl int64 // ttl of every gettyRPCClient, it is checked when getConn + rpcClient *Client + size int // size of []*gettyRPCClient + ttl int64 // ttl of every gettyRPCClient, it is checked when getConn + sslEnabled bool sync.Mutex conns []*gettyRPCClient diff --git a/protocol/dubbo/server.go b/protocol/dubbo/server.go index 8de353a0b3..bef7b3f680 100644 --- a/protocol/dubbo/server.go +++ b/protocol/dubbo/server.go @@ -30,6 +30,7 @@ import ( import ( "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/config" ) @@ -163,9 +164,18 @@ func (s *Server) Start(url common.URL) { ) addr = url.Location - tcpServer = getty.NewTCPServer( - getty.WithLocalAddress(addr), - ) + if url.GetParamBool(constant.SSL_ENABLED_KEY, false) { + tcpServer = getty.NewTCPServer( + getty.WithLocalAddress(addr), + getty.WithServerSslEnabled(url.GetParamBool(constant.SSL_ENABLED_KEY, false)), + getty.WithServerTlsConfigBuilder(config.GetServerTlsConfigBuilder()), + ) + + } else { + tcpServer = getty.NewTCPServer( + getty.WithLocalAddress(addr), + ) + } tcpServer.RunEventLoop(s.newSession) logger.Debugf("s bind addr{%s} ok!", addr) s.tcpServer = tcpServer From 01c14c33dcd6618d6f8780ff95560406837a7140 Mon Sep 17 00:00:00 2001 From: aliiohs Date: Sun, 9 Aug 2020 15:48:01 +0800 Subject: [PATCH 125/242] change module version --- go.mod | 6 ++---- go.sum | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index af6882adff..cb13fd2a20 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect github.com/creasty/defaults v1.3.0 github.com/docker/go-connections v0.4.0 // indirect - github.com/dubbogo/getty v1.3.7 + github.com/dubbogo/getty v1.3.8 github.com/dubbogo/go-zookeeper v1.0.1 github.com/dubbogo/gost v1.9.0 github.com/elazarl/go-bindata-assetfs v1.0.0 // indirect @@ -66,6 +66,4 @@ require ( k8s.io/apimachinery v0.16.9 k8s.io/client-go v0.16.9 k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a // indirect -) - -replace github.com/dubbogo/getty v1.2.2 => github.com/aliiohs/getty v1.1.1-0.20200802094147-169328c4ff38 +) \ No newline at end of file diff --git a/go.sum b/go.sum index aa6ecc86e2..399d26b2d5 100644 --- a/go.sum +++ b/go.sum @@ -145,6 +145,7 @@ github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5Xh github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/dubbogo/getty v1.3.7 h1:xlkYD2/AH34iGteuLMsGjLl2PwBVrbIhHjf3tlUsv1M= github.com/dubbogo/getty v1.3.7/go.mod h1:XWO4+wAaMqgnBN9Ykv2YxxOAkGxymg6LGO9RK+EiCDY= +github.com/dubbogo/getty v1.3.8/go.mod h1:JIN5lNZznGnBuoYjlEe/yjNmq7eSjKaawHdnBAdtiEo= github.com/dubbogo/go-zookeeper v1.0.1 h1:irLzvOsDOTNsN8Sv9tvYYxVu6DCQfLtziZQtUHmZgz8= github.com/dubbogo/go-zookeeper v1.0.1/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c= github.com/dubbogo/gost v1.9.0 h1:UT+dWwvLyJiDotxJERO75jB3Yxgsdy10KztR5ycxRAk= From 3b2dc85a8826374a945bc5de6ffabd173b66680a Mon Sep 17 00:00:00 2001 From: aliiohs Date: Sun, 9 Aug 2020 15:49:48 +0800 Subject: [PATCH 126/242] upgrade getty version --- go.mod | 5 +--- go.sum | 89 +--------------------------------------------------------- 2 files changed, 2 insertions(+), 92 deletions(-) diff --git a/go.mod b/go.mod index cb13fd2a20..f3cc4183b3 100644 --- a/go.mod +++ b/go.mod @@ -38,8 +38,6 @@ require ( github.com/hashicorp/vault/api v1.0.5-0.20191108163347-bdd38fca2cff // indirect github.com/hashicorp/vault/sdk v0.1.14-0.20191112033314-390e96e22eb2 github.com/jinzhu/copier v0.0.0-20190625015134-976e0346caa8 - github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect - github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b // indirect github.com/magiconair/properties v1.8.1 github.com/mitchellh/hashstructure v1.0.0 // indirect github.com/mitchellh/mapstructure v1.2.3 @@ -60,10 +58,9 @@ require ( go.uber.org/zap v1.15.0 google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64 // indirect google.golang.org/grpc v1.23.0 - gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce // indirect gopkg.in/yaml.v2 v2.2.8 k8s.io/api v0.16.9 k8s.io/apimachinery v0.16.9 k8s.io/client-go v0.16.9 k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a // indirect -) \ No newline at end of file +) diff --git a/go.sum b/go.sum index 399d26b2d5..fc7015759d 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ -cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.39.0 h1:UgQP9na6OTfp4dsAiz/eFpFA1C6tPdH5wiRdi19tuMw= cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts= @@ -37,16 +35,13 @@ github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VY github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/DataDog/datadog-go v2.2.0+incompatible h1:V5BKkxACZLjzHjSgBbr2gvLA2Ae49yhc6CSY7MLy5k4= github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/Microsoft/go-winio v0.4.3 h1:M3NHMuPgMSUPdE5epwNUHlRPSVzHs8HpRTrVXhR0myo= github.com/Microsoft/go-winio v0.4.3/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.13 h1:Hmi80lzZuI/CaYmlJp/b+FjZdRZhKu9c2mDVqKlLWVs= github.com/Microsoft/go-winio v0.4.13/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/NYTimes/gziphandler v1.0.1 h1:iLrQrdwjDd52kHDA5op2UBJFjmOb9g+7scBan4RN8F0= github.com/NYTimes/gziphandler v1.0.1/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= @@ -64,8 +59,6 @@ github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA= -github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA= -github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk= github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk= github.com/apache/dubbo-go-hessian2 v1.6.1 h1:mFKeCZzaCkk4mMOyP+LQ85GHbRyqKT7858KS21JQYA4= github.com/apache/dubbo-go-hessian2 v1.6.1/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= @@ -73,11 +66,9 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM= github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= github.com/armon/go-metrics v0.3.0 h1:B7AQgHi8QSEi4uHu7Sbsga+IJDU+CENgjxoo81vDUqU= github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26FmByPp2eC2qla6E1Tw+scpcg/to= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= @@ -138,20 +129,17 @@ github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TR github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/docker/go-connections v0.3.0 h1:3lOnM9cSzgGwx8VfK/NGOW5fLQ0GjIlCkaktF+n1M6o= github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/dubbogo/getty v1.3.7 h1:xlkYD2/AH34iGteuLMsGjLl2PwBVrbIhHjf3tlUsv1M= -github.com/dubbogo/getty v1.3.7/go.mod h1:XWO4+wAaMqgnBN9Ykv2YxxOAkGxymg6LGO9RK+EiCDY= +github.com/dubbogo/getty v1.3.8 h1:D9VQLlO4df0H+k8lEW+Re1hGymxzQjvvEKOzGNI35sI= github.com/dubbogo/getty v1.3.8/go.mod h1:JIN5lNZznGnBuoYjlEe/yjNmq7eSjKaawHdnBAdtiEo= github.com/dubbogo/go-zookeeper v1.0.1 h1:irLzvOsDOTNsN8Sv9tvYYxVu6DCQfLtziZQtUHmZgz8= github.com/dubbogo/go-zookeeper v1.0.1/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c= github.com/dubbogo/gost v1.9.0 h1:UT+dWwvLyJiDotxJERO75jB3Yxgsdy10KztR5ycxRAk= github.com/dubbogo/gost v1.9.0/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0 h1:ZoRgc53qJCfSLimXqJDrmBhnt5GChDsExMCK7t48o0Y= github.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk= github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= @@ -168,31 +156,25 @@ github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6 github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 h1:Ghm4eQYC0nEPnSJdVkTrXpu9KtoVCSo1hg7mtI7G9KU= github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/frankban/quicktest v1.4.1 h1:Wv2VwvNn73pAdFIVUQRXYDFp31lXKbqblIXo/Q5GPSg= github.com/frankban/quicktest v1.4.1/go.mod h1:36zfPVQyHxymz4cH7wlDmVwDrJuljRB60qkgn7rorfQ= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew= github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= -github.com/go-asn1-ber/asn1-ber v1.3.1 h1:gvPdv/Hr++TRFCl0UbPFHC54P9N9jgsRPnmnr419Uck= github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-co-op/gocron v0.1.1 h1:OfDmkqkCguFtFMsm6Eaayci3DADLa8pXvdmOlPU/JcU= github.com/go-co-op/gocron v0.1.1/go.mod h1:Y9PWlYqDChf2Nbgg7kfS+ZsXHDTZbMZYPEQ0MILqH+M= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-ldap/ldap v3.0.2+incompatible h1:kD5HQcAzlQ7yrhfn+h+MSABeAy/jAJhvIJ/QDllP44g= github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= -github.com/go-ldap/ldap/v3 v3.1.3 h1:RIgdpHXJpsUqUK5WXwKyVsESrGFqo5BRWPk3RR4/ogQ= github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -213,7 +195,6 @@ github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3a github.com/gogo/googleapis v1.1.0 h1:kFkMAZBNAn4j7K0GiZr8cRYzejq68VbheufiV3YuyFI= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -239,11 +220,9 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0= github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -255,9 +234,7 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2 h1:AtvtonGEH/fZK0XPNNBdB6swgy7Iudfx88wzyIpwqJ8= github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2/go.mod h1:DavVbd41y+b7ukKDmlnPR4nGYmkWXR6vHUkjQNiHPBs= -github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4 h1:hU4mGcQI4DaAYW+IbTun+2qEZVFxK0ySjQLTbS0VQKc= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= @@ -269,9 +246,7 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 h1:z53tR0945TRRQO/fLEVPI6SMv7ZflF0TEaTAoU7tOzg= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -292,7 +267,6 @@ github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/U github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-bexpr v0.1.2 h1:ijMXI4qERbzxbCnkxmfUtwMyjrrk3y+Vt0MxojNCbBs= github.com/hashicorp/go-bexpr v0.1.2/go.mod h1:ANbpTX1oAql27TZkKVeW8p1w8NTdnyzPe/0qqPCKohU= -github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de h1:XDCSythtg8aWSRSO29uwhgh7b127fWr+m5SemqjSUL8= github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de/go.mod h1:xIwEieBHERyEvaeKF/TcHh1Hu+lxPM+n2vT1+g9I4m4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= @@ -303,12 +277,10 @@ github.com/hashicorp/go-discover v0.0.0-20200501174627-ad1e96bde088 h1:jBvElOiln github.com/hashicorp/go-discover v0.0.0-20200501174627-ad1e96bde088/go.mod h1:vZu6Opqf49xX5lsFAu7iFNewkcVF1sn/wyapZh5ytlg= github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v0.9.1 h1:9PZfAcVEvez4yhLH2TBU64/h/z4xlFI80cWXRrxuKuM= github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.12.0 h1:d4QkX8FRTYaKaCZBoXYY8zJX2BXjWxurN/GA2tkrmZM= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.1.0 h1:vN9wG1D6KG6YHRTWr8512cxGOVgTMEfgEdSj/hr8MPc= github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -317,33 +289,26 @@ github.com/hashicorp/go-memdb v1.0.3/go.mod h1:LWQ8R70vPrS4OEY9k28D2z8/Zzyu34NVz github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= -github.com/hashicorp/go-plugin v1.0.1 h1:4OtAfUGbnKC6yS48p0CtMX2oFYtzFZVv6rok3cRWgnE= github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= -github.com/hashicorp/go-raftchunking v0.6.1 h1:moEnaG3gcwsWNyIBJoD5PCByE+Ewkqxh6N05CT+MbwA= github.com/hashicorp/go-raftchunking v0.6.1/go.mod h1:cGlg3JtDy7qy6c/3Bu660Mic1JF+7lWqIwCFSb08fX0= github.com/hashicorp/go-raftchunking v0.6.3-0.20191002164813-7e9e8525653a h1:FmnBDwGwlTgugDGbVxwV8UavqSMACbGrUpfc98yFLR4= github.com/hashicorp/go-raftchunking v0.6.3-0.20191002164813-7e9e8525653a/go.mod h1:xbXnmKqX9/+RhPkJ4zrEx4738HacP72aaUPlT2RZ4sU= -github.com/hashicorp/go-retryablehttp v0.5.3 h1:QlWt0KvWT0lq8MFppF9tsJGF+ynG7ztc2KIPhzRGk7s= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-retryablehttp v0.5.4 h1:1BZvpawXoJCWX6pNtow9+rpEj+3itIlutiqnntI6jOE= github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.6.2 h1:bHM2aVXwBtBJWxHtkSrWuI4umABCUczs52eiUS9nSiw= github.com/hashicorp/go-retryablehttp v0.6.2/go.mod h1:gEx6HMUGxYYhJScX7W1Il64m6cc2C1mDaW3NQ9sY1FY= github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2-0.20191001231223-f32f5fe8d6a8/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= @@ -351,9 +316,7 @@ github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= @@ -361,7 +324,6 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hil v0.0.0-20160711231837-1e86c6b523c5 h1:uk280DXEbQiCOZgCOI3elFSeNxf8YIZiNsbr2pQLYD0= github.com/hashicorp/hil v0.0.0-20160711231837-1e86c6b523c5/go.mod h1:KHvg/R2/dPtaePb16oW4qIyzkMxXOL38xjRN64adsts= -github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.1 h1:XFSOubp8KWB+Jd2PDyaX5xUd5bhSP/+pTDZVDMzZJM8= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= @@ -370,7 +332,6 @@ github.com/hashicorp/memberlist v0.2.2 h1:5+RffWKwqJ71YPu9mWsF7ZOscZmwfasdA8kbdC github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/net-rpc-msgpackrpc v0.0.0-20151116020338-a14192a58a69 h1:lc3c72qGlIMDqQpQH82Y4vaglRMMFdJbziYWriR4UcE= github.com/hashicorp/net-rpc-msgpackrpc v0.0.0-20151116020338-a14192a58a69/go.mod h1:/z+jUGRBlwVpUZfjute9jWaF6/HuhjuFQuL1YXzVD1Q= -github.com/hashicorp/raft v1.1.1 h1:HJr7UE1x/JrJSc9Oy6aDBHtNHUUBHjcQjTgvUVihoZs= github.com/hashicorp/raft v1.1.1/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= github.com/hashicorp/raft v1.1.2-0.20191002163536-9c6bd3e3eb17/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= github.com/hashicorp/raft v1.1.2 h1:oxEL5DDeurYxLd3UbcY/hccgSPhLLpiBZ1YxtWEq59c= @@ -380,7 +341,6 @@ github.com/hashicorp/raft-boltdb v0.0.0-20171010151810-6e5ba93211ea/go.mod h1:pN github.com/hashicorp/serf v0.9.0/go.mod h1:YL0HO+FifKOW2u1ke99DGVu1zhcpZzNwrLIqBC7vbYU= github.com/hashicorp/serf v0.9.2 h1:yJoyfZXo4Pk2p/M/viW+YLibBFiIbKoP79gu7kDAFP0= github.com/hashicorp/serf v0.9.2/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= -github.com/hashicorp/vault/api v1.0.4 h1:j08Or/wryXT4AcHj1oCbMd7IijXcKzYUGw59LGu9onU= github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= github.com/hashicorp/vault/api v1.0.5-0.20191108163347-bdd38fca2cff h1:cl94LQIrs/mNbh3ny1R8lM1gtYcUBa7HnGtOCi35SlQ= github.com/hashicorp/vault/api v1.0.5-0.20191108163347-bdd38fca2cff/go.mod h1:Uf8LaHyrYsgVgHzO2tMZKhqRGlL3UJ6XaSwW2EA1Iqo= @@ -400,7 +360,6 @@ github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= -github.com/jackc/pgx v3.3.0+incompatible h1:Wa90/+qsITBAPkAZjiByeIGHFcj3Ztu+VzrrIpHjL90= github.com/jackc/pgx v3.3.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da h1:FjHUJJ7oBW4G/9j1KzlHaXL09LyMVM9rupS39lncbXk= github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= @@ -412,7 +371,6 @@ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5i github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62 h1:JHCT6xuyPUrbbgAPE/3dqlvUKzRHMNuTBKKUb6OeR/k= github.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA= github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f h1:ENpDacvnr8faw5ugQmEF1QYk+f/Y9lXFvuYmRxykago= github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f/go.mod h1:KDSfL7qe5ZfQqvlDMkVjCztbmcpp/c8M77vhQP8ZPvk= @@ -424,22 +382,14 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/juju/errors v0.0.0-20190930114154-d42613fe1ab9 h1:hJix6idebFclqlfZCHE7EUX7uqLCyb70nHNHH1XKGBg= -github.com/juju/errors v0.0.0-20190930114154-d42613fe1ab9/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= -github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI= -github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= -github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b h1:Rrp0ByJXEjhREMPGTt3aWYjoIsUGCbt21ekbeJcTWv0= -github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -452,7 +402,6 @@ github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f h1:sgU github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f/go.mod h1:UGmTpUd3rjbtfIpwAPrcfmGf/Z1HS95TATB+m57TPB8= github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 h1:Bvq8AziQ5jFF4BHGAEDSqwPW1NJS3XshxbRCxtjFAZc= github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042/go.mod h1:TPpsiPUEh0zFL1Snz4crhMlBe60PYxRHr5oFF3rRYg0= -github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/linode/linodego v0.7.1 h1:4WZmMpSA2NRwlPZcc0+4Gyn7rr99Evk9bnr0B3gXRKE= github.com/linode/linodego v0.7.1/go.mod h1:ga11n3ivecUrPCHN0rANxKmfWBJVkOXfLMZinAbj2sY= @@ -460,12 +409,10 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= @@ -475,11 +422,9 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0 h1:tEElEatulEHDeedTxwckzyYMA5c86fbmNIUL1hBIiTg= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= @@ -488,17 +433,14 @@ github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFW github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.0 h1:/x0XQ6h+3U3nAyk1yx+bHPURrKa9sVVvYbuqZ7pIAtI= github.com/mitchellh/go-testing-interface v1.14.0/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452 h1:hOY53G+kBFhbYFpRVxHl5eS7laP6B1+Cq+Z9Dry1iMU= github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= github.com/mitchellh/hashstructure v1.0.0 h1:ZkRJX1CyOoTkar7p/mLS5TZU4nJ1Rn/F8u9dGS02Q3Y= github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.2.3 h1:f/MjBEBDLttYCGfRaKBbKSRVF5aV2O6fnBpzknuE3jU= github.com/mitchellh/mapstructure v1.2.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -521,12 +463,10 @@ github.com/nacos-group/nacos-sdk-go v1.0.0 h1:CufUF7DZca2ZzIrJtMMCDih1sA58BWCglA github.com/nacos-group/nacos-sdk-go v1.0.0/go.mod h1:hlAPn3UdzlxIlSILAyOXKxjFSvDJ9oLzTJ9hLAK1KzA= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 h1:BQ1HW7hr4IVovMwWg0E0PYcyW8CzqDcVmaew9cujU4s= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk= -github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20180130162743-b8a9be070da4/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= @@ -545,21 +485,17 @@ github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144T github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.2.6+incompatible h1:6aCX4/YZ9v8q69hTyiR7dNLnTA3fgtKHVVW5BCd5Znw= github.com/pierrec/lz4 v2.2.6+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= @@ -587,7 +523,6 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rboyer/safeio v0.2.1 h1:05xhhdRNAdS3apYm7JRjOqngf4xruaW959jmRxGDuSU= github.com/rboyer/safeio v0.2.1/go.mod h1:Cq/cEPK+YXFn622lsQ0K4KsPZSPtaptHHEldsy7Fmig= github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 h1:Wdi9nwnhFNAlseAOekn6B5G/+GMtks9UKbvRU/CMM/o= github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03/go.mod h1:gRAiPF5C5Nd0eyyRdqIu9qTiFSoZzpTq727b5B8fkkU= @@ -596,20 +531,16 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rs/zerolog v1.4.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/columnize v2.1.0+incompatible h1:j1Wcmh8OrK4Q7GXY+V7SVSY8nUWQxHW5TkBe7YUl+2s= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= -github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= -github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/conswriter v0.0.0-20180208195008-f5ae3917a627/go.mod h1:7zjs06qF79/FKAJpBvFx3P8Ww4UTIMAe+lpNXDHziac= github.com/sean-/pager v0.0.0-20180208200047-666be9bf53b5/go.mod h1:BeybITEsBEg6qbIiqJ6/Bqeq25bCLbL7YFmpaFfJDuM= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shirou/gopsutil v0.0.0-20181107111621-48177ef5f880 h1:1Ge4j/3uB2rxzPWD3TC+daeCw+w91z8UCUL/7WH5gn8= github.com/shirou/gopsutil v0.0.0-20181107111621-48177ef5f880/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v2.19.9+incompatible h1:IrPVlK4nfwW10DF7pW+7YJKws9NkgNzWozwwWv9FsgY= github.com/shirou/gopsutil v2.19.9+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= @@ -617,7 +548,6 @@ github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+D github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= @@ -639,14 +569,12 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -700,7 +628,6 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= @@ -733,13 +660,11 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= @@ -748,7 +673,6 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -773,7 +697,6 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190508220229-2d0786266e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -782,7 +705,6 @@ golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -817,13 +739,11 @@ google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEt google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0 h1:9sdfJOzWlkqPltHAuzT2Cp+yrBeY1KRVYgms8soxMwM= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.0 h1:Tfd7cKwKbFRsI8RMAD3oqqw7JPFRrvFlOsfbgVkjOOw= google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -831,7 +751,6 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190530194941-fb225487d101 h1:wuGevabY6r+ivPNagjUXGGxF+GqgMd+dBhjsxW4q9u4= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64 h1:iKtrH9Y8mcbADOP0YFaEMth7OfuHY9xHOwNj4znpM1A= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= @@ -845,10 +764,8 @@ google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -860,11 +777,8 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU= -gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y= gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= @@ -872,7 +786,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From c7cb13bfb5e2a57856c0ea5e0d7ecd843432ee91 Mon Sep 17 00:00:00 2001 From: aliiohs Date: Sun, 9 Aug 2020 15:57:24 +0800 Subject: [PATCH 127/242] fix getty const --- config/consumer_config.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/config/consumer_config.go b/config/consumer_config.go index 1775312092..4f3de6c11f 100644 --- a/config/consumer_config.go +++ b/config/consumer_config.go @@ -24,7 +24,6 @@ import ( import ( "github.com/creasty/defaults" - "github.com/dubbogo/getty" perrors "github.com/pkg/errors" ) @@ -34,6 +33,10 @@ import ( "github.com/apache/dubbo-go/common/yaml" ) +const ( + maxWheelTimeSpan = 900e9 // 900s, 15 minute +) + ///////////////////////// // consumerConfig ///////////////////////// @@ -107,9 +110,9 @@ func ConsumerInit(confConFile string) error { if consumerConfig.RequestTimeout, err = time.ParseDuration(consumerConfig.Request_Timeout); err != nil { return perrors.WithMessagef(err, "time.ParseDuration(Request_Timeout{%#v})", consumerConfig.Request_Timeout) } - if consumerConfig.RequestTimeout >= time.Duration(getty.MaxWheelTimeSpan) { + if consumerConfig.RequestTimeout >= time.Duration(maxWheelTimeSpan) { return perrors.WithMessagef(err, "request_timeout %s should be less than %s", - consumerConfig.Request_Timeout, time.Duration(getty.MaxWheelTimeSpan)) + consumerConfig.Request_Timeout, time.Duration(maxWheelTimeSpan)) } } if consumerConfig.Connect_Timeout != "" { From addc4b4ec4213c48a5f4b054216c4987937d8ef0 Mon Sep 17 00:00:00 2001 From: aliiohs Date: Sun, 9 Aug 2020 16:07:59 +0800 Subject: [PATCH 128/242] fix getty const --- config/consumer_config.go | 6 +++--- protocol/dubbo/config.go | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/config/consumer_config.go b/config/consumer_config.go index 4f3de6c11f..9d283eeca7 100644 --- a/config/consumer_config.go +++ b/config/consumer_config.go @@ -34,7 +34,7 @@ import ( ) const ( - maxWheelTimeSpan = 900e9 // 900s, 15 minute + MaxWheelTimeSpan = 900e9 // 900s, 15 minute ) ///////////////////////// @@ -110,9 +110,9 @@ func ConsumerInit(confConFile string) error { if consumerConfig.RequestTimeout, err = time.ParseDuration(consumerConfig.Request_Timeout); err != nil { return perrors.WithMessagef(err, "time.ParseDuration(Request_Timeout{%#v})", consumerConfig.Request_Timeout) } - if consumerConfig.RequestTimeout >= time.Duration(maxWheelTimeSpan) { + if consumerConfig.RequestTimeout >= time.Duration(MaxWheelTimeSpan) { return perrors.WithMessagef(err, "request_timeout %s should be less than %s", - consumerConfig.Request_Timeout, time.Duration(maxWheelTimeSpan)) + consumerConfig.Request_Timeout, time.Duration(MaxWheelTimeSpan)) } } if consumerConfig.Connect_Timeout != "" { diff --git a/protocol/dubbo/config.go b/protocol/dubbo/config.go index 635d12109a..3280c12309 100644 --- a/protocol/dubbo/config.go +++ b/protocol/dubbo/config.go @@ -18,11 +18,11 @@ package dubbo import ( + "github.com/apache/dubbo-go/config" "time" ) import ( - "github.com/dubbogo/getty" perrors "github.com/pkg/errors" ) @@ -178,9 +178,9 @@ func (c *ClientConfig) CheckValidity() error { return perrors.WithMessagef(err, "time.ParseDuration(HeartbeatPeroid{%#v})", c.HeartbeatPeriod) } - if c.heartbeatPeriod >= time.Duration(getty.MaxWheelTimeSpan) { + if c.heartbeatPeriod >= time.Duration(config.MaxWheelTimeSpan) { return perrors.WithMessagef(err, "heartbeat_period %s should be less than %s", - c.HeartbeatPeriod, time.Duration(getty.MaxWheelTimeSpan)) + c.HeartbeatPeriod, time.Duration(config.MaxWheelTimeSpan)) } if c.sessionTimeout, err = time.ParseDuration(c.SessionTimeout); err != nil { @@ -198,9 +198,9 @@ func (c *ServerConfig) CheckValidity() error { return perrors.WithMessagef(err, "time.ParseDuration(SessionTimeout{%#v})", c.SessionTimeout) } - if c.sessionTimeout >= time.Duration(getty.MaxWheelTimeSpan) { + if c.sessionTimeout >= time.Duration(config.MaxWheelTimeSpan) { return perrors.WithMessagef(err, "session_timeout %s should be less than %s", - c.SessionTimeout, time.Duration(getty.MaxWheelTimeSpan)) + c.SessionTimeout, time.Duration(config.MaxWheelTimeSpan)) } return perrors.WithStack(c.GettySessionParam.CheckValidity()) From 49d1c946450ca8222e6be22eabc9e541edc1528e Mon Sep 17 00:00:00 2001 From: aliiohs Date: Sun, 9 Aug 2020 22:40:20 +0800 Subject: [PATCH 129/242] rm go version --- go.mod | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.mod b/go.mod index f3cc4183b3..0e522256b0 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,5 @@ module github.com/apache/dubbo-go -go 1.14 - require ( cloud.google.com/go v0.39.0 // indirect github.com/Microsoft/go-winio v0.4.13 // indirect From 9603daa7f5f6b138c9e733f5e03ebe5304380ae7 Mon Sep 17 00:00:00 2001 From: aliiohs Date: Sun, 9 Aug 2020 23:33:48 +0800 Subject: [PATCH 130/242] fix --- go.mod | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.mod b/go.mod index 0e522256b0..f3cc4183b3 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,7 @@ module github.com/apache/dubbo-go +go 1.14 + require ( cloud.google.com/go v0.39.0 // indirect github.com/Microsoft/go-winio v0.4.13 // indirect From cb30a9858ea523dc2f33ee4bd83f97e80803010b Mon Sep 17 00:00:00 2001 From: aliiohs Date: Sun, 9 Aug 2020 23:50:29 +0800 Subject: [PATCH 131/242] rm go version --- go.mod | 1 - 1 file changed, 1 deletion(-) diff --git a/go.mod b/go.mod index f3cc4183b3..960bf45005 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,5 @@ module github.com/apache/dubbo-go -go 1.14 require ( cloud.google.com/go v0.39.0 // indirect From 58dc14556ab8c0d7f77ddbde79ff5f2a73ca9f1f Mon Sep 17 00:00:00 2001 From: lihaowei Date: Sun, 9 Aug 2020 10:58:05 +0800 Subject: [PATCH 132/242] Correct words and Format codes --- README.md | 2 +- cluster/loadbalance/consistent_hash.go | 2 +- cluster/router/condition/router.go | 4 ++-- cluster/router/healthcheck/default_health_check.go | 10 +++++----- protocol/dubbo/client.go | 2 +- protocol/grpc/server.go | 2 +- protocol/invocation/rpcinvocation.go | 4 ++-- protocol/result.go | 2 +- registry/etcdv3/service_discovery.go | 2 +- registry/nacos/listener.go | 4 ++-- registry/registry.go | 2 +- remoting/kubernetes/registry_controller.go | 2 +- remoting/zookeeper/client.go | 2 +- 13 files changed, 20 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index a1c09fc3ca..7df7e7973a 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Both extension module and layered project architecture is according to Apache Du ![dubbo go extend](./doc/pic/arch/dubbo-go-ext.png) -If you wanna know more about dubbo-go, please visit this reference [Project Architeture design](https://github.com/apache/dubbo-go/wiki/dubbo-go-V1.0-design) +If you wanna know more about dubbo-go, please visit this reference [Project Architecture design](https://github.com/apache/dubbo-go/wiki/dubbo-go-V1.0-design) ## Feature list ## diff --git a/cluster/loadbalance/consistent_hash.go b/cluster/loadbalance/consistent_hash.go index 85eb96417c..27ce2369f9 100644 --- a/cluster/loadbalance/consistent_hash.go +++ b/cluster/loadbalance/consistent_hash.go @@ -42,7 +42,7 @@ const ( ConsistentHash = "consistenthash" // HashNodes hash nodes HashNodes = "hash.nodes" - // HashArguments key of hash arguments in url + // HashArguments key of hash arguments in url HashArguments = "hash.arguments" ) diff --git a/cluster/router/condition/router.go b/cluster/router/condition/router.go index 800293da6c..751b5a7111 100644 --- a/cluster/router/condition/router.go +++ b/cluster/router/condition/router.go @@ -294,7 +294,7 @@ func matchCondition(pairs map[string]MatchPair, url *common.URL, param *common.U return result } -// MatchPair Match key pair , condition process +// MatchPair Match key pair, condition process type MatchPair struct { Matches *gxset.HashSet Mismatches *gxset.HashSet @@ -320,7 +320,7 @@ func (pair MatchPair) isMatch(value string, param *common.URL) bool { return true } if !pair.Mismatches.Empty() && !pair.Matches.Empty() { - //when both mismatches and matches contain the same value, then using mismatches first + // when both mismatches and matches contain the same value, then using mismatches first for mismatch := range pair.Mismatches.Items { if isMatchGlobalPattern(mismatch.(string), value, param) { return false diff --git a/cluster/router/healthcheck/default_health_check.go b/cluster/router/healthcheck/default_health_check.go index a26f86ddac..c693b86ecd 100644 --- a/cluster/router/healthcheck/default_health_check.go +++ b/cluster/router/healthcheck/default_health_check.go @@ -49,7 +49,7 @@ type DefaultHealthChecker struct { // and the current active request func (c *DefaultHealthChecker) IsHealthy(invoker protocol.Invoker) bool { urlStatus := protocol.GetURLStatus(invoker.GetUrl()) - if c.isCircuitBreakerTripped(urlStatus) || urlStatus.GetActive() > c.GetOutStandingRequestConutLimit() { + if c.isCircuitBreakerTripped(urlStatus) || urlStatus.GetActive() > c.GetOutStandingRequestCountLimit() { logger.Debugf("Invoker [%s] is currently in circuitbreaker tripped state", invoker.GetUrl().Key()) return false } @@ -92,18 +92,18 @@ func (c *DefaultHealthChecker) getCircuitBreakerSleepWindowTime(status *protocol return int64(sleepWindow) } -// GetOutStandingRequestConutLimit return the requestSuccessiveFailureThreshold bound to this DefaultHealthChecker +// GetRequestSuccessiveFailureThreshold return the requestSuccessiveFailureThreshold bound to this DefaultHealthChecker func (c *DefaultHealthChecker) GetRequestSuccessiveFailureThreshold() int32 { return c.requestSuccessiveFailureThreshold } -// GetOutStandingRequestConutLimit return the circuitTrippedTimeoutFactor bound to this DefaultHealthChecker +// GetCircuitTrippedTimeoutFactor return the circuitTrippedTimeoutFactor bound to this DefaultHealthChecker func (c *DefaultHealthChecker) GetCircuitTrippedTimeoutFactor() int32 { return c.circuitTrippedTimeoutFactor } -// GetOutStandingRequestConutLimit return the outStandingRequestConutLimit bound to this DefaultHealthChecker -func (c *DefaultHealthChecker) GetOutStandingRequestConutLimit() int32 { +// GetOutStandingRequestCountLimit return the outStandingRequestConutLimit bound to this DefaultHealthChecker +func (c *DefaultHealthChecker) GetOutStandingRequestCountLimit() int32 { return c.outStandingRequestConutLimit } diff --git a/protocol/dubbo/client.go b/protocol/dubbo/client.go index 6d1b771bf4..54752ee5fa 100644 --- a/protocol/dubbo/client.go +++ b/protocol/dubbo/client.go @@ -193,7 +193,7 @@ type Response struct { atta map[string]string } -// NewResponse create a new Response. +// NewResponse create a new Response. func NewResponse(reply interface{}, atta map[string]string) *Response { return &Response{ reply: reply, diff --git a/protocol/grpc/server.go b/protocol/grpc/server.go index 4017b65dd5..2b7b1adddf 100644 --- a/protocol/grpc/server.go +++ b/protocol/grpc/server.go @@ -69,7 +69,7 @@ func (s *Server) Start(url common.URL) { panic(err) } - // if global trace instance was set , then server tracer instance can be get. If not , will return Nooptracer + // if global trace instance was set, then server tracer instance can be get. If not , will return Nooptracer tracer := opentracing.GlobalTracer() server := grpc.NewServer( grpc.UnaryInterceptor(otgrpc.OpenTracingServerInterceptor(tracer))) diff --git a/protocol/invocation/rpcinvocation.go b/protocol/invocation/rpcinvocation.go index 953d50ca46..c72e105d35 100644 --- a/protocol/invocation/rpcinvocation.go +++ b/protocol/invocation/rpcinvocation.go @@ -27,7 +27,7 @@ import ( ) // /////////////////////////// -// Invocation Impletment of RPC +// Invocation Implement of RPC // /////////////////////////// // todo: is it necessary to separate fields of consumer(provider) from RPCInvocation @@ -103,7 +103,7 @@ func (r *RPCInvocation) Attachments() map[string]string { return r.attachments } -// AttachmentsByKey gets RPC attachment by key , if nil then return default value. +// AttachmentsByKey gets RPC attachment by key, if nil then return default value. func (r *RPCInvocation) AttachmentsByKey(key string, defaultValue string) string { r.lock.RLock() defer r.lock.RUnlock() diff --git a/protocol/result.go b/protocol/result.go index 2e7a6e492a..2a33be612f 100644 --- a/protocol/result.go +++ b/protocol/result.go @@ -38,7 +38,7 @@ type Result interface { } ///////////////////////////// -// Result Impletment of RPC +// Result Implement of RPC ///////////////////////////// // RPCResult is default RPC result. diff --git a/registry/etcdv3/service_discovery.go b/registry/etcdv3/service_discovery.go index f381ba70d6..dceaa99df8 100644 --- a/registry/etcdv3/service_discovery.go +++ b/registry/etcdv3/service_discovery.go @@ -71,7 +71,7 @@ func (e *etcdV3ServiceDiscovery) String() string { return e.descriptor } -// Destory service discovery +// Destroy service discovery func (e *etcdV3ServiceDiscovery) Destroy() error { if e.client != nil { e.client.Close() diff --git a/registry/nacos/listener.go b/registry/nacos/listener.go index 36f733df5a..4282bf86be 100644 --- a/registry/nacos/listener.go +++ b/registry/nacos/listener.go @@ -132,7 +132,7 @@ func (nl *nacosListener) Callback(services []model.SubscribeService, err error) instance := generateInstance(services[i]) newInstanceMap[host] = instance if old, ok := nl.instanceMap[host]; !ok { - //instance is not exsit in cache,add it to cache + //instance is not exist in cache,add it to cache addInstances = append(addInstances, instance) } else { //instance is not different from cache,update it to cache @@ -144,7 +144,7 @@ func (nl *nacosListener) Callback(services []model.SubscribeService, err error) for host, inst := range nl.instanceMap { if _, ok := newInstanceMap[host]; !ok { - //cache instance is not exsit in new instance list, remove it from cache + //cache instance is not exist in new instance list, remove it from cache delInstances = append(delInstances, inst) } } diff --git a/registry/registry.go b/registry/registry.go index bb09ead7ef..5e77eab186 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -46,7 +46,7 @@ type Registry interface { //Deprecated! //subscribe(event.URL) (Listener, error) - //Will relace mode1 in dubbogo version v1.1.0 + //Will replace mode1 in dubbogo version v1.1.0 //mode2 : callback mode, subscribe with notify(notify listener). Subscribe(*common.URL, NotifyListener) error diff --git a/remoting/kubernetes/registry_controller.go b/remoting/kubernetes/registry_controller.go index f93a00a6f2..20be0d72ec 100644 --- a/remoting/kubernetes/registry_controller.go +++ b/remoting/kubernetes/registry_controller.go @@ -567,7 +567,7 @@ func (c *dubboRegistryController) addAnnotationForCurrentPod(k string, v string) c.lock.Lock() defer c.lock.Unlock() - // 1. accord old pod && (k, v) assemble new pod dubbo annotion v + // 1. accord old pod && (k, v) assemble new pod dubbo annotation v // 2. get patch data // 3. PATCH the pod currentPod, err := c.readCurrentPod() diff --git a/remoting/zookeeper/client.go b/remoting/zookeeper/client.go index 3db743ed58..fbd90762eb 100644 --- a/remoting/zookeeper/client.go +++ b/remoting/zookeeper/client.go @@ -190,7 +190,7 @@ func NewZookeeperClient(name string, zkAddrs []string, timeout time.Duration) (* return z, nil } -// WithTestCluster sets test cluser for zk client +// WithTestCluster sets test cluster for zk client func WithTestCluster(ts *zk.TestCluster) Option { return func(opt *Options) { opt.ts = ts From 2791895b74e3c9f6e40f436d59a9162c9d24511a Mon Sep 17 00:00:00 2001 From: lihaowei Date: Sun, 9 Aug 2020 11:02:32 +0800 Subject: [PATCH 133/242] Format codes --- registry/nacos/listener.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/registry/nacos/listener.go b/registry/nacos/listener.go index 4282bf86be..6699007362 100644 --- a/registry/nacos/listener.go +++ b/registry/nacos/listener.go @@ -132,10 +132,10 @@ func (nl *nacosListener) Callback(services []model.SubscribeService, err error) instance := generateInstance(services[i]) newInstanceMap[host] = instance if old, ok := nl.instanceMap[host]; !ok { - //instance is not exist in cache,add it to cache + // instance is not exist in cache, add it to cache addInstances = append(addInstances, instance) } else { - //instance is not different from cache,update it to cache + // instance is not different from cache, update it to cache if !reflect.DeepEqual(old, instance) { updateInstances = append(updateInstances, instance) } @@ -144,7 +144,7 @@ func (nl *nacosListener) Callback(services []model.SubscribeService, err error) for host, inst := range nl.instanceMap { if _, ok := newInstanceMap[host]; !ok { - //cache instance is not exist in new instance list, remove it from cache + // cache instance is not exist in new instance list, remove it from cache delInstances = append(delInstances, inst) } } From 25281bf920ca4be71d9e8088813261fd602ce7b5 Mon Sep 17 00:00:00 2001 From: lihaowei Date: Sun, 9 Aug 2020 14:12:22 +0800 Subject: [PATCH 134/242] add nolint --- protocol/dubbo/client.go | 2 +- protocol/dubbo/codec.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/protocol/dubbo/client.go b/protocol/dubbo/client.go index 54752ee5fa..253427f301 100644 --- a/protocol/dubbo/client.go +++ b/protocol/dubbo/client.go @@ -193,7 +193,7 @@ type Response struct { atta map[string]string } -// NewResponse create a new Response. +// nolint func NewResponse(reply interface{}, atta map[string]string) *Response { return &Response{ reply: reply, diff --git a/protocol/dubbo/codec.go b/protocol/dubbo/codec.go index 8ba725e3ce..9781c70115 100644 --- a/protocol/dubbo/codec.go +++ b/protocol/dubbo/codec.go @@ -82,7 +82,7 @@ func (p *DubboPackage) Marshal() (*bytes.Buffer, error) { return bytes.NewBuffer(pkg), nil } -// Unmarshal decode hessian package. +// Unmarshal decodes hessian package. func (p *DubboPackage) Unmarshal(buf *bytes.Buffer, opts ...interface{}) error { // fix issue https://github.com/apache/dubbo-go/issues/380 bufLen := buf.Len() From ef95dd97a9c0174c134323816d5676e9202dfbe1 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Mon, 10 Aug 2020 15:46:20 +0800 Subject: [PATCH 135/242] refactor the code style --- cluster/router/chain/chain.go | 133 ++++++++---------- cluster/router/chain/invoker_cache.go | 80 +++++++++++ cluster/router/condition/factory_test.go | 8 +- cluster/router/condition/listenable_router.go | 8 +- cluster/router/condition/router.go | 16 ++- .../router/healthcheck/health_check_route.go | 16 ++- .../healthcheck/health_check_route_test.go | 18 +-- cluster/router/router.go | 48 ++++--- cluster/router/tag/file.go | 6 +- cluster/router/tag/tag_router.go | 24 ++-- cluster/router/tag/tag_router_test.go | 18 +-- cluster/router/utils/bitmap_util.go | 30 ++-- common/url.go | 28 ++++ 13 files changed, 275 insertions(+), 158 deletions(-) create mode 100644 cluster/router/chain/invoker_cache.go diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go index 1ca7d479d1..3f8813631c 100644 --- a/cluster/router/chain/chain.go +++ b/cluster/router/chain/chain.go @@ -18,8 +18,6 @@ package chain import ( - "github.com/RoaringBitmap/roaring" - "github.com/apache/dubbo-go/common/constant" "math" "sort" "sync" @@ -34,6 +32,7 @@ import ( import ( "github.com/apache/dubbo-go/cluster/router" "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/protocol" @@ -56,44 +55,38 @@ type RouterChain struct { builtinRouters []router.PriorityRouter mutex sync.RWMutex - url common.URL + url common.URL + + // The times of address notification since last update for address cache count int64 - last time.Time - ch chan struct{} + // The timestamp of last update for address cache + last time.Time + // Channel for notify to update the address cache + ch chan struct{} + // Address cache cache atomic.Value } // Route Loop routers in RouterChain and call Route method to determine the target invokers list. func (c *RouterChain) Route(url *common.URL, invocation protocol.Invocation) []protocol.Invoker { - rs := c.copyRouters() - - v := c.cache.Load() - if v == nil { + cache := c.loadCache() + if cache == nil { return c.invokers } - cache := v.(*router.AddrCache) - bitmap := cache.Bitmap - for _, r := range rs { + bitmap := cache.bitmap + for _, r := range c.copyRouters() { bitmap = r.Route(bitmap, cache, url, invocation) } indexes := bitmap.ToArray() finalInvokers := make([]protocol.Invoker, len(indexes)) for i, index := range indexes { - finalInvokers[i] = cache.Invokers[index] + finalInvokers[i] = cache.invokers[index] } - return finalInvokers -} -func (c *RouterChain) copyRouters() []router.PriorityRouter { - l := len(c.routers) - rs := make([]router.PriorityRouter, l, int(math.Ceil(float64(l)*1.2))) - c.mutex.RLock() - copy(rs, c.routers) - c.mutex.RUnlock() - return rs + return finalInvokers } // AddRouters Add routers to router chain @@ -110,6 +103,8 @@ func (c *RouterChain) AddRouters(routers []router.PriorityRouter) { c.routers = newRouters } +// SetInvokers receives updated invokers from registry center. If the times of notification exceeds countThreshold and +// time interval exceeds timeThreshold since last cache update, then notify to update the cache. func (c *RouterChain) SetInvokers(invokers []protocol.Invoker) { c.invokers = invokers @@ -124,6 +119,8 @@ func (c *RouterChain) SetInvokers(invokers []protocol.Invoker) { } } +// loop listens on events to update the address cache when it's necessary, either when it receives notification +// from address update, or when timeInterval exceeds. func (c *RouterChain) loop() { for { select { @@ -138,6 +135,27 @@ func (c *RouterChain) loop() { } } +// copyRouters make a snapshot copy from RouterChain's router list. +func (c *RouterChain) copyRouters() []router.PriorityRouter { + l := len(c.routers) + rs := make([]router.PriorityRouter, l, int(math.Ceil(float64(l)*1.2))) + c.mutex.RLock() + copy(rs, c.routers) + c.mutex.RUnlock() + return rs +} + +// loadCache loads cache from sync.Value to guarantee the visibility +func (c *RouterChain) loadCache() *InvokerCache { + v := c.cache.Load() + if v == nil { + return nil + } + + return v.(*InvokerCache) +} + +// buildCache builds address cache with the new invokers for all poolable routers. func (c *RouterChain) buildCache() { if c.invokers == nil || len(c.invokers) == 0 { return @@ -146,18 +164,8 @@ func (c *RouterChain) buildCache() { // FIXME: should lock here, it is fine with dirty read if no panic happens I believe. invokers := make([]protocol.Invoker, len(c.invokers)) copy(invokers, c.invokers) - cache := &router.AddrCache{ - Invokers: invokers, - Bitmap: ToBitmap(invokers), - AddrPool: make(map[string]router.RouterAddrPool), - AddrMeta: make(map[string]router.AddrMetadata), - } - - var origin *router.AddrCache - v := c.cache.Load() - if v != nil { - origin = v.(*router.AddrCache) - } + cache := BuildCache(invokers) + origin := c.loadCache() var mutex sync.Mutex var wg sync.WaitGroup @@ -168,8 +176,8 @@ func (c *RouterChain) buildCache() { go func(p router.Poolable) { pool, info := poolRouter(p, origin, invokers) mutex.Lock() - cache.AddrPool[p.Name()] = pool - cache.AddrMeta[p.Name()] = info + cache.pools[p.Name()] = pool + cache.metadatas[p.Name()] = info mutex.Unlock() wg.Done() }(p) @@ -221,25 +229,32 @@ func NewRouterChain(url *common.URL) (*RouterChain, error) { return chain, nil } -func poolRouter(p router.Poolable, origin *router.AddrCache, invokers []protocol.Invoker) (router.RouterAddrPool, router.AddrMetadata) { +// poolRouter calls poolable router's Pool() to create new address pool and address metadata if necessary. +// If the corresponding cache entry exists, and the poolable router answers no need to re-pool (possibly because its +// rule doesn't change), and the address list doesn't change, then the existing data will be re-used. +func poolRouter(p router.Poolable, origin *InvokerCache, invokers []protocol.Invoker) (router.AddrPool, router.AddrMetadata) { name := p.Name() - if isCacheMiss(origin, name) || p.ShouldRePool() || IsDiff(origin.Invokers, invokers) { + if isCacheMiss(origin, name) || p.ShouldPool() || isInvokersChanged(origin.invokers, invokers) { logger.Debugf("build address cache for router %q", name) return p.Pool(invokers) } else { logger.Debugf("reuse existing address cache for router %q", name) - return origin.AddrPool[name], origin.AddrMeta[name] + return origin.pools[name], origin.metadatas[name] } } -func isCacheMiss(cache *router.AddrCache, key string) bool { - if cache == nil || cache.AddrPool == nil || cache.Invokers == nil || cache.AddrPool[key] == nil { +// isCacheMiss checks if the corresponding cache entry for a poolable router has already existed. +// False returns when the cache is nil, or cache's pool is nil, or cache's invokers snapshot is nil, or the entry +// doesn't exist. +func isCacheMiss(cache *InvokerCache, key string) bool { + if cache == nil || cache.pools == nil || cache.invokers == nil || cache.pools[key] == nil { return true } return false } -func IsDiff(left []protocol.Invoker, right []protocol.Invoker) bool { +// isInvokersChanged compares new invokers on the right changes, compared with the old invokers on the left. +func isInvokersChanged(left []protocol.Invoker, right []protocol.Invoker) bool { if len(right) != len(left) { return true } @@ -247,7 +262,7 @@ func IsDiff(left []protocol.Invoker, right []protocol.Invoker) bool { for _, r := range right { found := false for _, l := range left { - if IsEquals(l.GetUrl(), r.GetUrl()) { + if common.IsEquals(l.GetUrl(), r.GetUrl(), constant.TIMESTAMP_KEY, constant.REMOTE_TIMESTAMP_KEY) { found = true break } @@ -259,38 +274,6 @@ func IsDiff(left []protocol.Invoker, right []protocol.Invoker) bool { return false } -func IsEquals(left common.URL, right common.URL) bool { - if left.Ip != right.Ip || left.Port != right.Port { - return false - } - - leftMap := left.ToMap() - delete(leftMap, constant.TIMESTAMP_KEY) - delete(leftMap, constant.REMOTE_TIMESTAMP_KEY) - rightMap := right.ToMap() - delete(rightMap, constant.TIMESTAMP_KEY) - delete(rightMap, constant.REMOTE_TIMESTAMP_KEY) - - if len(leftMap) != len(rightMap) { - return false - } - - for lk, lv := range leftMap { - if rv, ok := rightMap[lk]; !ok { - return false - } else if lv != rv { - return false - } - } - return true -} - -func ToBitmap(invokers []protocol.Invoker) *roaring.Bitmap { - bitmap := roaring.NewBitmap() - bitmap.AddRange(0, uint64(len(invokers))) - return bitmap -} - // sortRouter Sort router instance by priority with stable algorithm func sortRouter(routers []router.PriorityRouter) { sort.Stable(byPriority(routers)) diff --git a/cluster/router/chain/invoker_cache.go b/cluster/router/chain/invoker_cache.go new file mode 100644 index 0000000000..9f96bddd42 --- /dev/null +++ b/cluster/router/chain/invoker_cache.go @@ -0,0 +1,80 @@ +/* + * 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 chain + +import ( + "github.com/RoaringBitmap/roaring" +) + +import ( + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/cluster/router/utils" + "github.com/apache/dubbo-go/protocol" +) + +// Cache caches all addresses relevant info for a snapshot of received invokers. It keeps a snapshot of the received +// address list, and also keeps address pools and address metadata from routers based on the same address snapshot, if +// the router implements Poolable. +type InvokerCache struct { + // The snapshot of invokers + invokers []protocol.Invoker + + // The bitmap representation for invokers snapshot + bitmap *roaring.Bitmap + + // Address pool from routers which implement Poolable + pools map[string]router.AddrPool + + // Address metadata from routers which implement Poolable + metadatas map[string]router.AddrMetadata +} + +// BuildCache builds address cache from the given invokers. +func BuildCache(invokers []protocol.Invoker) *InvokerCache { + return &InvokerCache{ + invokers: invokers, + bitmap: utils.ToBitmap(invokers), + pools: make(map[string]router.AddrPool), + metadatas: make(map[string]router.AddrMetadata), + } +} + +// GetInvokers get invokers snapshot. +func (c *InvokerCache) GetInvokers() []protocol.Invoker { + return c.invokers +} + +// FindAddrPool finds address pool for a poolable router. +func (c *InvokerCache) FindAddrPool(p router.Poolable) router.AddrPool { + return c.pools[p.Name()] +} + +// FindAddrMeta finds address metadata for a poolable router. +func (c *InvokerCache) FindAddrMeta(p router.Poolable) router.AddrMetadata { + return c.metadatas[p.Name()] +} + +// SetAddrPool sets address pool for a poolable router, for unit test only +func (c *InvokerCache) SetAddrPool(name string, pool router.AddrPool) { + c.pools[name] = pool +} + +// SetAddrMeta sets address metadata for a poolable router, for unit test only +func (c *InvokerCache) SetAddrMeta(name string, meta router.AddrMetadata) { + c.metadatas[name] = meta +} diff --git a/cluster/router/condition/factory_test.go b/cluster/router/condition/factory_test.go index 91aeb8d886..b31f6c7a82 100644 --- a/cluster/router/condition/factory_test.go +++ b/cluster/router/condition/factory_test.go @@ -33,6 +33,7 @@ import ( import ( "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/cluster/router/chain" "github.com/apache/dubbo-go/cluster/router/utils" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" @@ -380,9 +381,6 @@ func TestNewAppRouterFactory(t *testing.T) { assert.NotNil(t, factory) } -func setUpAddrCache(addrs []protocol.Invoker) *router.AddrCache { - cache := &router.AddrCache{ - Invokers: addrs, - } - return cache +func setUpAddrCache(addrs []protocol.Invoker) router.Cache { + return chain.BuildCache(addrs) } diff --git a/cluster/router/condition/listenable_router.go b/cluster/router/condition/listenable_router.go index 86f128bf4d..fd8068e827 100644 --- a/cluster/router/condition/listenable_router.go +++ b/cluster/router/condition/listenable_router.go @@ -66,14 +66,14 @@ func newListenableRouter(url *common.URL, ruleKey string) (*AppRouter, error) { l.priority = listenableRouterDefaultPriority routerKey := ruleKey + constant.ConditionRouterRuleSuffix - //add listener + // add listener dynamicConfiguration := config.GetEnvInstance().GetDynamicConfiguration() if dynamicConfiguration == nil { return nil, perrors.Errorf("Get dynamicConfiguration fail, dynamicConfiguration is nil, init config center plugin please") } dynamicConfiguration.AddListener(routerKey, l) - //get rule + // get rule rule, err := dynamicConfiguration.GetRule(routerKey, config_center.WithGroup(config_center.DEFAULT_GROUP)) if len(rule) == 0 || err != nil { return nil, perrors.Errorf("Get rule fail, config rule{%s}, error{%v}", rule, err) @@ -131,11 +131,11 @@ func (l *listenableRouter) generateConditions(rule *RouterRule) { } // Route Determine the target invokers list. -func (l *listenableRouter) Route(invokers *roaring.Bitmap, cache *router.AddrCache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap { +func (l *listenableRouter) Route(invokers *roaring.Bitmap, cache router.Cache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap { if invokers.IsEmpty() || len(l.conditionRouters) == 0 { return invokers } - //We will check enabled status inside each router. + // We will check enabled status inside each router. for _, r := range l.conditionRouters { invokers = r.Route(invokers, cache, url, invocation) } diff --git a/cluster/router/condition/router.go b/cluster/router/condition/router.go index cd544477a1..9c74757025 100644 --- a/cluster/router/condition/router.go +++ b/cluster/router/condition/router.go @@ -24,21 +24,22 @@ import ( import ( "github.com/RoaringBitmap/roaring" + "github.com/dubbogo/gost/container/set" + "github.com/dubbogo/gost/net" perrors "github.com/pkg/errors" ) import ( "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/cluster/router/utils" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/protocol" - "github.com/dubbogo/gost/container/set" - "github.com/dubbogo/gost/net" ) const ( - //pattern route pattern regex + // pattern route pattern regex pattern = `([&!=,]*)\\s*([^&!=,\\s]+)` ) @@ -144,7 +145,7 @@ func (c *ConditionRouter) Enabled() bool { } // Route Determine the target invokers list. -func (c *ConditionRouter) Route(invokers *roaring.Bitmap, cache *router.AddrCache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap { +func (c *ConditionRouter) Route(invokers *roaring.Bitmap, cache router.Cache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap { if !c.Enabled() { return invokers } @@ -159,13 +160,14 @@ func (c *ConditionRouter) Route(invokers *roaring.Bitmap, cache *router.AddrCach } if len(c.ThenCondition) == 0 { - return router.EmptyAddr + return utils.EmptyAddr } result := roaring.NewBitmap() for iter := invokers.Iterator(); iter.HasNext(); { index := iter.Next() - invokerUrl := cache.Invokers[index].GetUrl() + invoker := cache.GetInvokers()[index] + invokerUrl := invoker.GetUrl() isMatchThen := c.MatchThen(&invokerUrl, url) if isMatchThen { result.Add(index) @@ -323,7 +325,7 @@ func (pair MatchPair) isMatch(value string, param *common.URL) bool { return true } if !pair.Mismatches.Empty() && !pair.Matches.Empty() { - //when both mismatches and matches contain the same value, then using mismatches first + // when both mismatches and matches contain the same value, then using mismatches first for mismatch := range pair.Mismatches.Items { if isMatchGlobalPattern(mismatch.(string), value, param) { return false diff --git a/cluster/router/healthcheck/health_check_route.go b/cluster/router/healthcheck/health_check_route.go index 1199ff07ed..708c45c6d8 100644 --- a/cluster/router/healthcheck/health_check_route.go +++ b/cluster/router/healthcheck/health_check_route.go @@ -19,6 +19,9 @@ package healthcheck import ( "github.com/RoaringBitmap/roaring" +) + +import ( "github.com/apache/dubbo-go/cluster/router" "github.com/apache/dubbo-go/cluster/router/utils" "github.com/apache/dubbo-go/common" @@ -31,6 +34,7 @@ import ( const ( HEALTH_ROUTE_ENABLED_KEY = "health.route.enabled" healthy = "healthy" + name = "health-check-router" ) // HealthCheckRouter provides a health-first routing mechanism through HealthChecker @@ -54,7 +58,7 @@ func NewHealthCheckRouter(url *common.URL) (router.PriorityRouter, error) { } // Route gets a list of healthy invoker -func (r *HealthCheckRouter) Route(invokers *roaring.Bitmap, cache *router.AddrCache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap { +func (r *HealthCheckRouter) Route(invokers *roaring.Bitmap, cache router.Cache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap { if !r.enabled { return invokers } @@ -70,8 +74,9 @@ func (r *HealthCheckRouter) Route(invokers *roaring.Bitmap, cache *router.AddrCa return healthyInvokers } -func (r *HealthCheckRouter) Pool(invokers []protocol.Invoker) (router.RouterAddrPool, router.AddrMetadata) { - rb := make(router.RouterAddrPool) +// Pool separates healthy invokers from others. +func (r *HealthCheckRouter) Pool(invokers []protocol.Invoker) (router.AddrPool, router.AddrMetadata) { + rb := make(router.AddrPool) rb[healthy] = roaring.NewBitmap() for i, invoker := range invokers { if r.checker.IsHealthy(invoker) { @@ -82,12 +87,13 @@ func (r *HealthCheckRouter) Pool(invokers []protocol.Invoker) (router.RouterAddr return rb, nil } -func (r *HealthCheckRouter) ShouldRePool() bool { +// ShouldPool will always return true to make sure healthy check constantly. +func (r *HealthCheckRouter) ShouldPool() bool { return true } func (r *HealthCheckRouter) Name() string { - return "health-check-router" + return name } // Priority diff --git a/cluster/router/healthcheck/health_check_route_test.go b/cluster/router/healthcheck/health_check_route_test.go index ddd68ec403..c321b56156 100644 --- a/cluster/router/healthcheck/health_check_route_test.go +++ b/cluster/router/healthcheck/health_check_route_test.go @@ -19,8 +19,6 @@ package healthcheck import ( "fmt" - "github.com/apache/dubbo-go/cluster/router" - "github.com/apache/dubbo-go/cluster/router/utils" "math" "testing" "time" @@ -31,6 +29,9 @@ import ( ) import ( + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/cluster/router/chain" + "github.com/apache/dubbo-go/cluster/router/utils" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/protocol" @@ -137,15 +138,10 @@ func TestNewHealthCheckRouter(t *testing.T) { assert.Equal(t, dhc.circuitTrippedTimeoutFactor, int32(500)) } -func setUpAddrCache(r router.Poolable, addrs []protocol.Invoker) *router.AddrCache { +func setUpAddrCache(r router.Poolable, addrs []protocol.Invoker) router.Cache { pool, info := r.Pool(addrs) - cache := &router.AddrCache{ - Invokers: addrs, - AddrPool: make(map[string]router.RouterAddrPool), - AddrMeta: make(map[string]router.AddrMetadata), - } - - cache.AddrMeta[r.Name()] = info - cache.AddrPool[r.Name()] = pool + cache := chain.BuildCache(addrs) + cache.SetAddrMeta(r.Name(), info) + cache.SetAddrPool(r.Name(), pool) return cache } diff --git a/cluster/router/router.go b/cluster/router/router.go index 998b7d8b02..ddca42a01d 100644 --- a/cluster/router/router.go +++ b/cluster/router/router.go @@ -19,6 +19,9 @@ package router import ( "github.com/RoaringBitmap/roaring" +) + +import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/protocol" ) @@ -39,7 +42,7 @@ type FilePriorityRouterFactory interface { // Router type router interface { // Route Determine the target invokers list. - Route(*roaring.Bitmap, *AddrCache, *common.URL, protocol.Invocation) *roaring.Bitmap + Route(*roaring.Bitmap, Cache, *common.URL, protocol.Invocation) *roaring.Bitmap // URL Return URL in router URL() common.URL @@ -53,33 +56,38 @@ type PriorityRouter interface { Priority() int64 } +// Poolable caches address pool and address metadata for a router instance which will be used later in Router's Route. type Poolable interface { - Pool([]protocol.Invoker) (RouterAddrPool, AddrMetadata) - ShouldRePool() bool + // Pool created address pool and address metadata from the invokers. + Pool([]protocol.Invoker) (AddrPool, AddrMetadata) + + // ShouldPool returns if it should pool. One typical scenario is a router rule changes, in this case, a pooling + // is necessary, even if the addresses not changed at all. + ShouldPool() bool + + // Name return the Poolable's name. Name() string } +// AddrPool is an address pool, backed by a snapshot of address list, divided into categories. +type AddrPool map[string]*roaring.Bitmap + +// AddrMetadta is address metadata, collected from a snapshot of address list by a router, if it implements Poolable. type AddrMetadata interface { + // Source indicates where the metadata comes from. Source() string } -type RouterAddrPool map[string]*roaring.Bitmap +// Cache caches all addresses relevant info for a snapshot of received invokers. It keeps a snapshot of the received +// address list, and also keeps address pools and address metadata from routers based on the same address snapshot, if +// the router implements Poolable. +type Cache interface { + // GetInvokers returns the snapshot of received invokers. + GetInvokers() []protocol.Invoker -// AddrCache caches all addresses relevant info for a snapshot of received invokers, the calculation logic is -// different from router to router. -type AddrCache struct { - Invokers []protocol.Invoker // invokers snapshot - Bitmap *roaring.Bitmap // bitmap for invokers - AddrPool map[string]RouterAddrPool // address pool from the invokers for one particular router - AddrMeta map[string]AddrMetadata // address meta info collected from the invokers for one particular router -} + // FindAddrPool returns address pool associated with the given Poolable instance. + FindAddrPool(Poolable) AddrPool -func (c *AddrCache) FindAddrPool(p Poolable) RouterAddrPool { - return c.AddrPool[p.Name()] + // FindAddrMeta returns address metadata associated with the given Poolable instance. + FindAddrMeta(Poolable) AddrMetadata } - -func (c *AddrCache) FindAddrMeta(p Poolable) AddrMetadata { - return c.AddrMeta[p.Name()] -} - -var EmptyAddr = roaring.NewBitmap() diff --git a/cluster/router/tag/file.go b/cluster/router/tag/file.go index 4f0f8517bd..5754399e5f 100644 --- a/cluster/router/tag/file.go +++ b/cluster/router/tag/file.go @@ -18,18 +18,18 @@ package tag import ( - "github.com/RoaringBitmap/roaring" - "github.com/apache/dubbo-go/cluster/router" "net/url" "strconv" "sync" ) import ( + "github.com/RoaringBitmap/roaring" perrors "github.com/pkg/errors" ) import ( + "github.com/apache/dubbo-go/cluster/router" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/protocol" @@ -76,7 +76,7 @@ func (f *FileTagRouter) Priority() int64 { return f.router.priority } -func (f *FileTagRouter) Route(invokers *roaring.Bitmap, cache *router.AddrCache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap { +func (f *FileTagRouter) Route(invokers *roaring.Bitmap, cache router.Cache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap { if invokers.IsEmpty() { return invokers } diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index 6b359b869d..79e3eeccb5 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -18,22 +18,26 @@ package tag import ( - "github.com/RoaringBitmap/roaring" - "github.com/apache/dubbo-go/cluster/router" - "github.com/apache/dubbo-go/cluster/router/utils" "strconv" ) import ( + "github.com/RoaringBitmap/roaring" perrors "github.com/pkg/errors" ) import ( + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/cluster/router/utils" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/protocol" ) +const ( + name = "tag-router" +) + type tagRouter struct { url *common.URL enabled bool @@ -55,7 +59,7 @@ func (c *tagRouter) isEnabled() bool { return c.enabled } -func (c *tagRouter) Route(invokers *roaring.Bitmap, cache *router.AddrCache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap { +func (c *tagRouter) Route(invokers *roaring.Bitmap, cache router.Cache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap { if !c.isEnabled() || invokers.IsEmpty() { return invokers } @@ -65,7 +69,7 @@ func (c *tagRouter) Route(invokers *roaring.Bitmap, cache *router.AddrCache, url return invokers } - ret := router.EmptyAddr + ret := utils.EmptyAddr if target, ok := cache.FindAddrPool(c)[tag]; ok { ret = utils.JoinIfNotEqual(target, invokers) } @@ -85,8 +89,9 @@ func (c *tagRouter) Priority() int64 { return c.priority } -func (c *tagRouter) Pool(invokers []protocol.Invoker) (router.RouterAddrPool, router.AddrMetadata) { - rb := make(router.RouterAddrPool) +// Pool divided invokers into different address pool by tag. +func (c *tagRouter) Pool(invokers []protocol.Invoker) (router.AddrPool, router.AddrMetadata) { + rb := make(router.AddrPool) for i, invoker := range invokers { url := invoker.GetUrl() tag := url.GetParam(constant.Tagkey, "") @@ -100,12 +105,13 @@ func (c *tagRouter) Pool(invokers []protocol.Invoker) (router.RouterAddrPool, ro return rb, nil } -func (c *tagRouter) ShouldRePool() bool { +// ShouldPool returns false, to make sure address cache for tag router happens once and only once. +func (c *tagRouter) ShouldPool() bool { return false } func (c *tagRouter) Name() string { - return "tag-router" + return name } func findStaticTag(invocation protocol.Invocation) string { diff --git a/cluster/router/tag/tag_router_test.go b/cluster/router/tag/tag_router_test.go index 465f550492..76553365fd 100644 --- a/cluster/router/tag/tag_router_test.go +++ b/cluster/router/tag/tag_router_test.go @@ -19,8 +19,6 @@ package tag import ( "context" - "github.com/apache/dubbo-go/cluster/router" - "github.com/apache/dubbo-go/cluster/router/utils" "testing" ) @@ -29,6 +27,9 @@ import ( ) import ( + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/cluster/router/chain" + "github.com/apache/dubbo-go/cluster/router/utils" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/protocol" "github.com/apache/dubbo-go/protocol/invocation" @@ -163,15 +164,10 @@ func TestTagRouterRouteNoForce(t *testing.T) { assert.Equal(t, 3, len(invRst2.ToArray())) } -func setUpAddrCache(r router.Poolable, addrs []protocol.Invoker) *router.AddrCache { +func setUpAddrCache(r router.Poolable, addrs []protocol.Invoker) router.Cache { pool, info := r.Pool(addrs) - cache := &router.AddrCache{ - Invokers: addrs, - AddrPool: make(map[string]router.RouterAddrPool), - AddrMeta: make(map[string]router.AddrMetadata), - } - - cache.AddrMeta[r.Name()] = info - cache.AddrPool[r.Name()] = pool + cache := chain.BuildCache(addrs) + cache.SetAddrPool(r.Name(), pool) + cache.SetAddrMeta(r.Name(), info) return cache } diff --git a/cluster/router/utils/bitmap_util.go b/cluster/router/utils/bitmap_util.go index b51cff96a2..b34ecf8933 100644 --- a/cluster/router/utils/bitmap_util.go +++ b/cluster/router/utils/bitmap_util.go @@ -1,10 +1,32 @@ +/* + * 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 utils import ( "github.com/RoaringBitmap/roaring" +) + +import ( "github.com/apache/dubbo-go/protocol" ) +var EmptyAddr = roaring.NewBitmap() + func JoinIfNotEqual(left *roaring.Bitmap, right *roaring.Bitmap) *roaring.Bitmap { if !left.Equals(right) { left = left.Clone() @@ -22,14 +44,6 @@ func FallbackIfJoinToEmpty(left *roaring.Bitmap, right *roaring.Bitmap) *roaring } } -func ToIndex(invokers []protocol.Invoker) []int { - var ret []int - for i := range invokers { - ret = append(ret, i) - } - return ret -} - func ToBitmap(invokers []protocol.Invoker) *roaring.Bitmap { bitmap := roaring.NewBitmap() bitmap.AddRange(0, uint64(len(invokers))) diff --git a/common/url.go b/common/url.go index 807d0ed5ef..7e5ac1e0be 100644 --- a/common/url.go +++ b/common/url.go @@ -643,6 +643,34 @@ func (c *URL) CloneWithParams(reserveParams []string) *URL { ) } +// IsEquals compares if two URLs equals with each other. Excludes are all parameter keys which should ignored. +func IsEquals(left URL, right URL, excludes ...string) bool { + if left.Ip != right.Ip || left.Port != right.Port { + return false + } + + leftMap := left.ToMap() + rightMap := right.ToMap() + for _, exclude := range excludes { + delete(leftMap, exclude) + delete(rightMap, exclude) + } + + if len(leftMap) != len(rightMap) { + return false + } + + for lk, lv := range leftMap { + if rv, ok := rightMap[lk]; !ok { + return false + } else if lv != rv { + return false + } + } + + return true +} + func mergeNormalParam(mergedUrl *URL, referenceUrl *URL, paramKeys []string) []func(method string) { methodConfigMergeFcn := make([]func(method string), 0, len(paramKeys)) for _, paramKey := range paramKeys { From 83ff9e70d49ec46f0108ba0973d7bd779d0d5fa6 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Mon, 10 Aug 2020 16:53:51 +0800 Subject: [PATCH 136/242] honor HealthCheckRouter's enabled flag --- cluster/router/healthcheck/health_check_route.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cluster/router/healthcheck/health_check_route.go b/cluster/router/healthcheck/health_check_route.go index 708c45c6d8..06eea3b937 100644 --- a/cluster/router/healthcheck/health_check_route.go +++ b/cluster/router/healthcheck/health_check_route.go @@ -76,6 +76,10 @@ func (r *HealthCheckRouter) Route(invokers *roaring.Bitmap, cache router.Cache, // Pool separates healthy invokers from others. func (r *HealthCheckRouter) Pool(invokers []protocol.Invoker) (router.AddrPool, router.AddrMetadata) { + if !r.enabled { + return nil, nil + } + rb := make(router.AddrPool) rb[healthy] = roaring.NewBitmap() for i, invoker := range invokers { @@ -89,7 +93,7 @@ func (r *HealthCheckRouter) Pool(invokers []protocol.Invoker) (router.AddrPool, // ShouldPool will always return true to make sure healthy check constantly. func (r *HealthCheckRouter) ShouldPool() bool { - return true + return r.enabled } func (r *HealthCheckRouter) Name() string { From 30c587ebc7c50d0aebed5f9b61ef0c710f2cb143 Mon Sep 17 00:00:00 2001 From: william feng <> Date: Mon, 10 Aug 2020 18:06:42 +0800 Subject: [PATCH 137/242] get application from both dir.url and dir.url.suburl --- cluster/directory/base_directory.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cluster/directory/base_directory.go b/cluster/directory/base_directory.go index 651c90ff68..20db1f2b7d 100644 --- a/cluster/directory/base_directory.go +++ b/cluster/directory/base_directory.go @@ -112,11 +112,15 @@ func (dir *BaseDirectory) SetRouters(urls []*common.URL) { func (dir *BaseDirectory) isProperRouter(url *common.URL) bool { app := url.GetParam(constant.APPLICATION_KEY, "") + dirApp := dir.GetUrl().GetParam(constant.APPLICATION_KEY, "") + if len(dirApp) == 0 && dir.GetUrl().SubURL != nil { + dirApp = dir.GetUrl().SubURL.GetParam(constant.APPLICATION_KEY, "") + } serviceKey := dir.GetUrl().ServiceKey() - if serviceKey == "" { + if len(serviceKey) == 0 { serviceKey = dir.GetUrl().SubURL.ServiceKey() } - if len(app) > 0 && app == dir.GetUrl().GetParam(constant.APPLICATION_KEY, "") { + if len(app) > 0 && app == dirApp { return true } if url.ServiceKey() == serviceKey { From 367c42bf7f4062a29ebf0d9eafa6d49481b450c8 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Mon, 10 Aug 2020 18:55:52 +0800 Subject: [PATCH 138/242] use mutex to copy invokers --- cluster/router/chain/chain.go | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go index 3f8813631c..f8a27ee615 100644 --- a/cluster/router/chain/chain.go +++ b/cluster/router/chain/chain.go @@ -18,7 +18,7 @@ package chain import ( - "math" + "reflect" "sort" "sync" "sync/atomic" @@ -137,12 +137,25 @@ func (c *RouterChain) loop() { // copyRouters make a snapshot copy from RouterChain's router list. func (c *RouterChain) copyRouters() []router.PriorityRouter { - l := len(c.routers) - rs := make([]router.PriorityRouter, l, int(math.Ceil(float64(l)*1.2))) c.mutex.RLock() - copy(rs, c.routers) + ret := copySlice(c.routers) c.mutex.RUnlock() - return rs + return ret.([]router.PriorityRouter) +} + +// copyInvokers copies a snapshot of the received invokers. +func (c *RouterChain) copyInvokers() []protocol.Invoker { + c.mutex.RLock() + ret := copySlice(c.invokers) + c.mutex.RUnlock() + return ret.([]protocol.Invoker) +} + +func copySlice(s interface{}) interface{} { + t, v := reflect.TypeOf(s), reflect.ValueOf(s) + c := reflect.MakeSlice(t, v.Len(), v.Len()) + reflect.Copy(c, v) + return c.Interface() } // loadCache loads cache from sync.Value to guarantee the visibility @@ -161,9 +174,7 @@ func (c *RouterChain) buildCache() { return } - // FIXME: should lock here, it is fine with dirty read if no panic happens I believe. - invokers := make([]protocol.Invoker, len(c.invokers)) - copy(invokers, c.invokers) + invokers := c.copyInvokers() cache := BuildCache(invokers) origin := c.loadCache() From ef1ddcf39e9628589d9db9d4993317263f6a1934 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Mon, 10 Aug 2020 19:21:43 +0800 Subject: [PATCH 139/242] set invokers for router chain in static directory to pass unit test --- cluster/directory/static_directory.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cluster/directory/static_directory.go b/cluster/directory/static_directory.go index c5b0637bd7..6d75dff5da 100644 --- a/cluster/directory/static_directory.go +++ b/cluster/directory/static_directory.go @@ -39,10 +39,13 @@ func NewStaticDirectory(invokers []protocol.Invoker) *staticDirectory { if len(invokers) > 0 { url = invokers[0].GetUrl() } - return &staticDirectory{ + dir := &staticDirectory{ BaseDirectory: NewBaseDirectory(&url), invokers: invokers, } + + dir.routerChain.SetInvokers(invokers) + return dir } //for-loop invokers ,if all invokers is available ,then it means directory is available From fedaae512ee6144f0633b5e6c6b090e7a71b8a84 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Mon, 10 Aug 2020 19:48:43 +0800 Subject: [PATCH 140/242] fix unit test --- cluster/router/chain/chain.go | 3 --- registry/directory/directory.go | 14 +++++++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go index f8a27ee615..9148679e0e 100644 --- a/cluster/router/chain/chain.go +++ b/cluster/router/chain/chain.go @@ -125,11 +125,8 @@ func (c *RouterChain) loop() { for { select { case <-time.Tick(timeInterval): - logger.Debugf("start to build address cache since time interval %d second ticks", int(timeInterval.Seconds())) c.buildCache() case <-c.ch: - logger.Debugf("start to build address cache since at least %d times of address notified within %d seconds", - countThreshold, int(timeThreshold.Seconds())) c.buildCache() } } diff --git a/registry/directory/directory.go b/registry/directory/directory.go index 0c59d5e677..60e36c3737 100644 --- a/registry/directory/directory.go +++ b/registry/directory/directory.go @@ -60,6 +60,7 @@ type RegistryDirectory struct { serviceType string registry registry.Registry cacheInvokersMap *sync.Map // use sync.map + consumerURL *common.URL cacheOriginUrl *common.URL configurators []config_center.Configurator consumerConfigurationListener *consumerConfigurationListener @@ -81,9 +82,9 @@ func NewRegistryDirectory(url *common.URL, registry registry.Registry) (cluster. registry: registry, } - dir.cacheOriginUrl = dir.getConsumerUrl(url.SubURL) + dir.consumerURL= dir.getConsumerUrl(url.SubURL) - if routerChain, err := chain.NewRouterChain(dir.cacheOriginUrl); err == nil { + if routerChain, err := chain.NewRouterChain(dir.consumerURL); err == nil { dir.BaseDirectory.SetRouterChain(routerChain) } else { logger.Warnf("fail to create router chain with url: %s, err is: %v", url.SubURL, err) @@ -159,7 +160,9 @@ func (dir *RegistryDirectory) refreshInvokers(res *registry.ServiceEvent) { newInvokers := dir.toGroupInvokers() dir.listenerLock.Lock() dir.cacheInvokers = newInvokers - dir.RouterChain().SetInvokers(newInvokers) + if res != nil { + dir.RouterChain().SetInvokers(newInvokers) + } dir.listenerLock.Unlock() // After dir.cacheInvokers is updated,destroy the oldInvoker // Ensure that no request will enter the oldInvoker @@ -229,8 +232,9 @@ func (dir *RegistryDirectory) cacheInvoker(url *common.URL) protocol.Invoker { if url == nil && dir.cacheOriginUrl != nil { url = dir.cacheOriginUrl + } else { + dir.cacheOriginUrl = url } - if url == nil { logger.Error("URL is nil ,pls check if service url is subscribe successfully!") return nil @@ -265,7 +269,7 @@ func (dir *RegistryDirectory) List(invocation protocol.Invocation) []protocol.In if routerChain == nil { return invokers } - return routerChain.Route(dir.cacheOriginUrl, invocation) + return routerChain.Route(dir.consumerURL, invocation) } // IsAvailable whether the directory is available From 48d713a848e639a7e51b947382250eccb4c2ca89 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Mon, 10 Aug 2020 21:12:24 +0800 Subject: [PATCH 141/242] correct go fmt --- registry/directory/directory.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/directory/directory.go b/registry/directory/directory.go index 60e36c3737..8940d2ab5e 100644 --- a/registry/directory/directory.go +++ b/registry/directory/directory.go @@ -82,7 +82,7 @@ func NewRegistryDirectory(url *common.URL, registry registry.Registry) (cluster. registry: registry, } - dir.consumerURL= dir.getConsumerUrl(url.SubURL) + dir.consumerURL = dir.getConsumerUrl(url.SubURL) if routerChain, err := chain.NewRouterChain(dir.consumerURL); err == nil { dir.BaseDirectory.SetRouterChain(routerChain) From bc5ad7626c0409ebfb69631cce20491887ea7fd0 Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Mon, 10 Aug 2020 23:00:06 +0800 Subject: [PATCH 142/242] update hessian2 pkg --- go.mod | 2 +- go.sum | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 8895ffbdc2..3679b8c344 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/apache/dubbo-go require ( github.com/Workiva/go-datastructures v1.0.50 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 - github.com/apache/dubbo-go-hessian2 v1.4.0 + github.com/apache/dubbo-go-hessian2 v1.6.2 github.com/coreos/bbolt v1.3.3 // indirect github.com/coreos/etcd v3.3.13+incompatible github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect diff --git a/go.sum b/go.sum index 03b5572fbc..12d5a7c15d 100644 --- a/go.sum +++ b/go.sum @@ -53,8 +53,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA= github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk= -github.com/apache/dubbo-go-hessian2 v1.4.0 h1:Cb9FQVTy3G93dnDr7P93U8DeKFYpDTJjQp44JG5TafA= -github.com/apache/dubbo-go-hessian2 v1.4.0/go.mod h1:VwEnsOMidkM1usya2uPfGpSLO9XUF//WQcWn3y+jFz8= +github.com/apache/dubbo-go-hessian2 v1.6.2 h1:i7F5GjVaUatLQz1x9vUmmSIFj49L8J6rVICdF6xw4qw= +github.com/apache/dubbo-go-hessian2 v1.6.2/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -126,14 +126,11 @@ github.com/docker/go-connections v0.3.0 h1:3lOnM9cSzgGwx8VfK/NGOW5fLQ0GjIlCkaktF github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dubbogo/getty v1.3.3 h1:8m4zZBqFHO+NmhH7rMPlFuuYRVjcPD7cUhumevqMZZs= -github.com/dubbogo/getty v1.3.3/go.mod h1:U92BDyJ6sW9Jpohr2Vlz8w2uUbIbNZ3d+6rJvFTSPp0= +github.com/dubbogo/getty v1.3.8 h1:D9VQLlO4df0H+k8lEW+Re1hGymxzQjvvEKOzGNI35sI= github.com/dubbogo/getty v1.3.8/go.mod h1:JIN5lNZznGnBuoYjlEe/yjNmq7eSjKaawHdnBAdtiEo= github.com/dubbogo/go-zookeeper v1.0.0 h1:RsYdlGwhDW+iKXM3eIIcvt34P2swLdmQfuIJxsHlGoM= github.com/dubbogo/go-zookeeper v1.0.0/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c= -github.com/dubbogo/gost v1.5.1/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= -github.com/dubbogo/gost v1.5.2 h1:ri/03971hdpnn3QeCU+4UZgnRNGDXLDGDucR/iozZm8= -github.com/dubbogo/gost v1.5.2/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= +github.com/dubbogo/gost v1.9.0 h1:UT+dWwvLyJiDotxJERO75jB3Yxgsdy10KztR5ycxRAk= github.com/dubbogo/gost v1.9.0/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74 h1:2MIhn2R6oXQbgW5yHfS+d6YqyMfXiu2L55rFZC4UD/M= github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74/go.mod h1:UqXY1lYT/ERa4OEAywUqdok1T4RCRdArkhic1Opuavo= @@ -236,8 +233,6 @@ github.com/gophercloud/gophercloud v0.0.0-20180828235145-f29afc2cceca/go.mod h1: github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI= @@ -568,13 +563,17 @@ go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -597,6 +596,7 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -681,6 +681,7 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.0.0-20180829000535-087779f1d2c9 h1:z1TeLUmxf9ws9KLICfmX+KGXTs+rjm+aGWzfsv7MZ9w= @@ -752,6 +753,7 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= istio.io/gogo-genproto v0.0.0-20190124151557-6d926a6e6feb/go.mod h1:eIDJ6jNk/IeJz6ODSksHl5Aiczy5JUq6vFhJWI5OtiI= k8s.io/api v0.0.0-20180806132203-61b11ee65332/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= From d7014ee0a681e89bde2c7b9f00cd73485f0d75b5 Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Mon, 10 Aug 2020 23:33:05 +0800 Subject: [PATCH 143/242] update getty pkg --- config/consumer_config.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/config/consumer_config.go b/config/consumer_config.go index 1fa68415bf..b65aacacd4 100644 --- a/config/consumer_config.go +++ b/config/consumer_config.go @@ -24,7 +24,6 @@ import ( import ( "github.com/creasty/defaults" - "github.com/dubbogo/getty" perrors "github.com/pkg/errors" ) @@ -34,6 +33,10 @@ import ( "github.com/apache/dubbo-go/common/yaml" ) +const ( + MaxWheelTimeSpan = 900e9 // 900s, 15 minute +) + ///////////////////////// // consumerConfig ///////////////////////// @@ -107,9 +110,9 @@ func ConsumerInit(confConFile string) error { if consumerConfig.RequestTimeout, err = time.ParseDuration(consumerConfig.Request_Timeout); err != nil { return perrors.WithMessagef(err, "time.ParseDuration(Request_Timeout{%#v})", consumerConfig.Request_Timeout) } - if consumerConfig.RequestTimeout >= time.Duration(getty.MaxWheelTimeSpan) { + if consumerConfig.RequestTimeout >= time.Duration(MaxWheelTimeSpan) { return perrors.WithMessagef(err, "request_timeout %s should be less than %s", - consumerConfig.Request_Timeout, time.Duration(getty.MaxWheelTimeSpan)) + consumerConfig.Request_Timeout, time.Duration(MaxWheelTimeSpan)) } } if consumerConfig.Connect_Timeout != "" { From 316e276b83190bafb98c8727b56e97e280a7e978 Mon Sep 17 00:00:00 2001 From: watermelo <80680489@qq.com> Date: Mon, 10 Aug 2020 23:18:15 +0800 Subject: [PATCH 144/242] Mod: modify tag router strings compare --- cluster/router/tag/tag_router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index ef222ac963..c5bc87675b 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -114,7 +114,7 @@ func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocati // static tag group. filter := func(invoker protocol.Invoker) bool { localTag := invoker.GetUrl().GetParam(constant.Tagkey, "") - return localTag == "" || !(tagRouterRuleCopy.hasTag(localTag)) + return len(localTag) == 0 || !(tagRouterRuleCopy.hasTag(localTag)) } return filterInvoker(result, filter) } From b8e15bf97a09c752d032989a465915c5ffdd5657 Mon Sep 17 00:00:00 2001 From: aliiohs Date: Mon, 10 Aug 2020 23:53:01 +0800 Subject: [PATCH 145/242] fix --- go.mod | 1 + protocol/dubbo/pool.go | 1 + 2 files changed, 2 insertions(+) diff --git a/go.mod b/go.mod index 960bf45005..f3cc4183b3 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,6 @@ module github.com/apache/dubbo-go +go 1.14 require ( cloud.google.com/go v0.39.0 // indirect diff --git a/protocol/dubbo/pool.go b/protocol/dubbo/pool.go index cb1960a756..67a9c50a59 100644 --- a/protocol/dubbo/pool.go +++ b/protocol/dubbo/pool.go @@ -60,6 +60,7 @@ func newGettyRPCClientConn(pool *gettyRPCClientPool, protocol, addr string) (*ge getty.WithServerAddress(addr), getty.WithConnectionNumber((int)(pool.rpcClient.conf.ConnectionNum)), getty.WithReconnectInterval(pool.rpcClient.conf.ReconnectInterval), + getty.WithClientSslEnabled(pool.sslEnabled), getty.WithClientTlsConfigBuilder(config.GetClientTlsConfigBuilder()), ) } else { From 07bf1c4cd5274d909ba64ada7718145ba3bb77a7 Mon Sep 17 00:00:00 2001 From: aliiohs Date: Tue, 11 Aug 2020 00:46:36 +0800 Subject: [PATCH 146/242] fix --- protocol/dubbo/pool.go | 19 ++++++++++++++++++- protocol/dubbo/server.go | 16 +++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/protocol/dubbo/pool.go b/protocol/dubbo/pool.go index 67a9c50a59..b0de26ad87 100644 --- a/protocol/dubbo/pool.go +++ b/protocol/dubbo/pool.go @@ -18,6 +18,7 @@ package dubbo import ( + "crypto/tls" "fmt" "math/rand" "net" @@ -116,7 +117,23 @@ func (c *gettyRPCClient) newSession(session getty.Session) error { if conf.GettySessionParam.CompressEncoding { session.SetCompressType(getty.CompressZip) } - + if c.pool.sslEnabled { + if _, ok = session.Conn().(*tls.Conn); !ok { + panic(fmt.Sprintf("%s, session.conn{%#v} is not tls connection\n", session.Stat(), session.Conn())) + } + session.SetName(conf.GettySessionParam.SessionName) + session.SetMaxMsgLen(conf.GettySessionParam.MaxMsgLen) + session.SetPkgHandler(NewRpcClientPackageHandler(c.pool.rpcClient)) + session.SetEventListener(NewRpcClientHandler(c)) + session.SetWQLen(conf.GettySessionParam.PkgWQSize) + session.SetReadTimeout(conf.GettySessionParam.tcpReadTimeout) + session.SetWriteTimeout(conf.GettySessionParam.tcpWriteTimeout) + session.SetCronPeriod((int)(conf.heartbeatPeriod.Nanoseconds() / 1e6)) + session.SetWaitTime(conf.GettySessionParam.waitTimeout) + logger.Debugf("client new session:%s\n", session.Stat()) + session.SetTaskPool(clientGrpool) + return nil + } if tcpConn, ok = session.Conn().(*net.TCPConn); !ok { panic(fmt.Sprintf("%s, session.conn{%#v} is not tcp connection\n", session.Stat(), session.Conn())) } diff --git a/protocol/dubbo/server.go b/protocol/dubbo/server.go index bef7b3f680..638d822083 100644 --- a/protocol/dubbo/server.go +++ b/protocol/dubbo/server.go @@ -18,6 +18,7 @@ package dubbo import ( + "crypto/tls" "fmt" "net" ) @@ -127,7 +128,20 @@ func (s *Server) newSession(session getty.Session) error { if conf.GettySessionParam.CompressEncoding { session.SetCompressType(getty.CompressZip) } - + if _, ok = session.Conn().(*tls.Conn); ok { + session.SetName(conf.GettySessionParam.SessionName) + session.SetMaxMsgLen(conf.GettySessionParam.MaxMsgLen) + session.SetPkgHandler(rpcServerPkgHandler) + session.SetEventListener(s.rpcHandler) + session.SetWQLen(conf.GettySessionParam.PkgWQSize) + session.SetReadTimeout(conf.GettySessionParam.tcpReadTimeout) + session.SetWriteTimeout(conf.GettySessionParam.tcpWriteTimeout) + session.SetCronPeriod((int)(conf.sessionTimeout.Nanoseconds() / 1e6)) + session.SetWaitTime(conf.GettySessionParam.waitTimeout) + logger.Debugf("app accepts new session:%s\n", session.Stat()) + session.SetTaskPool(srvGrpool) + return nil + } if tcpConn, ok = session.Conn().(*net.TCPConn); !ok { panic(fmt.Sprintf("%s, session.conn{%#v} is not tcp connection\n", session.Stat(), session.Conn())) } From 67abe6b5cae30f0f65539fb3fcf45df95c2a5ebc Mon Sep 17 00:00:00 2001 From: aliiohs Date: Tue, 11 Aug 2020 01:29:32 +0800 Subject: [PATCH 147/242] fix --- config/config_loader.go | 7 +++++++ config/protocol_config.go | 7 +++---- config/service_config.go | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/config/config_loader.go b/config/config_loader.go index d5f8c68c1b..8b196305b9 100644 --- a/config/config_loader.go +++ b/config/config_loader.go @@ -42,6 +42,7 @@ var ( providerConfig *ProviderConfig // baseConfig = providerConfig.BaseConfig or consumerConfig baseConfig *BaseConfig + sslEnabled = false // configAccessMutex is used to make sure that xxxxConfig will only be created once if needed. // it should be used combine with double-check to avoid the race condition @@ -325,6 +326,12 @@ func GetBaseConfig() *BaseConfig { return baseConfig } +func GetSslEnabled() bool { + return sslEnabled +} +func SetSslEnabled(enabled bool) { + sslEnabled = enabled +} func IsProvider() bool { return providerConfig != nil } diff --git a/config/protocol_config.go b/config/protocol_config.go index 3d9e1185d6..cee5b7aa75 100644 --- a/config/protocol_config.go +++ b/config/protocol_config.go @@ -27,10 +27,9 @@ import ( // ProtocolConfig is protocol configuration type ProtocolConfig struct { - Name string `required:"true" yaml:"name" json:"name,omitempty" property:"name"` - Ip string `required:"true" yaml:"ip" json:"ip,omitempty" property:"ip"` - Port string `required:"true" yaml:"port" json:"port,omitempty" property:"port"` - SslEnabled bool `required:"false" yaml:"sslEnabled" json:"sslEnabled,omitempty" property:"sslEnabled"` + Name string `required:"true" yaml:"name" json:"name,omitempty" property:"name"` + Ip string `required:"true" yaml:"ip" json:"ip,omitempty" property:"ip"` + Port string `required:"true" yaml:"port" json:"port,omitempty" property:"port"` } // nolint diff --git a/config/service_config.go b/config/service_config.go index 728915381a..54383e4791 100644 --- a/config/service_config.go +++ b/config/service_config.go @@ -188,7 +188,7 @@ func (c *ServiceConfig) Export() error { common.WithPort(port), common.WithParams(urlMap), common.WithParamsValue(constant.BEAN_NAME_KEY, c.id), - common.WithParamsValue(constant.SSL_ENABLED_KEY, strconv.FormatBool(proto.SslEnabled)), + common.WithParamsValue(constant.SSL_ENABLED_KEY, strconv.FormatBool(GetSslEnabled())), common.WithMethods(strings.Split(methods, ",")), common.WithToken(c.Token), ) From ee46d46a2f6564cd3d3f8259330e328ddc44547b Mon Sep 17 00:00:00 2001 From: aliiohs Date: Tue, 11 Aug 2020 01:46:17 +0800 Subject: [PATCH 148/242] split pkg --- protocol/dubbo/config.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/protocol/dubbo/config.go b/protocol/dubbo/config.go index 3280c12309..44900f1cc7 100644 --- a/protocol/dubbo/config.go +++ b/protocol/dubbo/config.go @@ -18,13 +18,15 @@ package dubbo import ( - "github.com/apache/dubbo-go/config" "time" ) import ( perrors "github.com/pkg/errors" ) +import ( + "github.com/apache/dubbo-go/config" +) type ( // GettySessionParam is session configuration for getty. From 350185edb4a15b6621ea9c5268b73aa435d5ab26 Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Tue, 11 Aug 2020 10:28:28 +0800 Subject: [PATCH 149/242] fix travis --- protocol/dubbo/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/dubbo/config.go b/protocol/dubbo/config.go index dbc6989c54..f9e9599514 100644 --- a/protocol/dubbo/config.go +++ b/protocol/dubbo/config.go @@ -180,7 +180,7 @@ func (c *ClientConfig) CheckValidity() error { return perrors.WithMessagef(err, "time.ParseDuration(HeartbeatPeroid{%#v})", c.HeartbeatPeriod) } - if c.heartbeatPeriod >= time.Duration(getty.MaxWheelTimeSpan) { + if c.heartbeatPeriod >= time.Duration(config.MaxWheelTimeSpan) { return perrors.WithMessagef(err, "heartbeat_period %s should be less than %s", c.HeartbeatPeriod, time.Duration(getty.MaxWheelTimeSpan)) } From 9aa2bc8f74c2058c7c310a1cb5bab103562109df Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Tue, 11 Aug 2020 12:03:35 +0800 Subject: [PATCH 150/242] fix review comment --- protocol/dubbo/config.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/protocol/dubbo/config.go b/protocol/dubbo/config.go index f9e9599514..3e0199d007 100644 --- a/protocol/dubbo/config.go +++ b/protocol/dubbo/config.go @@ -22,7 +22,7 @@ import ( ) import ( - "github.com/dubbogo/getty" + "github.com/apache/dubbo-go/config" perrors "github.com/pkg/errors" ) @@ -182,7 +182,7 @@ func (c *ClientConfig) CheckValidity() error { if c.heartbeatPeriod >= time.Duration(config.MaxWheelTimeSpan) { return perrors.WithMessagef(err, "heartbeat_period %s should be less than %s", - c.HeartbeatPeriod, time.Duration(getty.MaxWheelTimeSpan)) + c.HeartbeatPeriod, time.Duration(config.MaxWheelTimeSpan)) } if c.sessionTimeout, err = time.ParseDuration(c.SessionTimeout); err != nil { @@ -200,9 +200,9 @@ func (c *ServerConfig) CheckValidity() error { return perrors.WithMessagef(err, "time.ParseDuration(SessionTimeout{%#v})", c.SessionTimeout) } - if c.sessionTimeout >= time.Duration(getty.MaxWheelTimeSpan) { + if c.sessionTimeout >= time.Duration(config.MaxWheelTimeSpan) { return perrors.WithMessagef(err, "session_timeout %s should be less than %s", - c.SessionTimeout, time.Duration(getty.MaxWheelTimeSpan)) + c.SessionTimeout, time.Duration(config.MaxWheelTimeSpan)) } return perrors.WithStack(c.GettySessionParam.CheckValidity()) From 5faedba619f2a7391d903d1a762191dc5fa988ba Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Tue, 11 Aug 2020 12:04:36 +0800 Subject: [PATCH 151/242] fix review comment --- protocol/dubbo/config.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/protocol/dubbo/config.go b/protocol/dubbo/config.go index 3e0199d007..e2bddb0f94 100644 --- a/protocol/dubbo/config.go +++ b/protocol/dubbo/config.go @@ -22,10 +22,13 @@ import ( ) import ( - "github.com/apache/dubbo-go/config" perrors "github.com/pkg/errors" ) +import ( + "github.com/apache/dubbo-go/config" +) + type ( // GettySessionParam ... GettySessionParam struct { From 6c606c101a1ff8ed37b940e57b28323037d2b58a Mon Sep 17 00:00:00 2001 From: aliiohs Date: Wed, 12 Aug 2020 19:11:39 +0800 Subject: [PATCH 152/242] split pkg --- protocol/dubbo/config.go | 1 + 1 file changed, 1 insertion(+) diff --git a/protocol/dubbo/config.go b/protocol/dubbo/config.go index 44900f1cc7..b47ec1cc34 100644 --- a/protocol/dubbo/config.go +++ b/protocol/dubbo/config.go @@ -24,6 +24,7 @@ import ( import ( perrors "github.com/pkg/errors" ) + import ( "github.com/apache/dubbo-go/config" ) From f2d2ba60f206a181b6798161e5f4b143b8eb4474 Mon Sep 17 00:00:00 2001 From: aliiohs Date: Wed, 12 Aug 2020 19:19:00 +0800 Subject: [PATCH 153/242] make a comment variable --- protocol/dubbo/pool.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/protocol/dubbo/pool.go b/protocol/dubbo/pool.go index b0de26ad87..59a81d6e9d 100644 --- a/protocol/dubbo/pool.go +++ b/protocol/dubbo/pool.go @@ -55,8 +55,12 @@ var ( ) func newGettyRPCClientConn(pool *gettyRPCClientPool, protocol, addr string) (*gettyRPCClient, error) { - var gettyClient getty.Client - if pool.sslEnabled { + var ( + gettyClient getty.Client + sslEnabled bool + ) + sslEnabled = pool.sslEnabled + if sslEnabled { gettyClient = getty.NewTCPClient( getty.WithServerAddress(addr), getty.WithConnectionNumber((int)(pool.rpcClient.conf.ConnectionNum)), @@ -108,16 +112,18 @@ func (c *gettyRPCClient) getActive() int64 { func (c *gettyRPCClient) newSession(session getty.Session) error { var ( - ok bool - tcpConn *net.TCPConn - conf ClientConfig + ok bool + tcpConn *net.TCPConn + conf ClientConfig + sslEnabled bool ) conf = c.pool.rpcClient.conf + sslEnabled = c.pool.sslEnabled if conf.GettySessionParam.CompressEncoding { session.SetCompressType(getty.CompressZip) } - if c.pool.sslEnabled { + if sslEnabled { if _, ok = session.Conn().(*tls.Conn); !ok { panic(fmt.Sprintf("%s, session.conn{%#v} is not tls connection\n", session.Stat(), session.Conn())) } From 6cf2dc84e6cea7d95d55bc976de80db893bc5022 Mon Sep 17 00:00:00 2001 From: watermelo <80680489@qq.com> Date: Wed, 12 Aug 2020 20:13:00 +0800 Subject: [PATCH 154/242] Mod: move match codes to gost net --- cluster/router/chain/chain.go | 2 +- cluster/router/tag/router_rule.go | 8 +- cluster/router/tag/tag.go | 16 ---- cluster/router/tag/tag_router.go | 139 +----------------------------- go.mod | 2 +- go.sum | 2 + 6 files changed, 12 insertions(+), 157 deletions(-) diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go index 1fd6fb5e24..6ae9920e0a 100644 --- a/cluster/router/chain/chain.go +++ b/cluster/router/chain/chain.go @@ -65,7 +65,7 @@ func (c *RouterChain) Route(invokers []protocol.Invoker, url *common.URL, invoca return finalInvokers } -// Notify router chain of the initial addresses from registry at the first time. Notify whenever addresses in registry change. +// SetInvokers router chain of the initial addresses from registry at the first time. Notify whenever addresses in registry change. func (c *RouterChain) SetInvokers(invokers []protocol.Invoker) { for _, r := range c.routers { if notifyRouter, ok := r.(router.NotifyRouter); ok { diff --git a/cluster/router/tag/router_rule.go b/cluster/router/tag/router_rule.go index 78479c0f1d..b35d455a6e 100644 --- a/cluster/router/tag/router_rule.go +++ b/cluster/router/tag/router_rule.go @@ -39,7 +39,7 @@ import ( */ // RouterRule RouterRule config read from config file or config center type RouterRule struct { - router.BaseRouterRule `yaml:",inline""` + router.BaseRouterRule `yaml:",inline"` Tags []Tag addressToTagNames map[string][]string tagNameToAddresses map[string][]string @@ -52,12 +52,12 @@ func getRule(rawRule string) (*RouterRule, error) { return r, err } r.RawRule = rawRule - r.init() + r.parseTags() return r, nil } -// init use for flattening tags data to @addressToTagNames and @tagNameToAddresses -func (t *RouterRule) init() { +// parseTags use for flattening tags data to @addressToTagNames and @tagNameToAddresses +func (t *RouterRule) parseTags() { t.addressToTagNames = make(map[string][]string, 2*len(t.Tags)) t.tagNameToAddresses = make(map[string][]string, len(t.Tags)) for _, tag := range t.Tags { diff --git a/cluster/router/tag/tag.go b/cluster/router/tag/tag.go index 73d10b5db4..468b426a4c 100644 --- a/cluster/router/tag/tag.go +++ b/cluster/router/tag/tag.go @@ -21,19 +21,3 @@ type Tag struct { Name string Addresses []string } - -func (t *Tag) getName() string { - return t.Name -} - -func (t *Tag) setName(name string) { - t.Name = name -} - -func (t *Tag) getAddresses() []string { - return t.Addresses -} - -func (t *Tag) setAddresses(addresses []string) { - t.Addresses = addresses -} diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index c5bc87675b..c3ee3cbc16 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -18,13 +18,12 @@ package tag import ( - "errors" "net" "strconv" - "strings" ) import ( + gxnet "github.com/dubbogo/gost/net" perrors "github.com/pkg/errors" ) @@ -272,144 +271,14 @@ OUTER: } func checkAddressMatch(addresses []string, host, port string) bool { + addr := net.JoinHostPort(constant.ANYHOST_VALUE, port) for _, address := range addresses { - if matchIp(address, host, port) { + if gxnet.MatchIP(address, host, port) { return true } - if address == net.JoinHostPort(constant.ANYHOST_VALUE, port) { + if address == addr { return true } } return false } - -// TODO: Already moved to dubbogo/gost, after gost by merged the follows codes will be deleted. -func matchIp(pattern, host, port string) bool { - // if the pattern is subnet format, it will not be allowed to config port param in pattern. - if strings.Contains(pattern, "/") { - _, subnet, _ := net.ParseCIDR(pattern) - if subnet != nil && subnet.Contains(net.ParseIP(host)) { - return true - } - return false - } - return matchIpRange(pattern, host, port) -} - -func matchIpRange(pattern, host, port string) bool { - if pattern == "" || host == "" { - logger.Error("Illegal Argument pattern or hostName. Pattern:" + pattern + ", Host:" + host) - return false - } - - pattern = strings.TrimSpace(pattern) - if "*.*.*.*" == pattern || "*" == pattern { - return true - } - - isIpv4 := true - ip4 := net.ParseIP(host).To4() - - if ip4 == nil { - isIpv4 = false - } - - hostAndPort := getPatternHostAndPort(pattern, isIpv4) - if hostAndPort[1] != "" && hostAndPort[1] != port { - return false - } - - pattern = hostAndPort[0] - splitCharacter := "." - if !isIpv4 { - splitCharacter = ":" - } - - mask := strings.Split(pattern, splitCharacter) - // check format of pattern - if err := checkHostPattern(pattern, mask, isIpv4); err != nil { - logger.Error(err) - return false - } - - if pattern == host { - return true - } - - // short name condition - if !ipPatternContains(pattern) { - return pattern == host - } - - ipAddress := strings.Split(host, splitCharacter) - for i := 0; i < len(mask); i++ { - if "*" == mask[i] || mask[i] == ipAddress[i] { - continue - } else if strings.Contains(mask[i], "-") { - rangeNumStrs := strings.Split(mask[i], "-") - if len(rangeNumStrs) != 2 { - logger.Error("There is wrong format of ip Address: " + mask[i]) - return false - } - min := getNumOfIpSegment(rangeNumStrs[0], isIpv4) - max := getNumOfIpSegment(rangeNumStrs[1], isIpv4) - ip := getNumOfIpSegment(ipAddress[i], isIpv4) - if ip < min || ip > max { - return false - } - } else if "0" == ipAddress[i] && "0" == mask[i] || "00" == mask[i] || "000" == mask[i] || "0000" == mask[i] { - continue - } else if mask[i] != ipAddress[i] { - return false - } - } - return true -} - -func ipPatternContains(pattern string) bool { - return strings.Contains(pattern, "*") || strings.Contains(pattern, "-") -} - -func checkHostPattern(pattern string, mask []string, isIpv4 bool) error { - if !isIpv4 { - if len(mask) != 8 && ipPatternContains(pattern) { - return errors.New("If you config ip expression that contains '*' or '-', please fill qualified ip pattern like 234e:0:4567:0:0:0:3d:*. ") - } - if len(mask) != 8 && !strings.Contains(pattern, "::") { - return errors.New("The host is ipv6, but the pattern is not ipv6 pattern : " + pattern) - } - } else { - if len(mask) != 4 { - return errors.New("The host is ipv4, but the pattern is not ipv4 pattern : " + pattern) - } - } - return nil -} - -func getPatternHostAndPort(pattern string, isIpv4 bool) []string { - result := make([]string, 2) - if strings.HasPrefix(pattern, "[") && strings.Contains(pattern, "]:") { - end := strings.Index(pattern, "]:") - result[0] = pattern[1:end] - result[1] = pattern[end+2:] - } else if strings.HasPrefix(pattern, "[") && strings.HasSuffix(pattern, "]") { - result[0] = pattern[1 : len(pattern)-1] - result[1] = "" - } else if isIpv4 && strings.Contains(pattern, ":") { - end := strings.Index(pattern, ":") - result[0] = pattern[:end] - result[1] = pattern[end+1:] - } else { - result[0] = pattern - } - return result -} - -func getNumOfIpSegment(ipSegment string, isIpv4 bool) int { - if isIpv4 { - ipSeg, _ := strconv.Atoi(ipSegment) - return ipSeg - } - ipSeg, _ := strconv.ParseInt(ipSegment, 0, 16) - return int(ipSeg) -} diff --git a/go.mod b/go.mod index 197f2a3012..30f7a7770b 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/docker/go-connections v0.4.0 // indirect github.com/dubbogo/getty v1.3.7 github.com/dubbogo/go-zookeeper v1.0.1 - github.com/dubbogo/gost v1.9.0 + github.com/dubbogo/gost v1.9.1 github.com/elazarl/go-bindata-assetfs v1.0.0 // indirect github.com/emicklei/go-restful/v3 v3.0.0 github.com/frankban/quicktest v1.4.1 // indirect diff --git a/go.sum b/go.sum index 6df5fc5248..aa46f2164d 100644 --- a/go.sum +++ b/go.sum @@ -149,6 +149,8 @@ github.com/dubbogo/go-zookeeper v1.0.1 h1:irLzvOsDOTNsN8Sv9tvYYxVu6DCQfLtziZQtUH github.com/dubbogo/go-zookeeper v1.0.1/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c= github.com/dubbogo/gost v1.9.0 h1:UT+dWwvLyJiDotxJERO75jB3Yxgsdy10KztR5ycxRAk= github.com/dubbogo/gost v1.9.0/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= +github.com/dubbogo/gost v1.9.1 h1:0/PPFo13zPbjt4Ia0zYWMFi3C6rAe9X7O1J2Iv+BHNM= +github.com/dubbogo/gost v1.9.1/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0 h1:ZoRgc53qJCfSLimXqJDrmBhnt5GChDsExMCK7t48o0Y= github.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= From 0121001d024543fa0f633b54aaba1e91a52670f0 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Wed, 12 Aug 2020 20:21:06 +0800 Subject: [PATCH 155/242] use mutex for invokers set and get --- cluster/router/chain/chain.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go index 9148679e0e..091bcaa051 100644 --- a/cluster/router/chain/chain.go +++ b/cluster/router/chain/chain.go @@ -72,6 +72,8 @@ type RouterChain struct { func (c *RouterChain) Route(url *common.URL, invocation protocol.Invocation) []protocol.Invoker { cache := c.loadCache() if cache == nil { + c.mutex.RLock() + defer c.mutex.RUnlock() return c.invokers } @@ -106,7 +108,9 @@ func (c *RouterChain) AddRouters(routers []router.PriorityRouter) { // SetInvokers receives updated invokers from registry center. If the times of notification exceeds countThreshold and // time interval exceeds timeThreshold since last cache update, then notify to update the cache. func (c *RouterChain) SetInvokers(invokers []protocol.Invoker) { + c.mutex.Lock() c.invokers = invokers + c.mutex.Unlock() c.count++ now := time.Now() From 6708d2491ce7490f407aab0f1401cb3f74186073 Mon Sep 17 00:00:00 2001 From: watermelo <80680489@qq.com> Date: Wed, 12 Aug 2020 20:26:19 +0800 Subject: [PATCH 156/242] Mod: modify comments --- cluster/router/chain/chain.go | 2 +- cluster/router/tag/tag_router.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go index 6ae9920e0a..8746c1daf7 100644 --- a/cluster/router/chain/chain.go +++ b/cluster/router/chain/chain.go @@ -65,7 +65,7 @@ func (c *RouterChain) Route(invokers []protocol.Invoker, url *common.URL, invoca return finalInvokers } -// SetInvokers router chain of the initial addresses from registry at the first time. Notify whenever addresses in registry change. +// SetInvokers notify router chain of the initial addresses from registry at the first time. Notify whenever addresses in registry change. func (c *RouterChain) SetInvokers(invokers []protocol.Invoker) { for _, r := range c.routers { if notifyRouter, ok := r.(router.NotifyRouter); ok { diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index c3ee3cbc16..1a4b74d486 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -146,7 +146,7 @@ func (c *tagRouter) Notify(invokers []protocol.Invoker) { invoker := invokers[0] url := invoker.GetUrl() providerApplication := url.GetParam(constant.RemoteApplicationKey, "") - if providerApplication == "" { + if len(providerApplication) == 0 { logger.Error("TagRouter must getConfig from or subscribe to a specific application, but the application " + "in this TagRouter is not specified.") return From 8458c8872a1f2d3ee58b4dddfcfedeffc3af0b54 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Thu, 13 Aug 2020 10:41:55 +0800 Subject: [PATCH 157/242] use defer to unlock --- cluster/router/chain/chain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go index 091bcaa051..1c7d48aef9 100644 --- a/cluster/router/chain/chain.go +++ b/cluster/router/chain/chain.go @@ -188,9 +188,9 @@ func (c *RouterChain) buildCache() { go func(p router.Poolable) { pool, info := poolRouter(p, origin, invokers) mutex.Lock() + defer mutex.Unlock() cache.pools[p.Name()] = pool cache.metadatas[p.Name()] = info - mutex.Unlock() wg.Done() }(p) } From e46939b5def8927ed66aa5655492fd70db713451 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Thu, 13 Aug 2020 13:24:57 +0800 Subject: [PATCH 158/242] code polish for else branch --- cluster/router/chain/chain.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go index 1c7d48aef9..0ab2c781fb 100644 --- a/cluster/router/chain/chain.go +++ b/cluster/router/chain/chain.go @@ -249,10 +249,10 @@ func poolRouter(p router.Poolable, origin *InvokerCache, invokers []protocol.Inv if isCacheMiss(origin, name) || p.ShouldPool() || isInvokersChanged(origin.invokers, invokers) { logger.Debugf("build address cache for router %q", name) return p.Pool(invokers) - } else { - logger.Debugf("reuse existing address cache for router %q", name) - return origin.pools[name], origin.metadatas[name] } + + logger.Debugf("reuse existing address cache for router %q", name) + return origin.pools[name], origin.metadatas[name] } // isCacheMiss checks if the corresponding cache entry for a poolable router has already existed. From 9c1291997a59b3daecd7b47e8dce0de96497b6b1 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Thu, 13 Aug 2020 13:29:13 +0800 Subject: [PATCH 159/242] make sure lock for invokers correctly --- cluster/router/chain/chain.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go index 0ab2c781fb..0e9bcd8db7 100644 --- a/cluster/router/chain/chain.go +++ b/cluster/router/chain/chain.go @@ -147,6 +147,9 @@ func (c *RouterChain) copyRouters() []router.PriorityRouter { // copyInvokers copies a snapshot of the received invokers. func (c *RouterChain) copyInvokers() []protocol.Invoker { c.mutex.RLock() + if c.invokers == nil || len(c.invokers) == 0 { + return nil + } ret := copySlice(c.invokers) c.mutex.RUnlock() return ret.([]protocol.Invoker) @@ -171,11 +174,11 @@ func (c *RouterChain) loadCache() *InvokerCache { // buildCache builds address cache with the new invokers for all poolable routers. func (c *RouterChain) buildCache() { - if c.invokers == nil || len(c.invokers) == 0 { + invokers := c.copyInvokers() + if invokers == nil || len(c.invokers) == 0 { return } - invokers := c.copyInvokers() cache := BuildCache(invokers) origin := c.loadCache() From 37ebc63c0847065331532f1b433ba229c7fbb1b1 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Thu, 13 Aug 2020 13:32:54 +0800 Subject: [PATCH 160/242] code polish for else branch --- cluster/router/utils/bitmap_util.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cluster/router/utils/bitmap_util.go b/cluster/router/utils/bitmap_util.go index b34ecf8933..8b4ee5538f 100644 --- a/cluster/router/utils/bitmap_util.go +++ b/cluster/router/utils/bitmap_util.go @@ -39,9 +39,8 @@ func FallbackIfJoinToEmpty(left *roaring.Bitmap, right *roaring.Bitmap) *roaring ret := JoinIfNotEqual(left, right) if ret == nil || ret.IsEmpty() { return right - } else { - return ret } + return ret } func ToBitmap(invokers []protocol.Invoker) *roaring.Bitmap { From 88e097f3854c010cb54d2c5005e9fb3fd345e67b Mon Sep 17 00:00:00 2001 From: aliiohs Date: Thu, 13 Aug 2020 22:00:55 +0800 Subject: [PATCH 161/242] fix --- config/consumer_config.go | 9 +++------ go.mod | 2 +- go.sum | 4 ++-- protocol/dubbo/config.go | 9 +++++---- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/config/consumer_config.go b/config/consumer_config.go index 9d283eeca7..09c43d886c 100644 --- a/config/consumer_config.go +++ b/config/consumer_config.go @@ -19,6 +19,7 @@ package config import ( "bytes" + "github.com/dubbogo/getty" "time" ) @@ -33,10 +34,6 @@ import ( "github.com/apache/dubbo-go/common/yaml" ) -const ( - MaxWheelTimeSpan = 900e9 // 900s, 15 minute -) - ///////////////////////// // consumerConfig ///////////////////////// @@ -110,9 +107,9 @@ func ConsumerInit(confConFile string) error { if consumerConfig.RequestTimeout, err = time.ParseDuration(consumerConfig.Request_Timeout); err != nil { return perrors.WithMessagef(err, "time.ParseDuration(Request_Timeout{%#v})", consumerConfig.Request_Timeout) } - if consumerConfig.RequestTimeout >= time.Duration(MaxWheelTimeSpan) { + if consumerConfig.RequestTimeout >= time.Duration(getty.MaxWheelTimeSpan) { return perrors.WithMessagef(err, "request_timeout %s should be less than %s", - consumerConfig.Request_Timeout, time.Duration(MaxWheelTimeSpan)) + consumerConfig.Request_Timeout, time.Duration(getty.MaxWheelTimeSpan)) } } if consumerConfig.Connect_Timeout != "" { diff --git a/go.mod b/go.mod index 22912f31bb..5d14bfb8f4 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect github.com/creasty/defaults v1.3.0 github.com/docker/go-connections v0.4.0 // indirect - github.com/dubbogo/getty v1.3.8 + github.com/dubbogo/getty v1.3.9 github.com/dubbogo/go-zookeeper v1.0.1 github.com/dubbogo/gost v1.9.0 github.com/elazarl/go-bindata-assetfs v1.0.0 // indirect diff --git a/go.sum b/go.sum index 812afcaaee..59f6ca8b8a 100644 --- a/go.sum +++ b/go.sum @@ -133,8 +133,8 @@ github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5Xh github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/dubbogo/getty v1.3.8 h1:D9VQLlO4df0H+k8lEW+Re1hGymxzQjvvEKOzGNI35sI= -github.com/dubbogo/getty v1.3.8/go.mod h1:JIN5lNZznGnBuoYjlEe/yjNmq7eSjKaawHdnBAdtiEo= +github.com/dubbogo/getty v1.3.9 h1:Ip/4Yl7GyDzt3ddZhjXN1Vmxvo9y/hdtoJ4yFc7czLk= +github.com/dubbogo/getty v1.3.9/go.mod h1:JIN5lNZznGnBuoYjlEe/yjNmq7eSjKaawHdnBAdtiEo= github.com/dubbogo/go-zookeeper v1.0.1 h1:irLzvOsDOTNsN8Sv9tvYYxVu6DCQfLtziZQtUHmZgz8= github.com/dubbogo/go-zookeeper v1.0.1/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c= github.com/dubbogo/gost v1.9.0 h1:UT+dWwvLyJiDotxJERO75jB3Yxgsdy10KztR5ycxRAk= diff --git a/protocol/dubbo/config.go b/protocol/dubbo/config.go index b47ec1cc34..142ce81f1e 100644 --- a/protocol/dubbo/config.go +++ b/protocol/dubbo/config.go @@ -18,6 +18,7 @@ package dubbo import ( + "github.com/dubbogo/getty" "time" ) @@ -181,9 +182,9 @@ func (c *ClientConfig) CheckValidity() error { return perrors.WithMessagef(err, "time.ParseDuration(HeartbeatPeroid{%#v})", c.HeartbeatPeriod) } - if c.heartbeatPeriod >= time.Duration(config.MaxWheelTimeSpan) { + if c.heartbeatPeriod >= time.Duration(getty.MaxWheelTimeSpan) { return perrors.WithMessagef(err, "heartbeat_period %s should be less than %s", - c.HeartbeatPeriod, time.Duration(config.MaxWheelTimeSpan)) + c.HeartbeatPeriod, time.Duration(getty.MaxWheelTimeSpan)) } if c.sessionTimeout, err = time.ParseDuration(c.SessionTimeout); err != nil { @@ -201,9 +202,9 @@ func (c *ServerConfig) CheckValidity() error { return perrors.WithMessagef(err, "time.ParseDuration(SessionTimeout{%#v})", c.SessionTimeout) } - if c.sessionTimeout >= time.Duration(config.MaxWheelTimeSpan) { + if c.sessionTimeout >= time.Duration(getty.MaxWheelTimeSpan) { return perrors.WithMessagef(err, "session_timeout %s should be less than %s", - c.SessionTimeout, time.Duration(config.MaxWheelTimeSpan)) + c.SessionTimeout, time.Duration(getty.MaxWheelTimeSpan)) } return perrors.WithStack(c.GettySessionParam.CheckValidity()) From d0345ecbad6c8587a99f2305002e390856f90e13 Mon Sep 17 00:00:00 2001 From: aliiohs Date: Thu, 13 Aug 2020 22:04:27 +0800 Subject: [PATCH 162/242] fix --- protocol/dubbo/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/dubbo/server.go b/protocol/dubbo/server.go index 638d822083..c02c2ac193 100644 --- a/protocol/dubbo/server.go +++ b/protocol/dubbo/server.go @@ -138,7 +138,7 @@ func (s *Server) newSession(session getty.Session) error { session.SetWriteTimeout(conf.GettySessionParam.tcpWriteTimeout) session.SetCronPeriod((int)(conf.sessionTimeout.Nanoseconds() / 1e6)) session.SetWaitTime(conf.GettySessionParam.waitTimeout) - logger.Debugf("app accepts new session:%s\n", session.Stat()) + logger.Debugf("server accepts new session:%s\n", session.Stat()) session.SetTaskPool(srvGrpool) return nil } From 622e6c50a8a8dbb1a456cfee32f12465a0118e9e Mon Sep 17 00:00:00 2001 From: aliiohs Date: Fri, 14 Aug 2020 00:08:49 +0800 Subject: [PATCH 163/242] fix --- protocol/dubbo/config.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/protocol/dubbo/config.go b/protocol/dubbo/config.go index 142ce81f1e..635d12109a 100644 --- a/protocol/dubbo/config.go +++ b/protocol/dubbo/config.go @@ -18,18 +18,14 @@ package dubbo import ( - "github.com/dubbogo/getty" "time" ) import ( + "github.com/dubbogo/getty" perrors "github.com/pkg/errors" ) -import ( - "github.com/apache/dubbo-go/config" -) - type ( // GettySessionParam is session configuration for getty. GettySessionParam struct { From c7e052229b1d99b8638cb7ad4310af3afdc1926d Mon Sep 17 00:00:00 2001 From: aliiohs Date: Fri, 14 Aug 2020 00:10:06 +0800 Subject: [PATCH 164/242] fix --- config/consumer_config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/consumer_config.go b/config/consumer_config.go index 09c43d886c..1775312092 100644 --- a/config/consumer_config.go +++ b/config/consumer_config.go @@ -19,12 +19,12 @@ package config import ( "bytes" - "github.com/dubbogo/getty" "time" ) import ( "github.com/creasty/defaults" + "github.com/dubbogo/getty" perrors "github.com/pkg/errors" ) From b88d28fb7e0d04c56bde4900d694bb8635952464 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Fri, 14 Aug 2020 08:57:30 +0800 Subject: [PATCH 165/242] make router.AddrPool with initial size --- cluster/router/healthcheck/health_check_route.go | 2 +- cluster/router/tag/tag_router.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cluster/router/healthcheck/health_check_route.go b/cluster/router/healthcheck/health_check_route.go index 06eea3b937..3a973193a5 100644 --- a/cluster/router/healthcheck/health_check_route.go +++ b/cluster/router/healthcheck/health_check_route.go @@ -80,7 +80,7 @@ func (r *HealthCheckRouter) Pool(invokers []protocol.Invoker) (router.AddrPool, return nil, nil } - rb := make(router.AddrPool) + rb := make(router.AddrPool, 8) rb[healthy] = roaring.NewBitmap() for i, invoker := range invokers { if r.checker.IsHealthy(invoker) { diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index 5f0ff0a17b..8f3a3c55f6 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -97,7 +97,7 @@ func (c *tagRouter) Priority() int64 { // Pool divided invokers into different address pool by tag. func (c *tagRouter) Pool(invokers []protocol.Invoker) (router.AddrPool, router.AddrMetadata) { - rb := make(router.AddrPool) + rb := make(router.AddrPool, 8) for i, invoker := range invokers { url := invoker.GetUrl() tag := url.GetParam(constant.Tagkey, "") From 7425cd760830a5a4decbe89e841e320630de3bbc Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Fri, 14 Aug 2020 08:59:18 +0800 Subject: [PATCH 166/242] correct unlock --- cluster/router/chain/chain.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go index 0e9bcd8db7..2ceb231b79 100644 --- a/cluster/router/chain/chain.go +++ b/cluster/router/chain/chain.go @@ -139,19 +139,19 @@ func (c *RouterChain) loop() { // copyRouters make a snapshot copy from RouterChain's router list. func (c *RouterChain) copyRouters() []router.PriorityRouter { c.mutex.RLock() + defer c.mutex.RUnlock() ret := copySlice(c.routers) - c.mutex.RUnlock() return ret.([]router.PriorityRouter) } // copyInvokers copies a snapshot of the received invokers. func (c *RouterChain) copyInvokers() []protocol.Invoker { c.mutex.RLock() + defer c.mutex.RUnlock() if c.invokers == nil || len(c.invokers) == 0 { return nil } ret := copySlice(c.invokers) - c.mutex.RUnlock() return ret.([]protocol.Invoker) } From 21e572c74be6febe7293993e0a10088525fdb6ca Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Fri, 14 Aug 2020 09:02:33 +0800 Subject: [PATCH 167/242] rename ch to notify --- cluster/router/chain/chain.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go index 2ceb231b79..80f0b25d8a 100644 --- a/cluster/router/chain/chain.go +++ b/cluster/router/chain/chain.go @@ -63,7 +63,7 @@ type RouterChain struct { // The timestamp of last update for address cache last time.Time // Channel for notify to update the address cache - ch chan struct{} + notify chan struct{} // Address cache cache atomic.Value } @@ -118,7 +118,7 @@ func (c *RouterChain) SetInvokers(invokers []protocol.Invoker) { c.last = now c.count = 0 go func() { - c.ch <- struct{}{} + c.notify <- struct{}{} }() } } @@ -130,7 +130,7 @@ func (c *RouterChain) loop() { select { case <-time.Tick(timeInterval): c.buildCache() - case <-c.ch: + case <-c.notify: c.buildCache() } } @@ -234,7 +234,7 @@ func NewRouterChain(url *common.URL) (*RouterChain, error) { builtinRouters: routers, routers: newRouters, last: time.Now(), - ch: make(chan struct{}), + notify: make(chan struct{}), } if url != nil { chain.url = *url From 0da31510925e105e217a5e4fad5cce7cfb8129a5 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Fri, 14 Aug 2020 09:03:51 +0800 Subject: [PATCH 168/242] make map with init size --- cluster/router/chain/invoker_cache.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cluster/router/chain/invoker_cache.go b/cluster/router/chain/invoker_cache.go index 9f96bddd42..43cdfa5067 100644 --- a/cluster/router/chain/invoker_cache.go +++ b/cluster/router/chain/invoker_cache.go @@ -49,8 +49,8 @@ func BuildCache(invokers []protocol.Invoker) *InvokerCache { return &InvokerCache{ invokers: invokers, bitmap: utils.ToBitmap(invokers), - pools: make(map[string]router.AddrPool), - metadatas: make(map[string]router.AddrMetadata), + pools: make(map[string]router.AddrPool, 8), + metadatas: make(map[string]router.AddrMetadata, 8), } } From 0a475bf190768510307eb2f7c12ad4316090be8d Mon Sep 17 00:00:00 2001 From: watermelo <80680489@qq.com> Date: Fri, 14 Aug 2020 10:26:17 +0800 Subject: [PATCH 169/242] Mod: add router tag unit tests scripts --- before_ut.bat | 3 +++ cluster/router/tag/tag_router.go | 32 +++++++++++++++----------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/before_ut.bat b/before_ut.bat index 5d2b9e4682..7f5cf50e90 100644 --- a/before_ut.bat +++ b/before_ut.bat @@ -36,5 +36,8 @@ xcopy /f "%zkJar%" "cluster/router/chain/zookeeper-4unittest/contrib/fatjar/" md cluster\router\condition\zookeeper-4unittest\contrib\fatjar xcopy /f "%zkJar%" "cluster/router/condition/zookeeper-4unittest/contrib/fatjar/" +mkdir -p cluster/router/tag/zookeeper-4unittest/contrib/fatjar +cp ${zkJar} cluster/router/tag/zookeeper-4unittest/contrib/fatjar + md metadata\report\zookeeper\zookeeper-4unittest\contrib\fatjar xcopy /f "%zkJar%" "metadata/report/zookeeper/zookeeper-4unittest/contrib/fatjar/" \ No newline at end of file diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index 1a4b74d486..bd5c359524 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -221,29 +221,27 @@ func filterInvokersWithTag(invokers []protocol.Invoker, url *common.URL, invocat if len(result) > 0 || tagRouterRule.Force { return result } - } else { - // dynamic tag group doesn't have any item about the requested app OR it's null after filtered by - // dynamic tag group but force=false. check static tag - filter := func(invoker protocol.Invoker) bool { - return invoker.GetUrl().GetParam(constant.Tagkey, "") == tag - } - result = filterInvoker(invokers, filter) } + // dynamic tag group doesn't have any item about the requested app OR it's null after filtered by + // dynamic tag group but force=false. check static tag + filter := func(invoker protocol.Invoker) bool { + return invoker.GetUrl().GetParam(constant.Tagkey, "") == tag + } + result = filterInvoker(invokers, filter) // If there's no tagged providers that can match the current tagged request. force.tag is set by default // to false, which means it will invoke any providers without a tag unless it's explicitly disallowed. if len(result) > 0 || isForceUseTag(url, invocation) { return result - } else { - // FAILOVER: return all Providers without any tags. - filterAddressNotMatches := func(invoker protocol.Invoker) bool { - url := invoker.GetUrl() - return len(addresses) == 0 || !checkAddressMatch(tagRouterRule.getAddresses(), url.Ip, url.Port) - } - filterTagIsEmpty := func(invoker protocol.Invoker) bool { - return invoker.GetUrl().GetParam(constant.Tagkey, "") == "" - } - return filterInvoker(invokers, filterAddressNotMatches, filterTagIsEmpty) } + // FAILOVER: return all Providers without any tags. + filterAddressNotMatches := func(invoker protocol.Invoker) bool { + url := invoker.GetUrl() + return len(addresses) == 0 || !checkAddressMatch(tagRouterRule.getAddresses(), url.Ip, url.Port) + } + filterTagIsEmpty := func(invoker protocol.Invoker) bool { + return invoker.GetUrl().GetParam(constant.Tagkey, "") == "" + } + return filterInvoker(invokers, filterAddressNotMatches, filterTagIsEmpty) } // isForceUseTag returns whether force use tag From 2756d13b6a3aedf9bc2e09b4ac5d02f62910d942 Mon Sep 17 00:00:00 2001 From: isaaczhang Date: Sat, 15 Aug 2020 17:13:33 +0800 Subject: [PATCH 170/242] upgrade arch png --- doc/pic/arch/dubbo-go-arch.png | Bin 131372 -> 190013 bytes doc/pic/arch/dubbo-go-ext.png | Bin 76469 -> 194550 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/pic/arch/dubbo-go-arch.png b/doc/pic/arch/dubbo-go-arch.png index e5f192715216257929cf4a550a1adf3588ab0e0f..0a28cb6a3af6953d2ad10390ddff5b7d82166ecc 100644 GIT binary patch literal 190013 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

ahe|i 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 Cm)48` literal 131372 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

g8Umc>=0jY{bJo|C+! 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 lPWfZI3+tpx~#DSINvoYvCS%vLwM`#=1nngaj; diff --git a/doc/pic/arch/dubbo-go-ext.png b/doc/pic/arch/dubbo-go-ext.png index d065ef4d8e28e507b2ee36fd8c6c6928ab7c9b5d..b60ecdf47ef190f097fccaf2d74070f6712ea80d 100644 GIT binary patch literal 194550 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%0A$jvoEi!;| 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

HF=fZ+V%iNpNTPfA>U$K!CYQ1tNL>`y-? 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 TX*f)iw0QeJiVB0l literal 76469 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%0A`%R&<2pmRglib0F6`hZ* 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 Date: Sun, 16 Aug 2020 19:58:19 +0800 Subject: [PATCH 171/242] merge 1.4 --- config_center/apollo/impl.go | 3 +- config_center/nacos/client.go | 2 +- go.mod | 3 -- registry/nacos/registry.go | 76 ----------------------------------- 4 files changed, 3 insertions(+), 81 deletions(-) diff --git a/config_center/apollo/impl.go b/config_center/apollo/impl.go index 5164f5d1cf..8030a2c800 100644 --- a/config_center/apollo/impl.go +++ b/config_center/apollo/impl.go @@ -25,7 +25,8 @@ import ( ) import ( - "github.com/pkg/errors" + gxset "github.com/dubbogo/gost/container/set" + perrors "github.com/pkg/errors" "github.com/zouyx/agollo/v3" agolloConstant "github.com/zouyx/agollo/v3/constant" "github.com/zouyx/agollo/v3/env/config" diff --git a/config_center/nacos/client.go b/config_center/nacos/client.go index e8d18d8487..acfe2609ce 100644 --- a/config_center/nacos/client.go +++ b/config_center/nacos/client.go @@ -176,7 +176,7 @@ func initNacosConfigClient(nacosAddrs []string, timeout time.Duration, url commo Endpoint: url.GetParam(constant.NACOS_ENDPOINT, ""), Username: url.GetParam(constant.NACOS_USERNAME, ""), Password: url.GetParam(constant.NACOS_PASSWORD, ""), - NamespaceId: url.GetParam(constant.NACOS_NAMESPACEID, ""), + NamespaceId: url.GetParam(constant.NACOS_NAMESPACE_ID, ""), }, }) } diff --git a/go.mod b/go.mod index 09d2a388cb..0e2e2d6876 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,6 @@ module github.com/apache/dubbo-go require ( - cloud.google.com/go v0.39.0 // indirect github.com/Microsoft/go-winio v0.4.13 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/Workiva/go-datastructures v1.0.50 @@ -46,14 +45,12 @@ require ( github.com/prometheus/client_golang v1.1.0 github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b github.com/shirou/gopsutil v2.19.9+incompatible // indirect - github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 // indirect github.com/stretchr/objx v0.2.0 // indirect github.com/stretchr/testify v1.5.1 github.com/zouyx/agollo/v3 v3.4.4 go.etcd.io/bbolt v1.3.4 // indirect go.uber.org/atomic v1.6.0 go.uber.org/zap v1.15.0 - google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64 // indirect google.golang.org/grpc v1.23.0 gopkg.in/yaml.v2 v2.2.8 k8s.io/api v0.16.9 diff --git a/registry/nacos/registry.go b/registry/nacos/registry.go index d524d0b41c..411090820c 100644 --- a/registry/nacos/registry.go +++ b/registry/nacos/registry.go @@ -62,66 +62,6 @@ type nacosRegistry struct { registryUrls []common.URL } -func getNacosConfig(url *common.URL) (map[string]interface{}, error) { - if url == nil { - return nil, perrors.New("url is empty!") - } - if len(url.Location) == 0 { - return nil, perrors.New("url.location is empty!") - } - configMap := make(map[string]interface{}, 2) - - addresses := strings.Split(url.Location, ",") - serverConfigs := make([]nacosConstant.ServerConfig, 0, len(addresses)) - for _, addr := range addresses { - ip, portStr, err := net.SplitHostPort(addr) - if err != nil { - return nil, perrors.WithMessagef(err, "split [%s] ", addr) - } - port, _ := strconv.Atoi(portStr) - serverConfigs = append(serverConfigs, nacosConstant.ServerConfig{ - IpAddr: ip, - Port: uint64(port), - }) - } - configMap["serverConfigs"] = serverConfigs - - var clientConfig nacosConstant.ClientConfig - timeout, err := time.ParseDuration(url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT)) - if err != nil { - return nil, err - } - clientConfig.TimeoutMs = uint64(timeout.Seconds() * 1000) - clientConfig.ListenInterval = 2 * clientConfig.TimeoutMs - clientConfig.CacheDir = url.GetParam(constant.NACOS_CACHE_DIR_KEY, "") - clientConfig.LogDir = url.GetParam(constant.NACOS_LOG_DIR_KEY, "") - clientConfig.Endpoint = url.GetParam(constant.NACOS_ENDPOINT, "") - clientConfig.Username = url.GetParam(constant.NACOS_USERNAME, "") - clientConfig.Password = url.GetParam(constant.NACOS_PASSWORD, "") - clientConfig.NamespaceId = url.GetParam(constant.NACOS_NAMESPACEID, "") - clientConfig.NotLoadCacheAtStart = true - configMap["clientConfig"] = clientConfig - - return configMap, nil -} - -func newNacosRegistry(url *common.URL) (registry.Registry, error) { - nacosConfig, err := getNacosConfig(url) - if err != nil { - return nil, err - } - client, err := clients.CreateNamingClient(nacosConfig) - if err != nil { - return nil, err - } - registry := nacosRegistry{ - URL: url, - namingClient: client, - registryUrls: []common.URL{}, - } - return ®istry, nil -} - func getCategory(url common.URL) string { role, _ := strconv.Atoi(url.GetParam(constant.ROLE_KEY, strconv.Itoa(constant.NACOS_DEFAULT_ROLETYPE))) category := common.DubboNodes[role] @@ -178,22 +118,6 @@ func createRegisterParam(url common.URL, serviceName string) vo.RegisterInstance return instance } -func createDeregisterParam(url common.URL, serviceName string) vo.DeregisterInstanceParam { - if len(url.Ip) == 0 { - url.Ip = localIP - } - if len(url.Port) == 0 || url.Port == "0" { - url.Port = "80" - } - port, _ := strconv.Atoi(url.Port) - return vo.DeregisterInstanceParam{ - Ip: url.Ip, - Port: uint64(port), - ServiceName: serviceName, - Ephemeral: true, - } -} - // Register will register the service @url to its nacos registry center func (nr *nacosRegistry) Register(url common.URL) error { serviceName := getServiceName(url) From f0809f6af2c722607e4bd87ff5d21374bb882488 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 16 Aug 2020 21:00:09 +0800 Subject: [PATCH 172/242] add register service instance unit test --- config/config_loader.go | 3 +- config/config_loader_test.go | 276 +++++++++++++++++++++++++++++++- config/reference_config_test.go | 29 ++++ config/service_config_test.go | 34 +++- 4 files changed, 335 insertions(+), 7 deletions(-) diff --git a/config/config_loader.go b/config/config_loader.go index 98553fc316..ca373b0796 100644 --- a/config/config_loader.go +++ b/config/config_loader.go @@ -27,6 +27,7 @@ import ( ) import ( + gxnet "github.com/dubbogo/gost/net" perrors "github.com/pkg/errors" ) @@ -211,7 +212,7 @@ func loadProviderConfig() { } // registerServiceInstance register service instance -func registerServiceInstance(){ +func registerServiceInstance() { url := selectMetadataServiceExportedURL() if url == nil { return diff --git a/config/config_loader_test.go b/config/config_loader_test.go index 01d2ca812a..55116f6023 100644 --- a/config/config_loader_test.go +++ b/config/config_loader_test.go @@ -19,10 +19,14 @@ package config import ( "path/filepath" + "sort" + "sync" "testing" ) import ( + cm "github.com/Workiva/go-datastructures/common" + "github.com/Workiva/go-datastructures/slice/skip" "github.com/stretchr/testify/assert" "go.uber.org/atomic" ) @@ -33,8 +37,13 @@ import ( "github.com/apache/dubbo-go/common/config" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/common/proxy/proxy_factory" "github.com/apache/dubbo-go/config_center" + "github.com/apache/dubbo-go/metadata/service" + "github.com/apache/dubbo-go/registry" + gxset "github.com/dubbogo/gost/container/set" + gxpage "github.com/dubbogo/gost/page" ) const mockConsumerConfigPath = "./testdata/consumer_config.yml" @@ -74,7 +83,17 @@ func TestLoad(t *testing.T) { extension.SetProtocol("registry", GetProtocol) extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster) extension.SetProxyFactory("default", proxy_factory.NewDefaultProxyFactory) - + GetApplicationConfig().MetadataType = "mock" + var mm *mockMetadataService + extension.SetMetadataService("mock", func() (metadataService service.MetadataService, err error) { + if mm == nil { + mm = &mockMetadataService{ + exportedServiceURLs: new(sync.Map), + lock: new(sync.RWMutex), + } + } + return mm, nil + }) Load() assert.Equal(t, ms, GetRPCService(ms.Reference())) @@ -103,7 +122,17 @@ func TestLoadWithSingleReg(t *testing.T) { extension.SetProtocol("registry", GetProtocol) extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster) extension.SetProxyFactory("default", proxy_factory.NewDefaultProxyFactory) - + var mm *mockMetadataService + GetApplicationConfig().MetadataType = "mock" + extension.SetMetadataService("mock", func() (metadataService service.MetadataService, err error) { + if mm == nil { + mm = &mockMetadataService{ + exportedServiceURLs: new(sync.Map), + lock: new(sync.RWMutex), + } + } + return mm, nil + }) Load() assert.Equal(t, ms, GetRPCService(ms.Reference())) @@ -132,7 +161,17 @@ func TestWithNoRegLoad(t *testing.T) { extension.SetProtocol("registry", GetProtocol) extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster) extension.SetProxyFactory("default", proxy_factory.NewDefaultProxyFactory) - + var mm *mockMetadataService + GetApplicationConfig().MetadataType = "mock" + extension.SetMetadataService("mock", func() (metadataService service.MetadataService, err error) { + if mm == nil { + mm = &mockMetadataService{ + exportedServiceURLs: new(sync.Map), + lock: new(sync.RWMutex), + } + } + return mm, nil + }) Load() assert.Equal(t, ms, GetRPCService(ms.Reference())) @@ -300,3 +339,234 @@ func mockInitProviderWithSingleRegistry() { }, } } + +type mockMetadataService struct { + exportedServiceURLs *sync.Map + lock *sync.RWMutex +} + +func (m *mockMetadataService) Reference() string { + panic("implement me") +} + +func (m *mockMetadataService) ServiceName() (string, error) { + panic("implement me") +} + +func (m *mockMetadataService) ExportURL(url common.URL) (bool, error) { + return m.addURL(m.exportedServiceURLs, &url), nil +} + +func (m *mockMetadataService) UnexportURL(url common.URL) error { + panic("implement me") +} + +func (m *mockMetadataService) SubscribeURL(url common.URL) (bool, error) { + panic("implement me") +} + +func (m *mockMetadataService) UnsubscribeURL(url common.URL) error { + panic("implement me") +} + +func (m *mockMetadataService) PublishServiceDefinition(url common.URL) error { + return nil +} + +func (m *mockMetadataService) GetExportedURLs(serviceInterface string, group string, version string, protocol string) ([]interface{}, error) { + return ConvertURLArrToIntfArr(m.getAllService(m.exportedServiceURLs)), nil +} + +func (m *mockMetadataService) MethodMapper() map[string]string { + panic("implement me") +} + +func (m *mockMetadataService) GetSubscribedURLs() ([]common.URL, error) { + panic("implement me") +} + +func (m *mockMetadataService) GetServiceDefinition(interfaceName string, group string, version string) (string, error) { + panic("implement me") +} + +func (m *mockMetadataService) GetServiceDefinitionByServiceKey(serviceKey string) (string, error) { + panic("implement me") +} + +func (m *mockMetadataService) RefreshMetadata(exportedRevision string, subscribedRevision string) (bool, error) { + panic("implement me") +} + +func (m *mockMetadataService) Version() (string, error) { + panic("implement me") +} + +func (mts *mockMetadataService) addURL(targetMap *sync.Map, url *common.URL) bool { + var ( + urlSet interface{} + loaded bool + ) + logger.Debug(url.ServiceKey()) + if urlSet, loaded = targetMap.LoadOrStore(url.ServiceKey(), skip.New(uint64(0))); loaded { + mts.lock.RLock() + wantedUrl := urlSet.(*skip.SkipList).Get(Comparator(*url)) + if len(wantedUrl) > 0 && wantedUrl[0] != nil { + mts.lock.RUnlock() + return false + } + mts.lock.RUnlock() + } + mts.lock.Lock() + // double chk + wantedUrl := urlSet.(*skip.SkipList).Get(Comparator(*url)) + if len(wantedUrl) > 0 && wantedUrl[0] != nil { + mts.lock.Unlock() + return false + } + urlSet.(*skip.SkipList).Insert(Comparator(*url)) + mts.lock.Unlock() + return true +} + +func (m *mockMetadataService) getAllService(services *sync.Map) []common.URL { + // using skip list to dedup and sorting + res := make([]common.URL, 0) + services.Range(func(key, value interface{}) bool { + urls := value.(*skip.SkipList) + for i := uint64(0); i < urls.Len(); i++ { + url := common.URL(urls.ByPosition(i).(Comparator)) + if url.GetParam(constant.INTERFACE_KEY, url.Path) != constant.METADATA_SERVICE_NAME { + res = append(res, url) + } + } + return true + }) + sort.Sort(common.URLSlice(res)) + return res +} + +type Comparator common.URL + +// Compare is defined as Comparator for skip list to compare the URL +func (c Comparator) Compare(comp cm.Comparator) int { + a := common.URL(c).String() + b := common.URL(comp.(Comparator)).String() + switch { + case a > b: + return 1 + case a < b: + return -1 + default: + return 0 + } +} + +type mockServiceDiscoveryRegistry struct { +} + +func (mr *mockServiceDiscoveryRegistry) GetUrl() common.URL { + panic("implement me") +} + +func (mr *mockServiceDiscoveryRegistry) IsAvailable() bool { + panic("implement me") +} + +func (mr *mockServiceDiscoveryRegistry) Destroy() { + panic("implement me") +} + +func (mr *mockServiceDiscoveryRegistry) Register(url common.URL) error { + panic("implement me") +} + +func (mr *mockServiceDiscoveryRegistry) UnRegister(url common.URL) error { + panic("implement me") +} + +func (mr *mockServiceDiscoveryRegistry) Subscribe(*common.URL, registry.NotifyListener) error { + panic("implement me") +} + +func (mr *mockServiceDiscoveryRegistry) UnSubscribe(*common.URL, registry.NotifyListener) error { + panic("implement me") +} + +func (s *mockServiceDiscoveryRegistry) GetServiceDiscovery() registry.ServiceDiscovery { + return &mockServiceDiscovery{} +} + +type mockServiceDiscovery struct { +} + +func (m *mockServiceDiscovery) String() string { + panic("implement me") +} + +func (m *mockServiceDiscovery) Destroy() error { + panic("implement me") +} + +func (m *mockServiceDiscovery) Register(instance registry.ServiceInstance) error { + return nil +} + +func (m *mockServiceDiscovery) Update(instance registry.ServiceInstance) error { + panic("implement me") +} + +func (m *mockServiceDiscovery) Unregister(instance registry.ServiceInstance) error { + panic("implement me") +} + +func (m *mockServiceDiscovery) GetDefaultPageSize() int { + panic("implement me") +} + +func (m *mockServiceDiscovery) GetServices() *gxset.HashSet { + panic("implement me") +} + +func (m *mockServiceDiscovery) GetInstances(serviceName string) []registry.ServiceInstance { + panic("implement me") +} + +func (m *mockServiceDiscovery) GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager { + panic("implement me") +} + +func (m *mockServiceDiscovery) GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager { + panic("implement me") +} + +func (m *mockServiceDiscovery) GetRequestInstances(serviceNames []string, offset int, requestedSize int) map[string]gxpage.Pager { + panic("implement me") +} + +func (m *mockServiceDiscovery) AddListener(listener *registry.ServiceInstancesChangedListener) error { + panic("implement me") +} + +func (m *mockServiceDiscovery) DispatchEventByServiceName(serviceName string) error { + panic("implement me") +} + +func (m *mockServiceDiscovery) DispatchEventForInstances(serviceName string, instances []registry.ServiceInstance) error { + panic("implement me") +} + +func (m *mockServiceDiscovery) DispatchEvent(event *registry.ServiceInstancesChangedEvent) error { + panic("implement me") +} + +func ConvertURLArrToIntfArr(urls []common.URL) []interface{} { + if len(urls) == 0 { + return []interface{}{} + } + + res := make([]interface{}, 0, len(urls)) + for _, u := range urls { + res = append(res, u.String()) + } + return res +} diff --git a/config/reference_config_test.go b/config/reference_config_test.go index 45cdb2dfac..430c77500e 100644 --- a/config/reference_config_test.go +++ b/config/reference_config_test.go @@ -32,6 +32,7 @@ import ( "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/registry" ) var regProtocol protocol.Protocol @@ -338,9 +339,37 @@ func (*mockRegistryProtocol) Refer(url common.URL) protocol.Invoker { } func (*mockRegistryProtocol) Export(invoker protocol.Invoker) protocol.Exporter { + registryUrl := getRegistryUrl(invoker) + if registryUrl.Protocol == "service-discovery" { + metaDataService, err := extension.GetMetadataService(GetApplicationConfig().MetadataType) + if err != nil { + panic(err) + } + ok, err := metaDataService.ExportURL(*invoker.GetUrl().SubURL.Clone()) + if err != nil { + panic(err) + } + if !ok { + panic("The URL has been registry!") + } + } return protocol.NewBaseExporter("test", invoker, &sync.Map{}) } func (*mockRegistryProtocol) Destroy() { // Destroy is a mock function } +func getRegistryUrl(invoker protocol.Invoker) *common.URL { + // here add * for return a new url + url := invoker.GetUrl() + // if the protocol == registry ,set protocol the registry value in url.params + if url.Protocol == constant.REGISTRY_PROTOCOL { + protocol := url.GetParam(constant.REGISTRY_KEY, "") + url.Protocol = protocol + } + return &url +} + +func (p *mockRegistryProtocol) GetRegistries() []registry.Registry { + return []registry.Registry{&mockServiceDiscoveryRegistry{}} +} diff --git a/config/service_config_test.go b/config/service_config_test.go index 0f7e404f6e..312c278d5b 100644 --- a/config/service_config_test.go +++ b/config/service_config_test.go @@ -40,13 +40,33 @@ func doInitProvider() { Module: "module", Version: "2.6.0", Owner: "dubbo", - Environment: "test"}, + Environment: "test", + }, + Remotes: map[string]*RemoteConfig{ + "test1": { + Address: "127.0.0.5:2181", + TimeoutStr: "5s", + Username: "user1", + Password: "pwd1", + Params: nil, + }, + }, + ServiceDiscoveries: map[string]*ServiceDiscoveryConfig{ + "mock_servicediscovery": { + Protocol: "mock", + RemoteRef: "test1", + }, + }, + MetadataReportConfig: &MetadataReportConfig{ + Protocol: "mock", + RemoteRef: "test1", + }, }, Services: map[string]*ServiceConfig{ "MockService": { InterfaceName: "com.MockService", Protocol: "mock", - Registry: "shanghai_reg1,shanghai_reg2,hangzhou_reg1,hangzhou_reg2", + Registry: "shanghai_reg1,shanghai_reg2,hangzhou_reg1,hangzhou_reg2,hangzhou_service_discovery_reg", Cluster: "failover", Loadbalance: "random", Retries: "3", @@ -71,7 +91,7 @@ func doInitProvider() { "MockServiceNoRightProtocol": { InterfaceName: "com.MockService", Protocol: "mock1", - Registry: "shanghai_reg1,shanghai_reg2,hangzhou_reg1,hangzhou_reg2", + Registry: "shanghai_reg1,shanghai_reg2,hangzhou_reg1,hangzhou_reg2,hangzhou_service_discovery_reg", Cluster: "failover", Loadbalance: "random", Retries: "3", @@ -128,6 +148,14 @@ func doInitProvider() { Username: "user1", Password: "pwd1", }, + "hangzhou_service_discovery_reg": { + Protocol: "service-discovery", + Params: map[string]string{ + "service_discovery": "mock_servicediscovery", + "name_mapping": "in-memory", + "metadata": "default", + }, + }, }, Protocols: map[string]*ProtocolConfig{ From e4ba8dbda005c66194e3a10d34f14086195e9349 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 5 Aug 2020 23:06:46 +0800 Subject: [PATCH 173/242] register service instance after provider config load --- config/config_loader.go | 94 +++++++++++++++++++ registry/protocol/protocol.go | 33 ++++--- registry/registry_factory.go | 24 +++++ registry/service_discovery_factory.go | 24 +++++ .../service_discovery_registry.go | 43 --------- 5 files changed, 164 insertions(+), 54 deletions(-) create mode 100644 registry/registry_factory.go create mode 100644 registry/service_discovery_factory.go diff --git a/config/config_loader.go b/config/config_loader.go index 8b196305b9..befa01b0a6 100644 --- a/config/config_loader.go +++ b/config/config_loader.go @@ -21,6 +21,7 @@ import ( "fmt" "log" "os" + "strconv" "sync" "time" ) @@ -35,6 +36,7 @@ import ( "github.com/apache/dubbo-go/common/extension" "github.com/apache/dubbo-go/common/logger" _ "github.com/apache/dubbo-go/common/observer/dispatcher" + "github.com/apache/dubbo-go/registry" ) var ( @@ -206,6 +208,98 @@ func loadProviderConfig() { panic(fmt.Sprintf("service %s export failed! err: %#v", key, err)) } } + registerServiceInstance() +} + +// registerServiceInstance register service instance +func registerServiceInstance(){ + url := selectMetadataServiceExportedURL() + if url == nil { + return + } + instance, err := createInstance(*url) + if err != nil { + panic(err) + } + p := extension.GetProtocol(constant.REGISTRY_KEY) + var rp registry.RegistryFactory + var ok bool + if rp, ok = p.(registry.RegistryFactory); !ok { + panic("dubbo registry protocol is invalid") + } + rs := rp.GetRegistries() + for _, r := range rs { + var sdr registry.ServiceDiscoveryFactory + if sdr, ok = r.(registry.ServiceDiscoveryFactory); !ok { + continue + } + err := sdr.GetServiceDiscovery().Register(instance) + if err != nil { + panic(err) + } + } +} + +// createInstance +func createInstance(url common.URL) (registry.ServiceInstance, error) { + appConfig := GetApplicationConfig() + port, err := strconv.ParseInt(url.Port, 10, 32) + if err != nil { + return nil, perrors.WithMessage(err, "invalid port: "+url.Port) + } + + host := url.Ip + if len(host) == 0 { + host, err = gxnet.GetLocalIP() + if err != nil { + return nil, perrors.WithMessage(err, "could not get the local Ip") + } + } + + // usually we will add more metadata + metadata := make(map[string]string, 8) + metadata[constant.METADATA_STORAGE_TYPE_PROPERTY_NAME] = appConfig.MetadataType + + return ®istry.DefaultServiceInstance{ + ServiceName: appConfig.Name, + Host: host, + Port: int(port), + Id: host + constant.KEY_SEPARATOR + url.Port, + Enable: true, + Healthy: true, + Metadata: metadata, + }, nil +} + +// selectMetadataServiceExportedURL get already be exported url +func selectMetadataServiceExportedURL() *common.URL { + var selectedUrl common.URL + metaDataService, err := extension.GetMetadataService(GetApplicationConfig().MetadataType) + if err != nil { + panic(err) + } + list, err := metaDataService.GetExportedURLs(constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE) + if err != nil { + panic(err) + } + if len(list) == 0 { + return nil + } + for _, urlStr := range list { + url, err := common.NewURL(urlStr.(string)) + if err != nil { + logger.Errorf("url format error {%v}", url) + continue + } + // rest first + if url.Protocol == "rest" { + selectedUrl = url + break + } else { + selectedUrl = url + } + } + return &selectedUrl } func initRouter() { diff --git a/registry/protocol/protocol.go b/registry/protocol/protocol.go index 4c669b2cee..963a3cba95 100644 --- a/registry/protocol/protocol.go +++ b/registry/protocol/protocol.go @@ -43,7 +43,7 @@ import ( ) var ( - regProtocol *registryProtocol + regProtocol *RegistryProtocol once sync.Once reserveParams = []string{ "application", "codec", "exchanger", "serialization", "cluster", "connections", "deprecated", "group", @@ -52,7 +52,7 @@ var ( } ) -type registryProtocol struct { +type RegistryProtocol struct { invokers []protocol.Invoker // Registry Map registries *sync.Map @@ -74,8 +74,8 @@ func getCacheKey(url *common.URL) string { return url.CloneExceptParams(delKeys).String() } -func newRegistryProtocol() *registryProtocol { - return ®istryProtocol{ +func newRegistryProtocol() *RegistryProtocol { + return &RegistryProtocol{ registries: &sync.Map{}, bounds: &sync.Map{}, } @@ -111,14 +111,25 @@ func filterHideKey(url *common.URL) *common.URL { return url.CloneExceptParams(removeSet) } -func (proto *registryProtocol) initConfigurationListeners() { +func (proto *RegistryProtocol) initConfigurationListeners() { proto.overrideListeners = &sync.Map{} proto.serviceConfigurationListeners = &sync.Map{} proto.providerConfigurationListener = newProviderConfigurationListener(proto.overrideListeners) } +func (proto *RegistryProtocol) GetRegistries() []registry.Registry{ + var rs []registry.Registry + proto.registries.Range(func(_, v interface{}) bool { + if r, ok := v.(registry.Registry); ok { + rs = append(rs, r) + } + return true + }) + return rs +} + // Refer provider service from registry center -func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker { +func (proto *RegistryProtocol) Refer(url common.URL) protocol.Invoker { var registryUrl = url var serviceUrl = registryUrl.SubURL if registryUrl.Protocol == constant.REGISTRY_PROTOCOL { @@ -158,7 +169,7 @@ func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker { } // Export provider service to registry center -func (proto *registryProtocol) Export(invoker protocol.Invoker) protocol.Exporter { +func (proto *RegistryProtocol) Export(invoker protocol.Invoker) protocol.Exporter { proto.once.Do(func() { proto.initConfigurationListeners() }) @@ -207,7 +218,7 @@ func (proto *registryProtocol) Export(invoker protocol.Invoker) protocol.Exporte } -func (proto *registryProtocol) reExport(invoker protocol.Invoker, newUrl *common.URL) { +func (proto *RegistryProtocol) reExport(invoker protocol.Invoker, newUrl *common.URL) { url := getProviderUrl(invoker) key := getCacheKey(url) if oldExporter, loaded := proto.bounds.Load(key); loaded { @@ -223,11 +234,11 @@ func (proto *registryProtocol) reExport(invoker protocol.Invoker, newUrl *common type overrideSubscribeListener struct { url *common.URL originInvoker protocol.Invoker - protocol *registryProtocol + protocol *RegistryProtocol configurator config_center.Configurator } -func newOverrideSubscribeListener(overriderUrl *common.URL, invoker protocol.Invoker, proto *registryProtocol) *overrideSubscribeListener { +func newOverrideSubscribeListener(overriderUrl *common.URL, invoker protocol.Invoker, proto *RegistryProtocol) *overrideSubscribeListener { return &overrideSubscribeListener{url: overriderUrl, originInvoker: invoker, protocol: proto} } @@ -329,7 +340,7 @@ func getSubscribedOverrideUrl(providerUrl *common.URL) *common.URL { } // Destroy registry protocol -func (proto *registryProtocol) Destroy() { +func (proto *RegistryProtocol) Destroy() { for _, ivk := range proto.invokers { ivk.Destroy() } diff --git a/registry/registry_factory.go b/registry/registry_factory.go new file mode 100644 index 0000000000..58fbe39553 --- /dev/null +++ b/registry/registry_factory.go @@ -0,0 +1,24 @@ +/* + * 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 registry + +// RegistryFactory +type RegistryFactory interface { + // GetRegistries get registries + GetRegistries() []Registry +} diff --git a/registry/service_discovery_factory.go b/registry/service_discovery_factory.go new file mode 100644 index 0000000000..6382403a45 --- /dev/null +++ b/registry/service_discovery_factory.go @@ -0,0 +1,24 @@ +/* + * 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 registry + +// ServiceDiscoveryFactory +type ServiceDiscoveryFactory interface { + // GetServiceDiscovery get service discovery + GetServiceDiscovery() ServiceDiscovery +} diff --git a/registry/servicediscovery/service_discovery_registry.go b/registry/servicediscovery/service_discovery_registry.go index 061d832b03..cdb586c137 100644 --- a/registry/servicediscovery/service_discovery_registry.go +++ b/registry/servicediscovery/service_discovery_registry.go @@ -28,7 +28,6 @@ import ( import ( cm "github.com/Workiva/go-datastructures/common" gxset "github.com/dubbogo/gost/container/set" - gxnet "github.com/dubbogo/gost/net" perrors "github.com/pkg/errors" "go.uber.org/atomic" ) @@ -176,18 +175,6 @@ func (s *serviceDiscoveryRegistry) Register(url common.URL) error { logger.Warnf("The URL[%s] has been registry!", url.String()) } - // we try to register this instance. Dubbo do this in org.apache.dubbo.config.bootstrap.DubboBootstrap - // But we don't want to design a similar bootstrap class. - ins, err := createInstance(url) - if err != nil { - return perrors.WithMessage(err, "could not create servcie instance, please check your service url") - } - - err = s.serviceDiscovery.Register(ins) - if err != nil { - return perrors.WithMessage(err, "register the service failed") - } - err = s.metaDataService.PublishServiceDefinition(url) if err != nil { return perrors.WithMessage(err, "publish the service definition failed. ") @@ -198,36 +185,6 @@ func (s *serviceDiscoveryRegistry) Register(url common.URL) error { url.Protocol) } -func createInstance(url common.URL) (registry.ServiceInstance, error) { - appConfig := config.GetApplicationConfig() - port, err := strconv.ParseInt(url.Port, 10, 32) - if err != nil { - return nil, perrors.WithMessage(err, "invalid port: "+url.Port) - } - - host := url.Ip - if len(host) == 0 { - host, err = gxnet.GetLocalIP() - if err != nil { - return nil, perrors.WithMessage(err, "could not get the local Ip") - } - } - - // usually we will add more metadata - metadata := make(map[string]string, 8) - metadata[constant.METADATA_STORAGE_TYPE_PROPERTY_NAME] = appConfig.MetadataType - - return ®istry.DefaultServiceInstance{ - ServiceName: appConfig.Name, - Host: host, - Port: int(port), - Id: host + constant.KEY_SEPARATOR + url.Port, - Enable: true, - Healthy: true, - Metadata: metadata, - }, nil -} - func shouldRegister(url common.URL) bool { side := url.GetParam(constant.SIDE_KEY, "") if side == constant.PROVIDER_PROTOCOL { From b3ab3231a653697438c1548facec3f983a9bb38a Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 5 Aug 2020 23:16:19 +0800 Subject: [PATCH 174/242] modify name --- config/config_loader.go | 4 ++-- registry/protocol/protocol.go | 26 +++++++++++++------------- registry/service_discovery_factory.go | 4 ++-- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/config/config_loader.go b/config/config_loader.go index befa01b0a6..f57fd9f2de 100644 --- a/config/config_loader.go +++ b/config/config_loader.go @@ -229,8 +229,8 @@ func registerServiceInstance(){ } rs := rp.GetRegistries() for _, r := range rs { - var sdr registry.ServiceDiscoveryFactory - if sdr, ok = r.(registry.ServiceDiscoveryFactory); !ok { + var sdr registry.ServiceDiscoveryHolder + if sdr, ok = r.(registry.ServiceDiscoveryHolder); !ok { continue } err := sdr.GetServiceDiscovery().Register(instance) diff --git a/registry/protocol/protocol.go b/registry/protocol/protocol.go index 963a3cba95..f7189a9954 100644 --- a/registry/protocol/protocol.go +++ b/registry/protocol/protocol.go @@ -43,7 +43,7 @@ import ( ) var ( - regProtocol *RegistryProtocol + regProtocol *registryProtocol once sync.Once reserveParams = []string{ "application", "codec", "exchanger", "serialization", "cluster", "connections", "deprecated", "group", @@ -52,7 +52,7 @@ var ( } ) -type RegistryProtocol struct { +type registryProtocol struct { invokers []protocol.Invoker // Registry Map registries *sync.Map @@ -74,8 +74,8 @@ func getCacheKey(url *common.URL) string { return url.CloneExceptParams(delKeys).String() } -func newRegistryProtocol() *RegistryProtocol { - return &RegistryProtocol{ +func newRegistryProtocol() *registryProtocol { + return ®istryProtocol{ registries: &sync.Map{}, bounds: &sync.Map{}, } @@ -111,13 +111,13 @@ func filterHideKey(url *common.URL) *common.URL { return url.CloneExceptParams(removeSet) } -func (proto *RegistryProtocol) initConfigurationListeners() { +func (proto *registryProtocol) initConfigurationListeners() { proto.overrideListeners = &sync.Map{} proto.serviceConfigurationListeners = &sync.Map{} proto.providerConfigurationListener = newProviderConfigurationListener(proto.overrideListeners) } -func (proto *RegistryProtocol) GetRegistries() []registry.Registry{ +func (proto *registryProtocol) GetRegistries() []registry.Registry{ var rs []registry.Registry proto.registries.Range(func(_, v interface{}) bool { if r, ok := v.(registry.Registry); ok { @@ -129,7 +129,7 @@ func (proto *RegistryProtocol) GetRegistries() []registry.Registry{ } // Refer provider service from registry center -func (proto *RegistryProtocol) Refer(url common.URL) protocol.Invoker { +func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker { var registryUrl = url var serviceUrl = registryUrl.SubURL if registryUrl.Protocol == constant.REGISTRY_PROTOCOL { @@ -169,7 +169,7 @@ func (proto *RegistryProtocol) Refer(url common.URL) protocol.Invoker { } // Export provider service to registry center -func (proto *RegistryProtocol) Export(invoker protocol.Invoker) protocol.Exporter { +func (proto *registryProtocol) Export(invoker protocol.Invoker) protocol.Exporter { proto.once.Do(func() { proto.initConfigurationListeners() }) @@ -218,7 +218,7 @@ func (proto *RegistryProtocol) Export(invoker protocol.Invoker) protocol.Exporte } -func (proto *RegistryProtocol) reExport(invoker protocol.Invoker, newUrl *common.URL) { +func (proto *registryProtocol) reExport(invoker protocol.Invoker, newUrl *common.URL) { url := getProviderUrl(invoker) key := getCacheKey(url) if oldExporter, loaded := proto.bounds.Load(key); loaded { @@ -234,11 +234,11 @@ func (proto *RegistryProtocol) reExport(invoker protocol.Invoker, newUrl *common type overrideSubscribeListener struct { url *common.URL originInvoker protocol.Invoker - protocol *RegistryProtocol + protocol *registryProtocol configurator config_center.Configurator } -func newOverrideSubscribeListener(overriderUrl *common.URL, invoker protocol.Invoker, proto *RegistryProtocol) *overrideSubscribeListener { +func newOverrideSubscribeListener(overriderUrl *common.URL, invoker protocol.Invoker, proto *registryProtocol) *overrideSubscribeListener { return &overrideSubscribeListener{url: overriderUrl, originInvoker: invoker, protocol: proto} } @@ -340,7 +340,7 @@ func getSubscribedOverrideUrl(providerUrl *common.URL) *common.URL { } // Destroy registry protocol -func (proto *RegistryProtocol) Destroy() { +func (proto *registryProtocol) Destroy() { for _, ivk := range proto.invokers { ivk.Destroy() } @@ -384,7 +384,7 @@ func setProviderUrl(regURL *common.URL, providerURL *common.URL) { regURL.SubURL = providerURL } -// GetProtocol return the singleton RegistryProtocol +// GetProtocol return the singleton registryProtocol func GetProtocol() protocol.Protocol { once.Do(func() { regProtocol = newRegistryProtocol() diff --git a/registry/service_discovery_factory.go b/registry/service_discovery_factory.go index 6382403a45..86c38d0cf3 100644 --- a/registry/service_discovery_factory.go +++ b/registry/service_discovery_factory.go @@ -17,8 +17,8 @@ package registry -// ServiceDiscoveryFactory -type ServiceDiscoveryFactory interface { +// ServiceDiscoveryHolder +type ServiceDiscoveryHolder interface { // GetServiceDiscovery get service discovery GetServiceDiscovery() ServiceDiscovery } From 52e8a9e51aa022a24c96f9af861e8fd30bb2e0fb Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 16 Aug 2020 21:00:09 +0800 Subject: [PATCH 175/242] add register service instance unit test --- config/config_loader.go | 3 +- config/config_loader_test.go | 276 +++++++++++++++++++++++++++++++- config/reference_config_test.go | 29 ++++ config/service_config_test.go | 34 +++- 4 files changed, 335 insertions(+), 7 deletions(-) diff --git a/config/config_loader.go b/config/config_loader.go index f57fd9f2de..f7703e5394 100644 --- a/config/config_loader.go +++ b/config/config_loader.go @@ -27,6 +27,7 @@ import ( ) import ( + gxnet "github.com/dubbogo/gost/net" perrors "github.com/pkg/errors" ) @@ -212,7 +213,7 @@ func loadProviderConfig() { } // registerServiceInstance register service instance -func registerServiceInstance(){ +func registerServiceInstance() { url := selectMetadataServiceExportedURL() if url == nil { return diff --git a/config/config_loader_test.go b/config/config_loader_test.go index a219b9f465..88d3ca4e52 100644 --- a/config/config_loader_test.go +++ b/config/config_loader_test.go @@ -19,10 +19,14 @@ package config import ( "path/filepath" + "sort" + "sync" "testing" ) import ( + cm "github.com/Workiva/go-datastructures/common" + "github.com/Workiva/go-datastructures/slice/skip" "github.com/stretchr/testify/assert" "go.uber.org/atomic" ) @@ -33,8 +37,13 @@ import ( "github.com/apache/dubbo-go/common/config" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/common/proxy/proxy_factory" "github.com/apache/dubbo-go/config_center" + "github.com/apache/dubbo-go/metadata/service" + "github.com/apache/dubbo-go/registry" + gxset "github.com/dubbogo/gost/container/set" + gxpage "github.com/dubbogo/gost/page" ) const mockConsumerConfigPath = "./testdata/consumer_config.yml" @@ -74,7 +83,17 @@ func TestLoad(t *testing.T) { extension.SetProtocol("registry", GetProtocol) extension.SetCluster(constant.ZONEAWARE_CLUSTER_NAME, cluster_impl.NewZoneAwareCluster) extension.SetProxyFactory("default", proxy_factory.NewDefaultProxyFactory) - + GetApplicationConfig().MetadataType = "mock" + var mm *mockMetadataService + extension.SetMetadataService("mock", func() (metadataService service.MetadataService, err error) { + if mm == nil { + mm = &mockMetadataService{ + exportedServiceURLs: new(sync.Map), + lock: new(sync.RWMutex), + } + } + return mm, nil + }) Load() assert.Equal(t, ms, GetRPCService(ms.Reference())) @@ -103,7 +122,17 @@ func TestLoadWithSingleReg(t *testing.T) { extension.SetProtocol("registry", GetProtocol) extension.SetCluster(constant.ZONEAWARE_CLUSTER_NAME, cluster_impl.NewZoneAwareCluster) extension.SetProxyFactory("default", proxy_factory.NewDefaultProxyFactory) - + var mm *mockMetadataService + GetApplicationConfig().MetadataType = "mock" + extension.SetMetadataService("mock", func() (metadataService service.MetadataService, err error) { + if mm == nil { + mm = &mockMetadataService{ + exportedServiceURLs: new(sync.Map), + lock: new(sync.RWMutex), + } + } + return mm, nil + }) Load() assert.Equal(t, ms, GetRPCService(ms.Reference())) @@ -132,7 +161,17 @@ func TestWithNoRegLoad(t *testing.T) { extension.SetProtocol("registry", GetProtocol) extension.SetCluster(constant.ZONEAWARE_CLUSTER_NAME, cluster_impl.NewZoneAwareCluster) extension.SetProxyFactory("default", proxy_factory.NewDefaultProxyFactory) - + var mm *mockMetadataService + GetApplicationConfig().MetadataType = "mock" + extension.SetMetadataService("mock", func() (metadataService service.MetadataService, err error) { + if mm == nil { + mm = &mockMetadataService{ + exportedServiceURLs: new(sync.Map), + lock: new(sync.RWMutex), + } + } + return mm, nil + }) Load() assert.Equal(t, ms, GetRPCService(ms.Reference())) @@ -300,3 +339,234 @@ func mockInitProviderWithSingleRegistry() { }, } } + +type mockMetadataService struct { + exportedServiceURLs *sync.Map + lock *sync.RWMutex +} + +func (m *mockMetadataService) Reference() string { + panic("implement me") +} + +func (m *mockMetadataService) ServiceName() (string, error) { + panic("implement me") +} + +func (m *mockMetadataService) ExportURL(url common.URL) (bool, error) { + return m.addURL(m.exportedServiceURLs, &url), nil +} + +func (m *mockMetadataService) UnexportURL(url common.URL) error { + panic("implement me") +} + +func (m *mockMetadataService) SubscribeURL(url common.URL) (bool, error) { + panic("implement me") +} + +func (m *mockMetadataService) UnsubscribeURL(url common.URL) error { + panic("implement me") +} + +func (m *mockMetadataService) PublishServiceDefinition(url common.URL) error { + return nil +} + +func (m *mockMetadataService) GetExportedURLs(serviceInterface string, group string, version string, protocol string) ([]interface{}, error) { + return ConvertURLArrToIntfArr(m.getAllService(m.exportedServiceURLs)), nil +} + +func (m *mockMetadataService) MethodMapper() map[string]string { + panic("implement me") +} + +func (m *mockMetadataService) GetSubscribedURLs() ([]common.URL, error) { + panic("implement me") +} + +func (m *mockMetadataService) GetServiceDefinition(interfaceName string, group string, version string) (string, error) { + panic("implement me") +} + +func (m *mockMetadataService) GetServiceDefinitionByServiceKey(serviceKey string) (string, error) { + panic("implement me") +} + +func (m *mockMetadataService) RefreshMetadata(exportedRevision string, subscribedRevision string) (bool, error) { + panic("implement me") +} + +func (m *mockMetadataService) Version() (string, error) { + panic("implement me") +} + +func (mts *mockMetadataService) addURL(targetMap *sync.Map, url *common.URL) bool { + var ( + urlSet interface{} + loaded bool + ) + logger.Debug(url.ServiceKey()) + if urlSet, loaded = targetMap.LoadOrStore(url.ServiceKey(), skip.New(uint64(0))); loaded { + mts.lock.RLock() + wantedUrl := urlSet.(*skip.SkipList).Get(Comparator(*url)) + if len(wantedUrl) > 0 && wantedUrl[0] != nil { + mts.lock.RUnlock() + return false + } + mts.lock.RUnlock() + } + mts.lock.Lock() + // double chk + wantedUrl := urlSet.(*skip.SkipList).Get(Comparator(*url)) + if len(wantedUrl) > 0 && wantedUrl[0] != nil { + mts.lock.Unlock() + return false + } + urlSet.(*skip.SkipList).Insert(Comparator(*url)) + mts.lock.Unlock() + return true +} + +func (m *mockMetadataService) getAllService(services *sync.Map) []common.URL { + // using skip list to dedup and sorting + res := make([]common.URL, 0) + services.Range(func(key, value interface{}) bool { + urls := value.(*skip.SkipList) + for i := uint64(0); i < urls.Len(); i++ { + url := common.URL(urls.ByPosition(i).(Comparator)) + if url.GetParam(constant.INTERFACE_KEY, url.Path) != constant.METADATA_SERVICE_NAME { + res = append(res, url) + } + } + return true + }) + sort.Sort(common.URLSlice(res)) + return res +} + +type Comparator common.URL + +// Compare is defined as Comparator for skip list to compare the URL +func (c Comparator) Compare(comp cm.Comparator) int { + a := common.URL(c).String() + b := common.URL(comp.(Comparator)).String() + switch { + case a > b: + return 1 + case a < b: + return -1 + default: + return 0 + } +} + +type mockServiceDiscoveryRegistry struct { +} + +func (mr *mockServiceDiscoveryRegistry) GetUrl() common.URL { + panic("implement me") +} + +func (mr *mockServiceDiscoveryRegistry) IsAvailable() bool { + panic("implement me") +} + +func (mr *mockServiceDiscoveryRegistry) Destroy() { + panic("implement me") +} + +func (mr *mockServiceDiscoveryRegistry) Register(url common.URL) error { + panic("implement me") +} + +func (mr *mockServiceDiscoveryRegistry) UnRegister(url common.URL) error { + panic("implement me") +} + +func (mr *mockServiceDiscoveryRegistry) Subscribe(*common.URL, registry.NotifyListener) error { + panic("implement me") +} + +func (mr *mockServiceDiscoveryRegistry) UnSubscribe(*common.URL, registry.NotifyListener) error { + panic("implement me") +} + +func (s *mockServiceDiscoveryRegistry) GetServiceDiscovery() registry.ServiceDiscovery { + return &mockServiceDiscovery{} +} + +type mockServiceDiscovery struct { +} + +func (m *mockServiceDiscovery) String() string { + panic("implement me") +} + +func (m *mockServiceDiscovery) Destroy() error { + panic("implement me") +} + +func (m *mockServiceDiscovery) Register(instance registry.ServiceInstance) error { + return nil +} + +func (m *mockServiceDiscovery) Update(instance registry.ServiceInstance) error { + panic("implement me") +} + +func (m *mockServiceDiscovery) Unregister(instance registry.ServiceInstance) error { + panic("implement me") +} + +func (m *mockServiceDiscovery) GetDefaultPageSize() int { + panic("implement me") +} + +func (m *mockServiceDiscovery) GetServices() *gxset.HashSet { + panic("implement me") +} + +func (m *mockServiceDiscovery) GetInstances(serviceName string) []registry.ServiceInstance { + panic("implement me") +} + +func (m *mockServiceDiscovery) GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager { + panic("implement me") +} + +func (m *mockServiceDiscovery) GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager { + panic("implement me") +} + +func (m *mockServiceDiscovery) GetRequestInstances(serviceNames []string, offset int, requestedSize int) map[string]gxpage.Pager { + panic("implement me") +} + +func (m *mockServiceDiscovery) AddListener(listener *registry.ServiceInstancesChangedListener) error { + panic("implement me") +} + +func (m *mockServiceDiscovery) DispatchEventByServiceName(serviceName string) error { + panic("implement me") +} + +func (m *mockServiceDiscovery) DispatchEventForInstances(serviceName string, instances []registry.ServiceInstance) error { + panic("implement me") +} + +func (m *mockServiceDiscovery) DispatchEvent(event *registry.ServiceInstancesChangedEvent) error { + panic("implement me") +} + +func ConvertURLArrToIntfArr(urls []common.URL) []interface{} { + if len(urls) == 0 { + return []interface{}{} + } + + res := make([]interface{}, 0, len(urls)) + for _, u := range urls { + res = append(res, u.String()) + } + return res +} diff --git a/config/reference_config_test.go b/config/reference_config_test.go index e457801596..a4345ad13d 100644 --- a/config/reference_config_test.go +++ b/config/reference_config_test.go @@ -32,6 +32,7 @@ import ( "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/registry" ) var regProtocol protocol.Protocol @@ -338,9 +339,37 @@ func (*mockRegistryProtocol) Refer(url common.URL) protocol.Invoker { } func (*mockRegistryProtocol) Export(invoker protocol.Invoker) protocol.Exporter { + registryUrl := getRegistryUrl(invoker) + if registryUrl.Protocol == "service-discovery" { + metaDataService, err := extension.GetMetadataService(GetApplicationConfig().MetadataType) + if err != nil { + panic(err) + } + ok, err := metaDataService.ExportURL(*invoker.GetUrl().SubURL.Clone()) + if err != nil { + panic(err) + } + if !ok { + panic("The URL has been registry!") + } + } return protocol.NewBaseExporter("test", invoker, &sync.Map{}) } func (*mockRegistryProtocol) Destroy() { // Destroy is a mock function } +func getRegistryUrl(invoker protocol.Invoker) *common.URL { + // here add * for return a new url + url := invoker.GetUrl() + // if the protocol == registry ,set protocol the registry value in url.params + if url.Protocol == constant.REGISTRY_PROTOCOL { + protocol := url.GetParam(constant.REGISTRY_KEY, "") + url.Protocol = protocol + } + return &url +} + +func (p *mockRegistryProtocol) GetRegistries() []registry.Registry { + return []registry.Registry{&mockServiceDiscoveryRegistry{}} +} diff --git a/config/service_config_test.go b/config/service_config_test.go index d2bbda0c49..4d4122ee70 100644 --- a/config/service_config_test.go +++ b/config/service_config_test.go @@ -40,13 +40,33 @@ func doInitProvider() { Module: "module", Version: "2.6.0", Owner: "dubbo", - Environment: "test"}, + Environment: "test", + }, + Remotes: map[string]*RemoteConfig{ + "test1": { + Address: "127.0.0.5:2181", + TimeoutStr: "5s", + Username: "user1", + Password: "pwd1", + Params: nil, + }, + }, + ServiceDiscoveries: map[string]*ServiceDiscoveryConfig{ + "mock_servicediscovery": { + Protocol: "mock", + RemoteRef: "test1", + }, + }, + MetadataReportConfig: &MetadataReportConfig{ + Protocol: "mock", + RemoteRef: "test1", + }, }, Services: map[string]*ServiceConfig{ "MockService": { InterfaceName: "com.MockService", Protocol: "mock", - Registry: "shanghai_reg1,shanghai_reg2,hangzhou_reg1,hangzhou_reg2", + Registry: "shanghai_reg1,shanghai_reg2,hangzhou_reg1,hangzhou_reg2,hangzhou_service_discovery_reg", Cluster: "failover", Loadbalance: "random", Retries: "3", @@ -71,7 +91,7 @@ func doInitProvider() { "MockServiceNoRightProtocol": { InterfaceName: "com.MockService", Protocol: "mock1", - Registry: "shanghai_reg1,shanghai_reg2,hangzhou_reg1,hangzhou_reg2", + Registry: "shanghai_reg1,shanghai_reg2,hangzhou_reg1,hangzhou_reg2,hangzhou_service_discovery_reg", Cluster: "failover", Loadbalance: "random", Retries: "3", @@ -128,6 +148,14 @@ func doInitProvider() { Username: "user1", Password: "pwd1", }, + "hangzhou_service_discovery_reg": { + Protocol: "service-discovery", + Params: map[string]string{ + "service_discovery": "mock_servicediscovery", + "name_mapping": "in-memory", + "metadata": "default", + }, + }, }, Protocols: map[string]*ProtocolConfig{ From ce5751ba6d14252c19feefd1f9f106a2d08cfca7 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 17 Aug 2020 01:02:37 +0800 Subject: [PATCH 176/242] when don't has metadataService ignore this error --- common/extension/metadata_service.go | 7 +++++-- config/config_loader.go | 7 +++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/common/extension/metadata_service.go b/common/extension/metadata_service.go index 1823273b8f..e35677d148 100644 --- a/common/extension/metadata_service.go +++ b/common/extension/metadata_service.go @@ -21,6 +21,10 @@ import ( "fmt" ) +import ( + perrors "github.com/pkg/errors" +) + import ( "github.com/apache/dubbo-go/metadata/service" ) @@ -36,12 +40,11 @@ func SetMetadataService(msType string, creator func() (service.MetadataService, } // GetMetadataService will create a MetadataService instance -// it will panic if msType not found func GetMetadataService(msType string) (service.MetadataService, error) { if creator, ok := metadataServiceInsMap[msType]; ok { return creator() } - panic(fmt.Sprintf("could not find the metadata service creator for metadataType: %s, please check whether you have imported relative packages, \n"+ + return nil, perrors.New(fmt.Sprintf("could not find the metadata service creator for metadataType: %s, please check whether you have imported relative packages, \n"+ "local - github.com/apache/dubbo-go/metadata/service/inmemory, \n"+ "remote - github.com/apache/dubbo-go/metadata/service/remote", msType)) } diff --git a/config/config_loader.go b/config/config_loader.go index f7703e5394..ec935346f9 100644 --- a/config/config_loader.go +++ b/config/config_loader.go @@ -277,7 +277,8 @@ func selectMetadataServiceExportedURL() *common.URL { var selectedUrl common.URL metaDataService, err := extension.GetMetadataService(GetApplicationConfig().MetadataType) if err != nil { - panic(err) + logger.Warn(err) + return nil } list, err := metaDataService.GetExportedURLs(constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE) if err != nil { @@ -292,12 +293,10 @@ func selectMetadataServiceExportedURL() *common.URL { logger.Errorf("url format error {%v}", url) continue } + selectedUrl = url // rest first if url.Protocol == "rest" { - selectedUrl = url break - } else { - selectedUrl = url } } return &selectedUrl From de36a0d67c5812e4f38e257fc5665b7f5cc7f9f9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 17 Aug 2020 10:45:52 +0800 Subject: [PATCH 177/242] format code --- registry/protocol/protocol.go | 2 +- registry/registry_factory.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/registry/protocol/protocol.go b/registry/protocol/protocol.go index f7189a9954..33e3774596 100644 --- a/registry/protocol/protocol.go +++ b/registry/protocol/protocol.go @@ -117,7 +117,7 @@ func (proto *registryProtocol) initConfigurationListeners() { proto.providerConfigurationListener = newProviderConfigurationListener(proto.overrideListeners) } -func (proto *registryProtocol) GetRegistries() []registry.Registry{ +func (proto *registryProtocol) GetRegistries() []registry.Registry { var rs []registry.Registry proto.registries.Range(func(_, v interface{}) bool { if r, ok := v.(registry.Registry); ok { diff --git a/registry/registry_factory.go b/registry/registry_factory.go index 58fbe39553..caefbce2eb 100644 --- a/registry/registry_factory.go +++ b/registry/registry_factory.go @@ -19,6 +19,6 @@ package registry // RegistryFactory type RegistryFactory interface { - // GetRegistries get registries - GetRegistries() []Registry + // GetRegistries get registries + GetRegistries() []Registry } From 5e35cd6bf630ea706f08d5b6b66df355018eb8fb Mon Sep 17 00:00:00 2001 From: AlexStocks Date: Tue, 18 Aug 2020 10:45:21 +0800 Subject: [PATCH 178/242] Update: dubbogo/getty -> apache/dubbo-getty --- common/logger/logger.go | 2 +- config/ssl_config.go | 2 +- config_center/nacos/facade.go | 2 +- go.mod | 2 +- go.sum | 2 ++ protocol/dubbo/client.go | 2 +- protocol/dubbo/listener.go | 2 +- protocol/dubbo/pool.go | 2 +- protocol/dubbo/readwriter.go | 2 +- protocol/dubbo/server.go | 2 +- registry/etcdv3/listener_test.go | 2 +- registry/kubernetes/registry.go | 2 +- remoting/etcdv3/facade.go | 2 +- remoting/zookeeper/facade.go | 2 +- remoting/zookeeper/listener.go | 2 +- 15 files changed, 16 insertions(+), 14 deletions(-) diff --git a/common/logger/logger.go b/common/logger/logger.go index 88ed0c29f8..b648f8a0af 100644 --- a/common/logger/logger.go +++ b/common/logger/logger.go @@ -25,7 +25,7 @@ import ( ) import ( - "github.com/dubbogo/getty" + "github.com/apache/dubbo-getty" perrors "github.com/pkg/errors" "go.uber.org/zap" "go.uber.org/zap/zapcore" diff --git a/config/ssl_config.go b/config/ssl_config.go index 019f83d3cb..8576930367 100644 --- a/config/ssl_config.go +++ b/config/ssl_config.go @@ -18,7 +18,7 @@ package config import ( - "github.com/dubbogo/getty" + "github.com/apache/dubbo-getty" ) var ( diff --git a/config_center/nacos/facade.go b/config_center/nacos/facade.go index 77a79ed091..d089ed26d2 100644 --- a/config_center/nacos/facade.go +++ b/config_center/nacos/facade.go @@ -22,7 +22,7 @@ import ( "time" ) import ( - "github.com/dubbogo/getty" + "github.com/apache/dubbo-getty" perrors "github.com/pkg/errors" ) diff --git a/go.mod b/go.mod index 0e2e2d6876..263bbff37e 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect github.com/creasty/defaults v1.3.0 github.com/docker/go-connections v0.4.0 // indirect - github.com/dubbogo/getty v1.3.9 + github.com/apache/dubbo-getty v1.3.10 github.com/dubbogo/go-zookeeper v1.0.1 github.com/dubbogo/gost v1.9.1 github.com/elazarl/go-bindata-assetfs v1.0.0 // indirect diff --git a/go.sum b/go.sum index b959b1e6e7..4089b7302d 100644 --- a/go.sum +++ b/go.sum @@ -72,6 +72,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA= github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk= +github.com/apache/dubbo-getty v1.3.10 h1:ys5mwjPdxG/KwkPjS6EI0RzQtU6p6FCPoKpaFEzpAL0= +github.com/apache/dubbo-getty v1.3.10/go.mod h1:x6rraK01BL5C7jUM2fPl5KMkAxLVIx54ZB8/XEOik9Y= github.com/apache/dubbo-go-hessian2 v1.6.1 h1:mFKeCZzaCkk4mMOyP+LQ85GHbRyqKT7858KS21JQYA4= github.com/apache/dubbo-go-hessian2 v1.6.1/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= github.com/apache/dubbo-go-hessian2 v1.6.2 h1:i7F5GjVaUatLQz1x9vUmmSIFj49L8J6rVICdF6xw4qw= diff --git a/protocol/dubbo/client.go b/protocol/dubbo/client.go index 0bc591d29b..9f70d9377b 100644 --- a/protocol/dubbo/client.go +++ b/protocol/dubbo/client.go @@ -26,7 +26,7 @@ import ( import ( hessian "github.com/apache/dubbo-go-hessian2" - "github.com/dubbogo/getty" + "github.com/apache/dubbo-getty" gxsync "github.com/dubbogo/gost/sync" perrors "github.com/pkg/errors" "go.uber.org/atomic" diff --git a/protocol/dubbo/listener.go b/protocol/dubbo/listener.go index 4834459390..916230ce15 100644 --- a/protocol/dubbo/listener.go +++ b/protocol/dubbo/listener.go @@ -28,7 +28,7 @@ import ( import ( "github.com/apache/dubbo-go-hessian2" - "github.com/dubbogo/getty" + "github.com/apache/dubbo-getty" "github.com/opentracing/opentracing-go" perrors "github.com/pkg/errors" ) diff --git a/protocol/dubbo/pool.go b/protocol/dubbo/pool.go index 59a81d6e9d..6a7d211b49 100644 --- a/protocol/dubbo/pool.go +++ b/protocol/dubbo/pool.go @@ -28,7 +28,7 @@ import ( ) import ( - "github.com/dubbogo/getty" + "github.com/apache/dubbo-getty" perrors "github.com/pkg/errors" ) diff --git a/protocol/dubbo/readwriter.go b/protocol/dubbo/readwriter.go index 9cc7ea25cd..bdd89a8793 100644 --- a/protocol/dubbo/readwriter.go +++ b/protocol/dubbo/readwriter.go @@ -24,7 +24,7 @@ import ( import ( "github.com/apache/dubbo-go-hessian2" - "github.com/dubbogo/getty" + "github.com/apache/dubbo-getty" perrors "github.com/pkg/errors" ) diff --git a/protocol/dubbo/server.go b/protocol/dubbo/server.go index c02c2ac193..4ad4796c54 100644 --- a/protocol/dubbo/server.go +++ b/protocol/dubbo/server.go @@ -24,7 +24,7 @@ import ( ) import ( - "github.com/dubbogo/getty" + "github.com/apache/dubbo-getty" "github.com/dubbogo/gost/sync" "gopkg.in/yaml.v2" ) diff --git a/registry/etcdv3/listener_test.go b/registry/etcdv3/listener_test.go index f27e7ce8ba..543ddf6c1f 100644 --- a/registry/etcdv3/listener_test.go +++ b/registry/etcdv3/listener_test.go @@ -25,7 +25,7 @@ import ( import ( "github.com/coreos/etcd/embed" - "github.com/dubbogo/getty" + "github.com/apache/dubbo-getty" "github.com/stretchr/testify/suite" ) diff --git a/registry/kubernetes/registry.go b/registry/kubernetes/registry.go index 7c5162670d..8889585568 100644 --- a/registry/kubernetes/registry.go +++ b/registry/kubernetes/registry.go @@ -26,7 +26,7 @@ import ( ) import ( - "github.com/dubbogo/getty" + "github.com/apache/dubbo-getty" "github.com/dubbogo/gost/net" perrors "github.com/pkg/errors" v1 "k8s.io/api/core/v1" diff --git a/remoting/etcdv3/facade.go b/remoting/etcdv3/facade.go index 2edbb66508..52b1cce3e4 100644 --- a/remoting/etcdv3/facade.go +++ b/remoting/etcdv3/facade.go @@ -23,7 +23,7 @@ import ( ) import ( - "github.com/dubbogo/getty" + "github.com/apache/dubbo-getty" perrors "github.com/pkg/errors" ) diff --git a/remoting/zookeeper/facade.go b/remoting/zookeeper/facade.go index d5d9e6e748..2a034390c0 100644 --- a/remoting/zookeeper/facade.go +++ b/remoting/zookeeper/facade.go @@ -21,7 +21,7 @@ import ( "sync" ) import ( - "github.com/dubbogo/getty" + "github.com/apache/dubbo-getty" perrors "github.com/pkg/errors" ) diff --git a/remoting/zookeeper/listener.go b/remoting/zookeeper/listener.go index 4f50c18ab6..486a67e20a 100644 --- a/remoting/zookeeper/listener.go +++ b/remoting/zookeeper/listener.go @@ -25,7 +25,7 @@ import ( ) import ( - "github.com/dubbogo/getty" + "github.com/apache/dubbo-getty" "github.com/dubbogo/go-zookeeper/zk" perrors "github.com/pkg/errors" ) From 31194a853d4e6e5dc715269a58fe1f214c84276e Mon Sep 17 00:00:00 2001 From: lihaowei Date: Sun, 9 Aug 2020 10:58:05 +0800 Subject: [PATCH 179/242] Correct words and Format codes --- protocol/dubbo/client.go | 2 +- registry/nacos/listener.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/protocol/dubbo/client.go b/protocol/dubbo/client.go index 9f70d9377b..f020ec9340 100644 --- a/protocol/dubbo/client.go +++ b/protocol/dubbo/client.go @@ -193,7 +193,7 @@ type Response struct { atta map[string]string } -// nolint +// NewResponse create a new Response. func NewResponse(reply interface{}, atta map[string]string) *Response { return &Response{ reply: reply, diff --git a/registry/nacos/listener.go b/registry/nacos/listener.go index 6699007362..67c35d1abd 100644 --- a/registry/nacos/listener.go +++ b/registry/nacos/listener.go @@ -132,7 +132,7 @@ func (nl *nacosListener) Callback(services []model.SubscribeService, err error) instance := generateInstance(services[i]) newInstanceMap[host] = instance if old, ok := nl.instanceMap[host]; !ok { - // instance is not exist in cache, add it to cache + // instance does not exist in cache, add it to cache addInstances = append(addInstances, instance) } else { // instance is not different from cache, update it to cache @@ -144,7 +144,7 @@ func (nl *nacosListener) Callback(services []model.SubscribeService, err error) for host, inst := range nl.instanceMap { if _, ok := newInstanceMap[host]; !ok { - // cache instance is not exist in new instance list, remove it from cache + // cache instance does not exist in new instance list, remove it from cache delInstances = append(delInstances, inst) } } From 12b6d6aa5e70b0da2a667c4d822edc5593d2105d Mon Sep 17 00:00:00 2001 From: AlexStocks Date: Tue, 18 Aug 2020 10:56:21 +0800 Subject: [PATCH 180/242] Fix: comment error --- protocol/dubbo/client.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/protocol/dubbo/client.go b/protocol/dubbo/client.go index cd25e0a07a..ee7b6ed6f3 100644 --- a/protocol/dubbo/client.go +++ b/protocol/dubbo/client.go @@ -193,11 +193,7 @@ type Response struct { atta map[string]string } -<<<<<<< HEAD -// NewResponse create a new Response. -======= -// nolint ->>>>>>> aaa8be31fea3c0fe7ebbc1247cb273e74eaeb83b +// NewResponse creates a new Response. func NewResponse(reply interface{}, atta map[string]string) *Response { return &Response{ reply: reply, From 7c752e8416577c8a50d6df303cadf2c1f5ae21fc Mon Sep 17 00:00:00 2001 From: AlexStocks Date: Tue, 18 Aug 2020 11:12:29 +0800 Subject: [PATCH 181/242] Fix: format error --- protocol/dubbo/client.go | 2 +- protocol/dubbo/listener.go | 2 +- protocol/dubbo/readwriter.go | 2 +- registry/etcdv3/listener_test.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/protocol/dubbo/client.go b/protocol/dubbo/client.go index ee7b6ed6f3..530beba351 100644 --- a/protocol/dubbo/client.go +++ b/protocol/dubbo/client.go @@ -25,8 +25,8 @@ import ( ) import ( - hessian "github.com/apache/dubbo-go-hessian2" "github.com/apache/dubbo-getty" + hessian "github.com/apache/dubbo-go-hessian2" gxsync "github.com/dubbogo/gost/sync" perrors "github.com/pkg/errors" "go.uber.org/atomic" diff --git a/protocol/dubbo/listener.go b/protocol/dubbo/listener.go index 916230ce15..a17b282fdf 100644 --- a/protocol/dubbo/listener.go +++ b/protocol/dubbo/listener.go @@ -27,8 +27,8 @@ import ( ) import ( - "github.com/apache/dubbo-go-hessian2" "github.com/apache/dubbo-getty" + "github.com/apache/dubbo-go-hessian2" "github.com/opentracing/opentracing-go" perrors "github.com/pkg/errors" ) diff --git a/protocol/dubbo/readwriter.go b/protocol/dubbo/readwriter.go index bdd89a8793..adc6311b0e 100644 --- a/protocol/dubbo/readwriter.go +++ b/protocol/dubbo/readwriter.go @@ -23,8 +23,8 @@ import ( ) import ( - "github.com/apache/dubbo-go-hessian2" "github.com/apache/dubbo-getty" + "github.com/apache/dubbo-go-hessian2" perrors "github.com/pkg/errors" ) diff --git a/registry/etcdv3/listener_test.go b/registry/etcdv3/listener_test.go index 543ddf6c1f..cc4ea257cc 100644 --- a/registry/etcdv3/listener_test.go +++ b/registry/etcdv3/listener_test.go @@ -24,8 +24,8 @@ import ( ) import ( - "github.com/coreos/etcd/embed" "github.com/apache/dubbo-getty" + "github.com/coreos/etcd/embed" "github.com/stretchr/testify/suite" ) From d4336cc27d61f068b500622de4a74bd1687aacef Mon Sep 17 00:00:00 2001 From: AlexStocks Date: Tue, 18 Aug 2020 11:18:20 +0800 Subject: [PATCH 182/242] Fix: format test/integrate/dubbo/go-server/server.go --- test/integrate/dubbo/go-server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integrate/dubbo/go-server/server.go b/test/integrate/dubbo/go-server/server.go index 115bf0a4d7..a5d18dba3c 100644 --- a/test/integrate/dubbo/go-server/server.go +++ b/test/integrate/dubbo/go-server/server.go @@ -48,7 +48,7 @@ func main() { select { case <-stopC: // wait getty send resp to consumer - time.Sleep(3*time.Second) + time.Sleep(3 * time.Second) return case <-time.After(time.Minute): panic("provider already running 1 min, but can't be call by consumer") From e36106786b910e8a34a5a821a62e932fcb915f99 Mon Sep 17 00:00:00 2001 From: AlexStocks Date: Tue, 18 Aug 2020 11:49:13 +0800 Subject: [PATCH 183/242] foramt go.mod --- go.mod | 2 +- go.sum | 14 ++------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index 263bbff37e..3b622d9248 100644 --- a/go.mod +++ b/go.mod @@ -5,13 +5,13 @@ require ( github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/Workiva/go-datastructures v1.0.50 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 + github.com/apache/dubbo-getty v1.3.10 github.com/apache/dubbo-go-hessian2 v1.6.2 github.com/coreos/bbolt v1.3.3 // indirect github.com/coreos/etcd v3.3.13+incompatible github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect github.com/creasty/defaults v1.3.0 github.com/docker/go-connections v0.4.0 // indirect - github.com/apache/dubbo-getty v1.3.10 github.com/dubbogo/go-zookeeper v1.0.1 github.com/dubbogo/gost v1.9.1 github.com/elazarl/go-bindata-assetfs v1.0.0 // indirect diff --git a/go.sum b/go.sum index 4089b7302d..91cdb0da19 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.39.0 h1:UgQP9na6OTfp4dsAiz/eFpFA1C6tPdH5wiRdi19tuMw= -cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= @@ -74,8 +72,6 @@ github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFm github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk= github.com/apache/dubbo-getty v1.3.10 h1:ys5mwjPdxG/KwkPjS6EI0RzQtU6p6FCPoKpaFEzpAL0= github.com/apache/dubbo-getty v1.3.10/go.mod h1:x6rraK01BL5C7jUM2fPl5KMkAxLVIx54ZB8/XEOik9Y= -github.com/apache/dubbo-go-hessian2 v1.6.1 h1:mFKeCZzaCkk4mMOyP+LQ85GHbRyqKT7858KS21JQYA4= -github.com/apache/dubbo-go-hessian2 v1.6.1/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= github.com/apache/dubbo-go-hessian2 v1.6.2 h1:i7F5GjVaUatLQz1x9vUmmSIFj49L8J6rVICdF6xw4qw= github.com/apache/dubbo-go-hessian2 v1.6.2/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= @@ -150,8 +146,6 @@ github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5Xh github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/dubbogo/getty v1.3.9 h1:Ip/4Yl7GyDzt3ddZhjXN1Vmxvo9y/hdtoJ4yFc7czLk= -github.com/dubbogo/getty v1.3.9/go.mod h1:JIN5lNZznGnBuoYjlEe/yjNmq7eSjKaawHdnBAdtiEo= github.com/dubbogo/go-zookeeper v1.0.1 h1:irLzvOsDOTNsN8Sv9tvYYxVu6DCQfLtziZQtUHmZgz8= github.com/dubbogo/go-zookeeper v1.0.1/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c= github.com/dubbogo/gost v1.9.0 h1:UT+dWwvLyJiDotxJERO75jB3Yxgsdy10KztR5ycxRAk= @@ -590,8 +584,7 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 h1:N8Bg45zpk/UcpNGnfJt2y/3lRWASHNTUET8owPYCgYI= -github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d h1:bVQRCxQvfjNUeRqaY/uT0tFuvuFY0ulgnczuR684Xic= github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d/go.mod h1:Cw4GTlQccdRGSEf6KiMju767x0NEHE0YIVPJSaXjlsw= @@ -647,8 +640,6 @@ github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59b github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8 h1:k8TV7Gz7cpWpOw/dz71fx8cCZdWoPuckHJ/wkJl+meg= -github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8/go.mod h1:S1cAa98KMFv4Sa8SbJ6ZtvOmf0VlgH0QJ1gXI0lBfBY= github.com/zouyx/agollo/v3 v3.4.4 h1:5G7QNw3fw74Ns8SfnHNhjndV2mlz5Fg8bB7q84ydFYI= github.com/zouyx/agollo/v3 v3.4.4/go.mod h1:ag0XmE1r4iAgPd6PUnU9TJ0DMEjM1VKX1HUNqQJ2ywU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -800,10 +791,10 @@ golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0 h1:9sdfJOzWlkqPltHAuzT2Cp+yrBeY1KRVYgms8soxMwM= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -823,7 +814,6 @@ google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64 h1:iKtrH9Y8mcbADOP0YFaEMth7OfuHY9xHOwNj4znpM1A= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= From fc8d7b078731ceeba16169457ec6e68187fd557d Mon Sep 17 00:00:00 2001 From: "beiwei.ly" Date: Tue, 18 Aug 2020 14:30:39 +0800 Subject: [PATCH 184/242] use NewTicker instead of builtin to avoid of GC pressure. --- cluster/router/chain/chain.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go index 80f0b25d8a..369f24b3c4 100644 --- a/cluster/router/chain/chain.go +++ b/cluster/router/chain/chain.go @@ -127,8 +127,9 @@ func (c *RouterChain) SetInvokers(invokers []protocol.Invoker) { // from address update, or when timeInterval exceeds. func (c *RouterChain) loop() { for { + ticker := time.NewTicker(timeInterval) select { - case <-time.Tick(timeInterval): + case <-ticker.C: c.buildCache() case <-c.notify: c.buildCache() From 70bd9332b0a9d5b89676f67e5755ae39da87dcef Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Tue, 18 Aug 2020 14:37:27 +0800 Subject: [PATCH 185/242] avoid reflection api --- cluster/router/chain/chain.go | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go index 369f24b3c4..353ea82b40 100644 --- a/cluster/router/chain/chain.go +++ b/cluster/router/chain/chain.go @@ -18,7 +18,6 @@ package chain import ( - "reflect" "sort" "sync" "sync/atomic" @@ -141,8 +140,9 @@ func (c *RouterChain) loop() { func (c *RouterChain) copyRouters() []router.PriorityRouter { c.mutex.RLock() defer c.mutex.RUnlock() - ret := copySlice(c.routers) - return ret.([]router.PriorityRouter) + ret := make([]router.PriorityRouter, 0, len(c.routers)) + ret = append(ret, c.routers...) + return ret } // copyInvokers copies a snapshot of the received invokers. @@ -152,15 +152,9 @@ func (c *RouterChain) copyInvokers() []protocol.Invoker { if c.invokers == nil || len(c.invokers) == 0 { return nil } - ret := copySlice(c.invokers) - return ret.([]protocol.Invoker) -} - -func copySlice(s interface{}) interface{} { - t, v := reflect.TypeOf(s), reflect.ValueOf(s) - c := reflect.MakeSlice(t, v.Len(), v.Len()) - reflect.Copy(c, v) - return c.Interface() + ret := make([]protocol.Invoker, 0, len(c.invokers)) + ret = append(ret, c.invokers...) + return ret } // loadCache loads cache from sync.Value to guarantee the visibility From 7f478948d2f796b7b695cea0e78c04912b1af326 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Tue, 18 Aug 2020 14:40:57 +0800 Subject: [PATCH 186/242] fix typo in comment --- cluster/router/healthcheck/health_check_route.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cluster/router/healthcheck/health_check_route.go b/cluster/router/healthcheck/health_check_route.go index 3a973193a5..75ad189967 100644 --- a/cluster/router/healthcheck/health_check_route.go +++ b/cluster/router/healthcheck/health_check_route.go @@ -66,7 +66,7 @@ func (r *HealthCheckRouter) Route(invokers *roaring.Bitmap, cache router.Cache, addrPool := cache.FindAddrPool(r) // Add healthy invoker to the list healthyInvokers := utils.JoinIfNotEqual(addrPool[healthy], invokers) - // If all Invoke are considered unhealthy, downgrade to all invoker + // If all invokers are considered unhealthy, downgrade to all invoker if healthyInvokers.IsEmpty() { logger.Warnf(" Now all invokers are unhealthy, so downgraded to all! Service: [%s]", url.ServiceKey()) return invokers From 1166752b6f9bd3ce80aa6ae7dc243ca64c851cb7 Mon Sep 17 00:00:00 2001 From: cvictory Date: Tue, 18 Aug 2020 14:54:06 +0800 Subject: [PATCH 187/242] forbid excuting twice --- common/logger/logger.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/logger/logger.go b/common/logger/logger.go index b648f8a0af..63eda231dd 100644 --- a/common/logger/logger.go +++ b/common/logger/logger.go @@ -60,6 +60,10 @@ type Logger interface { } func init() { + // forbidden to executing twice. + if logger != nil { + return + } logConfFile := os.Getenv(constant.APP_LOG_CONF_FILE) err := InitLog(logConfFile) if err != nil { From b04224bbe03d4676122c0972be06c42d4eceb79e Mon Sep 17 00:00:00 2001 From: watermelo <80680489@qq.com> Date: Tue, 18 Aug 2020 18:40:45 +0800 Subject: [PATCH 188/242] Fix: fix tag router rule copy --- cluster/router/tag/router_rule.go | 18 +++++++++--------- cluster/router/tag/tag_router.go | 11 ++++------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/cluster/router/tag/router_rule.go b/cluster/router/tag/router_rule.go index b35d455a6e..c0a2d763ae 100644 --- a/cluster/router/tag/router_rule.go +++ b/cluster/router/tag/router_rule.go @@ -41,8 +41,8 @@ import ( type RouterRule struct { router.BaseRouterRule `yaml:",inline"` Tags []Tag - addressToTagNames map[string][]string - tagNameToAddresses map[string][]string + AddressToTagNames map[string][]string + TagNameToAddresses map[string][]string } func getRule(rawRule string) (*RouterRule, error) { @@ -58,13 +58,13 @@ func getRule(rawRule string) (*RouterRule, error) { // parseTags use for flattening tags data to @addressToTagNames and @tagNameToAddresses func (t *RouterRule) parseTags() { - t.addressToTagNames = make(map[string][]string, 2*len(t.Tags)) - t.tagNameToAddresses = make(map[string][]string, len(t.Tags)) + t.AddressToTagNames = make(map[string][]string, 2*len(t.Tags)) + t.TagNameToAddresses = make(map[string][]string, len(t.Tags)) for _, tag := range t.Tags { for _, address := range tag.Addresses { - t.addressToTagNames[address] = append(t.addressToTagNames[address], tag.Name) + t.AddressToTagNames[address] = append(t.AddressToTagNames[address], tag.Name) } - t.tagNameToAddresses[tag.Name] = tag.Addresses + t.TagNameToAddresses[tag.Name] = tag.Addresses } } @@ -85,15 +85,15 @@ func (t *RouterRule) getTagNames() []string { } func (t *RouterRule) hasTag(tag string) bool { - return len(t.tagNameToAddresses[tag]) > 0 + return len(t.TagNameToAddresses[tag]) > 0 } func (t *RouterRule) getAddressToTagNames() map[string][]string { - return t.addressToTagNames + return t.AddressToTagNames } func (t *RouterRule) getTagNameToAddresses() map[string][]string { - return t.tagNameToAddresses + return t.TagNameToAddresses } func (t *RouterRule) getTags() []Tag { diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index bd5c359524..a5f1dc13d9 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -24,6 +24,7 @@ import ( import ( gxnet "github.com/dubbogo/gost/net" + "github.com/jinzhu/copier" perrors "github.com/pkg/errors" ) @@ -63,11 +64,6 @@ func (c *tagRouter) isEnabled() bool { return c.enabled } -func (c *tagRouter) tagRouterRuleCopy() RouterRule { - routerRule := *c.tagRouterRule - return routerRule -} - // Route gets a list of invoker func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { var ( @@ -85,7 +81,8 @@ func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocati } // since the rule can be changed by config center, we should copy one to use. - tagRouterRuleCopy := c.tagRouterRuleCopy() + tagRouterRuleCopy := new(RouterRule) + _ = copier.Copy(tagRouterRuleCopy, c.tagRouterRule) tag, ok := invocation.Attachments()[constant.Tagkey] if !ok { tag = url.GetParam(constant.Tagkey, "") @@ -93,7 +90,7 @@ func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocati // if we are requesting for a Provider with a specific tag if len(tag) > 0 { - return filterInvokersWithTag(invokers, url, invocation, tagRouterRuleCopy, tag) + return filterInvokersWithTag(invokers, url, invocation, *tagRouterRuleCopy, tag) } // return all addresses in dynamic tag group. From 8c4d42eaee394bbfcd4a4962909a11c6d94cf98b Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Tue, 18 Aug 2020 22:05:04 +0800 Subject: [PATCH 189/242] group var --- cluster/router/chain/chain.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go index 353ea82b40..a7f139b235 100644 --- a/cluster/router/chain/chain.go +++ b/cluster/router/chain/chain.go @@ -177,8 +177,10 @@ func (c *RouterChain) buildCache() { cache := BuildCache(invokers) origin := c.loadCache() - var mutex sync.Mutex - var wg sync.WaitGroup + var ( + mutex sync.Mutex + wg sync.WaitGroup + ) for _, r := range c.copyRouters() { if p, ok := r.(router.Poolable); ok { From f606124479e03b13e713fdba4bf52139277d1bdb Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Tue, 18 Aug 2020 22:07:35 +0800 Subject: [PATCH 190/242] use len(tag) > 0 --- cluster/router/tag/tag_router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index dfd8d451ee..0acdf70ce9 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -298,7 +298,7 @@ func poolWithStaticTag(invokers []protocol.Invoker, pool router.AddrPool) { for i, invoker := range invokers { url := invoker.GetUrl() tag := url.GetParam(constant.Tagkey, "") - if tag != "" { + if len(tag) > 0 { if _, ok := pool[staticPrefix+tag]; !ok { pool[staticPrefix+tag] = roaring.NewBitmap() } From c50da8563dfca762098bd81222e1561842114bd8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 18 Aug 2020 22:29:26 +0800 Subject: [PATCH 191/242] modify msg --- config/config_loader.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/config_loader.go b/config/config_loader.go index ec935346f9..0859f8c3e4 100644 --- a/config/config_loader.go +++ b/config/config_loader.go @@ -21,6 +21,7 @@ import ( "fmt" "log" "os" + "reflect" "strconv" "sync" "time" @@ -226,7 +227,7 @@ func registerServiceInstance() { var rp registry.RegistryFactory var ok bool if rp, ok = p.(registry.RegistryFactory); !ok { - panic("dubbo registry protocol is invalid") + panic("dubbo registry protocol{" + reflect.TypeOf(p).String() + "} is invalid") } rs := rp.GetRegistries() for _, r := range rs { From 5c3f71dc7b435f1e1bf4848e68cbd0dd48d1ff0f Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 18 Aug 2020 22:38:58 +0800 Subject: [PATCH 192/242] add comment --- registry/protocol/protocol.go | 1 + registry/service_discovery_factory.go | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/registry/protocol/protocol.go b/registry/protocol/protocol.go index 33e3774596..cbaafd7731 100644 --- a/registry/protocol/protocol.go +++ b/registry/protocol/protocol.go @@ -117,6 +117,7 @@ func (proto *registryProtocol) initConfigurationListeners() { proto.providerConfigurationListener = newProviderConfigurationListener(proto.overrideListeners) } +// nolint func (proto *registryProtocol) GetRegistries() []registry.Registry { var rs []registry.Registry proto.registries.Range(func(_, v interface{}) bool { diff --git a/registry/service_discovery_factory.go b/registry/service_discovery_factory.go index 86c38d0cf3..3bcf72612a 100644 --- a/registry/service_discovery_factory.go +++ b/registry/service_discovery_factory.go @@ -17,7 +17,8 @@ package registry -// ServiceDiscoveryHolder +// ServiceDiscoveryHolder we can get a service discovery +// it always be a service discovery registry type ServiceDiscoveryHolder interface { // GetServiceDiscovery get service discovery GetServiceDiscovery() ServiceDiscovery From 0cb2789383a28c972dd0eccb57293a6bd959d7a7 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Tue, 18 Aug 2020 23:19:01 +0800 Subject: [PATCH 193/242] switch to use sync.Mutex instead of atomic.Value. --- cluster/router/chain/chain.go | 50 +++++++++++---------------- cluster/router/chain/invoker_cache.go | 27 +++++++++++++++ cluster/router/router.go | 4 +-- cluster/router/tag/tag_router.go | 7 ++-- 4 files changed, 53 insertions(+), 35 deletions(-) diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go index a7f139b235..ee4dca5b4f 100644 --- a/cluster/router/chain/chain.go +++ b/cluster/router/chain/chain.go @@ -20,7 +20,6 @@ package chain import ( "sort" "sync" - "sync/atomic" "time" ) @@ -64,17 +63,20 @@ type RouterChain struct { // Channel for notify to update the address cache notify chan struct{} // Address cache - cache atomic.Value + cache *InvokerCache } // Route Loop routers in RouterChain and call Route method to determine the target invokers list. func (c *RouterChain) Route(url *common.URL, invocation protocol.Invocation) []protocol.Invoker { - cache := c.loadCache() - if cache == nil { - c.mutex.RLock() + var cache *InvokerCache + c.mutex.RLock() + if c.cache == nil { defer c.mutex.RUnlock() return c.invokers } + // FIXME: this means clone happens in request scope which should be avoid + cache = c.cache.Clone() + c.mutex.RUnlock() bitmap := cache.bitmap for _, r := range c.copyRouters() { @@ -145,37 +147,23 @@ func (c *RouterChain) copyRouters() []router.PriorityRouter { return ret } -// copyInvokers copies a snapshot of the received invokers. -func (c *RouterChain) copyInvokers() []protocol.Invoker { +// buildCache builds address cache with the new invokers for all poolable routers. +func (c *RouterChain) buildCache() { c.mutex.RLock() - defer c.mutex.RUnlock() if c.invokers == nil || len(c.invokers) == 0 { - return nil - } - ret := make([]protocol.Invoker, 0, len(c.invokers)) - ret = append(ret, c.invokers...) - return ret -} - -// loadCache loads cache from sync.Value to guarantee the visibility -func (c *RouterChain) loadCache() *InvokerCache { - v := c.cache.Load() - if v == nil { - return nil + defer c.mutex.RUnlock() + return } - return v.(*InvokerCache) -} - -// buildCache builds address cache with the new invokers for all poolable routers. -func (c *RouterChain) buildCache() { - invokers := c.copyInvokers() - if invokers == nil || len(c.invokers) == 0 { - return + invokers := make([]protocol.Invoker, 0, len(c.invokers)) + invokers = append(invokers, c.invokers...) + var origin *InvokerCache + if c.cache != nil { + origin = c.cache.Clone() } + c.mutex.RUnlock() cache := BuildCache(invokers) - origin := c.loadCache() var ( mutex sync.Mutex @@ -197,7 +185,9 @@ func (c *RouterChain) buildCache() { } wg.Wait() - c.cache.Store(cache) + c.mutex.Lock() + defer c.mutex.Unlock() + c.cache = cache } // URL Return URL in RouterChain diff --git a/cluster/router/chain/invoker_cache.go b/cluster/router/chain/invoker_cache.go index 43cdfa5067..b1b39d09a7 100644 --- a/cluster/router/chain/invoker_cache.go +++ b/cluster/router/chain/invoker_cache.go @@ -78,3 +78,30 @@ func (c *InvokerCache) SetAddrPool(name string, pool router.AddrPool) { func (c *InvokerCache) SetAddrMeta(name string, meta router.AddrMetadata) { c.metadatas[name] = meta } + +func (c *InvokerCache) Clone() *InvokerCache { + ret := &InvokerCache{ + pools: make(map[string]router.AddrPool, len(c.pools)), + metadatas: make(map[string]router.AddrMetadata, len(c.metadatas)), + } + + invokers := make([]protocol.Invoker, 0, len(c.invokers)) + invokers = append(invokers, c.invokers...) + ret.invokers = invokers + + ret.bitmap = c.bitmap.Clone() + + for k, v := range c.pools { + pool := make(router.AddrPool, len(v)) + for k1, v1 := range pool { + pool[k1] = v1.Clone() + } + ret.pools[k] = pool + } + + for k, v := range c.metadatas { + ret.metadatas[k] = v.Clone() + } + + return ret +} diff --git a/cluster/router/router.go b/cluster/router/router.go index ddca42a01d..f68e4fcb5c 100644 --- a/cluster/router/router.go +++ b/cluster/router/router.go @@ -74,8 +74,8 @@ type AddrPool map[string]*roaring.Bitmap // AddrMetadta is address metadata, collected from a snapshot of address list by a router, if it implements Poolable. type AddrMetadata interface { - // Source indicates where the metadata comes from. - Source() string + // Copy returns a copy of address metadata. + Clone() AddrMetadata } // Cache caches all addresses relevant info for a snapshot of received invokers. It keeps a snapshot of the received diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index 0acdf70ce9..efa6dd179c 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -62,9 +62,10 @@ type addrMetadata struct { ruleEnabled bool } -// Source indicates where the metadata comes from. -func (m *addrMetadata) Source() string { - return name +// Clone returns a copy of addrMetadata +func (m *addrMetadata) Clone() router.AddrMetadata { + ret := *m + return &ret } // tagRouter defines url, enable and the priority From 066629bcee50e5f8e4c0cad8902156437897edde Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Wed, 19 Aug 2020 11:30:42 +0800 Subject: [PATCH 194/242] new impl for address cache lifecycle --- cluster/router/chain/chain.go | 49 +++++++++++++++++++-------- cluster/router/chain/invoker_cache.go | 37 ++++++++------------ cluster/router/router.go | 4 +-- cluster/router/tag/tag_router.go | 7 ++-- 4 files changed, 54 insertions(+), 43 deletions(-) diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go index ee4dca5b4f..10d812a2e9 100644 --- a/cluster/router/chain/chain.go +++ b/cluster/router/chain/chain.go @@ -62,21 +62,19 @@ type RouterChain struct { last time.Time // Channel for notify to update the address cache notify chan struct{} - // Address cache - cache *InvokerCache + // Address caches, all caches still in used will be kept + caches []*InvokerCache } // Route Loop routers in RouterChain and call Route method to determine the target invokers list. func (c *RouterChain) Route(url *common.URL, invocation protocol.Invocation) []protocol.Invoker { - var cache *InvokerCache - c.mutex.RLock() - if c.cache == nil { - defer c.mutex.RUnlock() + cache, ok := c.findCache() + if !ok { return c.invokers } - // FIXME: this means clone happens in request scope which should be avoid - cache = c.cache.Clone() - c.mutex.RUnlock() + + cache.in() + defer cache.out() bitmap := cache.bitmap for _, r := range c.copyRouters() { @@ -147,6 +145,27 @@ func (c *RouterChain) copyRouters() []router.PriorityRouter { return ret } +// findCache returns the latest cache (at the end of the cache list), if there's no valid entry in the list yet, +// return false instead. +func (c *RouterChain) findCache() (*InvokerCache, bool) { + c.mutex.RLock() + defer c.mutex.RUnlock() + if c.caches == nil || len(c.caches) == 0 { + return nil, false + } + return c.caches[len(c.caches)-1], true +} + +func sweepUnusedCache(caches []*InvokerCache) []*InvokerCache { + ret := caches[:0] + for _, c := range caches { + if c.isInUse() { + ret = append(ret, c) + } + } + return ret +} + // buildCache builds address cache with the new invokers for all poolable routers. func (c *RouterChain) buildCache() { c.mutex.RLock() @@ -157,19 +176,20 @@ func (c *RouterChain) buildCache() { invokers := make([]protocol.Invoker, 0, len(c.invokers)) invokers = append(invokers, c.invokers...) + var origin *InvokerCache - if c.cache != nil { - origin = c.cache.Clone() + if c.caches != nil && len(c.caches) > 0 { + origin = c.caches[len(c.caches)-1] + c.caches = sweepUnusedCache(c.caches) } c.mutex.RUnlock() - cache := BuildCache(invokers) - var ( mutex sync.Mutex wg sync.WaitGroup ) + cache := BuildCache(invokers) for _, r := range c.copyRouters() { if p, ok := r.(router.Poolable); ok { wg.Add(1) @@ -187,7 +207,7 @@ func (c *RouterChain) buildCache() { c.mutex.Lock() defer c.mutex.Unlock() - c.cache = cache + c.caches = append(c.caches, cache) } // URL Return URL in RouterChain @@ -221,6 +241,7 @@ func NewRouterChain(url *common.URL) (*RouterChain, error) { builtinRouters: routers, routers: newRouters, last: time.Now(), + caches: []*InvokerCache{}, notify: make(chan struct{}), } if url != nil { diff --git a/cluster/router/chain/invoker_cache.go b/cluster/router/chain/invoker_cache.go index b1b39d09a7..da12d5af93 100644 --- a/cluster/router/chain/invoker_cache.go +++ b/cluster/router/chain/invoker_cache.go @@ -19,6 +19,7 @@ package chain import ( "github.com/RoaringBitmap/roaring" + "go.uber.org/atomic" ) import ( @@ -42,6 +43,8 @@ type InvokerCache struct { // Address metadata from routers which implement Poolable metadatas map[string]router.AddrMetadata + + inUse atomic.Int32 } // BuildCache builds address cache from the given invokers. @@ -79,29 +82,17 @@ func (c *InvokerCache) SetAddrMeta(name string, meta router.AddrMetadata) { c.metadatas[name] = meta } -func (c *InvokerCache) Clone() *InvokerCache { - ret := &InvokerCache{ - pools: make(map[string]router.AddrPool, len(c.pools)), - metadatas: make(map[string]router.AddrMetadata, len(c.metadatas)), - } - - invokers := make([]protocol.Invoker, 0, len(c.invokers)) - invokers = append(invokers, c.invokers...) - ret.invokers = invokers - - ret.bitmap = c.bitmap.Clone() - - for k, v := range c.pools { - pool := make(router.AddrPool, len(v)) - for k1, v1 := range pool { - pool[k1] = v1.Clone() - } - ret.pools[k] = pool - } +// in increases inUse count at the beginning of every request, used only by router chain +func (c *InvokerCache) in() { + c.inUse.Inc() +} - for k, v := range c.metadatas { - ret.metadatas[k] = v.Clone() - } +// out decreases inUse count at the end of every request, used only by router chain +func (c *InvokerCache) out() { + c.inUse.Dec() +} - return ret +// isInUse returns false when inUse count equals to 0, so that it can be safely removed by router chain +func (c *InvokerCache) isInUse() bool { + return c.inUse.Load() > 0 } diff --git a/cluster/router/router.go b/cluster/router/router.go index f68e4fcb5c..ddca42a01d 100644 --- a/cluster/router/router.go +++ b/cluster/router/router.go @@ -74,8 +74,8 @@ type AddrPool map[string]*roaring.Bitmap // AddrMetadta is address metadata, collected from a snapshot of address list by a router, if it implements Poolable. type AddrMetadata interface { - // Copy returns a copy of address metadata. - Clone() AddrMetadata + // Source indicates where the metadata comes from. + Source() string } // Cache caches all addresses relevant info for a snapshot of received invokers. It keeps a snapshot of the received diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index efa6dd179c..0acdf70ce9 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -62,10 +62,9 @@ type addrMetadata struct { ruleEnabled bool } -// Clone returns a copy of addrMetadata -func (m *addrMetadata) Clone() router.AddrMetadata { - ret := *m - return &ret +// Source indicates where the metadata comes from. +func (m *addrMetadata) Source() string { + return name } // tagRouter defines url, enable and the priority From fd183d849f15113f3eccda89ce431152dc5b3268 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Wed, 19 Aug 2020 12:44:15 +0800 Subject: [PATCH 195/242] switch back to atomic value, I believe this is the best solution come up so far --- cluster/router/chain/chain.go | 63 +++++++++++---------------- cluster/router/chain/invoker_cache.go | 18 -------- 2 files changed, 26 insertions(+), 55 deletions(-) diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go index 10d812a2e9..a7f139b235 100644 --- a/cluster/router/chain/chain.go +++ b/cluster/router/chain/chain.go @@ -20,6 +20,7 @@ package chain import ( "sort" "sync" + "sync/atomic" "time" ) @@ -62,20 +63,19 @@ type RouterChain struct { last time.Time // Channel for notify to update the address cache notify chan struct{} - // Address caches, all caches still in used will be kept - caches []*InvokerCache + // Address cache + cache atomic.Value } // Route Loop routers in RouterChain and call Route method to determine the target invokers list. func (c *RouterChain) Route(url *common.URL, invocation protocol.Invocation) []protocol.Invoker { - cache, ok := c.findCache() - if !ok { + cache := c.loadCache() + if cache == nil { + c.mutex.RLock() + defer c.mutex.RUnlock() return c.invokers } - cache.in() - defer cache.out() - bitmap := cache.bitmap for _, r := range c.copyRouters() { bitmap = r.Route(bitmap, cache, url, invocation) @@ -145,51 +145,43 @@ func (c *RouterChain) copyRouters() []router.PriorityRouter { return ret } -// findCache returns the latest cache (at the end of the cache list), if there's no valid entry in the list yet, -// return false instead. -func (c *RouterChain) findCache() (*InvokerCache, bool) { +// copyInvokers copies a snapshot of the received invokers. +func (c *RouterChain) copyInvokers() []protocol.Invoker { c.mutex.RLock() defer c.mutex.RUnlock() - if c.caches == nil || len(c.caches) == 0 { - return nil, false + if c.invokers == nil || len(c.invokers) == 0 { + return nil } - return c.caches[len(c.caches)-1], true + ret := make([]protocol.Invoker, 0, len(c.invokers)) + ret = append(ret, c.invokers...) + return ret } -func sweepUnusedCache(caches []*InvokerCache) []*InvokerCache { - ret := caches[:0] - for _, c := range caches { - if c.isInUse() { - ret = append(ret, c) - } +// loadCache loads cache from sync.Value to guarantee the visibility +func (c *RouterChain) loadCache() *InvokerCache { + v := c.cache.Load() + if v == nil { + return nil } - return ret + + return v.(*InvokerCache) } // buildCache builds address cache with the new invokers for all poolable routers. func (c *RouterChain) buildCache() { - c.mutex.RLock() - if c.invokers == nil || len(c.invokers) == 0 { - defer c.mutex.RUnlock() + invokers := c.copyInvokers() + if invokers == nil || len(c.invokers) == 0 { return } - invokers := make([]protocol.Invoker, 0, len(c.invokers)) - invokers = append(invokers, c.invokers...) - - var origin *InvokerCache - if c.caches != nil && len(c.caches) > 0 { - origin = c.caches[len(c.caches)-1] - c.caches = sweepUnusedCache(c.caches) - } - c.mutex.RUnlock() + cache := BuildCache(invokers) + origin := c.loadCache() var ( mutex sync.Mutex wg sync.WaitGroup ) - cache := BuildCache(invokers) for _, r := range c.copyRouters() { if p, ok := r.(router.Poolable); ok { wg.Add(1) @@ -205,9 +197,7 @@ func (c *RouterChain) buildCache() { } wg.Wait() - c.mutex.Lock() - defer c.mutex.Unlock() - c.caches = append(c.caches, cache) + c.cache.Store(cache) } // URL Return URL in RouterChain @@ -241,7 +231,6 @@ func NewRouterChain(url *common.URL) (*RouterChain, error) { builtinRouters: routers, routers: newRouters, last: time.Now(), - caches: []*InvokerCache{}, notify: make(chan struct{}), } if url != nil { diff --git a/cluster/router/chain/invoker_cache.go b/cluster/router/chain/invoker_cache.go index da12d5af93..43cdfa5067 100644 --- a/cluster/router/chain/invoker_cache.go +++ b/cluster/router/chain/invoker_cache.go @@ -19,7 +19,6 @@ package chain import ( "github.com/RoaringBitmap/roaring" - "go.uber.org/atomic" ) import ( @@ -43,8 +42,6 @@ type InvokerCache struct { // Address metadata from routers which implement Poolable metadatas map[string]router.AddrMetadata - - inUse atomic.Int32 } // BuildCache builds address cache from the given invokers. @@ -81,18 +78,3 @@ func (c *InvokerCache) SetAddrPool(name string, pool router.AddrPool) { func (c *InvokerCache) SetAddrMeta(name string, meta router.AddrMetadata) { c.metadatas[name] = meta } - -// in increases inUse count at the beginning of every request, used only by router chain -func (c *InvokerCache) in() { - c.inUse.Inc() -} - -// out decreases inUse count at the end of every request, used only by router chain -func (c *InvokerCache) out() { - c.inUse.Dec() -} - -// isInUse returns false when inUse count equals to 0, so that it can be safely removed by router chain -func (c *InvokerCache) isInUse() bool { - return c.inUse.Load() > 0 -} From c1678424b67bda775e1728ceb20a440ca1292196 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Wed, 19 Aug 2020 13:28:27 +0800 Subject: [PATCH 196/242] avoid of copying and comparing invokers as much as possible --- cluster/router/chain/chain.go | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go index a7f139b235..b15b845cf1 100644 --- a/cluster/router/chain/chain.go +++ b/cluster/router/chain/chain.go @@ -167,21 +167,35 @@ func (c *RouterChain) loadCache() *InvokerCache { return v.(*InvokerCache) } +// copyInvokerIfNecessary compares chain's invokers copy and cache's invokers copy, to avoid copy as much as possible +func (c *RouterChain) copyInvokerIfNecessary(cache *InvokerCache) []protocol.Invoker { + var invokers []protocol.Invoker + if cache != nil { + invokers = cache.invokers + } + + c.mutex.RLock() + defer c.mutex.RUnlock() + if isInvokersChanged(invokers, c.invokers) { + invokers = c.copyInvokers() + } + return invokers +} + // buildCache builds address cache with the new invokers for all poolable routers. func (c *RouterChain) buildCache() { - invokers := c.copyInvokers() - if invokers == nil || len(c.invokers) == 0 { + origin := c.loadCache() + invokers := c.copyInvokerIfNecessary(origin) + if invokers == nil || len(invokers) == 0 { return } - cache := BuildCache(invokers) - origin := c.loadCache() - var ( mutex sync.Mutex wg sync.WaitGroup ) + cache := BuildCache(invokers) for _, r := range c.copyRouters() { if p, ok := r.(router.Poolable); ok { wg.Add(1) @@ -246,7 +260,7 @@ func NewRouterChain(url *common.URL) (*RouterChain, error) { // rule doesn't change), and the address list doesn't change, then the existing data will be re-used. func poolRouter(p router.Poolable, origin *InvokerCache, invokers []protocol.Invoker) (router.AddrPool, router.AddrMetadata) { name := p.Name() - if isCacheMiss(origin, name) || p.ShouldPool() || isInvokersChanged(origin.invokers, invokers) { + if isCacheMiss(origin, name) || p.ShouldPool() || &(origin.invokers) != &invokers { logger.Debugf("build address cache for router %q", name) return p.Pool(invokers) } From 43a30595f519c0236e060c05d78965910bf191f0 Mon Sep 17 00:00:00 2001 From: lzp0412 <641785844@qq.com> Date: Wed, 19 Aug 2020 19:31:06 +0800 Subject: [PATCH 197/242] asynchronous subscribe nacos service info --- registry/nacos/listener.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/registry/nacos/listener.go b/registry/nacos/listener.go index 67c35d1abd..cf6a73d38f 100644 --- a/registry/nacos/listener.go +++ b/registry/nacos/listener.go @@ -188,7 +188,8 @@ func (nl *nacosListener) startListen() error { } serviceName := getSubscribeName(nl.listenUrl) nl.subscribeParam = &vo.SubscribeParam{ServiceName: serviceName, SubscribeCallback: nl.Callback} - return nl.namingClient.Subscribe(nl.subscribeParam) + go nl.namingClient.Subscribe(nl.subscribeParam) + return nil } func (nl *nacosListener) stopListen() error { From cadae7b462d2ecf912e4dfd530ae800855953e18 Mon Sep 17 00:00:00 2001 From: "xg.gao" Date: Wed, 19 Aug 2020 23:24:36 +0800 Subject: [PATCH 198/242] format --- config/service_config.go | 16 +++++++------- protocol/jsonrpc/jsonrpc_protocol.go | 2 +- registry/protocol/protocol.go | 1 - registry/registry.go | 33 +++++++++++++++++----------- 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/config/service_config.go b/config/service_config.go index 54383e4791..b1e2303fc3 100644 --- a/config/service_config.go +++ b/config/service_config.go @@ -145,14 +145,14 @@ func getRandomPort(protocolConfigs []*ProtocolConfig) *list.List { func (c *ServiceConfig) Export() error { // TODO: config center start here - // TODO:delay export + // TODO: delay export if c.unexported != nil && c.unexported.Load() { - err := perrors.Errorf("The service %v has already unexported! ", c.InterfaceName) + err := perrors.Errorf("The service %v has already unexported!", c.InterfaceName) logger.Errorf(err.Error()) return err } if c.unexported != nil && c.exported.Load() { - logger.Warnf("The service %v has already exported! ", c.InterfaceName) + logger.Warnf("The service %v has already exported!", c.InterfaceName) return nil } @@ -160,7 +160,7 @@ func (c *ServiceConfig) Export() error { urlMap := c.getUrlMap() protocolConfigs := loadProtocol(c.Protocol, c.Protocols) if len(protocolConfigs) == 0 { - logger.Warnf("The service %v's '%v' protocols don't has right protocolConfigs ", c.InterfaceName, c.Protocol) + logger.Warnf("The service %v's '%v' protocols don't has right protocolConfigs", c.InterfaceName, c.Protocol) return nil } @@ -170,7 +170,7 @@ func (c *ServiceConfig) Export() error { // registry the service reflect methods, err := common.ServiceMap.Register(c.InterfaceName, proto.Name, c.rpcService) if err != nil { - formatErr := perrors.Errorf("The service %v export the protocol %v error! Error message is %v .", c.InterfaceName, proto.Name, err.Error()) + formatErr := perrors.Errorf("The service %v export the protocol %v error! Error message is %v.", c.InterfaceName, proto.Name, err.Error()) logger.Errorf(formatErr.Error()) return formatErr } @@ -204,7 +204,7 @@ func (c *ServiceConfig) Export() error { c.cacheMutex.Lock() if c.cacheProtocol == nil { - logger.Infof(fmt.Sprintf("First load the registry protocol , url is {%v}!", ivkURL)) + logger.Infof(fmt.Sprintf("First load the registry protocol, url is {%v}!", ivkURL)) c.cacheProtocol = extension.GetProtocol("registry") } c.cacheMutex.Unlock() @@ -212,14 +212,14 @@ func (c *ServiceConfig) Export() error { invoker := extension.GetProxyFactory(providerConfig.ProxyFactory).GetInvoker(*regUrl) exporter = c.cacheProtocol.Export(invoker) if exporter == nil { - panic(perrors.New(fmt.Sprintf("Registry protocol new exporter error,registry is {%v},url is {%v}", regUrl, ivkURL))) + return perrors.New(fmt.Sprintf("Registry protocol new exporter error, registry is {%v}, url is {%v}", regUrl, ivkURL)) } } } else { invoker := extension.GetProxyFactory(providerConfig.ProxyFactory).GetInvoker(*ivkURL) exporter = extension.GetProtocol(protocolwrapper.FILTER).Export(invoker) if exporter == nil { - panic(perrors.New(fmt.Sprintf("Filter protocol without registry new exporter error,url is {%v}", ivkURL))) + return perrors.New(fmt.Sprintf("Filter protocol without registry new exporter error, url is {%v}", ivkURL)) } } c.exporters = append(c.exporters, exporter) diff --git a/protocol/jsonrpc/jsonrpc_protocol.go b/protocol/jsonrpc/jsonrpc_protocol.go index 90a6bf5ef7..1778d99d40 100644 --- a/protocol/jsonrpc/jsonrpc_protocol.go +++ b/protocol/jsonrpc/jsonrpc_protocol.go @@ -59,7 +59,7 @@ func NewJsonrpcProtocol() *JsonrpcProtocol { } } -// Export JSON RPC service for remote invocation +// Export JSON RPC service for remote invocation func (jp *JsonrpcProtocol) Export(invoker protocol.Invoker) protocol.Exporter { url := invoker.GetUrl() serviceKey := strings.TrimPrefix(url.Path, "/") diff --git a/registry/protocol/protocol.go b/registry/protocol/protocol.go index e8f4dc4701..95f8155ee4 100644 --- a/registry/protocol/protocol.go +++ b/registry/protocol/protocol.go @@ -205,7 +205,6 @@ func (proto *registryProtocol) Export(invoker protocol.Invoker) protocol.Exporte go reg.Subscribe(overriderUrl, overrideSubscribeListener) return cachedExporter.(protocol.Exporter) - } func (proto *registryProtocol) reExport(invoker protocol.Invoker, newUrl *common.URL) { diff --git a/registry/registry.go b/registry/registry.go index 5e77eab186..2a6968c821 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -30,30 +30,37 @@ import ( // Registry Extension - Registry type Registry interface { common.Node - //used for service provider calling , register services to registry - //And it is also used for service consumer calling , register services cared about ,for dubbo's admin monitoring. + + // used for service provider calling, register services to registry + // And it is also used for service consumer calling, register + // services cared about, for dubbo's admin monitoring. Register(url common.URL) error // UnRegister is required to support the contract: - // 1. If it is the persistent stored data of dynamic=false, the registration data can not be found, then the IllegalStateException is thrown, otherwise it is ignored. + // 1. If it is the persistent stored data of dynamic=false, the + // registration data can not be found, then the IllegalStateException + // is thrown, otherwise it is ignored. // 2. Unregister according to the full url match. - // url Registration information , is not allowed to be empty, e.g: dubbo://10.20.153.10/org.apache.dubbo.foo.BarService?version=1.0.0&application=kylin + // url Registration information, is not allowed to be empty, e.g: + // dubbo://10.20.153.10/org.apache.dubbo.foo.BarService?version=1.0.0&application=kylin UnRegister(url common.URL) error - //When creating new registry extension,pls select one of the following modes. - //Will remove in dubbogo version v1.1.0 - //mode1 : return Listener with Next function which can return subscribe service event from registry - //Deprecated! - //subscribe(event.URL) (Listener, error) - - //Will replace mode1 in dubbogo version v1.1.0 - //mode2 : callback mode, subscribe with notify(notify listener). + // When creating new registry extension, pls select one of the + // following modes. + // Will remove in dubbogo version v1.1.0 + // mode1: return Listener with Next function which can return + // subscribe service event from registry + // Deprecated! + // subscribe(event.URL) (Listener, error) + // Will replace mode1 in dubbogo version v1.1.0 + // mode2: callback mode, subscribe with notify(notify listener). Subscribe(*common.URL, NotifyListener) error // UnSubscribe is required to support the contract: // 1. If don't subscribe, ignore it directly. // 2. Unsubscribe by full URL match. - // url Subscription condition, not allowed to be empty, e.g. consumer://10.20.153.10/org.apache.dubbo.foo.BarService?version=1.0.0&application=kylin + // url Subscription condition, not allowed to be empty, e.g. + // consumer://10.20.153.10/org.apache.dubbo.foo.BarService?version=1.0.0&application=kylin // listener A listener of the change event, not allowed to be empty UnSubscribe(*common.URL, NotifyListener) error } From 7442ec3f97a1eeca4ce8cbd7901af70eeeccae3d Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 20 Aug 2020 12:33:04 +0800 Subject: [PATCH 199/242] fix imports --- config/config_loader_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config_loader_test.go b/config/config_loader_test.go index 88d3ca4e52..9f95facb62 100644 --- a/config/config_loader_test.go +++ b/config/config_loader_test.go @@ -27,6 +27,7 @@ import ( import ( cm "github.com/Workiva/go-datastructures/common" "github.com/Workiva/go-datastructures/slice/skip" + gxpage "github.com/dubbogo/gost/page" "github.com/stretchr/testify/assert" "go.uber.org/atomic" ) @@ -43,7 +44,6 @@ import ( "github.com/apache/dubbo-go/metadata/service" "github.com/apache/dubbo-go/registry" gxset "github.com/dubbogo/gost/container/set" - gxpage "github.com/dubbogo/gost/page" ) const mockConsumerConfigPath = "./testdata/consumer_config.yml" From 941e69d49025a356c736dd83d15d41cbee8c4969 Mon Sep 17 00:00:00 2001 From: "xg.gao" Date: Thu, 20 Aug 2020 13:26:51 +0800 Subject: [PATCH 200/242] format --- config/service_config.go | 22 +++++++++---------- .../protocol_filter_wrapper.go | 15 +++++-------- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/config/service_config.go b/config/service_config.go index b1e2303fc3..70f9083939 100644 --- a/config/service_config.go +++ b/config/service_config.go @@ -166,6 +166,7 @@ func (c *ServiceConfig) Export() error { ports := getRandomPort(protocolConfigs) nextPort := ports.Front() + proxyFactory := extension.GetProxyFactory(providerConfig.ProxyFactory) for _, proto := range protocolConfigs { // registry the service reflect methods, err := common.ServiceMap.Register(c.InterfaceName, proto.Name, c.rpcService) @@ -176,7 +177,6 @@ func (c *ServiceConfig) Export() error { } port := proto.Port - if len(proto.Port) == 0 { port = nextPort.Value.(string) nextPort = nextPort.Next() @@ -197,26 +197,24 @@ func (c *ServiceConfig) Export() error { } var exporter protocol.Exporter - if len(regUrls) > 0 { + c.cacheMutex.Lock() + if c.cacheProtocol == nil { + logger.Infof(fmt.Sprintf("First load the registry protocol, url is {%v}!", ivkURL)) + c.cacheProtocol = extension.GetProtocol("registry") + } + c.cacheMutex.Unlock() + for _, regUrl := range regUrls { regUrl.SubURL = ivkURL - - c.cacheMutex.Lock() - if c.cacheProtocol == nil { - logger.Infof(fmt.Sprintf("First load the registry protocol, url is {%v}!", ivkURL)) - c.cacheProtocol = extension.GetProtocol("registry") - } - c.cacheMutex.Unlock() - - invoker := extension.GetProxyFactory(providerConfig.ProxyFactory).GetInvoker(*regUrl) + invoker := proxyFactory.GetInvoker(*regUrl) exporter = c.cacheProtocol.Export(invoker) if exporter == nil { return perrors.New(fmt.Sprintf("Registry protocol new exporter error, registry is {%v}, url is {%v}", regUrl, ivkURL)) } } } else { - invoker := extension.GetProxyFactory(providerConfig.ProxyFactory).GetInvoker(*ivkURL) + invoker := proxyFactory.GetInvoker(*ivkURL) exporter = extension.GetProtocol(protocolwrapper.FILTER).Export(invoker) if exporter == nil { return perrors.New(fmt.Sprintf("Filter protocol without registry new exporter error, url is {%v}", ivkURL)) diff --git a/protocol/protocolwrapper/protocol_filter_wrapper.go b/protocol/protocolwrapper/protocol_filter_wrapper.go index 87f90d3b7c..af64f8a922 100644 --- a/protocol/protocolwrapper/protocol_filter_wrapper.go +++ b/protocol/protocolwrapper/protocol_filter_wrapper.go @@ -68,21 +68,16 @@ func (pfw *ProtocolFilterWrapper) Destroy() { } func buildInvokerChain(invoker protocol.Invoker, key string) protocol.Invoker { - filtName := invoker.GetUrl().GetParam(key, "") - if filtName == "" { - return invoker - } - filtNames := strings.Split(filtName, ",") - next := invoker + filterName := invoker.GetUrl().GetParam(key, "") + filterNames := strings.Split(filterName, ",") // The order of filters is from left to right, so loading from right to left - - for i := len(filtNames) - 1; i >= 0; i-- { - flt := extension.GetFilter(filtNames[i]) + next := invoker + for i := len(filterNames) - 1; i >= 0; i-- { + flt := extension.GetFilter(filterNames[i]) fi := &FilterInvoker{next: next, invoker: invoker, filter: flt} next = fi } - return next } From 1708b4183220b097726e40f4caf43d79a42caee5 Mon Sep 17 00:00:00 2001 From: "xg.gao" Date: Thu, 20 Aug 2020 13:40:10 +0800 Subject: [PATCH 201/242] fix bug --- config/service_config.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/service_config.go b/config/service_config.go index 70f9083939..2b074b80cc 100644 --- a/config/service_config.go +++ b/config/service_config.go @@ -196,7 +196,6 @@ func (c *ServiceConfig) Export() error { ivkURL.AddParam(constant.Tagkey, c.Tag) } - var exporter protocol.Exporter if len(regUrls) > 0 { c.cacheMutex.Lock() if c.cacheProtocol == nil { @@ -208,19 +207,20 @@ func (c *ServiceConfig) Export() error { for _, regUrl := range regUrls { regUrl.SubURL = ivkURL invoker := proxyFactory.GetInvoker(*regUrl) - exporter = c.cacheProtocol.Export(invoker) + exporter := c.cacheProtocol.Export(invoker) if exporter == nil { return perrors.New(fmt.Sprintf("Registry protocol new exporter error, registry is {%v}, url is {%v}", regUrl, ivkURL)) } + c.exporters = append(c.exporters, exporter) } } else { invoker := proxyFactory.GetInvoker(*ivkURL) - exporter = extension.GetProtocol(protocolwrapper.FILTER).Export(invoker) + exporter := extension.GetProtocol(protocolwrapper.FILTER).Export(invoker) if exporter == nil { return perrors.New(fmt.Sprintf("Filter protocol without registry new exporter error, url is {%v}", ivkURL)) } + c.exporters = append(c.exporters, exporter) } - c.exporters = append(c.exporters, exporter) } c.exported.Store(true) return nil From 60f982288150392d860ed209de239c0425e9ec12 Mon Sep 17 00:00:00 2001 From: "xg.gao" Date: Thu, 20 Aug 2020 14:09:36 +0800 Subject: [PATCH 202/242] format --- config/service_config.go | 2 +- registry/protocol/protocol.go | 16 +++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/config/service_config.go b/config/service_config.go index 2b074b80cc..e1c9a5b187 100644 --- a/config/service_config.go +++ b/config/service_config.go @@ -133,7 +133,7 @@ func getRandomPort(protocolConfigs []*ProtocolConfig) *list.List { tcp, err := gxnet.ListenOnTCPRandomPort(proto.Ip) if err != nil { - panic(perrors.New(fmt.Sprintf("Get tcp port error,err is {%v}", err))) + panic(perrors.New(fmt.Sprintf("Get tcp port error, err is {%v}", err))) } defer tcp.Close() ports.PushBack(strings.Split(tcp.Addr().String(), ":")[1]) diff --git a/registry/protocol/protocol.go b/registry/protocol/protocol.go index 95f8155ee4..50b056c540 100644 --- a/registry/protocol/protocol.go +++ b/registry/protocol/protocol.go @@ -22,6 +22,7 @@ import ( "strings" "sync" ) + import ( gxset "github.com/dubbogo/gost/container/set" ) @@ -54,9 +55,10 @@ var ( type registryProtocol struct { invokers []protocol.Invoker - // Registry Map + // Registry Map registries *sync.Map - // To solve the problem of RMI repeated exposure port conflicts, the services that have been exposed are no longer exposed. + // To solve the problem of RMI repeated exposure port conflicts, + // the services that have been exposed are no longer exposed. // providerurl <--> exporter bounds *sync.Map overrideListeners *sync.Map @@ -100,7 +102,6 @@ func getUrlToRegistry(providerUrl *common.URL, registryUrl *common.URL) *common. // filterHideKey filter the parameters that do not need to be output in url(Starting with .) func filterHideKey(url *common.URL) *common.URL { - // be careful params maps in url is map type removeSet := gxset.NewSet() for k, _ := range url.GetParams() { @@ -127,7 +128,6 @@ func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker { } var reg registry.Registry - if regI, loaded := proto.registries.Load(registryUrl.Key()); !loaded { reg = getRegistry(®istryUrl) proto.registries.Store(registryUrl.Key(), reg) @@ -138,7 +138,7 @@ func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker { // new registry directory for store service url from registry directory, err := extension.GetDefaultRegistryDirectory(®istryUrl, reg) if err != nil { - logger.Errorf("consumer service %v create registry directory error, error message is %s, and will return nil invoker!", + logger.Errorf("consumer service %v create registry directory error, error message is %s, and will return nil invoker!", serviceUrl.String(), err.Error()) return nil } @@ -151,7 +151,6 @@ func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker { // new cluster invoker cluster := extension.GetCluster(serviceUrl.GetParam(constant.CLUSTER_KEY, constant.DEFAULT_CLUSTER)) - invoker := cluster.Join(directory) proto.invokers = append(proto.invokers, invoker) return invoker @@ -192,7 +191,7 @@ func (proto *registryProtocol) Export(invoker protocol.Invoker) protocol.Exporte } key := getCacheKey(providerUrl) - logger.Infof("The cached exporter keys is %v !", key) + logger.Infof("The cached exporter keys is %v!", key) cachedExporter, loaded := proto.bounds.Load(key) if loaded { logger.Infof("The exporter has been cached, and will return cached exporter!") @@ -216,7 +215,6 @@ func (proto *registryProtocol) reExport(invoker protocol.Invoker, newUrl *common proto.bounds.Delete(key) proto.Export(wrappedNewInvoker) // TODO: unregister & unsubscribe - } } @@ -353,7 +351,7 @@ func (proto *registryProtocol) Destroy() { func getRegistryUrl(invoker protocol.Invoker) *common.URL { // here add * for return a new url url := invoker.GetUrl() - // if the protocol == registry ,set protocol the registry value in url.params + // if the protocol == registry, set protocol the registry value in url.params if url.Protocol == constant.REGISTRY_PROTOCOL { protocol := url.GetParam(constant.REGISTRY_KEY, "") url.Protocol = protocol From 9e1b4d1908343ce08fccdef3a523b10d9b9fd4f5 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 20 Aug 2020 14:12:31 +0800 Subject: [PATCH 203/242] fix imports --- config/config_loader_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config_loader_test.go b/config/config_loader_test.go index 9f95facb62..461e607c1e 100644 --- a/config/config_loader_test.go +++ b/config/config_loader_test.go @@ -27,6 +27,7 @@ import ( import ( cm "github.com/Workiva/go-datastructures/common" "github.com/Workiva/go-datastructures/slice/skip" + gxset "github.com/dubbogo/gost/container/set" gxpage "github.com/dubbogo/gost/page" "github.com/stretchr/testify/assert" "go.uber.org/atomic" @@ -43,7 +44,6 @@ import ( "github.com/apache/dubbo-go/config_center" "github.com/apache/dubbo-go/metadata/service" "github.com/apache/dubbo-go/registry" - gxset "github.com/dubbogo/gost/container/set" ) const mockConsumerConfigPath = "./testdata/consumer_config.yml" From d58e4fa331e2c674b7f93da1a6b07336841ba8bc Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 20 Aug 2020 14:17:09 +0800 Subject: [PATCH 204/242] modify comment --- config/config_loader.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config_loader.go b/config/config_loader.go index 0859f8c3e4..75b82628d6 100644 --- a/config/config_loader.go +++ b/config/config_loader.go @@ -242,7 +242,7 @@ func registerServiceInstance() { } } -// createInstance +// nolint func createInstance(url common.URL) (registry.ServiceInstance, error) { appConfig := GetApplicationConfig() port, err := strconv.ParseInt(url.Port, 10, 32) From 6835365de698f8d1a4b82d19b45053ad1bb5cee2 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 21 Aug 2020 15:09:15 +0800 Subject: [PATCH 205/242] fix subscribe url wrong --- registry/servicediscovery/service_discovery_registry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/servicediscovery/service_discovery_registry.go b/registry/servicediscovery/service_discovery_registry.go index cdb586c137..7576804eb5 100644 --- a/registry/servicediscovery/service_discovery_registry.go +++ b/registry/servicediscovery/service_discovery_registry.go @@ -632,7 +632,7 @@ func (icn *InstanceChangeNotify) Notify(event observer.Event) { if se, ok := event.(*registry.ServiceInstancesChangedEvent); ok { sdr := icn.serviceDiscoveryRegistry - sdr.subscribe(sdr.url, icn.notify, se.ServiceName, se.Instances) + sdr.subscribe(sdr.url.SubURL, icn.notify, se.ServiceName, se.Instances) } } From b7ae2ce4263ee85e4f2492dc6b465d2ac7474e55 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 21 Aug 2020 15:19:03 +0800 Subject: [PATCH 206/242] revert commit "fix subscribe url wrong" --- registry/servicediscovery/service_discovery_registry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/servicediscovery/service_discovery_registry.go b/registry/servicediscovery/service_discovery_registry.go index 7576804eb5..cdb586c137 100644 --- a/registry/servicediscovery/service_discovery_registry.go +++ b/registry/servicediscovery/service_discovery_registry.go @@ -632,7 +632,7 @@ func (icn *InstanceChangeNotify) Notify(event observer.Event) { if se, ok := event.(*registry.ServiceInstancesChangedEvent); ok { sdr := icn.serviceDiscoveryRegistry - sdr.subscribe(sdr.url.SubURL, icn.notify, se.ServiceName, se.Instances) + sdr.subscribe(sdr.url, icn.notify, se.ServiceName, se.Instances) } } From bb39786f8d6f0a44ad0186e348f41c57bcf51839 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 21 Aug 2020 16:12:51 +0800 Subject: [PATCH 207/242] fix subscribe url wrong --- registry/servicediscovery/service_discovery_registry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/servicediscovery/service_discovery_registry.go b/registry/servicediscovery/service_discovery_registry.go index cdb586c137..7576804eb5 100644 --- a/registry/servicediscovery/service_discovery_registry.go +++ b/registry/servicediscovery/service_discovery_registry.go @@ -632,7 +632,7 @@ func (icn *InstanceChangeNotify) Notify(event observer.Event) { if se, ok := event.(*registry.ServiceInstancesChangedEvent); ok { sdr := icn.serviceDiscoveryRegistry - sdr.subscribe(sdr.url, icn.notify, se.ServiceName, se.Instances) + sdr.subscribe(sdr.url.SubURL, icn.notify, se.ServiceName, se.Instances) } } From aa796a8f8382a4a6140211637cfe92c1ebc9f110 Mon Sep 17 00:00:00 2001 From: cvictory Date: Tue, 18 Aug 2020 14:54:06 +0800 Subject: [PATCH 208/242] forbid excuting twice --- common/logger/logger.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/logger/logger.go b/common/logger/logger.go index b648f8a0af..63eda231dd 100644 --- a/common/logger/logger.go +++ b/common/logger/logger.go @@ -60,6 +60,10 @@ type Logger interface { } func init() { + // forbidden to executing twice. + if logger != nil { + return + } logConfFile := os.Getenv(constant.APP_LOG_CONF_FILE) err := InitLog(logConfFile) if err != nil { From 3ed9f66fb19029ea620371db3d12b08dfae52a8f Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Sat, 22 Aug 2020 16:29:09 +0800 Subject: [PATCH 209/242] modify changes.md --- CHANGE.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/CHANGE.md b/CHANGE.md index 4a75ad3519..90cb5a1443 100644 --- a/CHANGE.md +++ b/CHANGE.md @@ -1,6 +1,34 @@ # Release Notes --- +## 1.5.1 + +### New Features +- [Add dynamic tag router](https://github.com/apache/dubbo-go/pull/703) +- [Add TLS support](https://github.com/apache/dubbo-go/pull/685) +- [Add Nearest first for multiple registry](https://github.com/apache/dubbo-go/pull/659) +- [Add application and service level router](https://github.com/apache/dubbo-go/pull/662) +- [Add dynamic tag router](https://github.com/apache/dubbo-go/pull/665) + +### Enhancement +- [Avoid init the log twice](https://github.com/apache/dubbo-go/pull/719) +- [Correct words and format codes](https://github.com/apache/dubbo-go/pull/704) +- [Change log stack level from warn to error](https://github.com/apache/dubbo-go/pull/702) +- [Optimize remotes configuration](https://github.com/apache/dubbo-go/pull/687) + +### Bugfixes +- [Fix register service instance after provider config load](https://github.com/apache/dubbo-go/pull/694) +- [Fix call subscribe function asynchronously](https://github.com/apache/dubbo-go/pull/721) +- [Fix tag router rule copy](https://github.com/apache/dubbo-go/pull/721) +- [Fix nacos unit test failed](https://github.com/apache/dubbo-go/pull/705) +- [Fix can not inovke nacos destroy when graceful shutdown](https://github.com/apache/dubbo-go/pull/689) +- [Fix zk lost event](https://github.com/apache/dubbo-go/pull/692) +- [Fix k8s ut bug](https://github.com/apache/dubbo-go/pull/693) + +Milestone: [https://github.com/apache/dubbo-go/milestone/2?closed=1](https://github.com/apache/dubbo-go/milestone/2?closed=1) + +Project: [https://github.com/apache/dubbo-go/projects/8](https://github.com/apache/dubbo-go/projects/8) + ## 1.5.0 ### New Features From 7d1f424a18d077f9c3292d4de8213848ca16f26e Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Sat, 22 Aug 2020 22:16:22 +0800 Subject: [PATCH 210/242] Change file name to the same as interface name --- .../{service_discovery_factory.go => service_discovery_holder.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename registry/{service_discovery_factory.go => service_discovery_holder.go} (100%) diff --git a/registry/service_discovery_factory.go b/registry/service_discovery_holder.go similarity index 100% rename from registry/service_discovery_factory.go rename to registry/service_discovery_holder.go From 292850223fae4ed73506810efb179ac969fcbf5b Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Sun, 23 Aug 2020 22:50:25 +0800 Subject: [PATCH 211/242] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 7df7e7973a..9e1edd3af1 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ Apache License, Version 2.0 ## Release note ## +[v1.5.1 - Aug 23, 2020](https://github.com/apache/dubbo-go/releases/tag/v1.5.1) + [v1.5.0 - July 24, 2020](https://github.com/apache/dubbo-go/releases/tag/v1.5.0) [v1.4.0 - Mar 17, 2020](https://github.com/apache/dubbo-go/releases/tag/v1.4.0) From ace8f6b57e835988e2ae111d2fa2127c95f908a4 Mon Sep 17 00:00:00 2001 From: "beiwei.ly" Date: Tue, 25 Aug 2020 10:33:23 +0800 Subject: [PATCH 212/242] use defer for wg.Done() --- cluster/router/chain/chain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go index b15b845cf1..952aedf92d 100644 --- a/cluster/router/chain/chain.go +++ b/cluster/router/chain/chain.go @@ -200,12 +200,12 @@ func (c *RouterChain) buildCache() { if p, ok := r.(router.Poolable); ok { wg.Add(1) go func(p router.Poolable) { + defer wg.Done() pool, info := poolRouter(p, origin, invokers) mutex.Lock() defer mutex.Unlock() cache.pools[p.Name()] = pool cache.metadatas[p.Name()] = info - wg.Done() }(p) } } From 6dc2295fb4740d44cd90ac2a41c3c1ab25718153 Mon Sep 17 00:00:00 2001 From: YGrylls <910357107@qq.com> Date: Wed, 26 Aug 2020 09:39:22 +0800 Subject: [PATCH 213/242] fix getProviderConfig in initHystrixConfigProvider --- filter/filter_impl/hystrix_filter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filter/filter_impl/hystrix_filter.go b/filter/filter_impl/hystrix_filter.go index e2275149f1..8104d1a735 100644 --- a/filter/filter_impl/hystrix_filter.go +++ b/filter/filter_impl/hystrix_filter.go @@ -234,7 +234,7 @@ func initHystrixConfigProvider() error { if config.GetProviderConfig().FilterConf == nil { return perrors.Errorf("no config for hystrix") } - filterConfig := config.GetConsumerConfig().FilterConf.(map[interface{}]interface{})[HYSTRIX] + filterConfig := config.GetProviderConfig().FilterConf.(map[interface{}]interface{})[HYSTRIX] if filterConfig == nil { return perrors.Errorf("no config for hystrix") } From a0053d4d705de40d5325b88d2e160380604e1582 Mon Sep 17 00:00:00 2001 From: YGrylls <910357107@qq.com> Date: Wed, 26 Aug 2020 09:56:00 +0800 Subject: [PATCH 214/242] remove outdated comments --- filter/filter_impl/hystrix_filter.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/filter/filter_impl/hystrix_filter.go b/filter/filter_impl/hystrix_filter.go index 8104d1a735..4c5c06815c 100644 --- a/filter/filter_impl/hystrix_filter.go +++ b/filter/filter_impl/hystrix_filter.go @@ -53,10 +53,6 @@ var ( providerConfigOnce sync.Once ) -//The filter in the server end of dubbo-go can't get the invoke result for now, -//this filter ONLY works in CLIENT end (consumer side) temporarily -//Only after the callService logic is integrated into the filter chain of server end then the filter can be used, -//which will be done soon func init() { extension.SetFilter(HYSTRIX_CONSUMER, GetHystrixFilterConsumer) extension.SetFilter(HYSTRIX_PROVIDER, GetHystrixFilterProvider) From 96d0f79ead66fe2a8516e8f0f2b5d1a1122db976 Mon Sep 17 00:00:00 2001 From: "xg.gao" Date: Thu, 27 Aug 2020 12:57:04 +0800 Subject: [PATCH 215/242] format --- common/rpc_service.go | 2 +- config/config_loader.go | 7 +++---- config/service_config.go | 1 - protocol/jsonrpc/server.go | 8 +++----- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/common/rpc_service.go b/common/rpc_service.go index 9ef2b956aa..b1bbd3dabc 100644 --- a/common/rpc_service.go +++ b/common/rpc_service.go @@ -169,7 +169,7 @@ func (sm *serviceMap) GetService(protocol, name string) *Service { return nil } -// GetInterface gets an interface defination by interface name +// GetInterface gets an interface definition by interface name func (sm *serviceMap) GetInterface(interfaceName string) []*Service { sm.mutex.RLock() defer sm.mutex.RUnlock() diff --git a/config/config_loader.go b/config/config_loader.go index 8b196305b9..6100e635ef 100644 --- a/config/config_loader.go +++ b/config/config_loader.go @@ -144,12 +144,11 @@ func loadConsumerConfig() { (refconfig.Check == nil && consumerConfig.Check != nil && *consumerConfig.Check) || (refconfig.Check == nil && consumerConfig.Check == nil) { // default to true - if refconfig.invoker != nil && - !refconfig.invoker.IsAvailable() { + if refconfig.invoker != nil && !refconfig.invoker.IsAvailable() { checkok = false count++ if count > maxWait { - errMsg := fmt.Sprintf("Failed to check the status of the service %v . No provider available for the service to the consumer use dubbo version %v", refconfig.InterfaceName, constant.Version) + errMsg := fmt.Sprintf("Failed to check the status of the service %v. No provider available for the service to the consumer use dubbo version %v", refconfig.InterfaceName, constant.Version) logger.Error(errMsg) panic(errMsg) } @@ -157,7 +156,7 @@ func loadConsumerConfig() { break } if refconfig.invoker == nil { - logger.Warnf("The interface %s invoker not exist , may you should check your interface config.", refconfig.InterfaceName) + logger.Warnf("The interface %s invoker not exist, may you should check your interface config.", refconfig.InterfaceName) } } } diff --git a/config/service_config.go b/config/service_config.go index e1c9a5b187..8968bac88f 100644 --- a/config/service_config.go +++ b/config/service_config.go @@ -312,7 +312,6 @@ func (c *ServiceConfig) getUrlMap() url.Values { urlMap.Set(constant.EXECUTE_LIMIT_KEY, v.ExecuteLimit) urlMap.Set(constant.EXECUTE_REJECTED_EXECUTION_HANDLER_KEY, v.ExecuteLimitRejectedHandler) - } return urlMap diff --git a/protocol/jsonrpc/server.go b/protocol/jsonrpc/server.go index aa458a1614..29eba0223e 100644 --- a/protocol/jsonrpc/server.go +++ b/protocol/jsonrpc/server.go @@ -127,10 +127,10 @@ func (s *Server) handlePkg(conn net.Conn) { } reqBody, err := ioutil.ReadAll(r.Body) + r.Body.Close() if err != nil { return } - r.Body.Close() reqHeader := make(map[string]string) for k := range r.Header { @@ -263,8 +263,7 @@ func (s *Server) Stop() { }) } -func serveRequest(ctx context.Context, - header map[string]string, body []byte, conn net.Conn) error { +func serveRequest(ctx context.Context, header map[string]string, body []byte, conn net.Conn) error { sendErrorResp := func(header map[string]string, body []byte) error { rsp := &http.Response{ Header: make(http.Header), @@ -324,13 +323,12 @@ func serveRequest(ctx context.Context, if err == io.EOF || err == io.ErrUnexpectedEOF { return perrors.WithStack(err) } - return perrors.New("server cannot decode request: " + err.Error()) } + path := header["Path"] methodName := codec.req.Method if len(path) == 0 || len(methodName) == 0 { - codec.ReadBody(nil) return perrors.New("service/method request ill-formed: " + path + "/" + methodName) } From 09fa354e8eb76ce1ef4244622f12ca1f7d90d1ff Mon Sep 17 00:00:00 2001 From: "xg.gao" Date: Fri, 28 Aug 2020 09:47:15 +0800 Subject: [PATCH 216/242] format --- config/consumer_config.go | 4 ++-- config/provider_config.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config/consumer_config.go b/config/consumer_config.go index 9d283eeca7..c8083603e1 100644 --- a/config/consumer_config.go +++ b/config/consumer_config.go @@ -60,8 +60,8 @@ type ConsumerConfig struct { References map[string]*ReferenceConfig `yaml:"references" json:"references,omitempty" property:"references"` ProtocolConf interface{} `yaml:"protocol_conf" json:"protocol_conf,omitempty" property:"protocol_conf"` - FilterConf interface{} `yaml:"filter_conf" json:"filter_conf,omitempty" property:"filter_conf" ` - ShutdownConfig *ShutdownConfig `yaml:"shutdown_conf" json:"shutdown_conf,omitempty" property:"shutdown_conf" ` + FilterConf interface{} `yaml:"filter_conf" json:"filter_conf,omitempty" property:"filter_conf"` + ShutdownConfig *ShutdownConfig `yaml:"shutdown_conf" json:"shutdown_conf,omitempty" property:"shutdown_conf"` ConfigType map[string]string `yaml:"config_type" json:"config_type,omitempty" property:"config_type"` } diff --git a/config/provider_config.go b/config/provider_config.go index c710e48dc2..fcb429b640 100644 --- a/config/provider_config.go +++ b/config/provider_config.go @@ -43,9 +43,9 @@ type ProviderConfig struct { ProxyFactory string `yaml:"proxy_factory" default:"default" json:"proxy_factory,omitempty" property:"proxy_factory"` Services map[string]*ServiceConfig `yaml:"services" json:"services,omitempty" property:"services"` Protocols map[string]*ProtocolConfig `yaml:"protocols" json:"protocols,omitempty" property:"protocols"` - ProtocolConf interface{} `yaml:"protocol_conf" json:"protocol_conf,omitempty" property:"protocol_conf" ` - FilterConf interface{} `yaml:"filter_conf" json:"filter_conf,omitempty" property:"filter_conf" ` - ShutdownConfig *ShutdownConfig `yaml:"shutdown_conf" json:"shutdown_conf,omitempty" property:"shutdown_conf" ` + ProtocolConf interface{} `yaml:"protocol_conf" json:"protocol_conf,omitempty" property:"protocol_conf"` + FilterConf interface{} `yaml:"filter_conf" json:"filter_conf,omitempty" property:"filter_conf"` + ShutdownConfig *ShutdownConfig `yaml:"shutdown_conf" json:"shutdown_conf,omitempty" property:"shutdown_conf"` ConfigType map[string]string `yaml:"config_type" json:"config_type,omitempty" property:"config_type"` Registry *RegistryConfig `yaml:"registry" json:"registry,omitempty" property:"registry"` From a111829f2296d87d560135318252225347d890e7 Mon Sep 17 00:00:00 2001 From: YGrylls <910357107@qq.com> Date: Fri, 28 Aug 2020 20:49:32 +0800 Subject: [PATCH 217/242] add detailed comment --- filter/filter_impl/hystrix_filter.go | 50 +++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/filter/filter_impl/hystrix_filter.go b/filter/filter_impl/hystrix_filter.go index 4c5c06815c..d13e02c06f 100644 --- a/filter/filter_impl/hystrix_filter.go +++ b/filter/filter_impl/hystrix_filter.go @@ -81,7 +81,47 @@ func NewHystrixFilterError(err error, failByHystrix bool) error { } } -// nolint +/** + * HystrixFilter + * You should add hystrix related configuration in provider or consumer config or both, according to which side you are to apply HystrixFilter. + * For example: + * filter_conf: + * hystrix: + * configs: + * # =========== Define config here ============ + * "Default": + * timeout : 1000 + * max_concurrent_requests : 25 + * sleep_window : 5000 + * error_percent_threshold : 50 + * request_volume_threshold: 20 + * "userp": + * timeout: 2000 + * max_concurrent_requests: 512 + * sleep_window: 4000 + * error_percent_threshold: 35 + * request_volume_threshold: 6 + * "userp_m": + * timeout : 1200 + * max_concurrent_requests : 512 + * sleep_window : 6000 + * error_percent_threshold : 60 + * request_volume_threshold: 16 + * # =========== Define error whitelist which will be ignored by Hystrix counter ============ + * error_whitelist: [".*exception.*"] + * + * # =========== Apply default config here =========== + * default: "Default" + * + * services: + * "com.ikurento.user.UserProvider": + * # =========== Apply service level config =========== + * service_config: "userp" + * # =========== Apply method level config =========== + * methods: + * "GetUser": "userp_m" + * "GetUser1": "userp_m" + */ type HystrixFilter struct { COrP bool //true for consumer res map[string][]*regexp.Regexp @@ -209,11 +249,11 @@ func getConfig(service string, method string, cOrP bool) CommandConfigWithError func initHystrixConfigConsumer() error { if config.GetConsumerConfig().FilterConf == nil { - return perrors.Errorf("no config for hystrix") + return perrors.Errorf("no config for hystrix_consumer") } filterConfig := config.GetConsumerConfig().FilterConf.(map[interface{}]interface{})[HYSTRIX] if filterConfig == nil { - return perrors.Errorf("no config for hystrix") + return perrors.Errorf("no config for hystrix_consumer") } hystrixConfByte, err := yaml.Marshal(filterConfig) if err != nil { @@ -228,11 +268,11 @@ func initHystrixConfigConsumer() error { func initHystrixConfigProvider() error { if config.GetProviderConfig().FilterConf == nil { - return perrors.Errorf("no config for hystrix") + return perrors.Errorf("no config for hystrix_provider") } filterConfig := config.GetProviderConfig().FilterConf.(map[interface{}]interface{})[HYSTRIX] if filterConfig == nil { - return perrors.Errorf("no config for hystrix") + return perrors.Errorf("no config for hystrix_provider") } hystrixConfByte, err := yaml.Marshal(filterConfig) if err != nil { From d542f6209b7ccd263d795138c1b33753284e3890 Mon Sep 17 00:00:00 2001 From: AlexStocks Date: Fri, 28 Aug 2020 23:30:01 +0800 Subject: [PATCH 218/242] Add: import code rule --- contributing.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/contributing.md b/contributing.md index 9ee2dae32f..51301511e0 100644 --- a/contributing.md +++ b/contributing.md @@ -38,4 +38,27 @@ The title format of the pull request `MUST` follow the following rules: ### 3.3 comment >- 1 there should be comment for every export func/var. ->- 2 the comment should begin with function name/var name. \ No newline at end of file +>- 2 the comment should begin with function name/var name. + +### 3.4 import + +We dubbogo import blocks should be splited into 3 blocks. + +```Go +// block 1: the go internal package +import ( + "fmt" +) + +// block 2: the third package +import ( + "github.com/dubbogo/xxx" + + "github.com/RoaringBitmap/roaring" +) + +// block 3: the dubbo-go package +import ( + "github.com/apache/dubbo-go/common" +) +``` \ No newline at end of file From 7e4e7fd216ef8c6fa48bc77f21f3b5ef662053ee Mon Sep 17 00:00:00 2001 From: YongHaoHu Date: Sat, 29 Aug 2020 12:48:59 +0800 Subject: [PATCH 219/242] chore: Remove unnecessary return and judgement. --- cluster/router/tag/tag_router.go | 1 - common/rpc_service.go | 2 +- common/url.go | 2 +- config/base_config.go | 16 +++------------- config/reference_config.go | 1 - config_center/configurator/override.go | 2 +- protocol/rest/rest_exporter.go | 1 - registry/etcdv3/listener_test.go | 1 - registry/event/service_revision_customizer.go | 2 +- registry/protocol/protocol.go | 2 +- .../curator_discovery/service_discovery.go | 1 - 11 files changed, 8 insertions(+), 23 deletions(-) diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index 0acdf70ce9..12e6eda3e7 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -191,7 +191,6 @@ func (c *tagRouter) Process(event *config_center.ConfigChangeEvent) { defer c.mutex.Unlock() c.tagRouterRule = routerRule c.ruleChanged = true - return } // URL gets the url of tagRouter diff --git a/common/rpc_service.go b/common/rpc_service.go index 9ef2b956aa..eb68a49f72 100644 --- a/common/rpc_service.go +++ b/common/rpc_service.go @@ -271,7 +271,7 @@ func (sm *serviceMap) UnRegister(interfaceName, protocol, serviceId string) erro sm.mutex.Lock() defer sm.mutex.Unlock() sm.interfaceMap[interfaceName] = make([]*Service, 0, len(svrs)) - for i, _ := range svrs { + for i := range svrs { if i != index { sm.interfaceMap[interfaceName] = append(sm.interfaceMap[interfaceName], svrs[i]) } diff --git a/common/url.go b/common/url.go index c355857b9f..e5fa895adb 100644 --- a/common/url.go +++ b/common/url.go @@ -222,7 +222,7 @@ func NewURL(urlString string, opts ...option) (URL, error) { } // rawUrlString = "//" + rawUrlString - if strings.Index(rawUrlString, "//") < 0 { + if !strings.Contains(rawUrlString, "//") { t := URL{baseUrl: baseUrl{}} for _, opt := range opts { opt(&t) diff --git a/config/base_config.go b/config/base_config.go index 22a0832731..336bb03c7b 100644 --- a/config/base_config.go +++ b/config/base_config.go @@ -78,15 +78,8 @@ func getKeyPrefix(val reflect.Value) []string { } else { prefix = val.MethodByName(configPrefixMethod).Call(nil)[0].String() } - var retPrefixes []string - - for _, pfx := range strings.Split(prefix, "|") { - - retPrefixes = append(retPrefixes, pfx) - - } - return retPrefixes + return strings.Split(prefix, "|") } func getPtrElement(v reflect.Value) reflect.Value { @@ -216,12 +209,9 @@ func setFieldValue(val reflect.Value, id reflect.Value, config *config.InmemoryC prefix := s.MethodByName("Prefix").Call(nil)[0].String() for _, pfx := range strings.Split(prefix, "|") { m := config.GetSubProperty(pfx) - if m != nil { - for k := range m { - f.SetMapIndex(reflect.ValueOf(k), reflect.New(f.Type().Elem().Elem())) - } + for k := range m { + f.SetMapIndex(reflect.ValueOf(k), reflect.New(f.Type().Elem().Elem())) } - } } diff --git a/config/reference_config.go b/config/reference_config.go index bbc875192c..cd10f89eb7 100644 --- a/config/reference_config.go +++ b/config/reference_config.go @@ -251,5 +251,4 @@ func (c *ReferenceConfig) GenericLoad(id string) { c.id = id c.Refer(genericService) c.Implement(genericService) - return } diff --git a/config_center/configurator/override.go b/config_center/configurator/override.go index 294a60ebb2..ec4e606e0d 100644 --- a/config_center/configurator/override.go +++ b/config_center/configurator/override.go @@ -110,7 +110,7 @@ func (c *overrideConfigurator) configureIfMatchInternal(url *common.URL) { func (c *overrideConfigurator) configureIfMatch(host string, url *common.URL) { if constant.ANYHOST_VALUE == c.configuratorUrl.Ip || host == c.configuratorUrl.Ip { providers := c.configuratorUrl.GetParam(constant.OVERRIDE_PROVIDERS_KEY, "") - if len(providers) == 0 || strings.Index(providers, url.Location) >= 0 || strings.Index(providers, constant.ANYHOST_VALUE) >= 0 { + if len(providers) == 0 || strings.Contains(providers, url.Location) || strings.Contains(providers, constant.ANYHOST_VALUE) { c.configureIfMatchInternal(url) } } diff --git a/protocol/rest/rest_exporter.go b/protocol/rest/rest_exporter.go index e39558caea..359506a842 100644 --- a/protocol/rest/rest_exporter.go +++ b/protocol/rest/rest_exporter.go @@ -49,5 +49,4 @@ func (re *RestExporter) Unexport() { if err != nil { logger.Errorf("[RestExporter.Unexport] error: %v", err) } - return } diff --git a/registry/etcdv3/listener_test.go b/registry/etcdv3/listener_test.go index cc4ea257cc..1cf06d17dc 100644 --- a/registry/etcdv3/listener_test.go +++ b/registry/etcdv3/listener_test.go @@ -63,7 +63,6 @@ func (suite *RegistryTestSuite) SetupSuite() { } suite.etcd = e - return } // stop etcd server diff --git a/registry/event/service_revision_customizer.go b/registry/event/service_revision_customizer.go index fd21e8f4c7..4793e91948 100644 --- a/registry/event/service_revision_customizer.go +++ b/registry/event/service_revision_customizer.go @@ -126,7 +126,7 @@ func resolveRevision(urls []interface{}) string { // append url params if we need it } - sort.Sort(sort.StringSlice(candidates)) + sort.Strings(candidates) // it's nearly impossible to be overflow res := uint64(0) diff --git a/registry/protocol/protocol.go b/registry/protocol/protocol.go index c0608ad798..659afe86b1 100644 --- a/registry/protocol/protocol.go +++ b/registry/protocol/protocol.go @@ -103,7 +103,7 @@ func filterHideKey(url *common.URL) *common.URL { // be careful params maps in url is map type removeSet := gxset.NewSet() - for k, _ := range url.GetParams() { + for k := range url.GetParams() { if strings.HasPrefix(k, ".") { removeSet.Add(k) } diff --git a/remoting/zookeeper/curator_discovery/service_discovery.go b/remoting/zookeeper/curator_discovery/service_discovery.go index 9566b54943..acd43c0b92 100644 --- a/remoting/zookeeper/curator_discovery/service_discovery.go +++ b/remoting/zookeeper/curator_discovery/service_discovery.go @@ -154,7 +154,6 @@ func (sd *ServiceDiscovery) updateInternalService(name, id string) { return } entry.instance = instance - return } // UnregisterService un-register service in zookeeper and delete service in cache From f8434c317b7a175a395e3e9b470893898ad4ffcc Mon Sep 17 00:00:00 2001 From: "xg.gao" Date: Tue, 1 Sep 2020 22:16:41 +0800 Subject: [PATCH 220/242] format --- registry/registry.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index 2a6968c821..855b487d47 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -31,8 +31,8 @@ import ( type Registry interface { common.Node - // used for service provider calling, register services to registry - // And it is also used for service consumer calling, register + // Register is used for service provider calling, register services + // to registry. And it is also used for service consumer calling, register // services cared about, for dubbo's admin monitoring. Register(url common.URL) error @@ -45,6 +45,7 @@ type Registry interface { // dubbo://10.20.153.10/org.apache.dubbo.foo.BarService?version=1.0.0&application=kylin UnRegister(url common.URL) error + // Subscribe is required to support the contract: // When creating new registry extension, pls select one of the // following modes. // Will remove in dubbogo version v1.1.0 From c1e9b8424d63b527c499fdd3f9d0a379395e7318 Mon Sep 17 00:00:00 2001 From: "xg.gao" Date: Tue, 1 Sep 2020 22:49:56 +0800 Subject: [PATCH 221/242] format --- config/config_loader.go | 3 +-- metadata/service/exporter/configurable/exporter.go | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/config/config_loader.go b/config/config_loader.go index 2a0524250c..c66e526921 100644 --- a/config/config_loader.go +++ b/config/config_loader.go @@ -141,8 +141,8 @@ func loadConsumerConfig() { // wait for invoker is available, if wait over default 3s, then panic var count int - checkok := true for { + checkok := true for _, refconfig := range consumerConfig.References { if (refconfig.Check != nil && *refconfig.Check) || (refconfig.Check == nil && consumerConfig.Check != nil && *consumerConfig.Check) || @@ -167,7 +167,6 @@ func loadConsumerConfig() { if checkok { break } - checkok = true } } diff --git a/metadata/service/exporter/configurable/exporter.go b/metadata/service/exporter/configurable/exporter.go index f8b4b0c017..5a930c5e95 100644 --- a/metadata/service/exporter/configurable/exporter.go +++ b/metadata/service/exporter/configurable/exporter.go @@ -48,7 +48,6 @@ func NewMetadataServiceExporter(metadataService service.MetadataService) exporte // Export will export the metadataService func (exporter *MetadataServiceExporter) Export() error { if !exporter.IsExported() { - serviceConfig := config.NewServiceConfig(constant.SIMPLE_METADATA_SERVICE_NAME, context.Background()) serviceConfig.Protocol = constant.DEFAULT_PROTOCOL serviceConfig.Protocols = map[string]*config.ProtocolConfig{ From 763b4365fe5ae1f5dd0fcd75d20db9abb87daa5b Mon Sep 17 00:00:00 2001 From: "xg.gao" Date: Thu, 3 Sep 2020 12:42:32 +0800 Subject: [PATCH 222/242] fix bug --- protocol/protocolwrapper/protocol_filter_wrapper.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/protocol/protocolwrapper/protocol_filter_wrapper.go b/protocol/protocolwrapper/protocol_filter_wrapper.go index af64f8a922..4b2702b99f 100644 --- a/protocol/protocolwrapper/protocol_filter_wrapper.go +++ b/protocol/protocolwrapper/protocol_filter_wrapper.go @@ -69,6 +69,9 @@ func (pfw *ProtocolFilterWrapper) Destroy() { func buildInvokerChain(invoker protocol.Invoker, key string) protocol.Invoker { filterName := invoker.GetUrl().GetParam(key, "") + if filterName == "" { + return invoker + } filterNames := strings.Split(filterName, ",") // The order of filters is from left to right, so loading from right to left From 6dfac02b2c0baf916bba68d4c5b67799a2d8436c Mon Sep 17 00:00:00 2001 From: wangwx Date: Fri, 4 Sep 2020 11:13:36 +0800 Subject: [PATCH 223/242] try to fix error log prints issue --- remoting/zookeeper/listener.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/remoting/zookeeper/listener.go b/remoting/zookeeper/listener.go index 486a67e20a..b6c6d78106 100644 --- a/remoting/zookeeper/listener.go +++ b/remoting/zookeeper/listener.go @@ -322,7 +322,7 @@ func (l *ZkEventListener) listenDirEvent(conf *common.URL, zkPath string, listen for { select { case <-ticker.C: - l.handleZkNodeEvent(zkEvent.Path, children, listener) + l.handleZkNodeEvent(zkPath, children, listener) case zkEvent = <-childEventCh: logger.Warnf("get a zookeeper zkEvent{type:%s, server:%s, path:%s, state:%d-%s, err:%s}", zkEvent.Type.String(), zkEvent.Server, zkEvent.Path, zkEvent.State, StateToString(zkEvent.State), zkEvent.Err) From f11bc6d9b966b644d958ddbb4cd7f2567f3854ce Mon Sep 17 00:00:00 2001 From: Sky Ao Date: Mon, 7 Sep 2020 10:30:33 +0800 Subject: [PATCH 224/242] improve map access concurrency --- protocol/rpc_status.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/protocol/rpc_status.go b/protocol/rpc_status.go index 60becfb341..978534ea8b 100644 --- a/protocol/rpc_status.go +++ b/protocol/rpc_status.go @@ -98,7 +98,10 @@ func (rpc *RPCStatus) GetSuccessiveRequestFailureCount() int32 { // GetURLStatus get URL RPC status. func GetURLStatus(url common.URL) *RPCStatus { - rpcStatus, _ := serviceStatistic.LoadOrStore(url.Key(), &RPCStatus{}) + rpcStatus, found := serviceStatistic.Load(url.Key()) + if !found { + rpcStatus, _ = serviceStatistic.LoadOrStore(url.Key(), &RPCStatus{}) + } return rpcStatus.(*RPCStatus) } @@ -107,15 +110,13 @@ func GetMethodStatus(url common.URL, methodName string) *RPCStatus { identifier := url.Key() methodMap, found := methodStatistics.Load(identifier) if !found { - methodMap = &sync.Map{} - methodStatistics.Store(identifier, methodMap) + methodMap, _ = methodStatistics.LoadOrStore(identifier, &sync.Map{}) } methodActive := methodMap.(*sync.Map) rpcStatus, found := methodActive.Load(methodName) if !found { - rpcStatus = &RPCStatus{} - methodActive.Store(methodName, rpcStatus) + rpcStatus, _ = methodActive.LoadOrStore(methodName, &RPCStatus{}) } status := rpcStatus.(*RPCStatus) From 49c005ab8f01f7e929a502d2a1309ddfcd61cda1 Mon Sep 17 00:00:00 2001 From: cvictory Date: Mon, 7 Sep 2020 10:54:46 +0800 Subject: [PATCH 225/242] Ftr: [refer dubbo 2.7.6] attachment type from map[string]stiring to map[string]interface{} (#713) * support attachment from map[sting]string to map[string]interface{} in invocation and result --- .../zone_aware_cluster_invoker_test.go | 8 +- cluster/router/tag/tag_router.go | 6 +- common/proxy/proxy.go | 7 +- common/proxy/proxy_test.go | 34 ++ filter/filter_impl/access_log_filter.go | 28 +- filter/filter_impl/access_log_filter_test.go | 4 +- filter/filter_impl/active_filter_test.go | 4 +- .../auth/default_authenticator_test.go | 6 +- filter/filter_impl/auth/provider_auth_test.go | 2 +- .../filter_impl/execute_limit_filter_test.go | 6 +- .../graceful_shutdown_filter_test.go | 2 +- filter/filter_impl/metrics_filter_test.go | 2 +- filter/filter_impl/seata_filter_test.go | 7 +- filter/filter_impl/token_filter.go | 2 +- filter/filter_impl/token_filter_test.go | 8 +- .../tps/tps_limiter_method_service_test.go | 8 +- filter/filter_impl/tps_limit_filter_test.go | 6 +- filter/filter_impl/tracing_filter_test.go | 2 +- go.mod | 2 +- go.sum | 10 +- metadata/service/inmemory/service_proxy.go | 2 +- metrics/prometheus/reporter_test.go | 2 +- protocol/dubbo/client.go | 20 +- protocol/dubbo/client_test.go | 32 +- protocol/dubbo/codec.go | 21 +- protocol/dubbo/codec_test.go | 17 +- protocol/dubbo/dubbo_invoker.go | 3 +- protocol/dubbo/dubbo_invoker_test.go | 2 +- protocol/dubbo/hessian2/const.go | 243 +++++++++++ protocol/dubbo/hessian2/hessian_dubbo.go | 251 ++++++++++++ protocol/dubbo/hessian2/hessian_dubbo_test.go | 231 +++++++++++ protocol/dubbo/hessian2/hessian_request.go | 350 ++++++++++++++++ .../dubbo/hessian2/hessian_request_test.go | 158 ++++++++ protocol/dubbo/hessian2/hessian_response.go | 377 ++++++++++++++++++ .../dubbo/hessian2/hessian_response_test.go | 225 +++++++++++ protocol/dubbo/listener.go | 48 ++- protocol/dubbo/listener_test.go | 5 +- protocol/dubbo/opentracing.go | 60 +++ protocol/dubbo/readwriter.go | 34 +- protocol/invocation.go | 7 +- protocol/invocation/rpcinvocation.go | 28 +- protocol/jsonrpc/server.go | 2 +- protocol/rest/server/rest_server.go | 2 +- protocol/result.go | 19 +- test/integrate/dubbo/go-client/go.mod | 4 + test/integrate/dubbo/go-server/go.mod | 4 + 46 files changed, 2151 insertions(+), 150 deletions(-) create mode 100644 protocol/dubbo/hessian2/const.go create mode 100644 protocol/dubbo/hessian2/hessian_dubbo.go create mode 100644 protocol/dubbo/hessian2/hessian_dubbo_test.go create mode 100644 protocol/dubbo/hessian2/hessian_request.go create mode 100644 protocol/dubbo/hessian2/hessian_request_test.go create mode 100644 protocol/dubbo/hessian2/hessian_response.go create mode 100644 protocol/dubbo/hessian2/hessian_response_test.go create mode 100644 protocol/dubbo/opentracing.go diff --git a/cluster/cluster_impl/zone_aware_cluster_invoker_test.go b/cluster/cluster_impl/zone_aware_cluster_invoker_test.go index cd201a42c7..7f77f33166 100644 --- a/cluster/cluster_impl/zone_aware_cluster_invoker_test.go +++ b/cluster/cluster_impl/zone_aware_cluster_invoker_test.go @@ -44,7 +44,7 @@ func TestZoneWareInvokerWithPreferredSuccess(t *testing.T) { //defer ctrl.Finish() mockResult := &protocol.RPCResult{ - Attrs: map[string]string{constant.PREFERRED_KEY: "true"}, + Attrs: map[string]interface{}{constant.PREFERRED_KEY: "true"}, Rest: rest{tried: 0, success: true}} var invokers []protocol.Invoker @@ -99,7 +99,7 @@ func TestZoneWareInvokerWithWeightSuccess(t *testing.T) { invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn( func(invocation protocol.Invocation) protocol.Result { return &protocol.RPCResult{ - Attrs: map[string]string{constant.WEIGHT_KEY: w1}, + Attrs: map[string]interface{}{constant.WEIGHT_KEY: w1}, Rest: rest{tried: 0, success: true}} }).MaxTimes(100) } else { @@ -107,7 +107,7 @@ func TestZoneWareInvokerWithWeightSuccess(t *testing.T) { invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn( func(invocation protocol.Invocation) protocol.Result { return &protocol.RPCResult{ - Attrs: map[string]string{constant.WEIGHT_KEY: w2}, + Attrs: map[string]interface{}{constant.WEIGHT_KEY: w2}, Rest: rest{tried: 0, success: true}} }).MaxTimes(100) } @@ -154,7 +154,7 @@ func TestZoneWareInvokerWithZoneSuccess(t *testing.T) { invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn( func(invocation protocol.Invocation) protocol.Result { return &protocol.RPCResult{ - Attrs: map[string]string{constant.ZONE_KEY: zoneValue}, + Attrs: map[string]interface{}{constant.ZONE_KEY: zoneValue}, Rest: rest{tried: 0, success: true}} }) invokers = append(invokers, invoker) diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index 12e6eda3e7..9f92e83b21 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -361,7 +361,9 @@ func isAnyHost(addr string) bool { func findTag(invocation protocol.Invocation, consumerUrl *common.URL) string { tag, ok := invocation.Attachments()[constant.Tagkey] if !ok { - tag = consumerUrl.GetParam(constant.Tagkey, "") + return consumerUrl.GetParam(constant.Tagkey, "") + } else if v, t := tag.(string); t { + return v } - return tag + return "" } diff --git a/common/proxy/proxy.go b/common/proxy/proxy.go index ce0f4d1d3f..d51ce1cc1b 100644 --- a/common/proxy/proxy.go +++ b/common/proxy/proxy.go @@ -145,12 +145,17 @@ func (p *Proxy) Implement(v common.RPCService) { inv.SetAttachments(k, value) } - // add user setAttachment + // add user setAttachment. It is compatibility with previous versions. atm := invCtx.Value(constant.AttachmentKey) if m, ok := atm.(map[string]string); ok { for k, value := range m { inv.SetAttachments(k, value) } + } else if m2, ok2 := atm.(map[string]interface{}); ok2 { + // it is support to transfer map[string]interface{}. It refers to dubbo-java 2.7. + for k, value := range m2 { + inv.SetAttachments(k, value) + } } result := p.invoke.Invoke(invCtx, inv) diff --git a/common/proxy/proxy_test.go b/common/proxy/proxy_test.go index 14b2befbc4..c6066157fd 100644 --- a/common/proxy/proxy_test.go +++ b/common/proxy/proxy_test.go @@ -24,6 +24,7 @@ import ( ) import ( + "github.com/apache/dubbo-go/protocol/invocation" perrors "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -32,6 +33,7 @@ import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/dubbo/hessian2" ) type TestService struct { @@ -40,6 +42,7 @@ type TestService struct { MethodThree func(int, bool) (interface{}, error) MethodFour func(int, bool) (*interface{}, error) `dubbo:"methodFour"` MethodFive func() error + MethodSix func(context.Context, string) (interface{}, error) Echo func(interface{}, *interface{}) error } @@ -120,3 +123,34 @@ func TestProxyImplement(t *testing.T) { assert.Nil(t, s3.MethodOne) } + +func TestProxyImplementForContext(t *testing.T) { + invoker := &TestProxyInvoker{ + BaseInvoker: *protocol.NewBaseInvoker(common.URL{}), + } + p := NewProxy(invoker, nil, map[string]string{constant.ASYNC_KEY: "false"}) + s := &TestService{} + p.Implement(s) + attahments1 := make(map[string]interface{}, 4) + attahments1["k1"] = "v1" + attahments1["k2"] = "v2" + context := context.WithValue(context.Background(), constant.AttachmentKey, attahments1) + r, err := p.Get().(*TestService).MethodSix(context, "xxx") + v1 := r.(map[string]interface{}) + assert.NoError(t, err) + assert.Equal(t, v1["TestProxyInvoker"], "TestProxyInvokerValue") +} + +type TestProxyInvoker struct { + protocol.BaseInvoker +} + +func (bi *TestProxyInvoker) Invoke(context context.Context, inv protocol.Invocation) protocol.Result { + rpcInv := inv.(*invocation.RPCInvocation) + mapV := inv.Attachments() + mapV["TestProxyInvoker"] = "TestProxyInvokerValue" + hessian2.ReflectResponse(mapV, rpcInv.Reply()) + return &protocol.RPCResult{ + Rest: inv.Arguments(), + } +} diff --git a/filter/filter_impl/access_log_filter.go b/filter/filter_impl/access_log_filter.go index 621012c24c..6eaf9cb00b 100644 --- a/filter/filter_impl/access_log_filter.go +++ b/filter/filter_impl/access_log_filter.go @@ -105,13 +105,27 @@ func (ef *AccessLogFilter) logIntoChannel(accessLogData AccessLogData) { func (ef *AccessLogFilter) buildAccessLogData(_ protocol.Invoker, invocation protocol.Invocation) map[string]string { dataMap := make(map[string]string, 16) attachments := invocation.Attachments() - dataMap[constant.INTERFACE_KEY] = attachments[constant.INTERFACE_KEY] - dataMap[constant.METHOD_KEY] = invocation.MethodName() - dataMap[constant.VERSION_KEY] = attachments[constant.VERSION_KEY] - dataMap[constant.GROUP_KEY] = attachments[constant.GROUP_KEY] - dataMap[constant.TIMESTAMP_KEY] = time.Now().Format(MessageDateLayout) - dataMap[constant.LOCAL_ADDR], _ = attachments[constant.LOCAL_ADDR] - dataMap[constant.REMOTE_ADDR], _ = attachments[constant.REMOTE_ADDR] + if v, ok := attachments[constant.INTERFACE_KEY]; ok && v != nil { + dataMap[constant.INTERFACE_KEY] = v.(string) + } + if v, ok := attachments[constant.METHOD_KEY]; ok && v != nil { + dataMap[constant.METHOD_KEY] = v.(string) + } + if v, ok := attachments[constant.VERSION_KEY]; ok && v != nil { + dataMap[constant.VERSION_KEY] = v.(string) + } + if v, ok := attachments[constant.GROUP_KEY]; ok && v != nil { + dataMap[constant.GROUP_KEY] = v.(string) + } + if v, ok := attachments[constant.TIMESTAMP_KEY]; ok && v != nil { + dataMap[constant.TIMESTAMP_KEY] = v.(string) + } + if v, ok := attachments[constant.LOCAL_ADDR]; ok && v != nil { + dataMap[constant.LOCAL_ADDR] = v.(string) + } + if v, ok := attachments[constant.REMOTE_ADDR]; ok && v != nil { + dataMap[constant.REMOTE_ADDR] = v.(string) + } if len(invocation.Arguments()) > 0 { builder := strings.Builder{} diff --git a/filter/filter_impl/access_log_filter_test.go b/filter/filter_impl/access_log_filter_test.go index 55c328cc30..a3a6151aa1 100644 --- a/filter/filter_impl/access_log_filter_test.go +++ b/filter/filter_impl/access_log_filter_test.go @@ -45,7 +45,7 @@ func TestAccessLogFilter_Invoke_Not_Config(t *testing.T) { "service.filter=echo%2Ctoken%2Caccesslog×tamp=1569153406&token=934804bf-b007-4174-94eb-96e3e1d60cc7&version=&warmup=100") invoker := protocol.NewBaseInvoker(url) - attach := make(map[string]string, 10) + attach := make(map[string]interface{}, 10) inv := invocation.NewRPCInvocation("MethodName", []interface{}{"OK", "Hello"}, attach) accessLogFilter := GetAccessLogFilter() @@ -64,7 +64,7 @@ func TestAccessLogFilterInvokeDefaultConfig(t *testing.T) { "service.filter=echo%2Ctoken%2Caccesslog×tamp=1569153406&token=934804bf-b007-4174-94eb-96e3e1d60cc7&version=&warmup=100") invoker := protocol.NewBaseInvoker(url) - attach := make(map[string]string, 10) + attach := make(map[string]interface{}, 10) attach[constant.VERSION_KEY] = "1.0" attach[constant.GROUP_KEY] = "MyGroup" inv := invocation.NewRPCInvocation("MethodName", []interface{}{"OK", "Hello"}, attach) diff --git a/filter/filter_impl/active_filter_test.go b/filter/filter_impl/active_filter_test.go index 6b72830e6a..9837a49c72 100644 --- a/filter/filter_impl/active_filter_test.go +++ b/filter/filter_impl/active_filter_test.go @@ -37,7 +37,7 @@ import ( ) func TestActiveFilterInvoke(t *testing.T) { - invoc := invocation.NewRPCInvocation("test", []interface{}{"OK"}, make(map[string]string, 0)) + invoc := invocation.NewRPCInvocation("test", []interface{}{"OK"}, make(map[string]interface{}, 0)) url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") filter := ActiveFilter{} ctrl := gomock.NewController(t) @@ -53,7 +53,7 @@ func TestActiveFilterInvoke(t *testing.T) { func TestActiveFilterOnResponse(t *testing.T) { c := protocol.CurrentTimeMillis() elapsed := 100 - invoc := invocation.NewRPCInvocation("test", []interface{}{"OK"}, map[string]string{ + invoc := invocation.NewRPCInvocation("test", []interface{}{"OK"}, map[string]interface{}{ dubboInvokeStartTime: strconv.FormatInt(c-int64(elapsed), 10), }) url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") diff --git a/filter/filter_impl/auth/default_authenticator_test.go b/filter/filter_impl/auth/default_authenticator_test.go index 5b107b5960..8b0fb6b0e3 100644 --- a/filter/filter_impl/auth/default_authenticator_test.go +++ b/filter/filter_impl/auth/default_authenticator_test.go @@ -52,7 +52,7 @@ func TestDefaultAuthenticator_Authenticate(t *testing.T) { var authenticator = &DefaultAuthenticator{} - invcation := invocation.NewRPCInvocation("test", parmas, map[string]string{ + invcation := invocation.NewRPCInvocation("test", parmas, map[string]interface{}{ constant.REQUEST_SIGNATURE_KEY: signature, constant.CONSUMER: "test", constant.REQUEST_TIMESTAMP_KEY: requestTime, @@ -61,7 +61,7 @@ func TestDefaultAuthenticator_Authenticate(t *testing.T) { err := authenticator.Authenticate(invcation, &testurl) assert.Nil(t, err) // modify the params - invcation = invocation.NewRPCInvocation("test", parmas[:1], map[string]string{ + invcation = invocation.NewRPCInvocation("test", parmas[:1], map[string]interface{}{ constant.REQUEST_SIGNATURE_KEY: signature, constant.CONSUMER: "test", constant.REQUEST_TIMESTAMP_KEY: requestTime, @@ -119,7 +119,7 @@ func Test_getAccessKeyPairFailed(t *testing.T) { func Test_getSignatureWithinParams(t *testing.T) { testurl, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=gg&version=2.6.0") testurl.SetParam(constant.PARAMTER_SIGNATURE_ENABLE_KEY, "true") - inv := invocation.NewRPCInvocation("test", []interface{}{"OK"}, map[string]string{ + inv := invocation.NewRPCInvocation("test", []interface{}{"OK"}, map[string]interface{}{ "": "", }) secret := "dubbo" diff --git a/filter/filter_impl/auth/provider_auth_test.go b/filter/filter_impl/auth/provider_auth_test.go index 626782ae83..f6ebfcd7ab 100644 --- a/filter/filter_impl/auth/provider_auth_test.go +++ b/filter/filter_impl/auth/provider_auth_test.go @@ -54,7 +54,7 @@ func TestProviderAuthFilter_Invoke(t *testing.T) { requestTime := strconv.Itoa(int(time.Now().Unix() * 1000)) signature, _ := getSignature(&url, inv, secret, requestTime) - inv = invocation.NewRPCInvocation("test", []interface{}{"OK"}, map[string]string{ + inv = invocation.NewRPCInvocation("test", []interface{}{"OK"}, map[string]interface{}{ constant.REQUEST_SIGNATURE_KEY: signature, constant.CONSUMER: "test", constant.REQUEST_TIMESTAMP_KEY: requestTime, diff --git a/filter/filter_impl/execute_limit_filter_test.go b/filter/filter_impl/execute_limit_filter_test.go index d36d6edef1..953f5e1cc8 100644 --- a/filter/filter_impl/execute_limit_filter_test.go +++ b/filter/filter_impl/execute_limit_filter_test.go @@ -36,7 +36,7 @@ import ( func TestExecuteLimitFilterInvokeIgnored(t *testing.T) { methodName := "hello" - invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0)) + invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]interface{}, 0)) invokeUrl := common.NewURLWithOptions( common.WithParams(url.Values{}), @@ -51,7 +51,7 @@ func TestExecuteLimitFilterInvokeIgnored(t *testing.T) { func TestExecuteLimitFilterInvokeConfigureError(t *testing.T) { methodName := "hello1" - invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0)) + invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]interface{}, 0)) invokeUrl := common.NewURLWithOptions( common.WithParams(url.Values{}), @@ -68,7 +68,7 @@ func TestExecuteLimitFilterInvokeConfigureError(t *testing.T) { func TestExecuteLimitFilterInvoke(t *testing.T) { methodName := "hello1" - invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0)) + invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]interface{}, 0)) invokeUrl := common.NewURLWithOptions( common.WithParams(url.Values{}), diff --git a/filter/filter_impl/graceful_shutdown_filter_test.go b/filter/filter_impl/graceful_shutdown_filter_test.go index 87ac2eac61..220ef6f208 100644 --- a/filter/filter_impl/graceful_shutdown_filter_test.go +++ b/filter/filter_impl/graceful_shutdown_filter_test.go @@ -39,7 +39,7 @@ import ( ) func TestGenericFilterInvoke(t *testing.T) { - invoc := invocation.NewRPCInvocation("GetUser", []interface{}{"OK"}, make(map[string]string, 0)) + invoc := invocation.NewRPCInvocation("GetUser", []interface{}{"OK"}, make(map[string]interface{}, 0)) invokeUrl := common.NewURLWithOptions( common.WithParams(url.Values{})) diff --git a/filter/filter_impl/metrics_filter_test.go b/filter/filter_impl/metrics_filter_test.go index 881106f4bc..ac10d52cf3 100644 --- a/filter/filter_impl/metrics_filter_test.go +++ b/filter/filter_impl/metrics_filter_test.go @@ -57,7 +57,7 @@ func TestMetricsFilterInvoke(t *testing.T) { "service.filter=echo%2Ctoken%2Caccesslog×tamp=1569153406&token=934804bf-b007-4174-94eb-96e3e1d60cc7&version=&warmup=100") invoker := protocol.NewBaseInvoker(url) - attach := make(map[string]string, 10) + attach := make(map[string]interface{}, 10) inv := invocation.NewRPCInvocation("MethodName", []interface{}{"OK", "Hello"}, attach) ctx := context.Background() diff --git a/filter/filter_impl/seata_filter_test.go b/filter/filter_impl/seata_filter_test.go index 6c39897f7a..45817e95cb 100644 --- a/filter/filter_impl/seata_filter_test.go +++ b/filter/filter_impl/seata_filter_test.go @@ -48,8 +48,9 @@ func (iv *testMockSeataInvoker) Invoke(ctx context.Context, _ protocol.Invocatio func TestSeataFilter_Invoke(t *testing.T) { filter := getSeataFilter() - result := filter.Invoke(context.Background(), &testMockSeataInvoker{}, invocation.NewRPCInvocation("$echo", []interface{}{"OK"}, map[string]string{ - SEATA_XID: "10.30.21.227:8091:2000047792", - })) + result := filter.Invoke(context.Background(), &testMockSeataInvoker{}, invocation.NewRPCInvocation("$echo", + []interface{}{"OK"}, map[string]interface{}{ + SEATA_XID: "10.30.21.227:8091:2000047792", + })) assert.Equal(t, "10.30.21.227:8091:2000047792", result.Result()) } diff --git a/filter/filter_impl/token_filter.go b/filter/filter_impl/token_filter.go index fe4e38747e..b5e05605c2 100644 --- a/filter/filter_impl/token_filter.go +++ b/filter/filter_impl/token_filter.go @@ -51,7 +51,7 @@ func (tf *TokenFilter) Invoke(ctx context.Context, invoker protocol.Invoker, inv if len(invokerTkn) > 0 { attachs := invocation.Attachments() remoteTkn, exist := attachs[constant.TOKEN_KEY] - if exist && strings.EqualFold(invokerTkn, remoteTkn) { + if exist && remoteTkn != nil && strings.EqualFold(invokerTkn, remoteTkn.(string)) { return invoker.Invoke(ctx, invocation) } return &protocol.RPCResult{Err: perrors.Errorf("Invalid token! Forbid invoke remote service %v method %s ", diff --git a/filter/filter_impl/token_filter_test.go b/filter/filter_impl/token_filter_test.go index c2f69bd039..cd1bba3d4a 100644 --- a/filter/filter_impl/token_filter_test.go +++ b/filter/filter_impl/token_filter_test.go @@ -40,7 +40,7 @@ func TestTokenFilterInvoke(t *testing.T) { url := common.NewURLWithOptions( common.WithParams(url.Values{}), common.WithParamsValue(constant.TOKEN_KEY, "ori_key")) - attch := make(map[string]string, 0) + attch := make(map[string]interface{}, 0) attch[constant.TOKEN_KEY] = "ori_key" result := filter.Invoke(context.Background(), protocol.NewBaseInvoker(*url), @@ -54,7 +54,7 @@ func TestTokenFilterInvokeEmptyToken(t *testing.T) { filter := GetTokenFilter() testUrl := common.URL{} - attch := make(map[string]string, 0) + attch := make(map[string]interface{}, 0) attch[constant.TOKEN_KEY] = "ori_key" result := filter.Invoke(context.Background(), protocol.NewBaseInvoker(testUrl), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) assert.Nil(t, result.Error()) @@ -67,7 +67,7 @@ func TestTokenFilterInvokeEmptyAttach(t *testing.T) { testUrl := common.NewURLWithOptions( common.WithParams(url.Values{}), common.WithParamsValue(constant.TOKEN_KEY, "ori_key")) - attch := make(map[string]string, 0) + attch := make(map[string]interface{}, 0) result := filter.Invoke(context.Background(), protocol.NewBaseInvoker(*testUrl), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) assert.NotNil(t, result.Error()) } @@ -78,7 +78,7 @@ func TestTokenFilterInvokeNotEqual(t *testing.T) { testUrl := common.NewURLWithOptions( common.WithParams(url.Values{}), common.WithParamsValue(constant.TOKEN_KEY, "ori_key")) - attch := make(map[string]string, 0) + attch := make(map[string]interface{}, 0) attch[constant.TOKEN_KEY] = "err_key" result := filter.Invoke(context.Background(), protocol.NewBaseInvoker(*testUrl), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) diff --git a/filter/filter_impl/tps/tps_limiter_method_service_test.go b/filter/filter_impl/tps/tps_limiter_method_service_test.go index edae99ec2d..61f28e442f 100644 --- a/filter/filter_impl/tps/tps_limiter_method_service_test.go +++ b/filter/filter_impl/tps/tps_limiter_method_service_test.go @@ -36,7 +36,7 @@ import ( func TestMethodServiceTpsLimiterImplIsAllowableOnlyServiceLevel(t *testing.T) { methodName := "hello" - invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0)) + invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]interface{}, 0)) ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -63,7 +63,7 @@ func TestMethodServiceTpsLimiterImplIsAllowableOnlyServiceLevel(t *testing.T) { func TestMethodServiceTpsLimiterImplIsAllowableNoConfig(t *testing.T) { methodName := "hello1" - invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0)) + invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]interface{}, 0)) // ctrl := gomock.NewController(t) // defer ctrl.Finish() @@ -80,7 +80,7 @@ func TestMethodServiceTpsLimiterImplIsAllowableNoConfig(t *testing.T) { func TestMethodServiceTpsLimiterImplIsAllowableMethodLevelOverride(t *testing.T) { methodName := "hello2" methodConfigPrefix := "methods." + methodName + "." - invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0)) + invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]interface{}, 0)) ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -113,7 +113,7 @@ func TestMethodServiceTpsLimiterImplIsAllowableMethodLevelOverride(t *testing.T) func TestMethodServiceTpsLimiterImplIsAllowableBothMethodAndService(t *testing.T) { methodName := "hello3" methodConfigPrefix := "methods." + methodName + "." - invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0)) + invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]interface{}, 0)) ctrl := gomock.NewController(t) defer ctrl.Finish() diff --git a/filter/filter_impl/tps_limit_filter_test.go b/filter/filter_impl/tps_limit_filter_test.go index 274e4e6de6..da0fc482ce 100644 --- a/filter/filter_impl/tps_limit_filter_test.go +++ b/filter/filter_impl/tps_limit_filter_test.go @@ -44,7 +44,7 @@ func TestTpsLimitFilterInvokeWithNoTpsLimiter(t *testing.T) { invokeUrl := common.NewURLWithOptions( common.WithParams(url.Values{}), common.WithParamsValue(constant.TPS_LIMITER_KEY, "")) - attch := make(map[string]string, 0) + attch := make(map[string]interface{}, 0) result := tpsFilter.Invoke(context.Background(), protocol.NewBaseInvoker(*invokeUrl), @@ -68,7 +68,7 @@ func TestGenericFilterInvokeWithDefaultTpsLimiter(t *testing.T) { invokeUrl := common.NewURLWithOptions( common.WithParams(url.Values{}), common.WithParamsValue(constant.TPS_LIMITER_KEY, constant.DEFAULT_KEY)) - attch := make(map[string]string, 0) + attch := make(map[string]interface{}, 0) result := tpsFilter.Invoke(context.Background(), protocol.NewBaseInvoker(*invokeUrl), @@ -99,7 +99,7 @@ func TestGenericFilterInvokeWithDefaultTpsLimiterNotAllow(t *testing.T) { invokeUrl := common.NewURLWithOptions( common.WithParams(url.Values{}), common.WithParamsValue(constant.TPS_LIMITER_KEY, constant.DEFAULT_KEY)) - attch := make(map[string]string, 0) + attch := make(map[string]interface{}, 0) result := tpsFilter.Invoke(context.Background(), protocol.NewBaseInvoker(*invokeUrl), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) diff --git a/filter/filter_impl/tracing_filter_test.go b/filter/filter_impl/tracing_filter_test.go index 57f4095d49..e159b7400d 100644 --- a/filter/filter_impl/tracing_filter_test.go +++ b/filter/filter_impl/tracing_filter_test.go @@ -42,7 +42,7 @@ func TestTracingFilterInvoke(t *testing.T) { "service.filter=echo%2Ctoken%2Caccesslog×tamp=1569153406&token=934804bf-b007-4174-94eb-96e3e1d60cc7&version=&warmup=100") invoker := protocol.NewBaseInvoker(url) - attach := make(map[string]string, 10) + attach := make(map[string]interface{}, 10) inv := invocation.NewRPCInvocation("MethodName", []interface{}{"OK", "Hello"}, attach) ctx := context.Background() tf := newTracingFilter() diff --git a/go.mod b/go.mod index a26aec144e..9e18cc276a 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/Workiva/go-datastructures v1.0.50 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 github.com/apache/dubbo-getty v1.3.10 - github.com/apache/dubbo-go-hessian2 v1.6.2 + github.com/apache/dubbo-go-hessian2 v1.6.0-rc1.0.20200906044240-6c1fb5c3bd44 github.com/coreos/bbolt v1.3.3 // indirect github.com/coreos/etcd v3.3.13+incompatible github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect diff --git a/go.sum b/go.sum index c5ab85db14..f744fc313b 100644 --- a/go.sum +++ b/go.sum @@ -74,8 +74,8 @@ github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFm github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk= github.com/apache/dubbo-getty v1.3.10 h1:ys5mwjPdxG/KwkPjS6EI0RzQtU6p6FCPoKpaFEzpAL0= github.com/apache/dubbo-getty v1.3.10/go.mod h1:x6rraK01BL5C7jUM2fPl5KMkAxLVIx54ZB8/XEOik9Y= -github.com/apache/dubbo-go-hessian2 v1.6.2 h1:i7F5GjVaUatLQz1x9vUmmSIFj49L8J6rVICdF6xw4qw= -github.com/apache/dubbo-go-hessian2 v1.6.2/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= +github.com/apache/dubbo-go-hessian2 v1.6.0-rc1.0.20200906044240-6c1fb5c3bd44 h1:9biQu3Z0PjDN1m8h6poo76dFkvaIpfryUVpJ5VsYVrM= +github.com/apache/dubbo-go-hessian2 v1.6.0-rc1.0.20200906044240-6c1fb5c3bd44/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -150,7 +150,6 @@ github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5Xh github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/dubbogo/go-zookeeper v1.0.1 h1:irLzvOsDOTNsN8Sv9tvYYxVu6DCQfLtziZQtUHmZgz8= github.com/dubbogo/go-zookeeper v1.0.1/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c= -github.com/dubbogo/gost v1.9.0 h1:UT+dWwvLyJiDotxJERO75jB3Yxgsdy10KztR5ycxRAk= github.com/dubbogo/gost v1.9.0/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= github.com/dubbogo/gost v1.9.1 h1:0/PPFo13zPbjt4Ia0zYWMFi3C6rAe9X7O1J2Iv+BHNM= github.com/dubbogo/gost v1.9.1/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= @@ -805,13 +804,11 @@ golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0 h1:9sdfJOzWlkqPltHAuzT2Cp+yrBeY1KRVYgms8soxMwM= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -820,7 +817,6 @@ google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.0 h1:Tfd7cKwKbFRsI8RMAD3oqqw7JPFRrvFlOsfbgVkjOOw= google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= @@ -831,7 +827,6 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64 h1:iKtrH9Y8mcbADOP0YFaEMth7OfuHY9xHOwNj4znpM1A= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= @@ -859,7 +854,6 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= diff --git a/metadata/service/inmemory/service_proxy.go b/metadata/service/inmemory/service_proxy.go index 7e01439f04..e2b29686f4 100644 --- a/metadata/service/inmemory/service_proxy.go +++ b/metadata/service/inmemory/service_proxy.go @@ -55,7 +55,7 @@ func (m *MetadataServiceProxy) GetExportedURLs(serviceInterface string, group st inv := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName(methodName), invocation.WithArguments([]interface{}{siV.Interface(), gV.Interface(), vV.Interface(), pV.Interface()}), invocation.WithReply(reflect.ValueOf(&[]interface{}{}).Interface()), - invocation.WithAttachments(map[string]string{constant.ASYNC_KEY: "false"}), + invocation.WithAttachments(map[string]interface{}{constant.ASYNC_KEY: "false"}), invocation.WithParameterValues([]reflect.Value{siV, gV, vV, pV})) res := m.invkr.Invoke(context.Background(), inv) diff --git a/metrics/prometheus/reporter_test.go b/metrics/prometheus/reporter_test.go index 0cb7d09a2c..eaba0e324f 100644 --- a/metrics/prometheus/reporter_test.go +++ b/metrics/prometheus/reporter_test.go @@ -43,7 +43,7 @@ func TestPrometheusReporter_Report(t *testing.T) { "service.filter=echo%2Ctoken%2Caccesslog×tamp=1569153406&token=934804bf-b007-4174-94eb-96e3e1d60cc7&version=&warmup=100") invoker := protocol.NewBaseInvoker(url) - attach := make(map[string]string, 10) + attach := make(map[string]interface{}, 10) inv := invocation.NewRPCInvocation("MethodName", []interface{}{"OK", "Hello"}, attach) assert.False(t, isConsumer(url)) diff --git a/protocol/dubbo/client.go b/protocol/dubbo/client.go index 530beba351..b6e4618bb5 100644 --- a/protocol/dubbo/client.go +++ b/protocol/dubbo/client.go @@ -26,7 +26,6 @@ import ( import ( "github.com/apache/dubbo-getty" - hessian "github.com/apache/dubbo-go-hessian2" gxsync "github.com/dubbogo/gost/sync" perrors "github.com/pkg/errors" "go.uber.org/atomic" @@ -38,6 +37,7 @@ import ( "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/protocol/dubbo/hessian2" ) var ( @@ -173,11 +173,11 @@ type Request struct { svcUrl common.URL method string args interface{} - atta map[string]string + atta map[string]interface{} } // NewRequest create a new Request. -func NewRequest(addr string, svcUrl common.URL, method string, args interface{}, atta map[string]string) *Request { +func NewRequest(addr string, svcUrl common.URL, method string, args interface{}, atta map[string]interface{}) *Request { return &Request{ addr: addr, svcUrl: svcUrl, @@ -190,11 +190,11 @@ func NewRequest(addr string, svcUrl common.URL, method string, args interface{}, // Response is dubbo protocol response. type Response struct { reply interface{} - atta map[string]string + atta map[string]interface{} } // NewResponse creates a new Response. -func NewResponse(reply interface{}, atta map[string]string) *Response { +func NewResponse(reply interface{}, atta map[string]interface{}) *Response { return &Response{ reply: reply, atta: atta, @@ -240,16 +240,16 @@ func (c *Client) call(ct CallType, request *Request, response *Response, callbac } p.Header.SerialID = byte(S_Dubbo) - p.Body = hessian.NewRequest(request.args, request.atta) + p.Body = hessian2.NewRequest(request.args, request.atta) var rsp *PendingResponse if ct != CT_OneWay { - p.Header.Type = hessian.PackageRequest_TwoWay + p.Header.Type = hessian2.PackageRequest_TwoWay rsp = NewPendingResponse() rsp.response = response rsp.callback = callback } else { - p.Header.Type = hessian.PackageRequest + p.Header.Type = hessian2.PackageRequest } var ( @@ -323,9 +323,9 @@ func (c *Client) transfer(session getty.Session, pkg *DubboPackage, if pkg == nil { pkg = &DubboPackage{} - pkg.Body = hessian.NewRequest([]interface{}{}, nil) + pkg.Body = hessian2.NewRequest([]interface{}{}, nil) pkg.Body = []interface{}{} - pkg.Header.Type = hessian.PackageHeartbeat + pkg.Header.Type = hessian2.PackageHeartbeat pkg.Header.SerialID = byte(S_Dubbo) } pkg.Header.ID = int64(sequence) diff --git a/protocol/dubbo/client_test.go b/protocol/dubbo/client_test.go index 8b0ba169b8..a3b194ad40 100644 --- a/protocol/dubbo/client_test.go +++ b/protocol/dubbo/client_test.go @@ -20,6 +20,7 @@ package dubbo import ( "bytes" "context" + "fmt" "sync" "testing" "time" @@ -129,6 +130,15 @@ func TestClientCall(t *testing.T) { assert.NoError(t, err) assert.Equal(t, User{Id: "1", Name: ""}, *user) + user = &User{} + r1 := "v1" + r2 := &AttaTestObject{Ti: 45, Desc: "v2"} + err = c.Call(NewRequest(mockAddress, url, "GetUserForAttachment", []interface{}{1}, map[string]interface{}{"sim": r1, "comp": r2}), NewResponse(user, nil)) + assert.NoError(t, err) + // the param is transfered from client to server by attachment, and the server will return the attachment value. + // the test should check the value came back from server. + assert.Equal(t, User{Id: r1, Name: fmt.Sprintf("%+v", *r2)}, *user) + // destroy proto.Destroy() } @@ -166,10 +176,11 @@ func TestClientAsyncCall(t *testing.T) { func InitTest(t *testing.T) (protocol.Protocol, common.URL) { hessian.RegisterPOJO(&User{}) + hessian.RegisterPOJO(&AttaTestObject{}) methods, err := common.ServiceMap.Register("com.ikurento.user.UserProvider", "dubbo", &UserProvider{}) assert.NoError(t, err) - assert.Equal(t, "GetBigPkg,GetUser,GetUser0,GetUser1,GetUser2,GetUser3,GetUser4,GetUser5,GetUser6", methods) + assert.Equal(t, "GetBigPkg,GetUser,GetUser0,GetUser1,GetUser2,GetUser3,GetUser4,GetUser5,GetUser6,GetUserForAttachment", methods) // config SetClientConf(ClientConfig{ @@ -296,6 +307,16 @@ func (u *UserProvider) GetUser6(id int64) (*User, error) { return &User{Id: "1"}, nil } +func (u *UserProvider) GetUserForAttachment(context context.Context, id int64) (*User, error) { + if id == 0 { + return nil, nil + } + var attachments = context.Value("attachment").(map[string]interface{}) + Id := attachments["sim"].(string) + name := fmt.Sprintf("%+v", *(attachments["comp"].(*AttaTestObject))) + return &User{Id: Id, Name: name}, nil +} + func (u *UserProvider) Reference() string { return "UserProvider" } @@ -303,3 +324,12 @@ func (u *UserProvider) Reference() string { func (u User) JavaClassName() string { return "com.ikurento.user.User" } + +type AttaTestObject struct { + Ti int64 + Desc string +} + +func (u *AttaTestObject) JavaClassName() string { + return "UserProvider" +} diff --git a/protocol/dubbo/codec.go b/protocol/dubbo/codec.go index 9781c70115..c33c92dfd9 100644 --- a/protocol/dubbo/codec.go +++ b/protocol/dubbo/codec.go @@ -25,11 +25,14 @@ import ( ) import ( - "github.com/apache/dubbo-go-hessian2" "github.com/apache/dubbo-go/common" perrors "github.com/pkg/errors" ) +import ( + "github.com/apache/dubbo-go/protocol/dubbo/hessian2" +) + //SerialID serial ID type SerialID byte @@ -59,8 +62,8 @@ type SequenceType int64 // nolint type DubboPackage struct { - Header hessian.DubboHeader - Service hessian.Service + Header hessian2.DubboHeader + Service hessian2.Service Body interface{} Err error } @@ -72,7 +75,7 @@ func (p DubboPackage) String() string { // Marshal encode hessian package. func (p *DubboPackage) Marshal() (*bytes.Buffer, error) { - codec := hessian.NewHessianCodec(nil) + codec := hessian2.NewHessianCodec(nil) pkg, err := codec.Write(p.Service, p.Header, p.Body) if err != nil { @@ -86,11 +89,11 @@ func (p *DubboPackage) Marshal() (*bytes.Buffer, error) { func (p *DubboPackage) Unmarshal(buf *bytes.Buffer, opts ...interface{}) error { // fix issue https://github.com/apache/dubbo-go/issues/380 bufLen := buf.Len() - if bufLen < hessian.HEADER_LENGTH { - return perrors.WithStack(hessian.ErrHeaderNotEnough) + if bufLen < hessian2.HEADER_LENGTH { + return perrors.WithStack(hessian2.ErrHeaderNotEnough) } - codec := hessian.NewHessianCodec(bufio.NewReaderSize(buf, bufLen)) + codec := hessian2.NewHessianCodec(bufio.NewReaderSize(buf, bufLen)) // read header err := codec.ReadHeader(&p.Header) @@ -104,7 +107,7 @@ func (p *DubboPackage) Unmarshal(buf *bytes.Buffer, opts ...interface{}) error { return perrors.Errorf("opts[0] is not of type *Client") } - if p.Header.Type&hessian.PackageRequest != 0x00 { + if p.Header.Type&hessian2.PackageRequest != 0x00 { // size of this array must be '7' // https://github.com/apache/dubbo-go-hessian2/blob/master/request.go#L272 p.Body = make([]interface{}, 7) @@ -113,7 +116,7 @@ func (p *DubboPackage) Unmarshal(buf *bytes.Buffer, opts ...interface{}) error { if !ok { return perrors.Errorf("client.GetPendingResponse(%v) = nil", p.Header.ID) } - p.Body = &hessian.Response{RspObj: pendingRsp.(*PendingResponse).response.reply} + p.Body = &hessian2.DubboResponse{RspObj: pendingRsp.(*PendingResponse).response.reply} } } diff --git a/protocol/dubbo/codec_test.go b/protocol/dubbo/codec_test.go index c2ca443637..6dfb87ea00 100644 --- a/protocol/dubbo/codec_test.go +++ b/protocol/dubbo/codec_test.go @@ -24,15 +24,18 @@ import ( ) import ( - hessian "github.com/apache/dubbo-go-hessian2" perrors "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) +import ( + "github.com/apache/dubbo-go/protocol/dubbo/hessian2" +) + func TestDubboPackageMarshalAndUnmarshal(t *testing.T) { pkg := &DubboPackage{} pkg.Body = []interface{}{"a"} - pkg.Header.Type = hessian.PackageHeartbeat + pkg.Header.Type = hessian2.PackageHeartbeat pkg.Header.SerialID = byte(S_Dubbo) pkg.Header.ID = 10086 @@ -44,13 +47,13 @@ func TestDubboPackageMarshalAndUnmarshal(t *testing.T) { pkgres.Body = []interface{}{} err = pkgres.Unmarshal(data) assert.NoError(t, err) - assert.Equal(t, hessian.PackageHeartbeat|hessian.PackageRequest|hessian.PackageRequest_TwoWay, pkgres.Header.Type) + assert.Equal(t, hessian2.PackageHeartbeat|hessian2.PackageRequest|hessian2.PackageRequest_TwoWay, pkgres.Header.Type) assert.Equal(t, byte(S_Dubbo), pkgres.Header.SerialID) assert.Equal(t, int64(10086), pkgres.Header.ID) assert.Equal(t, 0, len(pkgres.Body.([]interface{}))) // request - pkg.Header.Type = hessian.PackageRequest + pkg.Header.Type = hessian2.PackageRequest pkg.Service.Interface = "Service" pkg.Service.Path = "path" pkg.Service.Version = "2.6" @@ -63,7 +66,7 @@ func TestDubboPackageMarshalAndUnmarshal(t *testing.T) { pkgres.Body = make([]interface{}, 7) err = pkgres.Unmarshal(data) assert.NoError(t, err) - assert.Equal(t, hessian.PackageRequest, pkgres.Header.Type) + assert.Equal(t, hessian2.PackageRequest, pkgres.Header.Type) assert.Equal(t, byte(S_Dubbo), pkgres.Header.SerialID) assert.Equal(t, int64(10086), pkgres.Header.ID) assert.Equal(t, "2.0.2", pkgres.Body.([]interface{})[0]) @@ -72,12 +75,12 @@ func TestDubboPackageMarshalAndUnmarshal(t *testing.T) { assert.Equal(t, "Method", pkgres.Body.([]interface{})[3]) assert.Equal(t, "Ljava/lang/String;", pkgres.Body.([]interface{})[4]) assert.Equal(t, []interface{}{"a"}, pkgres.Body.([]interface{})[5]) - assert.Equal(t, map[string]string{"dubbo": "2.0.2", "interface": "Service", "path": "path", "timeout": "1000", "version": "2.6"}, pkgres.Body.([]interface{})[6]) + assert.Equal(t, map[string]interface{}{"dubbo": "2.0.2", "interface": "Service", "path": "path", "timeout": "1000", "version": "2.6"}, pkgres.Body.([]interface{})[6]) } func TestIssue380(t *testing.T) { pkg := &DubboPackage{} buf := bytes.NewBuffer([]byte("hello")) err := pkg.Unmarshal(buf) - assert.True(t, perrors.Cause(err) == hessian.ErrHeaderNotEnough) + assert.True(t, perrors.Cause(err) == hessian2.ErrHeaderNotEnough) } diff --git a/protocol/dubbo/dubbo_invoker.go b/protocol/dubbo/dubbo_invoker.go index 59202d5f49..983a05dcec 100644 --- a/protocol/dubbo/dubbo_invoker.go +++ b/protocol/dubbo/dubbo_invoker.go @@ -150,8 +150,7 @@ func (di *DubboInvoker) appendCtx(ctx context.Context, inv *invocation_impl.RPCI // inject opentracing ctx currentSpan := opentracing.SpanFromContext(ctx) if currentSpan != nil { - carrier := opentracing.TextMapCarrier(inv.Attachments()) - err := opentracing.GlobalTracer().Inject(currentSpan.Context(), opentracing.TextMap, carrier) + err := injectTraceCtx(currentSpan, inv) if err != nil { logger.Errorf("Could not inject the span context into attachments: %v", err) } diff --git a/protocol/dubbo/dubbo_invoker_test.go b/protocol/dubbo/dubbo_invoker_test.go index c0640d5558..bf352c082c 100644 --- a/protocol/dubbo/dubbo_invoker_test.go +++ b/protocol/dubbo/dubbo_invoker_test.go @@ -52,7 +52,7 @@ func TestDubboInvokerInvoke(t *testing.T) { user := &User{} inv := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName(mockMethodNameGetUser), invocation.WithArguments([]interface{}{"1", "username"}), - invocation.WithReply(user), invocation.WithAttachments(map[string]string{"test_key": "test_value"})) + invocation.WithReply(user), invocation.WithAttachments(map[string]interface{}{"test_key": "test_value"})) // Call res := invoker.Invoke(context.Background(), inv) diff --git a/protocol/dubbo/hessian2/const.go b/protocol/dubbo/hessian2/const.go new file mode 100644 index 0000000000..74a00b601d --- /dev/null +++ b/protocol/dubbo/hessian2/const.go @@ -0,0 +1,243 @@ +/* + * 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. + */ + +// This constants are also defined in dubbo constants. But we will still used these until hessian is stable. + +package hessian2 + +import ( + "reflect" + "regexp" +) + +import ( + perrors "github.com/pkg/errors" +) + +const ( + mask = byte(127) + flag = byte(128) +) + +const ( + // Zero : byte zero + Zero = byte(0x00) +) + +// constansts +const ( + TAG_READ = int32(-1) + ASCII_GAP = 32 + CHUNK_SIZE = 4096 + BC_BINARY = byte('B') // final chunk + BC_BINARY_CHUNK = byte('A') // non-final chunk + + BC_BINARY_DIRECT = byte(0x20) // 1-byte length binary + BINARY_DIRECT_MAX = byte(0x0f) + BC_BINARY_SHORT = byte(0x34) // 2-byte length binary + BINARY_SHORT_MAX = 0x3ff // 0-1023 binary + + BC_DATE = byte(0x4a) // 64-bit millisecond UTC date + BC_DATE_MINUTE = byte(0x4b) // 32-bit minute UTC date + + BC_DOUBLE = byte('D') // IEEE 64-bit double + + BC_DOUBLE_ZERO = byte(0x5b) + BC_DOUBLE_ONE = byte(0x5c) + BC_DOUBLE_BYTE = byte(0x5d) + BC_DOUBLE_SHORT = byte(0x5e) + BC_DOUBLE_MILL = byte(0x5f) + + BC_FALSE = byte('F') // boolean false + + BC_INT = byte('I') // 32-bit int + + INT_DIRECT_MIN = -0x10 + INT_DIRECT_MAX = byte(0x2f) + BC_INT_ZERO = byte(0x90) + + INT_BYTE_MIN = -0x800 + INT_BYTE_MAX = 0x7ff + BC_INT_BYTE_ZERO = byte(0xc8) + + BC_END = byte('Z') + + INT_SHORT_MIN = -0x40000 + INT_SHORT_MAX = 0x3ffff + BC_INT_SHORT_ZERO = byte(0xd4) + + BC_LIST_VARIABLE = byte(0x55) + BC_LIST_FIXED = byte('V') + BC_LIST_VARIABLE_UNTYPED = byte(0x57) + BC_LIST_FIXED_UNTYPED = byte(0x58) + _listFixedTypedLenTagMin = byte(0x70) + _listFixedTypedLenTagMax = byte(0x77) + _listFixedUntypedLenTagMin = byte(0x78) + _listFixedUntypedLenTagMax = byte(0x7f) + + BC_LIST_DIRECT = byte(0x70) + BC_LIST_DIRECT_UNTYPED = byte(0x78) + LIST_DIRECT_MAX = byte(0x7) + + BC_LONG = byte('L') // 64-bit signed integer + LONG_DIRECT_MIN = -0x08 + LONG_DIRECT_MAX = byte(0x0f) + BC_LONG_ZERO = byte(0xe0) + + LONG_BYTE_MIN = -0x800 + LONG_BYTE_MAX = 0x7ff + BC_LONG_BYTE_ZERO = byte(0xf8) + + LONG_SHORT_MIN = -0x40000 + LONG_SHORT_MAX = 0x3ffff + BC_LONG_SHORT_ZERO = byte(0x3c) + + BC_LONG_INT = byte(0x59) + + BC_MAP = byte('M') + BC_MAP_UNTYPED = byte('H') + + BC_NULL = byte('N') // x4e + + BC_OBJECT = byte('O') + BC_OBJECT_DEF = byte('C') + + BC_OBJECT_DIRECT = byte(0x60) + OBJECT_DIRECT_MAX = byte(0x0f) + + BC_REF = byte(0x51) + + BC_STRING = byte('S') // final string + BC_STRING_CHUNK = byte('R') // non-final string + + BC_STRING_DIRECT = byte(0x00) + STRING_DIRECT_MAX = byte(0x1f) + BC_STRING_SHORT = byte(0x30) + STRING_SHORT_MAX = 0x3ff + + BC_TRUE = byte('T') + + P_PACKET_CHUNK = byte(0x4f) + P_PACKET = byte('P') + + P_PACKET_DIRECT = byte(0x80) + PACKET_DIRECT_MAX = byte(0x7f) + + P_PACKET_SHORT = byte(0x70) + PACKET_SHORT_MAX = 0xfff + ARRAY_STRING = "[string" + ARRAY_INT = "[int" + ARRAY_DOUBLE = "[double" + ARRAY_FLOAT = "[float" + ARRAY_BOOL = "[boolean" + ARRAY_LONG = "[long" + + PATH_KEY = "path" + GROUP_KEY = "group" + INTERFACE_KEY = "interface" + VERSION_KEY = "version" + TIMEOUT_KEY = "timeout" + + STRING_NIL = "" + STRING_TRUE = "true" + STRING_FALSE = "false" + STRING_ZERO = "0.0" + STRING_ONE = "1.0" +) + +// DubboResponse related consts +const ( + Response_OK byte = 20 + Response_CLIENT_TIMEOUT byte = 30 + Response_SERVER_TIMEOUT byte = 31 + Response_BAD_REQUEST byte = 40 + Response_BAD_RESPONSE byte = 50 + Response_SERVICE_NOT_FOUND byte = 60 + Response_SERVICE_ERROR byte = 70 + Response_SERVER_ERROR byte = 80 + Response_CLIENT_ERROR byte = 90 + + // According to "java dubbo" There are two cases of response: + // 1. with attachments + // 2. no attachments + RESPONSE_WITH_EXCEPTION int32 = 0 + RESPONSE_VALUE int32 = 1 + RESPONSE_NULL_VALUE int32 = 2 + RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS int32 = 3 + RESPONSE_VALUE_WITH_ATTACHMENTS int32 = 4 + RESPONSE_NULL_VALUE_WITH_ATTACHMENTS int32 = 5 +) + +/** + * the dubbo protocol header length is 16 Bytes. + * the first 2 Bytes is magic code '0xdabb' + * the next 1 Byte is message flags, in which its 16-20 bit is serial id, 21 for event, 22 for two way, 23 for request/response flag + * the next 1 Bytes is response state. + * the next 8 Bytes is package DI. + * the next 4 Bytes is package length. + **/ +const ( + // header length. + HEADER_LENGTH = 16 + + // magic header + MAGIC = uint16(0xdabb) + MAGIC_HIGH = byte(0xda) + MAGIC_LOW = byte(0xbb) + + // message flag. + FLAG_REQUEST = byte(0x80) + FLAG_TWOWAY = byte(0x40) + FLAG_EVENT = byte(0x20) // for heartbeat + SERIAL_MASK = 0x1f + + DUBBO_VERSION = "2.5.4" + DUBBO_VERSION_KEY = "dubbo" + DEFAULT_DUBBO_PROTOCOL_VERSION = "2.0.2" // Dubbo RPC protocol version, for compatibility, it must not be between 2.0.10 ~ 2.6.2 + LOWEST_VERSION_FOR_RESPONSE_ATTACHMENT = 2000200 + DEFAULT_LEN = 8388608 // 8 * 1024 * 1024 default body max length +) + +// regular +const ( + JAVA_IDENT_REGEX = "(?:[_$a-zA-Z][_$a-zA-Z0-9]*)" + CLASS_DESC = "(?:L" + JAVA_IDENT_REGEX + "(?:\\/" + JAVA_IDENT_REGEX + ")*;)" + ARRAY_DESC = "(?:\\[+(?:(?:[VZBCDFIJS])|" + CLASS_DESC + "))" + DESC_REGEX = "(?:(?:[VZBCDFIJS])|" + CLASS_DESC + "|" + ARRAY_DESC + ")" +) + +// Dubbo request response related consts +var ( + DubboRequestHeaderBytesTwoWay = [HEADER_LENGTH]byte{MAGIC_HIGH, MAGIC_LOW, FLAG_REQUEST | FLAG_TWOWAY} + DubboRequestHeaderBytes = [HEADER_LENGTH]byte{MAGIC_HIGH, MAGIC_LOW, FLAG_REQUEST} + DubboResponseHeaderBytes = [HEADER_LENGTH]byte{MAGIC_HIGH, MAGIC_LOW, Zero, Response_OK} + DubboRequestHeartbeatHeader = [HEADER_LENGTH]byte{MAGIC_HIGH, MAGIC_LOW, FLAG_REQUEST | FLAG_TWOWAY | FLAG_EVENT} + DubboResponseHeartbeatHeader = [HEADER_LENGTH]byte{MAGIC_HIGH, MAGIC_LOW, FLAG_EVENT} +) + +// Error part +var ( + ErrHeaderNotEnough = perrors.New("header buffer too short") + ErrBodyNotEnough = perrors.New("body buffer too short") + ErrJavaException = perrors.New("got java exception") + ErrIllegalPackage = perrors.New("illegal package!") +) + +// nolint +var DescRegex, _ = regexp.Compile(DESC_REGEX) + +var NilValue = reflect.Zero(reflect.TypeOf((*interface{})(nil)).Elem()) diff --git a/protocol/dubbo/hessian2/hessian_dubbo.go b/protocol/dubbo/hessian2/hessian_dubbo.go new file mode 100644 index 0000000000..1afa4ec96e --- /dev/null +++ b/protocol/dubbo/hessian2/hessian_dubbo.go @@ -0,0 +1,251 @@ +/* + * 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 hessian2 + +import ( + "bufio" + "encoding/binary" + "time" +) + +import ( + "github.com/apache/dubbo-go-hessian2" + perrors "github.com/pkg/errors" +) + +// enum part +const ( + PackageError = PackageType(0x01) + PackageRequest = PackageType(0x02) + PackageResponse = PackageType(0x04) + PackageHeartbeat = PackageType(0x08) + PackageRequest_TwoWay = PackageType(0x10) + PackageResponse_Exception = PackageType(0x20) + PackageType_BitSize = 0x2f +) + +// PackageType nolint +type PackageType int + +// DubboHeader dubbo header +type DubboHeader struct { + SerialID byte + Type PackageType + ID int64 + BodyLen int + ResponseStatus byte +} + +// Service defines service instance +type Service struct { + Path string + Interface string + Group string + Version string + Method string + Timeout time.Duration // request timeout +} + +// HessianCodec defines hessian codec +type HessianCodec struct { + pkgType PackageType + reader *bufio.Reader + bodyLen int +} + +// NewHessianCodec generate a new hessian codec instance +func NewHessianCodec(reader *bufio.Reader) *HessianCodec { + return &HessianCodec{ + reader: reader, + } +} + +// NewHessianCodec generate a new hessian codec instance +func NewHessianCodecCustom(pkgType PackageType, reader *bufio.Reader, bodyLen int) *HessianCodec { + return &HessianCodec{ + pkgType: pkgType, + reader: reader, + bodyLen: bodyLen, + } +} + +func (h *HessianCodec) Write(service Service, header DubboHeader, body interface{}) ([]byte, error) { + switch header.Type { + case PackageHeartbeat: + if header.ResponseStatus == Zero { + return packRequest(service, header, body) + } + return packResponse(header, body) + + case PackageRequest, PackageRequest_TwoWay: + return packRequest(service, header, body) + + case PackageResponse: + return packResponse(header, body) + + default: + return nil, perrors.Errorf("Unrecognised message type: %v", header.Type) + } + + // unreachable return nil, nil +} + +// ReadHeader uses hessian codec to read dubbo header +func (h *HessianCodec) ReadHeader(header *DubboHeader) error { + + var err error + + if h.reader.Size() < HEADER_LENGTH { + return ErrHeaderNotEnough + } + buf, err := h.reader.Peek(HEADER_LENGTH) + if err != nil { // this is impossible + return perrors.WithStack(err) + } + _, err = h.reader.Discard(HEADER_LENGTH) + if err != nil { // this is impossible + return perrors.WithStack(err) + } + + //// read header + + if buf[0] != MAGIC_HIGH && buf[1] != MAGIC_LOW { + return ErrIllegalPackage + } + + // Header{serialization id(5 bit), event, two way, req/response} + if header.SerialID = buf[2] & SERIAL_MASK; header.SerialID == Zero { + return perrors.Errorf("serialization ID:%v", header.SerialID) + } + + flag := buf[2] & FLAG_EVENT + if flag != Zero { + header.Type |= PackageHeartbeat + } + flag = buf[2] & FLAG_REQUEST + if flag != Zero { + header.Type |= PackageRequest + flag = buf[2] & FLAG_TWOWAY + if flag != Zero { + header.Type |= PackageRequest_TwoWay + } + } else { + header.Type |= PackageResponse + header.ResponseStatus = buf[3] + if header.ResponseStatus != Response_OK { + header.Type |= PackageResponse_Exception + } + } + + // Header{req id} + header.ID = int64(binary.BigEndian.Uint64(buf[4:])) + + // Header{body len} + header.BodyLen = int(binary.BigEndian.Uint32(buf[12:])) + if header.BodyLen < 0 { + return ErrIllegalPackage + } + + h.pkgType = header.Type + h.bodyLen = header.BodyLen + + if h.reader.Buffered() < h.bodyLen { + return ErrBodyNotEnough + } + + return perrors.WithStack(err) + +} + +// ReadBody uses hessian codec to read response body +func (h *HessianCodec) ReadBody(rspObj interface{}) error { + + if h.reader.Buffered() < h.bodyLen { + return ErrBodyNotEnough + } + buf, err := h.reader.Peek(h.bodyLen) + if err != nil { + return perrors.WithStack(err) + } + _, err = h.reader.Discard(h.bodyLen) + if err != nil { // this is impossible + return perrors.WithStack(err) + } + + switch h.pkgType & PackageType_BitSize { + case PackageResponse | PackageHeartbeat | PackageResponse_Exception, PackageResponse | PackageResponse_Exception: + decoder := hessian.NewDecoder(buf[:]) + exception, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + rsp, ok := rspObj.(*DubboResponse) + if !ok { + return perrors.Errorf("java exception:%s", exception.(string)) + } + rsp.Exception = perrors.Errorf("java exception:%s", exception.(string)) + return nil + case PackageRequest | PackageHeartbeat, PackageResponse | PackageHeartbeat: + case PackageRequest: + if rspObj != nil { + if err = unpackRequestBody(hessian.NewDecoder(buf[:]), rspObj); err != nil { + return perrors.WithStack(err) + } + } + case PackageResponse: + if rspObj != nil { + if err = unpackResponseBody(hessian.NewDecoder(buf[:]), rspObj); err != nil { + return perrors.WithStack(err) + } + } + } + + return nil +} + +// ignore body, but only read attachments +func (h *HessianCodec) ReadAttachments() (map[string]interface{}, error) { + if h.reader.Buffered() < h.bodyLen { + return nil, ErrBodyNotEnough + } + buf, err := h.reader.Peek(h.bodyLen) + if err != nil { + return nil, perrors.WithStack(err) + } + _, err = h.reader.Discard(h.bodyLen) + if err != nil { // this is impossible + return nil, perrors.WithStack(err) + } + + switch h.pkgType & PackageType_BitSize { + case PackageRequest: + rspObj := make([]interface{}, 7) + if err = unpackRequestBody(hessian.NewDecoderWithSkip(buf[:]), rspObj); err != nil { + return nil, perrors.WithStack(err) + } + return rspObj[6].(map[string]interface{}), nil + case PackageResponse: + rspObj := &DubboResponse{} + if err = unpackResponseBody(hessian.NewDecoderWithSkip(buf[:]), rspObj); err != nil { + return nil, perrors.WithStack(err) + } + return rspObj.Attachments, nil + } + + return nil, nil +} diff --git a/protocol/dubbo/hessian2/hessian_dubbo_test.go b/protocol/dubbo/hessian2/hessian_dubbo_test.go new file mode 100644 index 0000000000..c3f19f0453 --- /dev/null +++ b/protocol/dubbo/hessian2/hessian_dubbo_test.go @@ -0,0 +1,231 @@ +/* + * 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 hessian2 + +import ( + "bufio" + "bytes" + "reflect" + "testing" + "time" +) + +import ( + hessian "github.com/apache/dubbo-go-hessian2" + "github.com/stretchr/testify/assert" +) + +type Case struct { + A string + B int +} + +type CaseA struct { + A string + B int + C Case +} + +type CaseB struct { + A string + B CaseA +} + +func (c *CaseB) JavaClassName() string { + return "com.test.caseb" +} + +func (c CaseA) JavaClassName() string { + return "com.test.casea" +} + +//JavaClassName java fully qualified path +func (c Case) JavaClassName() string { + return "com.test.case" +} + +func doTestHessianEncodeHeader(t *testing.T, packageType PackageType, responseStatus byte, body interface{}) ([]byte, error) { + hessian.RegisterPOJO(&Case{}) + codecW := NewHessianCodec(nil) + resp, err := codecW.Write(Service{ + Path: "test", + Interface: "ITest", + Version: "v1.0", + Method: "test", + Timeout: time.Second * 10, + }, DubboHeader{ + SerialID: 2, + Type: packageType, + ID: 1, + ResponseStatus: responseStatus, + }, body) + assert.Nil(t, err) + return resp, err +} + +func doTestResponse(t *testing.T, packageType PackageType, responseStatus byte, body interface{}, decodedResponse *DubboResponse, assertFunc func()) { + resp, err := doTestHessianEncodeHeader(t, packageType, responseStatus, body) + + codecR := NewHessianCodec(bufio.NewReader(bytes.NewReader(resp))) + + h := &DubboHeader{} + err = codecR.ReadHeader(h) + assert.Nil(t, err) + + assert.Equal(t, byte(2), h.SerialID) + assert.Equal(t, packageType, h.Type&(PackageRequest|PackageResponse|PackageHeartbeat)) + assert.Equal(t, int64(1), h.ID) + assert.Equal(t, responseStatus, h.ResponseStatus) + + err = codecR.ReadBody(decodedResponse) + assert.Nil(t, err) + t.Log(decodedResponse) + + if assertFunc != nil { + assertFunc() + return + } + + if h.ResponseStatus != Zero && h.ResponseStatus != Response_OK { + assert.Equal(t, "java exception:"+body.(string), decodedResponse.Exception.Error()) + return + } + + in, _ := hessian.EnsureInterface(hessian.UnpackPtrValue(hessian.EnsurePackValue(body)), nil) + out, _ := hessian.EnsureInterface(hessian.UnpackPtrValue(hessian.EnsurePackValue(decodedResponse.RspObj)), nil) + assert.Equal(t, in, out) +} + +func TestResponse(t *testing.T) { + caseObj := Case{A: "a", B: 1} + decodedResponse := &DubboResponse{} + + arr := []*Case{&caseObj} + var arrRes []interface{} + decodedResponse.RspObj = &arrRes + doTestResponse(t, PackageResponse, Response_OK, arr, decodedResponse, func() { + assert.Equal(t, 1, len(arrRes)) + assert.Equal(t, &caseObj, arrRes[0]) + }) + + decodedResponse.RspObj = &Case{} + doTestResponse(t, PackageResponse, Response_OK, &Case{A: "a", B: 1}, decodedResponse, nil) + + s := "ok!!!!!" + strObj := "" + decodedResponse.RspObj = &strObj + doTestResponse(t, PackageResponse, Response_OK, s, decodedResponse, nil) + + var intObj int64 + decodedResponse.RspObj = &intObj + doTestResponse(t, PackageResponse, Response_OK, int64(3), decodedResponse, nil) + + boolObj := false + decodedResponse.RspObj = &boolObj + doTestResponse(t, PackageResponse, Response_OK, true, decodedResponse, nil) + + strObj = "" + decodedResponse.RspObj = &strObj + doTestResponse(t, PackageResponse, hessian.Response_SERVER_ERROR, "error!!!!!", decodedResponse, nil) + + mapObj := map[string][]*Case{"key": {&caseObj}} + mapRes := map[interface{}]interface{}{} + decodedResponse.RspObj = &mapRes + doTestResponse(t, PackageResponse, Response_OK, mapObj, decodedResponse, func() { + c, ok := mapRes["key"] + if !ok { + assert.FailNow(t, "no key in decoded response map") + } + + mapValueArr, ok := c.([]*Case) + if !ok { + assert.FailNow(t, "invalid decoded response map value", "expect []*Case, but get %v", reflect.TypeOf(c)) + } + assert.Equal(t, 1, len(mapValueArr)) + assert.Equal(t, &caseObj, mapValueArr[0]) + }) +} + +func doTestRequest(t *testing.T, packageType PackageType, responseStatus byte, body interface{}) { + resp, err := doTestHessianEncodeHeader(t, packageType, responseStatus, body) + + codecR := NewHessianCodec(bufio.NewReader(bytes.NewReader(resp))) + + h := &DubboHeader{} + err = codecR.ReadHeader(h) + assert.Nil(t, err) + assert.Equal(t, byte(2), h.SerialID) + assert.Equal(t, packageType, h.Type&(PackageRequest|PackageResponse|PackageHeartbeat)) + assert.Equal(t, int64(1), h.ID) + assert.Equal(t, responseStatus, h.ResponseStatus) + + c := make([]interface{}, 7) + err = codecR.ReadBody(c) + assert.Nil(t, err) + t.Log(c) + assert.True(t, len(body.([]interface{})) == len(c[5].([]interface{}))) +} + +func TestRequest(t *testing.T) { + doTestRequest(t, PackageRequest, Zero, []interface{}{"a"}) + doTestRequest(t, PackageRequest, Zero, []interface{}{"a", 3}) + doTestRequest(t, PackageRequest, Zero, []interface{}{"a", true}) + doTestRequest(t, PackageRequest, Zero, []interface{}{"a", 3, true}) + doTestRequest(t, PackageRequest, Zero, []interface{}{3.2, true}) + doTestRequest(t, PackageRequest, Zero, []interface{}{"a", 3, true, &Case{A: "a", B: 3}}) + doTestRequest(t, PackageRequest, Zero, []interface{}{"a", 3, true, []*Case{{A: "a", B: 3}}}) + doTestRequest(t, PackageRequest, Zero, []interface{}{map[string][]*Case{"key": {{A: "a", B: 3}}}}) +} + +func TestHessianCodec_ReadAttachments(t *testing.T) { + hessian.RegisterPOJO(&AttachTestObject{}) + body := &DubboResponse{ + RspObj: &CaseB{A: "A", B: CaseA{A: "a", B: 1, C: Case{A: "c", B: 2}}}, + Exception: nil, + Attachments: map[string]interface{}{DUBBO_VERSION_KEY: "2.6.4", "att": AttachTestObject{Id: 23, Name: "haha"}}, + } + resp, err := doTestHessianEncodeHeader(t, PackageResponse, Response_OK, body) + assert.NoError(t, err) + hessian.UnRegisterPOJOs(&CaseB{}, &CaseA{}) + codecR1 := NewHessianCodec(bufio.NewReader(bytes.NewReader(resp))) + codecR2 := NewHessianCodec(bufio.NewReader(bytes.NewReader(resp))) + h := &DubboHeader{} + assert.NoError(t, codecR1.ReadHeader(h)) + t.Log(h) + assert.NoError(t, codecR2.ReadHeader(h)) + t.Log(h) + + err = codecR1.ReadBody(body) + assert.Equal(t, "can not find go type name com.test.caseb in registry", err.Error()) + attrs, err := codecR2.ReadAttachments() + assert.NoError(t, err) + assert.Equal(t, "2.6.4", attrs[DUBBO_VERSION_KEY]) + assert.Equal(t, AttachTestObject{Id: 23, Name: "haha"}, *(attrs["att"].(*AttachTestObject))) + assert.NotEqual(t, AttachTestObject{Id: 24, Name: "nohaha"}, *(attrs["att"].(*AttachTestObject))) + + t.Log(attrs) +} + +type AttachTestObject struct { + Id int32 + Name string `dubbo:name` +} + +func (AttachTestObject) JavaClassName() string { + return "com.test.Test" +} diff --git a/protocol/dubbo/hessian2/hessian_request.go b/protocol/dubbo/hessian2/hessian_request.go new file mode 100644 index 0000000000..4ebb4aa1be --- /dev/null +++ b/protocol/dubbo/hessian2/hessian_request.go @@ -0,0 +1,350 @@ +/* + * 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 hessian2 + +import ( + "encoding/binary" + "reflect" + "strconv" + "strings" + "time" +) + +import ( + hessian "github.com/apache/dubbo-go-hessian2" + perrors "github.com/pkg/errors" +) + +///////////////////////////////////////// +// dubbo +///////////////////////////////////////// + +func getArgType(v interface{}) string { + if v == nil { + return "V" + } + + switch v.(type) { + // Serialized tags for base types + case nil: + return "V" + case bool: + return "Z" + case []bool: + return "[Z" + case byte: + return "B" + case []byte: + return "[B" + case int8: + return "B" + case []int8: + return "[B" + case int16: + return "S" + case []int16: + return "[S" + case uint16: // Equivalent to Char of Java + return "C" + case []uint16: + return "[C" + // case rune: + // return "C" + case int: + return "J" + case []int: + return "[J" + case int32: + return "I" + case []int32: + return "[I" + case int64: + return "J" + case []int64: + return "[J" + case time.Time: + return "java.util.Date" + case []time.Time: + return "[Ljava.util.Date" + case float32: + return "F" + case []float32: + return "[F" + case float64: + return "D" + case []float64: + return "[D" + case string: + return "java.lang.String" + case []string: + return "[Ljava.lang.String;" + case []hessian.Object: + return "[Ljava.lang.Object;" + case map[interface{}]interface{}: + // return "java.util.HashMap" + return "java.util.Map" + case hessian.POJOEnum: + return v.(hessian.POJOEnum).JavaClassName() + // Serialized tags for complex types + default: + t := reflect.TypeOf(v) + if reflect.Ptr == t.Kind() { + t = reflect.TypeOf(reflect.ValueOf(v).Elem()) + } + switch t.Kind() { + case reflect.Struct: + return "java.lang.Object" + case reflect.Slice, reflect.Array: + if t.Elem().Kind() == reflect.Struct { + return "[Ljava.lang.Object;" + } + // return "java.util.ArrayList" + return "java.util.List" + case reflect.Map: // Enter here, map may be map[string]int + return "java.util.Map" + default: + return "" + } + } + + // unreachable + // return "java.lang.RuntimeException" +} + +func getArgsTypeList(args []interface{}) (string, error) { + var ( + typ string + types string + ) + + for i := range args { + typ = getArgType(args[i]) + if typ == "" { + return types, perrors.Errorf("cat not get arg %#v type", args[i]) + } + if !strings.Contains(typ, ".") { + types += typ + } else if strings.Index(typ, "[") == 0 { + types += strings.Replace(typ, ".", "/", -1) + } else { + // java.util.List -> Ljava/util/List; + types += "L" + strings.Replace(typ, ".", "/", -1) + ";" + } + } + + return types, nil +} + +type DubboRequest struct { + Params interface{} + Attachments map[string]interface{} +} + +// NewRequest create a new DubboRequest +func NewRequest(params interface{}, atta map[string]interface{}) *DubboRequest { + if atta == nil { + atta = make(map[string]interface{}) + } + return &DubboRequest{ + Params: params, + Attachments: atta, + } +} + +func EnsureRequest(body interface{}) *DubboRequest { + if req, ok := body.(*DubboRequest); ok { + return req + } + return NewRequest(body, nil) +} + +func packRequest(service Service, header DubboHeader, req interface{}) ([]byte, error) { + var ( + err error + types string + byteArray []byte + pkgLen int + ) + + request := EnsureRequest(req) + + args, ok := request.Params.([]interface{}) + if !ok { + return nil, perrors.Errorf("@params is not of type: []interface{}") + } + + hb := header.Type == PackageHeartbeat + + ////////////////////////////////////////// + // byteArray + ////////////////////////////////////////// + // magic + switch header.Type { + case PackageHeartbeat: + byteArray = append(byteArray, DubboRequestHeartbeatHeader[:]...) + case PackageRequest_TwoWay: + byteArray = append(byteArray, DubboRequestHeaderBytesTwoWay[:]...) + default: + byteArray = append(byteArray, DubboRequestHeaderBytes[:]...) + } + + // serialization id, two way flag, event, request/response flag + // SerialID is id of serialization approach in java dubbo + byteArray[2] |= header.SerialID & SERIAL_MASK + // request id + binary.BigEndian.PutUint64(byteArray[4:], uint64(header.ID)) + + encoder := hessian.NewEncoder() + encoder.Append(byteArray[:HEADER_LENGTH]) + + ////////////////////////////////////////// + // body + ////////////////////////////////////////// + if hb { + encoder.Encode(nil) + goto END + } + + // dubbo version + path + version + method + encoder.Encode(DEFAULT_DUBBO_PROTOCOL_VERSION) + encoder.Encode(service.Path) + encoder.Encode(service.Version) + encoder.Encode(service.Method) + + // args = args type list + args value list + if types, err = getArgsTypeList(args); err != nil { + return nil, perrors.Wrapf(err, " PackRequest(args:%+v)", args) + } + encoder.Encode(types) + for _, v := range args { + encoder.Encode(v) + } + + request.Attachments[PATH_KEY] = service.Path + request.Attachments[VERSION_KEY] = service.Version + if len(service.Group) > 0 { + request.Attachments[GROUP_KEY] = service.Group + } + if len(service.Interface) > 0 { + request.Attachments[INTERFACE_KEY] = service.Interface + } + if service.Timeout != 0 { + request.Attachments[TIMEOUT_KEY] = strconv.Itoa(int(service.Timeout / time.Millisecond)) + } + + encoder.Encode(request.Attachments) + +END: + byteArray = encoder.Buffer() + pkgLen = len(byteArray) + if pkgLen > int(DEFAULT_LEN) { // 8M + return nil, perrors.Errorf("Data length %d too large, max payload %d", pkgLen, DEFAULT_LEN) + } + // byteArray{body length} + binary.BigEndian.PutUint32(byteArray[12:], uint32(pkgLen-HEADER_LENGTH)) + return byteArray, nil +} + +// hessian decode request body +func unpackRequestBody(decoder *hessian.Decoder, reqObj interface{}) error { + + if decoder == nil { + return perrors.Errorf("@decoder is nil") + } + + req, ok := reqObj.([]interface{}) + if !ok { + return perrors.Errorf("@reqObj is not of type: []interface{}") + } + if len(req) < 7 { + return perrors.New("length of @reqObj should be 7") + } + + var ( + err error + dubboVersion, target, serviceVersion, method, argsTypes interface{} + args []interface{} + ) + + dubboVersion, err = decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + req[0] = dubboVersion + + target, err = decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + req[1] = target + + serviceVersion, err = decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + req[2] = serviceVersion + + method, err = decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + req[3] = method + + argsTypes, err = decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + req[4] = argsTypes + + ats := DescRegex.FindAllString(argsTypes.(string), -1) + var arg interface{} + for i := 0; i < len(ats); i++ { + arg, err = decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + args = append(args, arg) + } + req[5] = args + + attachments, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + if v, ok := attachments.(map[interface{}]interface{}); ok { + v[DUBBO_VERSION_KEY] = dubboVersion + req[6] = ToMapStringInterface(v) + return nil + } + + return perrors.Errorf("get wrong attachments: %+v", attachments) +} + +func ToMapStringInterface(origin map[interface{}]interface{}) map[string]interface{} { + dest := make(map[string]interface{}, len(origin)) + for k, v := range origin { + if kv, ok := k.(string); ok { + if v == nil { + dest[kv] = "" + continue + } + dest[kv] = v + } + } + return dest +} diff --git a/protocol/dubbo/hessian2/hessian_request_test.go b/protocol/dubbo/hessian2/hessian_request_test.go new file mode 100644 index 0000000000..98d5f2399c --- /dev/null +++ b/protocol/dubbo/hessian2/hessian_request_test.go @@ -0,0 +1,158 @@ +/* + * 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 hessian2 + +import ( + "reflect" + "strconv" + "testing" + "time" +) + +import ( + hessian "github.com/apache/dubbo-go-hessian2" + "github.com/stretchr/testify/assert" +) + +type TestEnumGender hessian.JavaEnum + +const ( + MAN hessian.JavaEnum = iota + WOMAN +) + +var genderName = map[hessian.JavaEnum]string{ + MAN: "MAN", + WOMAN: "WOMAN", +} + +var genderValue = map[string]hessian.JavaEnum{ + "MAN": MAN, + "WOMAN": WOMAN, +} + +func (g TestEnumGender) JavaClassName() string { + return "com.ikurento.test.TestEnumGender" +} + +func (g TestEnumGender) String() string { + s, ok := genderName[hessian.JavaEnum(g)] + if ok { + return s + } + + return strconv.Itoa(int(g)) +} + +func (g TestEnumGender) EnumValue(s string) hessian.JavaEnum { + v, ok := genderValue[s] + if ok { + return v + } + + return hessian.InvalidJavaEnum +} + +func TestPackRequest(t *testing.T) { + bytes, err := packRequest(Service{ + Path: "test", + Interface: "ITest", + Version: "v1.0", + Method: "test", + Timeout: time.Second * 10, + }, DubboHeader{ + SerialID: 0, + Type: PackageRequest, + ID: 123, + }, []interface{}{1, 2}) + + assert.Nil(t, err) + + if bytes != nil { + t.Logf("pack request: %s", string(bytes)) + } +} + +func TestGetArgsTypeList(t *testing.T) { + type Test struct{} + str, err := getArgsTypeList([]interface{}{nil, 1, []int{2}, true, []bool{false}, "a", []string{"b"}, Test{}, &Test{}, []Test{}, map[string]Test{}, TestEnumGender(MAN)}) + assert.NoError(t, err) + assert.Equal(t, "VJ[JZ[ZLjava/lang/String;[Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;[Ljava/lang/Object;Ljava/util/Map;Lcom/ikurento/test/TestEnumGender;", str) +} + +func TestDescRegex(t *testing.T) { + results := DescRegex.FindAllString("Ljava/lang/String;", -1) + assert.Equal(t, 1, len(results)) + assert.Equal(t, "Ljava/lang/String;", results[0]) + + results = DescRegex.FindAllString("Ljava/lang/String;I", -1) + assert.Equal(t, 2, len(results)) + assert.Equal(t, "Ljava/lang/String;", results[0]) + assert.Equal(t, "I", results[1]) + + results = DescRegex.FindAllString("ILjava/lang/String;", -1) + assert.Equal(t, 2, len(results)) + assert.Equal(t, "I", results[0]) + assert.Equal(t, "Ljava/lang/String;", results[1]) + + results = DescRegex.FindAllString("ILjava/lang/String;IZ", -1) + assert.Equal(t, 4, len(results)) + assert.Equal(t, "I", results[0]) + assert.Equal(t, "Ljava/lang/String;", results[1]) + assert.Equal(t, "I", results[2]) + assert.Equal(t, "Z", results[3]) + + results = DescRegex.FindAllString("[Ljava/lang/String;[I", -1) + assert.Equal(t, 2, len(results)) + assert.Equal(t, "[Ljava/lang/String;", results[0]) + assert.Equal(t, "[I", results[1]) +} + +func TestIssue192(t *testing.T) { + type args struct { + origin map[interface{}]interface{} + } + tests := []struct { + name string + args args + want map[string]interface{} + }{ + { + name: "not null", + args: args{ + origin: map[interface{}]interface{}{ + "1": nil, + "2": "3", + "": "", + }, + }, + want: map[string]interface{}{ + "1": "", + "2": "3", + "": "", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ToMapStringInterface(tt.args.origin); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ToMapStringString() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/protocol/dubbo/hessian2/hessian_response.go b/protocol/dubbo/hessian2/hessian_response.go new file mode 100644 index 0000000000..982960ed87 --- /dev/null +++ b/protocol/dubbo/hessian2/hessian_response.go @@ -0,0 +1,377 @@ +/* + * 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 hessian2 + +import ( + "encoding/binary" + "math" + "reflect" + "strconv" + "strings" +) + +import ( + hessian "github.com/apache/dubbo-go-hessian2" + "github.com/apache/dubbo-go-hessian2/java_exception" + perrors "github.com/pkg/errors" +) + +// DubboResponse dubbo response +type DubboResponse struct { + RspObj interface{} + Exception error + Attachments map[string]interface{} +} + +// NewResponse create a new DubboResponse +func NewResponse(rspObj interface{}, exception error, attachments map[string]interface{}) *DubboResponse { + if attachments == nil { + attachments = make(map[string]interface{}, 8) + } + return &DubboResponse{ + RspObj: rspObj, + Exception: exception, + Attachments: attachments, + } +} + +// EnsureResponse check body type, make sure it's a DubboResponse or package it as a DubboResponse +func EnsureResponse(body interface{}) *DubboResponse { + if res, ok := body.(*DubboResponse); ok { + return res + } + if exp, ok := body.(error); ok { + return NewResponse(nil, exp, nil) + } + return NewResponse(body, nil, nil) +} + +// https://github.com/apache/dubbo/blob/dubbo-2.7.1/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/codec/ExchangeCodec.java#L256 +// hessian encode response +func packResponse(header DubboHeader, ret interface{}) ([]byte, error) { + var ( + byteArray []byte + ) + + response := EnsureResponse(ret) + + hb := header.Type == PackageHeartbeat + + // magic + if hb { + byteArray = append(byteArray, DubboResponseHeartbeatHeader[:]...) + } else { + byteArray = append(byteArray, DubboResponseHeaderBytes[:]...) + } + // set serialID, identify serialization types, eg: fastjson->6, hessian2->2 + byteArray[2] |= header.SerialID & SERIAL_MASK + // response status + if header.ResponseStatus != 0 { + byteArray[3] = header.ResponseStatus + } + + // request id + binary.BigEndian.PutUint64(byteArray[4:], uint64(header.ID)) + + // body + encoder := hessian.NewEncoder() + encoder.Append(byteArray[:HEADER_LENGTH]) + + if header.ResponseStatus == Response_OK { + if hb { + encoder.Encode(nil) + } else { + atta := isSupportResponseAttachment(response.Attachments[DUBBO_VERSION_KEY]) + + var resWithException, resValue, resNullValue int32 + if atta { + resWithException = RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS + resValue = RESPONSE_VALUE_WITH_ATTACHMENTS + resNullValue = RESPONSE_NULL_VALUE_WITH_ATTACHMENTS + } else { + resWithException = RESPONSE_WITH_EXCEPTION + resValue = RESPONSE_VALUE + resNullValue = RESPONSE_NULL_VALUE + } + + if response.Exception != nil { // throw error + encoder.Encode(resWithException) + if t, ok := response.Exception.(java_exception.Throwabler); ok { + encoder.Encode(t) + } else { + encoder.Encode(java_exception.NewThrowable(response.Exception.Error())) + } + } else { + if response.RspObj == nil { + encoder.Encode(resNullValue) + } else { + encoder.Encode(resValue) + encoder.Encode(response.RspObj) // result + } + } + + if atta { + encoder.Encode(response.Attachments) // attachments + } + } + } else { + if response.Exception != nil { // throw error + encoder.Encode(response.Exception.Error()) + } else { + encoder.Encode(response.RspObj) + } + } + + byteArray = encoder.Buffer() + byteArray = hessian.EncNull(byteArray) // if not, "java client" will throw exception "unexpected end of file" + pkgLen := len(byteArray) + if pkgLen > int(DEFAULT_LEN) { // 8M + return nil, perrors.Errorf("Data length %d too large, max payload %d", pkgLen, DEFAULT_LEN) + } + // byteArray{body length} + binary.BigEndian.PutUint32(byteArray[12:], uint32(pkgLen-HEADER_LENGTH)) + return byteArray, nil + +} + +// hessian decode response body +func unpackResponseBody(decoder *hessian.Decoder, resp interface{}) error { + // body + if decoder == nil { + return perrors.Errorf("@decoder is nil") + } + rspType, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + + response := EnsureResponse(resp) + + switch rspType { + case RESPONSE_WITH_EXCEPTION, RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS: + expt, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + if rspType == RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS { + attachments, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + if v, ok := attachments.(map[interface{}]interface{}); ok { + atta := ToMapStringInterface(v) + response.Attachments = atta + } else { + return perrors.Errorf("get wrong attachments: %+v", attachments) + } + } + + if e, ok := expt.(error); ok { + response.Exception = e + } else { + response.Exception = perrors.Errorf("got exception: %+v", expt) + } + return nil + + case RESPONSE_VALUE, RESPONSE_VALUE_WITH_ATTACHMENTS: + rsp, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + if rspType == RESPONSE_VALUE_WITH_ATTACHMENTS { + attachments, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + if v, ok := attachments.(map[interface{}]interface{}); ok { + response.Attachments = ToMapStringInterface(v) + } else { + return perrors.Errorf("get wrong attachments: %+v", attachments) + } + } + + // If the return value is nil, + // we should consider it normal + if rsp == nil { + return nil + } + + return perrors.WithStack(ReflectResponse(rsp, response.RspObj)) + + case RESPONSE_NULL_VALUE, RESPONSE_NULL_VALUE_WITH_ATTACHMENTS: + if rspType == RESPONSE_NULL_VALUE_WITH_ATTACHMENTS { + attachments, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + if v, ok := attachments.(map[interface{}]interface{}); ok { + atta := ToMapStringInterface(v) + response.Attachments = atta + } else { + return perrors.Errorf("get wrong attachments: %+v", attachments) + } + } + return nil + } + + return nil +} + +// CopySlice copy from inSlice to outSlice +func CopySlice(inSlice, outSlice reflect.Value) error { + if inSlice.IsNil() { + return perrors.New("@in is nil") + } + if inSlice.Kind() != reflect.Slice { + return perrors.Errorf("@in is not slice, but %v", inSlice.Kind()) + } + + for outSlice.Kind() == reflect.Ptr { + outSlice = outSlice.Elem() + } + + size := inSlice.Len() + outSlice.Set(reflect.MakeSlice(outSlice.Type(), size, size)) + + for i := 0; i < size; i++ { + inSliceValue := inSlice.Index(i) + if !inSliceValue.Type().AssignableTo(outSlice.Index(i).Type()) { + return perrors.Errorf("in element type [%s] can not assign to out element type [%s]", + inSliceValue.Type().String(), outSlice.Type().String()) + } + outSlice.Index(i).Set(inSliceValue) + } + + return nil +} + +// CopyMap copy from in map to out map +func CopyMap(inMapValue, outMapValue reflect.Value) error { + if inMapValue.IsNil() { + return perrors.New("@in is nil") + } + if !inMapValue.CanInterface() { + return perrors.New("@in's Interface can not be used.") + } + if inMapValue.Kind() != reflect.Map { + return perrors.Errorf("@in is not map, but %v", inMapValue.Kind()) + } + + outMapType := hessian.UnpackPtrType(outMapValue.Type()) + hessian.SetValue(outMapValue, reflect.MakeMap(outMapType)) + + outKeyType := outMapType.Key() + + outMapValue = hessian.UnpackPtrValue(outMapValue) + outValueType := outMapValue.Type().Elem() + + for _, inKey := range inMapValue.MapKeys() { + inValue := inMapValue.MapIndex(inKey) + + if !inKey.Type().AssignableTo(outKeyType) { + return perrors.Errorf("in Key:{type:%s, value:%#v} can not assign to out Key:{type:%s} ", + inKey.Type().String(), inKey, outKeyType.String()) + } + if !inValue.Type().AssignableTo(outValueType) { + return perrors.Errorf("in Value:{type:%s, value:%#v} can not assign to out value:{type:%s}", + inValue.Type().String(), inValue, outValueType.String()) + } + outMapValue.SetMapIndex(inKey, inValue) + } + + return nil +} + +// ReflectResponse reflect return value +// TODO response object should not be copied again to another object, it should be the exact type of the object +func ReflectResponse(in interface{}, out interface{}) error { + if in == nil { + return perrors.Errorf("@in is nil") + } + + if out == nil { + return perrors.Errorf("@out is nil") + } + if reflect.TypeOf(out).Kind() != reflect.Ptr { + return perrors.Errorf("@out should be a pointer") + } + + inValue := hessian.EnsurePackValue(in) + outValue := hessian.EnsurePackValue(out) + + outType := outValue.Type().String() + if outType == "interface {}" || outType == "*interface {}" { + hessian.SetValue(outValue, inValue) + return nil + } + + switch inValue.Type().Kind() { + case reflect.Slice, reflect.Array: + return CopySlice(inValue, outValue) + case reflect.Map: + return CopyMap(inValue, outValue) + default: + hessian.SetValue(outValue, inValue) + } + + return nil +} + +var versionInt = make(map[string]int) + +// https://github.com/apache/dubbo/blob/dubbo-2.7.1/dubbo-common/src/main/java/org/apache/dubbo/common/Version.java#L96 +// isSupportResponseAttachment is for compatibility among some dubbo version +func isSupportResponseAttachment(ver interface{}) bool { + version, ok := ver.(string) + if !ok || len(version) == 0 { + return false + } + + v, ok := versionInt[version] + if !ok { + v = version2Int(version) + if v == -1 { + return false + } + } + + if v >= 2001000 && v <= 2060200 { // 2.0.10 ~ 2.6.2 + return false + } + return v >= LOWEST_VERSION_FOR_RESPONSE_ATTACHMENT +} + +func version2Int(ver interface{}) int { + version, ok := ver.(string) + if !ok || len(version) == 0 { + return 0 + } + var v = 0 + varr := strings.Split(version, ".") + length := len(varr) + for key, value := range varr { + v0, err := strconv.Atoi(value) + if err != nil { + return -1 + } + v += v0 * int(math.Pow10((length-key-1)*2)) + } + if length == 3 { + return v * 100 + } + return v +} diff --git a/protocol/dubbo/hessian2/hessian_response_test.go b/protocol/dubbo/hessian2/hessian_response_test.go new file mode 100644 index 0000000000..f5c84baa90 --- /dev/null +++ b/protocol/dubbo/hessian2/hessian_response_test.go @@ -0,0 +1,225 @@ +/* + * 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 hessian2 + +import ( + "reflect" + "testing" +) + +import ( + hessian "github.com/apache/dubbo-go-hessian2" + "github.com/stretchr/testify/assert" +) + +func doTestReflectResponse(t *testing.T, in interface{}, out interface{}) { + err := ReflectResponse(in, out) + if err != nil { + t.Error(err) + t.FailNow() + } + + result := hessian.UnpackPtrValue(reflect.ValueOf(out)).Interface() + + equal := reflect.DeepEqual(in, result) + if !equal { + t.Errorf("expect [%v]: %v, but got [%v]: %v", reflect.TypeOf(in), in, reflect.TypeOf(result), result) + } +} + +func TestReflectResponse(t *testing.T) { + var b bool + doTestReflectResponse(t, true, &b) + doTestReflectResponse(t, false, &b) + + var i int + doTestReflectResponse(t, 123, &i) + doTestReflectResponse(t, 234, &i) + + var i16 int16 + doTestReflectResponse(t, int16(456), &i16) + + var i64 int64 + doTestReflectResponse(t, int64(789), &i64) + + var s string + doTestReflectResponse(t, "hello world", &s) + + type rr struct { + Name string + Num int + } + + var r1 rr + doTestReflectResponse(t, rr{"dubbogo", 32}, &r1) + + // ------ map test ------- + m1 := make(map[interface{}]interface{}) + var m1r map[interface{}]interface{} + m1["hello"] = "world" + m1[1] = "go" + m1["dubbo"] = 666 + doTestReflectResponse(t, m1, &m1r) + + m2 := make(map[string]string) + var m2r map[string]string + m2["hello"] = "world" + m2["dubbo"] = "666" + doTestReflectResponse(t, m2, &m2r) + + m3 := make(map[string]rr) + var m3r map[string]rr + m3["dubbo"] = rr{"hello", 123} + m3["go"] = rr{"world", 456} + doTestReflectResponse(t, m3, &m3r) + + // ------ slice test ------- + s1 := []string{"abc", "def", "hello", "world"} + var s1r []string + doTestReflectResponse(t, s1, &s1r) + + s2 := []rr{rr{"dubbo", 666}, rr{"go", 999}} + var s2r []rr + doTestReflectResponse(t, s2, &s2r) + + s3 := []interface{}{rr{"dubbo", 666}, 123, "hello"} + var s3r []interface{} + doTestReflectResponse(t, s3, &s3r) + + // ------ interface test ------- + in1 := []interface{}{rr{"dubbo", 666}, 123, "hello"} + var inr1 *interface{} + doTestReflectResponse(t, in1, reflect.New(reflect.TypeOf(inr1).Elem()).Interface()) + + in2 := make(map[string]rr) + var inr2 map[string]rr + m3["dubbo"] = rr{"hello", 123} + m3["go"] = rr{"world", 456} + doTestReflectResponse(t, in2, &inr2) +} + +// separately test copy normal map to map[interface{}]interface{} +func TestCopyMap(t *testing.T) { + type rr struct { + Name string + Num int + } + + m3 := make(map[string]rr) + var m3r map[interface{}]interface{} + r1 := rr{"hello", 123} + r2 := rr{"world", 456} + m3["dubbo"] = r1 + m3["go"] = r2 + + err := ReflectResponse(m3, &m3r) + if err != nil { + t.Error(err) + t.FailNow() + } + + assert.Equal(t, 2, len(m3r)) + + rr1, ok := m3r["dubbo"] + assert.True(t, ok) + assert.True(t, reflect.DeepEqual(r1, rr1)) + + rr2, ok := m3r["go"] + assert.True(t, ok) + assert.True(t, reflect.DeepEqual(r2, rr2)) +} + +// separately test copy normal slice to []interface{} +func TestCopySlice(t *testing.T) { + type rr struct { + Name string + Num int + } + + r1 := rr{"hello", 123} + r2 := rr{"world", 456} + + s1 := []rr{r1, r2} + var s1r []interface{} + + err := ReflectResponse(s1, &s1r) + if err != nil { + t.Error(err) + t.FailNow() + } + + assert.Equal(t, 2, len(s1r)) + assert.True(t, reflect.DeepEqual(r1, s1r[0])) + assert.True(t, reflect.DeepEqual(r2, s1r[1])) +} + +func TestIsSupportResponseAttachment(t *testing.T) { + is := isSupportResponseAttachment("2.X") + assert.False(t, is) + + is = isSupportResponseAttachment("2.0.10") + assert.False(t, is) + + is = isSupportResponseAttachment("2.5.3") + assert.False(t, is) + + is = isSupportResponseAttachment("2.6.2") + assert.False(t, is) + + is = isSupportResponseAttachment("1.5.5") + assert.False(t, is) + + is = isSupportResponseAttachment("0.0.0") + assert.False(t, is) + + is = isSupportResponseAttachment("2.0.2") + assert.True(t, is) + + is = isSupportResponseAttachment("2.7.2") + assert.True(t, is) +} + +func TestVersion2Int(t *testing.T) { + v := version2Int("2.1.3") + assert.Equal(t, 2010300, v) + + v = version2Int("22.11.33") + assert.Equal(t, 22113300, v) + + v = version2Int("222.111.333") + assert.Equal(t, 223143300, v) + + v = version2Int("220.110.333") + assert.Equal(t, 221133300, v) + + v = version2Int("229.119.333") + assert.Equal(t, 230223300, v) + + v = version2Int("2222.1111.3333") + assert.Equal(t, 2233443300, v) + + v = version2Int("2.11") + assert.Equal(t, 211, v) + + v = version2Int("2.1.3.4") + assert.Equal(t, 2010304, v) + + v = version2Int("2.1.3.4.5") + assert.Equal(t, 201030405, v) + +} diff --git a/protocol/dubbo/listener.go b/protocol/dubbo/listener.go index a17b282fdf..180fd176f9 100644 --- a/protocol/dubbo/listener.go +++ b/protocol/dubbo/listener.go @@ -28,8 +28,6 @@ import ( import ( "github.com/apache/dubbo-getty" - "github.com/apache/dubbo-go-hessian2" - "github.com/opentracing/opentracing-go" perrors "github.com/pkg/errors" ) @@ -38,6 +36,7 @@ import ( "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/dubbo/hessian2" "github.com/apache/dubbo-go/protocol/invocation" ) @@ -105,8 +104,8 @@ func (h *RpcClientHandler) OnMessage(session getty.Session, pkg interface{}) { return } - if p.Header.Type&hessian.PackageHeartbeat != 0x00 { - if p.Header.Type&hessian.PackageResponse != 0x00 { + if p.Header.Type&hessian2.PackageHeartbeat != 0x00 { + if p.Header.Type&hessian2.PackageResponse != 0x00 { logger.Debugf("get rpc heartbeat response{header: %#v, body: %#v}", p.Header, p.Body) if p.Err != nil { logger.Errorf("rpc heartbeat response{error: %#v}", p.Err) @@ -114,8 +113,8 @@ func (h *RpcClientHandler) OnMessage(session getty.Session, pkg interface{}) { h.conn.pool.rpcClient.removePendingResponse(SequenceType(p.Header.ID)) } else { logger.Debugf("get rpc heartbeat request{header: %#v, service: %#v, body: %#v}", p.Header, p.Service, p.Body) - p.Header.ResponseStatus = hessian.Response_OK - reply(session, p, hessian.PackageHeartbeat) + p.Header.ResponseStatus = hessian2.Response_OK + reply(session, p, hessian2.PackageHeartbeat) } return } @@ -229,24 +228,24 @@ func (h *RpcServerHandler) OnMessage(session getty.Session, pkg interface{}) { logger.Errorf("illegal package{%#v}", pkg) return } - p.Header.ResponseStatus = hessian.Response_OK + p.Header.ResponseStatus = hessian2.Response_OK // heartbeat - if p.Header.Type&hessian.PackageHeartbeat != 0x00 { + if p.Header.Type&hessian2.PackageHeartbeat != 0x00 { logger.Debugf("get rpc heartbeat request{header: %#v, service: %#v, body: %#v}", p.Header, p.Service, p.Body) - reply(session, p, hessian.PackageHeartbeat) + reply(session, p, hessian2.PackageHeartbeat) return } twoway := true // not twoway - if p.Header.Type&hessian.PackageRequest_TwoWay == 0x00 { + if p.Header.Type&hessian2.PackageRequest_TwoWay == 0x00 { twoway = false } defer func() { if e := recover(); e != nil { - p.Header.ResponseStatus = hessian.Response_SERVER_ERROR + p.Header.ResponseStatus = hessian2.Response_SERVER_ERROR if err, ok := e.(error); ok { logger.Errorf("OnMessage panic: %+v", perrors.WithStack(err)) p.Body = perrors.WithStack(err) @@ -261,7 +260,7 @@ func (h *RpcServerHandler) OnMessage(session getty.Session, pkg interface{}) { if !twoway { return } - reply(session, p, hessian.PackageResponse) + reply(session, p, hessian2.PackageResponse) } }() @@ -274,14 +273,14 @@ func (h *RpcServerHandler) OnMessage(session getty.Session, pkg interface{}) { if exporter == nil { err := fmt.Errorf("don't have this exporter, key: %s", u.ServiceKey()) logger.Errorf(err.Error()) - p.Header.ResponseStatus = hessian.Response_OK + p.Header.ResponseStatus = hessian2.Response_OK p.Body = err - reply(session, p, hessian.PackageResponse) + reply(session, p, hessian2.PackageResponse) return } invoker := exporter.(protocol.Exporter).GetInvoker() if invoker != nil { - attachments := p.Body.(map[string]interface{})["attachments"].(map[string]string) + attachments := p.Body.(map[string]interface{})["attachments"].(map[string]interface{}) attachments[constant.LOCAL_ADDR] = session.LocalAddr() attachments[constant.REMOTE_ADDR] = session.RemoteAddr() @@ -292,19 +291,19 @@ func (h *RpcServerHandler) OnMessage(session getty.Session, pkg interface{}) { result := invoker.Invoke(ctx, inv) if err := result.Error(); err != nil { - p.Header.ResponseStatus = hessian.Response_OK - p.Body = hessian.NewResponse(nil, err, result.Attachments()) + p.Header.ResponseStatus = hessian2.Response_OK + p.Body = hessian2.NewResponse(nil, err, result.Attachments()) } else { res := result.Result() - p.Header.ResponseStatus = hessian.Response_OK - p.Body = hessian.NewResponse(res, nil, result.Attachments()) + p.Header.ResponseStatus = hessian2.Response_OK + p.Body = hessian2.NewResponse(res, nil, result.Attachments()) } } if !twoway { return } - reply(session, p, hessian.PackageResponse) + reply(session, p, hessian2.PackageResponse) } // OnCron notified when RPC server session got any message in cron job @@ -340,17 +339,16 @@ func rebuildCtx(inv *invocation.RPCInvocation) context.Context { ctx := context.Background() // actually, if user do not use any opentracing framework, the err will not be nil. - spanCtx, err := opentracing.GlobalTracer().Extract(opentracing.TextMap, - opentracing.TextMapCarrier(inv.Attachments())) + spanCtx, err := extractTraceCtx(inv) if err == nil { ctx = context.WithValue(ctx, constant.TRACING_REMOTE_SPAN_CTX, spanCtx) } return ctx } -func reply(session getty.Session, req *DubboPackage, tp hessian.PackageType) { +func reply(session getty.Session, req *DubboPackage, tp hessian2.PackageType) { resp := &DubboPackage{ - Header: hessian.DubboHeader{ + Header: hessian2.DubboHeader{ SerialID: req.Header.SerialID, Type: tp, ID: req.Header.ID, @@ -358,7 +356,7 @@ func reply(session getty.Session, req *DubboPackage, tp hessian.PackageType) { }, } - if req.Header.Type&hessian.PackageRequest != 0x00 { + if req.Header.Type&hessian2.PackageRequest != 0x00 { resp.Body = req.Body } else { resp.Body = nil diff --git a/protocol/dubbo/listener_test.go b/protocol/dubbo/listener_test.go index 5f80981460..5ab73fd465 100644 --- a/protocol/dubbo/listener_test.go +++ b/protocol/dubbo/listener_test.go @@ -35,7 +35,7 @@ import ( // test rebuild the ctx func TestRebuildCtx(t *testing.T) { opentracing.SetGlobalTracer(mocktracer.New()) - attach := make(map[string]string, 10) + attach := make(map[string]interface{}, 10) attach[constant.VERSION_KEY] = "1.0" attach[constant.GROUP_KEY] = "MyGroup" inv := invocation.NewRPCInvocation("MethodName", []interface{}{"OK", "Hello"}, attach) @@ -47,8 +47,7 @@ func TestRebuildCtx(t *testing.T) { span, ctx := opentracing.StartSpanFromContext(ctx, "Test-Client") - opentracing.GlobalTracer().Inject(span.Context(), opentracing.TextMap, - opentracing.TextMapCarrier(inv.Attachments())) + injectTraceCtx(span, inv) // rebuild the context success inv = invocation.NewRPCInvocation("MethodName", []interface{}{"OK", "Hello"}, attach) ctx = rebuildCtx(inv) diff --git a/protocol/dubbo/opentracing.go b/protocol/dubbo/opentracing.go new file mode 100644 index 0000000000..2dcd6a4d0d --- /dev/null +++ b/protocol/dubbo/opentracing.go @@ -0,0 +1,60 @@ +/* + * 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 dubbo + +import ( + "github.com/opentracing/opentracing-go" +) +import ( + invocation_impl "github.com/apache/dubbo-go/protocol/invocation" +) + +func injectTraceCtx(currentSpan opentracing.Span, inv *invocation_impl.RPCInvocation) error { + // inject opentracing ctx + traceAttachments := filterContext(inv.Attachments()) + carrier := opentracing.TextMapCarrier(traceAttachments) + err := opentracing.GlobalTracer().Inject(currentSpan.Context(), opentracing.TextMap, carrier) + if err == nil { + fillTraceAttachments(inv.Attachments(), traceAttachments) + } + return err +} + +func extractTraceCtx(inv *invocation_impl.RPCInvocation) (opentracing.SpanContext, error) { + traceAttachments := filterContext(inv.Attachments()) + // actually, if user do not use any opentracing framework, the err will not be nil. + spanCtx, err := opentracing.GlobalTracer().Extract(opentracing.TextMap, + opentracing.TextMapCarrier(traceAttachments)) + return spanCtx, err +} + +func filterContext(attachments map[string]interface{}) map[string]string { + var traceAttchment = make(map[string]string) + for k, v := range attachments { + if r, ok := v.(string); ok { + traceAttchment[k] = r + } + } + return traceAttchment +} + +func fillTraceAttachments(attachments map[string]interface{}, traceAttachment map[string]string) { + for k, v := range traceAttachment { + attachments[k] = v + } +} diff --git a/protocol/dubbo/readwriter.go b/protocol/dubbo/readwriter.go index adc6311b0e..a7b37aae76 100644 --- a/protocol/dubbo/readwriter.go +++ b/protocol/dubbo/readwriter.go @@ -24,7 +24,6 @@ import ( import ( "github.com/apache/dubbo-getty" - "github.com/apache/dubbo-go-hessian2" perrors "github.com/pkg/errors" ) @@ -32,6 +31,7 @@ import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/protocol/dubbo/hessian2" ) //////////////////////////////////////////// @@ -56,7 +56,7 @@ func (p *RpcClientPackageHandler) Read(ss getty.Session, data []byte) (interface err := pkg.Unmarshal(buf, p.client) if err != nil { originErr := perrors.Cause(err) - if originErr == hessian.ErrHeaderNotEnough || originErr == hessian.ErrBodyNotEnough { + if originErr == hessian2.ErrHeaderNotEnough || originErr == hessian2.ErrBodyNotEnough { return nil, 0, nil } @@ -65,12 +65,12 @@ func (p *RpcClientPackageHandler) Read(ss getty.Session, data []byte) (interface return nil, 0, perrors.WithStack(err) } - if pkg.Header.Type&hessian.PackageRequest == 0x00 { - pkg.Err = pkg.Body.(*hessian.Response).Exception - pkg.Body = NewResponse(pkg.Body.(*hessian.Response).RspObj, pkg.Body.(*hessian.Response).Attachments) + if pkg.Header.Type&hessian2.PackageRequest == 0x00 { + pkg.Err = pkg.Body.(*hessian2.DubboResponse).Exception + pkg.Body = NewResponse(pkg.Body.(*hessian2.DubboResponse).RspObj, pkg.Body.(*hessian2.DubboResponse).Attachments) } - return pkg, hessian.HEADER_LENGTH + pkg.Header.BodyLen, nil + return pkg, hessian2.HEADER_LENGTH + pkg.Header.BodyLen, nil } // Write encode @pkg. @@ -111,7 +111,7 @@ func (p *RpcServerPackageHandler) Read(ss getty.Session, data []byte) (interface err := pkg.Unmarshal(buf) if err != nil { originErr := perrors.Cause(err) - if originErr == hessian.ErrHeaderNotEnough || originErr == hessian.ErrBodyNotEnough { + if originErr == hessian2.ErrHeaderNotEnough || originErr == hessian2.ErrBodyNotEnough { return nil, 0, nil } @@ -120,13 +120,13 @@ func (p *RpcServerPackageHandler) Read(ss getty.Session, data []byte) (interface return nil, 0, perrors.WithStack(err) } - if pkg.Header.Type&hessian.PackageHeartbeat == 0x00 { + if pkg.Header.Type&hessian2.PackageHeartbeat == 0x00 { // convert params of request req := pkg.Body.([]interface{}) // length of body should be 7 if len(req) > 0 { var dubboVersion, argsTypes string var args []interface{} - var attachments map[string]string + var attachments map[string]interface{} if req[0] != nil { dubboVersion = req[0].(string) } @@ -146,18 +146,18 @@ func (p *RpcServerPackageHandler) Read(ss getty.Session, data []byte) (interface args = req[5].([]interface{}) } if req[6] != nil { - attachments = req[6].(map[string]string) + attachments = req[6].(map[string]interface{}) } - if pkg.Service.Path == "" && len(attachments[constant.PATH_KEY]) > 0 { - pkg.Service.Path = attachments[constant.PATH_KEY] + if pkg.Service.Path == "" && attachments[constant.PATH_KEY] != nil && len(attachments[constant.PATH_KEY].(string)) > 0 { + pkg.Service.Path = attachments[constant.PATH_KEY].(string) } - if _, ok := attachments[constant.INTERFACE_KEY]; ok { - pkg.Service.Interface = attachments[constant.INTERFACE_KEY] + if inter, ok := attachments[constant.INTERFACE_KEY]; ok && inter != nil { + pkg.Service.Interface = inter.(string) } else { pkg.Service.Interface = pkg.Service.Path } - if len(attachments[constant.GROUP_KEY]) > 0 { - pkg.Service.Group = attachments[constant.GROUP_KEY] + if attachments[constant.GROUP_KEY] != nil && len(attachments[constant.GROUP_KEY].(string)) > 0 { + pkg.Service.Group = attachments[constant.GROUP_KEY].(string) } pkg.Body = map[string]interface{}{ "dubboVersion": dubboVersion, @@ -169,7 +169,7 @@ func (p *RpcServerPackageHandler) Read(ss getty.Session, data []byte) (interface } } - return pkg, hessian.HEADER_LENGTH + pkg.Header.BodyLen, nil + return pkg, hessian2.HEADER_LENGTH + pkg.Header.BodyLen, nil } // Write encode @pkg. diff --git a/protocol/invocation.go b/protocol/invocation.go index 296ec0540c..452f619e2d 100644 --- a/protocol/invocation.go +++ b/protocol/invocation.go @@ -34,15 +34,16 @@ type Invocation interface { // Reply gets response of request Reply() interface{} // Attachments gets all attachments - Attachments() map[string]string - // AttachmentsByKey gets attachment by key , if nil then return default value + Attachments() map[string]interface{} + // AttachmentsByKey gets attachment by key , if nil then return default value. (It will be deprecated in the future) AttachmentsByKey(string, string) string + Attachment(string) interface{} // Attributes refers to dubbo 2.7.6. It is different from attachment. It is used in internal process. Attributes() map[string]interface{} // AttributeByKey gets attribute by key , if nil then return default value AttributeByKey(string, interface{}) interface{} // SetAttachments sets attribute by @key and @value. - SetAttachments(key string, value string) + SetAttachments(key string, value interface{}) // Invoker gets the invoker in current context. Invoker() Invoker } diff --git a/protocol/invocation/rpcinvocation.go b/protocol/invocation/rpcinvocation.go index c72e105d35..35d12965e8 100644 --- a/protocol/invocation/rpcinvocation.go +++ b/protocol/invocation/rpcinvocation.go @@ -39,7 +39,7 @@ type RPCInvocation struct { arguments []interface{} reply interface{} callBack interface{} - attachments map[string]string + attachments map[string]interface{} // Refer to dubbo 2.7.6. It is different from attachment. It is used in internal process. attributes map[string]interface{} invoker protocol.Invoker @@ -47,7 +47,7 @@ type RPCInvocation struct { } // NewRPCInvocation creates a RPC invocation. -func NewRPCInvocation(methodName string, arguments []interface{}, attachments map[string]string) *RPCInvocation { +func NewRPCInvocation(methodName string, arguments []interface{}, attachments map[string]interface{}) *RPCInvocation { return &RPCInvocation{ methodName: methodName, arguments: arguments, @@ -99,7 +99,7 @@ func (r *RPCInvocation) SetReply(reply interface{}) { } // Attachments gets all attachments of RPC. -func (r *RPCInvocation) Attachments() map[string]string { +func (r *RPCInvocation) Attachments() map[string]interface{} { return r.attachments } @@ -112,11 +112,25 @@ func (r *RPCInvocation) AttachmentsByKey(key string, defaultValue string) string } value, ok := r.attachments[key] if ok { - return value + return value.(string) } return defaultValue } +// Attachment returns the corresponding value from dubbo's attachment with the given key. +func (r *RPCInvocation) Attachment(key string) interface{} { + r.lock.RLock() + defer r.lock.RUnlock() + if r.attachments == nil { + return nil + } + value, ok := r.attachments[key] + if ok { + return value + } + return nil +} + // Attributes gets all attributes of RPC. func (r *RPCInvocation) Attributes() map[string]interface{} { return r.attributes @@ -134,11 +148,11 @@ func (r *RPCInvocation) AttributeByKey(key string, defaultValue interface{}) int } // SetAttachments sets attribute by @key and @value. -func (r *RPCInvocation) SetAttachments(key string, value string) { +func (r *RPCInvocation) SetAttachments(key string, value interface{}) { r.lock.Lock() defer r.lock.Unlock() if r.attachments == nil { - r.attachments = make(map[string]string) + r.attachments = make(map[string]interface{}) } r.attachments[key] = value } @@ -221,7 +235,7 @@ func WithCallBack(callBack interface{}) option { } // WithAttachments creates option with @attachments. -func WithAttachments(attachments map[string]string) option { +func WithAttachments(attachments map[string]interface{}) option { return func(invo *RPCInvocation) { invo.attachments = attachments } diff --git a/protocol/jsonrpc/server.go b/protocol/jsonrpc/server.go index 29eba0223e..9755a481fd 100644 --- a/protocol/jsonrpc/server.go +++ b/protocol/jsonrpc/server.go @@ -343,7 +343,7 @@ func serveRequest(ctx context.Context, header map[string]string, body []byte, co exporter, _ := jsonrpcProtocol.ExporterMap().Load(path) invoker := exporter.(*JsonrpcExporter).GetInvoker() if invoker != nil { - result := invoker.Invoke(ctx, invocation.NewRPCInvocation(methodName, args, map[string]string{ + result := invoker.Invoke(ctx, invocation.NewRPCInvocation(methodName, args, map[string]interface{}{ constant.PATH_KEY: path, constant.VERSION_KEY: codec.req.Version})) if err := result.Error(); err != nil { diff --git a/protocol/rest/server/rest_server.go b/protocol/rest/server/rest_server.go index fbd6fb7ad9..d9542bb876 100644 --- a/protocol/rest/server/rest_server.go +++ b/protocol/rest/server/rest_server.go @@ -111,7 +111,7 @@ func GetRouteFunc(invoker protocol.Invoker, methodConfig *rest_config.RestMethod logger.Errorf("[Go Restful] WriteErrorString error:%v", err) } } - result := invoker.Invoke(context.Background(), invocation.NewRPCInvocation(methodConfig.MethodName, args, make(map[string]string))) + result := invoker.Invoke(context.Background(), invocation.NewRPCInvocation(methodConfig.MethodName, args, make(map[string]interface{}))) if result.Error() != nil { err = resp.WriteError(http.StatusInternalServerError, result.Error()) if err != nil { diff --git a/protocol/result.go b/protocol/result.go index 2a33be612f..a36b16d1cc 100644 --- a/protocol/result.go +++ b/protocol/result.go @@ -28,13 +28,14 @@ type Result interface { // Result gets invoker result. Result() interface{} // SetAttachments replaces the existing attachments with the specified param. - SetAttachments(map[string]string) + SetAttachments(map[string]interface{}) // Attachments gets all attachments - Attachments() map[string]string + Attachments() map[string]interface{} + // AddAttachment adds the specified map to existing attachments in this instance. - AddAttachment(string, string) + AddAttachment(string, interface{}) // Attachment gets attachment by key with default value. - Attachment(string, string) string + Attachment(string, interface{}) interface{} } ///////////////////////////// @@ -43,7 +44,7 @@ type Result interface { // RPCResult is default RPC result. type RPCResult struct { - Attrs map[string]string + Attrs map[string]interface{} Err error Rest interface{} } @@ -69,22 +70,22 @@ func (r *RPCResult) Result() interface{} { } // SetAttachments replaces the existing attachments with the specified param. -func (r *RPCResult) SetAttachments(attr map[string]string) { +func (r *RPCResult) SetAttachments(attr map[string]interface{}) { r.Attrs = attr } // Attachments gets all attachments -func (r *RPCResult) Attachments() map[string]string { +func (r *RPCResult) Attachments() map[string]interface{} { return r.Attrs } // AddAttachment adds the specified map to existing attachments in this instance. -func (r *RPCResult) AddAttachment(key, value string) { +func (r *RPCResult) AddAttachment(key string, value interface{}) { r.Attrs[key] = value } // Attachment gets attachment by key with default value. -func (r *RPCResult) Attachment(key, defaultValue string) string { +func (r *RPCResult) Attachment(key string, defaultValue interface{}) interface{} { v, ok := r.Attrs[key] if !ok { v = defaultValue diff --git a/test/integrate/dubbo/go-client/go.mod b/test/integrate/dubbo/go-client/go.mod index 4708eb1f0f..b0be45ae9c 100644 --- a/test/integrate/dubbo/go-client/go.mod +++ b/test/integrate/dubbo/go-client/go.mod @@ -1,3 +1,7 @@ module github.com/apache/dubbo-go/test/integrate/dubbo/go-client +require ( + github.com/apache/dubbo-go-hessian2 v1.6.0-rc1.0.20200906044240-6c1fb5c3bd44 +) + go 1.13 diff --git a/test/integrate/dubbo/go-server/go.mod b/test/integrate/dubbo/go-server/go.mod index 9e1162327d..6c530f6a59 100644 --- a/test/integrate/dubbo/go-server/go.mod +++ b/test/integrate/dubbo/go-server/go.mod @@ -1,3 +1,7 @@ module github.com/apache/dubbo-go/test/integrate/dubbo/go-server +require ( + github.com/apache/dubbo-go-hessian2 v1.6.0-rc1.0.20200906044240-6c1fb5c3bd44 +) + go 1.13 From aa4d36a58b95c63e53aa0ec8253cb4984c97c5bd Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Mon, 7 Sep 2020 13:38:52 +0800 Subject: [PATCH 226/242] Fix: integration testing problems --- test/integrate/dubbo/go-client/Dockerfile | 2 +- test/integrate/dubbo/go-client/go.mod | 4 ---- test/integrate/dubbo/go-server/Dockerfile | 2 +- test/integrate/dubbo/go-server/go.mod | 4 ---- 4 files changed, 2 insertions(+), 10 deletions(-) diff --git a/test/integrate/dubbo/go-client/Dockerfile b/test/integrate/dubbo/go-client/Dockerfile index d48df36dc7..317b10bac6 100644 --- a/test/integrate/dubbo/go-client/Dockerfile +++ b/test/integrate/dubbo/go-client/Dockerfile @@ -29,7 +29,7 @@ ADD . /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-client # update dubbo-go to current commit id RUN test ${PR_ORIGIN_REPO} && echo "github.com/apache/dubbo-go will be replace to github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID}" || echo 'go get github.com/apache/dubbo-go@develop' -RUN test ${PR_ORIGIN_REPO} && go mod edit -replace=github.com/apache/dubbo-go=github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID} || go get -u github.com/apache/dubbo-go@develop +RUN test ${PR_ORIGIN_REPO} && go get github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID} RUN go install github.com/apache/dubbo-go/test/integrate/dubbo/go-client diff --git a/test/integrate/dubbo/go-client/go.mod b/test/integrate/dubbo/go-client/go.mod index b0be45ae9c..4708eb1f0f 100644 --- a/test/integrate/dubbo/go-client/go.mod +++ b/test/integrate/dubbo/go-client/go.mod @@ -1,7 +1,3 @@ module github.com/apache/dubbo-go/test/integrate/dubbo/go-client -require ( - github.com/apache/dubbo-go-hessian2 v1.6.0-rc1.0.20200906044240-6c1fb5c3bd44 -) - go 1.13 diff --git a/test/integrate/dubbo/go-server/Dockerfile b/test/integrate/dubbo/go-server/Dockerfile index c2f2d63462..5cae46f931 100644 --- a/test/integrate/dubbo/go-server/Dockerfile +++ b/test/integrate/dubbo/go-server/Dockerfile @@ -28,7 +28,7 @@ ARG PR_ORIGIN_COMMITID ADD . /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-server # update dubbo-go to current commit id RUN test ${PR_ORIGIN_REPO} && echo "github.com/apache/dubbo-go will be replace to github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID}" || echo 'go get github.com/apache/dubbo-go@develop' -RUN test ${PR_ORIGIN_REPO} && go mod edit -replace=github.com/apache/dubbo-go=github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID} || go get -u github.com/apache/dubbo-go@develop +RUN test ${PR_ORIGIN_REPO} && go get github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID} RUN go install github.com/apache/dubbo-go/test/integrate/dubbo/go-server diff --git a/test/integrate/dubbo/go-server/go.mod b/test/integrate/dubbo/go-server/go.mod index 6c530f6a59..9e1162327d 100644 --- a/test/integrate/dubbo/go-server/go.mod +++ b/test/integrate/dubbo/go-server/go.mod @@ -1,7 +1,3 @@ module github.com/apache/dubbo-go/test/integrate/dubbo/go-server -require ( - github.com/apache/dubbo-go-hessian2 v1.6.0-rc1.0.20200906044240-6c1fb5c3bd44 -) - go 1.13 From 94fe6c650944ee3b0afc214cb510df87dcb0dc8c Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Mon, 7 Sep 2020 14:15:47 +0800 Subject: [PATCH 227/242] Fix: integration testing problems --- test/integrate/dubbo/go-client/Dockerfile | 2 +- test/integrate/dubbo/go-server/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integrate/dubbo/go-client/Dockerfile b/test/integrate/dubbo/go-client/Dockerfile index 317b10bac6..9f8668ea54 100644 --- a/test/integrate/dubbo/go-client/Dockerfile +++ b/test/integrate/dubbo/go-client/Dockerfile @@ -29,7 +29,7 @@ ADD . /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-client # update dubbo-go to current commit id RUN test ${PR_ORIGIN_REPO} && echo "github.com/apache/dubbo-go will be replace to github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID}" || echo 'go get github.com/apache/dubbo-go@develop' -RUN test ${PR_ORIGIN_REPO} && go get github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID} +RUN test ${PR_ORIGIN_REPO} && go mod edit -replace=github.com/apache/dubbo-go=github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID} || go mod vendor RUN go install github.com/apache/dubbo-go/test/integrate/dubbo/go-client diff --git a/test/integrate/dubbo/go-server/Dockerfile b/test/integrate/dubbo/go-server/Dockerfile index 5cae46f931..669b99e8cf 100644 --- a/test/integrate/dubbo/go-server/Dockerfile +++ b/test/integrate/dubbo/go-server/Dockerfile @@ -28,7 +28,7 @@ ARG PR_ORIGIN_COMMITID ADD . /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-server # update dubbo-go to current commit id RUN test ${PR_ORIGIN_REPO} && echo "github.com/apache/dubbo-go will be replace to github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID}" || echo 'go get github.com/apache/dubbo-go@develop' -RUN test ${PR_ORIGIN_REPO} && go get github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID} +RUN test ${PR_ORIGIN_REPO} && go mod edit -replace=github.com/apache/dubbo-go=github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID} || go mod vendor RUN go install github.com/apache/dubbo-go/test/integrate/dubbo/go-server From 93cb0acfec574ae577825c898f499391014976db Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Mon, 7 Sep 2020 15:31:16 +0800 Subject: [PATCH 228/242] Fix: integration testing problems --- test/integrate/dubbo/go-client/Dockerfile | 1 + test/integrate/dubbo/go-server/Dockerfile | 1 + 2 files changed, 2 insertions(+) diff --git a/test/integrate/dubbo/go-client/Dockerfile b/test/integrate/dubbo/go-client/Dockerfile index 9f8668ea54..046aa61b44 100644 --- a/test/integrate/dubbo/go-client/Dockerfile +++ b/test/integrate/dubbo/go-client/Dockerfile @@ -29,6 +29,7 @@ ADD . /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-client # update dubbo-go to current commit id RUN test ${PR_ORIGIN_REPO} && echo "github.com/apache/dubbo-go will be replace to github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID}" || echo 'go get github.com/apache/dubbo-go@develop' +RUN test ${PR_ORIGIN_REPO} && sed -n '2,$p' ../../../../go.mod >> go.mod RUN test ${PR_ORIGIN_REPO} && go mod edit -replace=github.com/apache/dubbo-go=github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID} || go mod vendor RUN go install github.com/apache/dubbo-go/test/integrate/dubbo/go-client diff --git a/test/integrate/dubbo/go-server/Dockerfile b/test/integrate/dubbo/go-server/Dockerfile index 669b99e8cf..81835f2ebb 100644 --- a/test/integrate/dubbo/go-server/Dockerfile +++ b/test/integrate/dubbo/go-server/Dockerfile @@ -28,6 +28,7 @@ ARG PR_ORIGIN_COMMITID ADD . /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-server # update dubbo-go to current commit id RUN test ${PR_ORIGIN_REPO} && echo "github.com/apache/dubbo-go will be replace to github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID}" || echo 'go get github.com/apache/dubbo-go@develop' +RUN test ${PR_ORIGIN_REPO} && sed -n '2,$p' ../../../../go.mod >> go.mod RUN test ${PR_ORIGIN_REPO} && go mod edit -replace=github.com/apache/dubbo-go=github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID} || go mod vendor RUN go install github.com/apache/dubbo-go/test/integrate/dubbo/go-server From bff2cd6e121b69b562182095d5524ce58c6e69f7 Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Mon, 7 Sep 2020 15:49:33 +0800 Subject: [PATCH 229/242] Fix: integration testing problems --- test/integrate/dubbo/go-client/Dockerfile | 3 ++- test/integrate/dubbo/go-server/Dockerfile | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/test/integrate/dubbo/go-client/Dockerfile b/test/integrate/dubbo/go-client/Dockerfile index 046aa61b44..d09daf0db8 100644 --- a/test/integrate/dubbo/go-client/Dockerfile +++ b/test/integrate/dubbo/go-client/Dockerfile @@ -26,10 +26,11 @@ ARG PR_ORIGIN_REPO ARG PR_ORIGIN_COMMITID ADD . /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-client +ADD ./go.main.mod /go/src/github.com/apache/dubbo-go/go.mod # update dubbo-go to current commit id RUN test ${PR_ORIGIN_REPO} && echo "github.com/apache/dubbo-go will be replace to github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID}" || echo 'go get github.com/apache/dubbo-go@develop' -RUN test ${PR_ORIGIN_REPO} && sed -n '2,$p' ../../../../go.mod >> go.mod +RUN test ${PR_ORIGIN_REPO} && sed -n '2,$p' go.main.mod >> go.mod RUN test ${PR_ORIGIN_REPO} && go mod edit -replace=github.com/apache/dubbo-go=github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID} || go mod vendor RUN go install github.com/apache/dubbo-go/test/integrate/dubbo/go-client diff --git a/test/integrate/dubbo/go-server/Dockerfile b/test/integrate/dubbo/go-server/Dockerfile index 81835f2ebb..173b836ea8 100644 --- a/test/integrate/dubbo/go-server/Dockerfile +++ b/test/integrate/dubbo/go-server/Dockerfile @@ -26,9 +26,11 @@ ARG PR_ORIGIN_REPO ARG PR_ORIGIN_COMMITID ADD . /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-server +ADD ./go.main.mod /go/src/github.com/apache/dubbo-go/go.mod + # update dubbo-go to current commit id RUN test ${PR_ORIGIN_REPO} && echo "github.com/apache/dubbo-go will be replace to github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID}" || echo 'go get github.com/apache/dubbo-go@develop' -RUN test ${PR_ORIGIN_REPO} && sed -n '2,$p' ../../../../go.mod >> go.mod +RUN test ${PR_ORIGIN_REPO} && sed -n '2,$p' go.main.mod >> go.mod RUN test ${PR_ORIGIN_REPO} && go mod edit -replace=github.com/apache/dubbo-go=github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID} || go mod vendor RUN go install github.com/apache/dubbo-go/test/integrate/dubbo/go-server From 4cd2487307fc7c62225b256476ab698c12634560 Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Mon, 7 Sep 2020 16:05:36 +0800 Subject: [PATCH 230/242] Fix: integration testing problems --- test/integrate/dubbo/go-client/Dockerfile | 2 +- test/integrate/dubbo/go-server/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integrate/dubbo/go-client/Dockerfile b/test/integrate/dubbo/go-client/Dockerfile index d09daf0db8..45bcae9ad6 100644 --- a/test/integrate/dubbo/go-client/Dockerfile +++ b/test/integrate/dubbo/go-client/Dockerfile @@ -26,7 +26,7 @@ ARG PR_ORIGIN_REPO ARG PR_ORIGIN_COMMITID ADD . /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-client -ADD ./go.main.mod /go/src/github.com/apache/dubbo-go/go.mod +ADD go.main.mod /go/src/github.com/apache/dubbo-go/go.mod # update dubbo-go to current commit id RUN test ${PR_ORIGIN_REPO} && echo "github.com/apache/dubbo-go will be replace to github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID}" || echo 'go get github.com/apache/dubbo-go@develop' diff --git a/test/integrate/dubbo/go-server/Dockerfile b/test/integrate/dubbo/go-server/Dockerfile index 173b836ea8..b2ce6bdaa5 100644 --- a/test/integrate/dubbo/go-server/Dockerfile +++ b/test/integrate/dubbo/go-server/Dockerfile @@ -26,7 +26,7 @@ ARG PR_ORIGIN_REPO ARG PR_ORIGIN_COMMITID ADD . /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-server -ADD ./go.main.mod /go/src/github.com/apache/dubbo-go/go.mod +ADD go.main.mod /go/src/github.com/apache/dubbo-go/go.mod # update dubbo-go to current commit id RUN test ${PR_ORIGIN_REPO} && echo "github.com/apache/dubbo-go will be replace to github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID}" || echo 'go get github.com/apache/dubbo-go@develop' From a192291b3691640981db29cdd6345d3793071279 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Mon, 7 Sep 2020 18:05:06 +0800 Subject: [PATCH 231/242] address batch mode notification --- registry/directory/directory.go | 175 ++++++++++++++++++++++---------- registry/protocol/protocol.go | 7 +- registry/registry.go | 2 +- 3 files changed, 129 insertions(+), 55 deletions(-) diff --git a/registry/directory/directory.go b/registry/directory/directory.go index 8940d2ab5e..79c90c1ceb 100644 --- a/registry/directory/directory.go +++ b/registry/directory/directory.go @@ -104,72 +104,129 @@ func (dir *RegistryDirectory) subscribe(url *common.URL) { } // Notify monitor changes from registry,and update the cacheServices -func (dir *RegistryDirectory) Notify(event *registry.ServiceEvent) { - go dir.update(event) +func (dir *RegistryDirectory) Notify(event ...*registry.ServiceEvent) { + go dir.update(event...) } // update the cacheServices and subscribe service from registry -func (dir *RegistryDirectory) update(res *registry.ServiceEvent) { - if res == nil { - return +func (dir *RegistryDirectory) update(events ...*registry.ServiceEvent) { + for _, event := range events { + logger.Debugf("registry update, result{%s}", event) + logger.Debugf("update service name: %s!", event.Service) } - logger.Debugf("registry update, result{%s}", res) - logger.Debugf("update service name: %s!", res.Service) - dir.refreshInvokers(res) + + dir.refreshInvokers(events...) } -func (dir *RegistryDirectory) refreshInvokers(res *registry.ServiceEvent) { - var ( - url *common.URL - oldInvoker protocol.Invoker = nil - ) - // judge is override or others - if res != nil { - url = &res.Service - // 1.for override url in 2.6.x - if url.Protocol == constant.OVERRIDE_PROTOCOL || - url.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY) == constant.CONFIGURATORS_CATEGORY { - dir.configurators = append(dir.configurators, extension.GetDefaultConfigurator(url)) - url = nil - } else if url.Protocol == constant.ROUTER_PROTOCOL || // 2.for router - url.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY) == constant.ROUTER_CATEGORY { - url = nil +// refreshInvokers refreshes service's events. It supports two modes: incremental mode and batch mode. If a single +// service event is passed in, then it is incremental mode, and if an array of service events are passed in, it is +// batch mode, in this mode, we assume the registry center have the complete list of the service events, therefore +// in this case, we can safely assume any cached invoker not in the incoming list can be removed. It is necessary +// since in batch mode, the register center handles the different type of events by itself, then notify the directory +// a batch of 'Update' events, instead of omit the different type of event one by one. +func (dir *RegistryDirectory) refreshInvokers(events ...*registry.ServiceEvent) { + var oldInvokers []protocol.Invoker + + // in batch mode, it is safe to remove since we have the complete list of events. + if events != nil && len(events) > 1 { + dir.cacheInvokersMap.Range(func(k, v interface{}) bool { + if !dir.eventMatched(k.(string), events) { + if invoker := dir.uncacheInvokerWithKey(k.(string)); invoker != nil { + oldInvokers = append(oldInvokers, invoker) + } + } + return true + }) + } + for _, event := range events { + if oldInvoker, _ := dir.cacheInvokerByEvent(event); oldInvoker != nil { + oldInvokers = append(oldInvokers, oldInvoker) } - switch res.Action { - case remoting.EventTypeAdd, remoting.EventTypeUpdate: - logger.Infof("selector add service url{%s}", res.Service) + } - var urls []*common.URL - for _, v := range config.GetRouterURLSet().Values() { - urls = append(urls, v.(*common.URL)) - } + if events != nil && len(events) > 0 { + dir.setNewInvokers() + } - if len(urls) > 0 { - dir.SetRouters(urls) - } - oldInvoker = dir.cacheInvoker(url) - case remoting.EventTypeDel: - oldInvoker = dir.uncacheInvoker(url) - logger.Infof("selector delete service url{%s}", res.Service) - default: - return + // After dir.cacheInvokers is updated,destroy the oldInvoker + // Ensure that no request will enter the oldInvoker + for _, invoker := range oldInvokers { + invoker.Destroy() + } +} + +// eventMatched checks if a cached invoker appears in the incoming invoker list, if no, then it is safe to remove. +func (dir *RegistryDirectory) eventMatched(key string, events []*registry.ServiceEvent) bool { + for _, event := range events { + if dir.invokerCacheKey(&event.Service) == key { + return true } } + return false +} +// invokerCacheKey generates the key in the cache for a given URL. +func (dir *RegistryDirectory) invokerCacheKey(url *common.URL) string { + referenceUrl := dir.GetDirectoryUrl().SubURL + newUrl := common.MergeUrl(url, referenceUrl) + return newUrl.Key() +} + +// setNewInvokers groups the invokers from the cache first, then set the result to both directory and router chain. +func (dir *RegistryDirectory) setNewInvokers() { newInvokers := dir.toGroupInvokers() dir.listenerLock.Lock() + defer dir.listenerLock.Unlock() dir.cacheInvokers = newInvokers - if res != nil { - dir.RouterChain().SetInvokers(newInvokers) + dir.RouterChain().SetInvokers(newInvokers) +} + +// cacheInvokerByEvent caches invokers from the service event +func (dir *RegistryDirectory) cacheInvokerByEvent(event *registry.ServiceEvent) (protocol.Invoker, error) { + // judge is override or others + if event != nil { + u := dir.convertUrl(event) + switch event.Action { + case remoting.EventTypeAdd, remoting.EventTypeUpdate: + logger.Infof("selector add service url{%s}", event.Service) + // FIXME: routers are built in every address notification? + dir.configRouters() + return dir.cacheInvoker(u), nil + case remoting.EventTypeDel: + logger.Infof("selector delete service url{%s}", event.Service) + return dir.uncacheInvoker(u), nil + default: + return nil, fmt.Errorf("illegal event type: %v", event.Action) + } } - dir.listenerLock.Unlock() - // After dir.cacheInvokers is updated,destroy the oldInvoker - // Ensure that no request will enter the oldInvoker - if oldInvoker != nil { - oldInvoker.Destroy() + return nil, nil +} + +// configRouters configures dynamic routers into the router chain, but, the current impl is incorrect, see FIXME above. +func (dir *RegistryDirectory) configRouters() { + var urls []*common.URL + for _, v := range config.GetRouterURLSet().Values() { + urls = append(urls, v.(*common.URL)) } + if len(urls) > 0 { + dir.SetRouters(urls) + } +} + +// convertUrl processes override:// and router:// +func (dir *RegistryDirectory) convertUrl(res *registry.ServiceEvent) *common.URL { + ret := &res.Service + if ret.Protocol == constant.OVERRIDE_PROTOCOL || // 1.for override url in 2.6.x + ret.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY) == constant.CONFIGURATORS_CATEGORY { + dir.configurators = append(dir.configurators, extension.GetDefaultConfigurator(ret)) + ret = nil + } else if ret.Protocol == constant.ROUTER_PROTOCOL || // 2.for router + ret.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY) == constant.ROUTER_CATEGORY { + ret = nil + } + return ret } func (dir *RegistryDirectory) toGroupInvokers() []protocol.Invoker { @@ -215,11 +272,15 @@ func (dir *RegistryDirectory) toGroupInvokers() []protocol.Invoker { return groupInvokersList } -// uncacheInvoker will return abandoned Invoker,if no Invoker to be abandoned,return nil +// uncacheInvoker will return abandoned Invoker, if no Invoker to be abandoned, return nil func (dir *RegistryDirectory) uncacheInvoker(url *common.URL) protocol.Invoker { - logger.Debugf("service will be deleted in cache invokers: invokers key is %s!", url.Key()) - if cacheInvoker, ok := dir.cacheInvokersMap.Load(url.Key()); ok { - dir.cacheInvokersMap.Delete(url.Key()) + return dir.uncacheInvokerWithKey(url.Key()) +} + +func (dir *RegistryDirectory) uncacheInvokerWithKey(key string) protocol.Invoker { + logger.Debugf("service will be deleted in cache invokers: invokers key is %s!", key) + if cacheInvoker, ok := dir.cacheInvokersMap.Load(key); ok { + dir.cacheInvokersMap.Delete(key) return cacheInvoker.(protocol.Invoker) } return nil @@ -250,6 +311,12 @@ func (dir *RegistryDirectory) cacheInvoker(url *common.URL) protocol.Invoker { dir.cacheInvokersMap.Store(newUrl.Key(), newInvoker) } } else { + // if cached invoker has the same URL with the new URL, then no need to re-refer, and no need to destroy + // the old invoker. + if common.IsEquals(*newUrl, cacheInvoker.(protocol.Invoker).GetUrl()) { + return nil + } + logger.Debugf("service will be updated in cache invokers: new invoker url is %s, old invoker url is %s", newUrl, cacheInvoker.(protocol.Invoker).GetUrl()) newInvoker := extension.GetProtocol(protocolwrapper.FILTER).Refer(*newUrl) if newInvoker != nil { @@ -348,7 +415,8 @@ func newReferenceConfigurationListener(dir *RegistryDirectory, url *common.URL) // Process handle events and update Invokers func (l *referenceConfigurationListener) Process(event *config_center.ConfigChangeEvent) { l.BaseConfigurationListener.Process(event) - l.directory.refreshInvokers(nil) + // FIXME: this doesn't trigger dir.overrideUrl() + l.directory.refreshInvokers() } type consumerConfigurationListener struct { @@ -374,5 +442,6 @@ func (l *consumerConfigurationListener) addNotifyListener(listener registry.Noti // Process handles events from Configuration Center and update Invokers func (l *consumerConfigurationListener) Process(event *config_center.ConfigChangeEvent) { l.BaseConfigurationListener.Process(event) - l.directory.refreshInvokers(nil) + // FIXME: this doesn't trigger dir.overrideUrl() + l.directory.refreshInvokers() } diff --git a/registry/protocol/protocol.go b/registry/protocol/protocol.go index 5b2a5d66f6..00283a117c 100644 --- a/registry/protocol/protocol.go +++ b/registry/protocol/protocol.go @@ -242,7 +242,12 @@ func newOverrideSubscribeListener(overriderUrl *common.URL, invoker protocol.Inv } // Notify will be triggered when a service change notification is received. -func (nl *overrideSubscribeListener) Notify(event *registry.ServiceEvent) { +func (nl *overrideSubscribeListener) Notify(events ...*registry.ServiceEvent) { + if events == nil || len(events) == 0 { + return + } + + event := events[0] if isMatched(&(event.Service), nl.url) && event.Action == remoting.EventTypeAdd { nl.configurator = extension.GetDefaultConfigurator(&(event.Service)) nl.doOverrideIfNecessary() diff --git a/registry/registry.go b/registry/registry.go index 855b487d47..e9210604bb 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -69,7 +69,7 @@ type Registry interface { // nolint type NotifyListener interface { // Notify supports notifications on the service interface and the dimension of the data type. - Notify(*ServiceEvent) + Notify(...*ServiceEvent) } // Listener Deprecated! From 27a8dc733c480b14c20556528bad00c141ad035e Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Mon, 7 Sep 2020 18:14:26 +0800 Subject: [PATCH 232/242] Fix: integration testing problems --- test/integrate/dubbo/go-client/Dockerfile | 2 +- test/integrate/dubbo/go-server/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integrate/dubbo/go-client/Dockerfile b/test/integrate/dubbo/go-client/Dockerfile index 45bcae9ad6..d452469c6d 100644 --- a/test/integrate/dubbo/go-client/Dockerfile +++ b/test/integrate/dubbo/go-client/Dockerfile @@ -26,7 +26,7 @@ ARG PR_ORIGIN_REPO ARG PR_ORIGIN_COMMITID ADD . /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-client -ADD go.main.mod /go/src/github.com/apache/dubbo-go/go.mod +ADD /go/src/github.com/apache/dubbo-go/go.mod /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-client/go.main.mod # update dubbo-go to current commit id RUN test ${PR_ORIGIN_REPO} && echo "github.com/apache/dubbo-go will be replace to github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID}" || echo 'go get github.com/apache/dubbo-go@develop' diff --git a/test/integrate/dubbo/go-server/Dockerfile b/test/integrate/dubbo/go-server/Dockerfile index b2ce6bdaa5..6c2d19209e 100644 --- a/test/integrate/dubbo/go-server/Dockerfile +++ b/test/integrate/dubbo/go-server/Dockerfile @@ -26,7 +26,7 @@ ARG PR_ORIGIN_REPO ARG PR_ORIGIN_COMMITID ADD . /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-server -ADD go.main.mod /go/src/github.com/apache/dubbo-go/go.mod +ADD /go/src/github.com/apache/dubbo-go/go.mod /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-server/go.main.mod # update dubbo-go to current commit id RUN test ${PR_ORIGIN_REPO} && echo "github.com/apache/dubbo-go will be replace to github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID}" || echo 'go get github.com/apache/dubbo-go@develop' From c3adeae12d812aea6653a250c681ab38235b3928 Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Mon, 7 Sep 2020 18:38:15 +0800 Subject: [PATCH 233/242] Fix: integration testing problems --- integrate_test.sh | 4 ++++ test/integrate/dubbo/go-client/Dockerfile | 1 - test/integrate/dubbo/go-server/Dockerfile | 1 - 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/integrate_test.sh b/integrate_test.sh index c9c2f23b5b..c54172ec5e 100644 --- a/integrate_test.sh +++ b/integrate_test.sh @@ -52,12 +52,16 @@ docker run -d --network host zookeeper echo "zookeeper listen in [:]2181" # build go-server image +# copy main go.mod to test dir +cp go.mod ./test/integrate/dubbo/go-server/go.main.mod cd ./test/integrate/dubbo/go-server docker build . -t ci-provider --build-arg PR_ORIGIN_REPO=${TRAVIS_PULL_REQUEST_SLUG} --build-arg PR_ORIGIN_COMMITID=${TRAVIS_PULL_REQUEST_SHA} cd ${ROOT_DIR} docker run -d --network host ci-provider # build go-client image +# copy main go.mod to test dir +cp go.mod ./test/integrate/dubbo/go-client/go.main.mod cd ./test/integrate/dubbo/go-client docker build . -t ci-consumer --build-arg PR_ORIGIN_REPO=${TRAVIS_PULL_REQUEST_SLUG} --build-arg PR_ORIGIN_COMMITID=${TRAVIS_PULL_REQUEST_SHA} cd ${ROOT_DIR} diff --git a/test/integrate/dubbo/go-client/Dockerfile b/test/integrate/dubbo/go-client/Dockerfile index d452469c6d..ab5bcd5037 100644 --- a/test/integrate/dubbo/go-client/Dockerfile +++ b/test/integrate/dubbo/go-client/Dockerfile @@ -26,7 +26,6 @@ ARG PR_ORIGIN_REPO ARG PR_ORIGIN_COMMITID ADD . /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-client -ADD /go/src/github.com/apache/dubbo-go/go.mod /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-client/go.main.mod # update dubbo-go to current commit id RUN test ${PR_ORIGIN_REPO} && echo "github.com/apache/dubbo-go will be replace to github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID}" || echo 'go get github.com/apache/dubbo-go@develop' diff --git a/test/integrate/dubbo/go-server/Dockerfile b/test/integrate/dubbo/go-server/Dockerfile index 6c2d19209e..39d7481bfb 100644 --- a/test/integrate/dubbo/go-server/Dockerfile +++ b/test/integrate/dubbo/go-server/Dockerfile @@ -26,7 +26,6 @@ ARG PR_ORIGIN_REPO ARG PR_ORIGIN_COMMITID ADD . /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-server -ADD /go/src/github.com/apache/dubbo-go/go.mod /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-server/go.main.mod # update dubbo-go to current commit id RUN test ${PR_ORIGIN_REPO} && echo "github.com/apache/dubbo-go will be replace to github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID}" || echo 'go get github.com/apache/dubbo-go@develop' From a15d6afb50d35e860d1d06bd3aadb5e1c927047e Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Mon, 7 Sep 2020 18:51:17 +0800 Subject: [PATCH 234/242] Fix: integration testing problems --- test/integrate/dubbo/go-server/go.sum | 687 ++++++++++++++++++++++++++ 1 file changed, 687 insertions(+) create mode 100644 test/integrate/dubbo/go-server/go.sum diff --git a/test/integrate/dubbo/go-server/go.sum b/test/integrate/dubbo/go-server/go.sum new file mode 100644 index 0000000000..a4ded6c5a4 --- /dev/null +++ b/test/integrate/dubbo/go-server/go.sum @@ -0,0 +1,687 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-sdk-for-go v40.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0= +github.com/Azure/go-autorest/autorest v0.10.0/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= +github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM= +github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= +github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/Microsoft/go-winio v0.4.3/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/go-winio v0.4.13/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/NYTimes/gziphandler v1.0.1/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/RoaringBitmap/roaring v0.4.23 h1:gpyfd12QohbqhFO4NVDUdoPOCXsyahYRQhINmlHxKeo= +github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/Workiva/go-datastructures v1.0.50 h1:slDmfW6KCHcC7U+LP3DDBbm4fqTwZGn1beOFPfGaLvo= +github.com/Workiva/go-datastructures v1.0.50/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA= +github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vajsk51NtYIcwSTkXr+JGrMd36kTDJw= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk= +github.com/apache/dubbo-getty v1.3.10 h1:ys5mwjPdxG/KwkPjS6EI0RzQtU6p6FCPoKpaFEzpAL0= +github.com/apache/dubbo-getty v1.3.10/go.mod h1:x6rraK01BL5C7jUM2fPl5KMkAxLVIx54ZB8/XEOik9Y= +github.com/apache/dubbo-go-hessian2 v1.6.0-rc1.0.20200906044240-6c1fb5c3bd44/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= +github.com/apache/dubbo-go-hessian2 v1.6.3 h1:HshZqqo2foC9OaFbTdLOvhTaf95uF3UL6x3UaJaLww8= +github.com/apache/dubbo-go-hessian2 v1.6.3/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= +github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aws/aws-sdk-go v1.25.41/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coredns/coredns v1.1.2/go.mod h1:zASH/MVDgR6XZTbxvOnsZfffS+31vg6Ackf/wo1+AM0= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/creasty/defaults v1.3.0 h1:uG+RAxYbJgOPCOdKEcec9ZJXeva7Y6mj/8egdzwmLtw= +github.com/creasty/defaults v1.3.0/go.mod h1:CIEEvs7oIVZm30R8VxtFJs+4k201gReYyuYHJxZc68I= +github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/digitalocean/godo v1.1.1/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= +github.com/digitalocean/godo v1.10.0/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= +github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/dubbogo/go-zookeeper v1.0.1 h1:irLzvOsDOTNsN8Sv9tvYYxVu6DCQfLtziZQtUHmZgz8= +github.com/dubbogo/go-zookeeper v1.0.1/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c= +github.com/dubbogo/gost v1.9.0/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= +github.com/dubbogo/gost v1.9.1 h1:0/PPFo13zPbjt4Ia0zYWMFi3C6rAe9X7O1J2Iv+BHNM= +github.com/dubbogo/gost v1.9.1/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= +github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful/v3 v3.0.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.8.0/go.mod h1:GSSbY9P1neVhdY7G4wu+IK1rk/dqhiCC/4ExuWJZVuk= +github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/frankban/quicktest v1.4.1/go.mod h1:36zfPVQyHxymz4cH7wlDmVwDrJuljRB60qkgn7rorfQ= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= +github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 h1:Ujru1hufTHVb++eG6OuNDKMxZnGIvF6o/u8q/8h2+I4= +github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= +github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= +github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-check/check v0.0.0-20140225173054-eb6ee6f84d0a/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-co-op/gocron v0.1.1/go.mod h1:Y9PWlYqDChf2Nbgg7kfS+ZsXHDTZbMZYPEQ0MILqH+M= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= +github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-redis/redis v6.15.5+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-resty/resty/v2 v2.1.0/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2/go.mod h1:DavVbd41y+b7ukKDmlnPR4nGYmkWXR6vHUkjQNiHPBs= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= +github.com/hashicorp/consul v1.8.0/go.mod h1:Gg9/UgAQ9rdY3CTvzQZ6g2jcIb7NlIfjI+0pvLk5D1A= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/api v1.5.0/go.mod h1:LqwrLNW876eYSuUOo4ZLHBcdKc038txr/IMfbLPATa4= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.5.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-bexpr v0.1.2/go.mod h1:ANbpTX1oAql27TZkKVeW8p1w8NTdnyzPe/0qqPCKohU= +github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de/go.mod h1:xIwEieBHERyEvaeKF/TcHh1Hu+lxPM+n2vT1+g9I4m4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-connlimit v0.2.0/go.mod h1:OUj9FGL1tPIhl/2RCfzYHrIiWj+VVPGNyVPnUX8AqS0= +github.com/hashicorp/go-discover v0.0.0-20200501174627-ad1e96bde088/go.mod h1:vZu6Opqf49xX5lsFAu7iFNewkcVF1sn/wyapZh5ytlg= +github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= +github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-memdb v1.0.3/go.mod h1:LWQ8R70vPrS4OEY9k28D2z8/Zzyu34NVzeRibGAzHO0= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= +github.com/hashicorp/go-raftchunking v0.6.1/go.mod h1:cGlg3JtDy7qy6c/3Bu660Mic1JF+7lWqIwCFSb08fX0= +github.com/hashicorp/go-raftchunking v0.6.3-0.20191002164813-7e9e8525653a/go.mod h1:xbXnmKqX9/+RhPkJ4zrEx4738HacP72aaUPlT2RZ4sU= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-retryablehttp v0.6.2/go.mod h1:gEx6HMUGxYYhJScX7W1Il64m6cc2C1mDaW3NQ9sY1FY= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2-0.20191001231223-f32f5fe8d6a8/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/hil v0.0.0-20160711231837-1e86c6b523c5/go.mod h1:KHvg/R2/dPtaePb16oW4qIyzkMxXOL38xjRN64adsts= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/memberlist v0.2.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/net-rpc-msgpackrpc v0.0.0-20151116020338-a14192a58a69/go.mod h1:/z+jUGRBlwVpUZfjute9jWaF6/HuhjuFQuL1YXzVD1Q= +github.com/hashicorp/raft v1.1.1/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= +github.com/hashicorp/raft v1.1.2-0.20191002163536-9c6bd3e3eb17/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= +github.com/hashicorp/raft v1.1.2/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= +github.com/hashicorp/raft-boltdb v0.0.0-20171010151810-6e5ba93211ea/go.mod h1:pNv7Wc3ycL6F5oOWn+tPGo2gWD4a5X+yp/ntwdKLjRk= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/serf v0.9.0/go.mod h1:YL0HO+FifKOW2u1ke99DGVu1zhcpZzNwrLIqBC7vbYU= +github.com/hashicorp/serf v0.9.2/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= +github.com/hashicorp/vault/api v1.0.5-0.20191108163347-bdd38fca2cff/go.mod h1:Uf8LaHyrYsgVgHzO2tMZKhqRGlL3UJ6XaSwW2EA1Iqo= +github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= +github.com/hashicorp/vault/sdk v0.1.14-0.20191108161836-82f2b5571044/go.mod h1:PcekaFGiPJyHnFy+NZhP6ll650zEw51Ag7g/YEa+EOU= +github.com/hashicorp/vault/sdk v0.1.14-0.20191112033314-390e96e22eb2/go.mod h1:PcekaFGiPJyHnFy+NZhP6ll650zEw51Ag7g/YEa+EOU= +github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443/go.mod h1:bEpDU35nTu0ey1EXjwNwPjI9xErAsoOCmcMb9GKvyxo= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= +github.com/jackc/pgx v3.3.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= +github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag= +github.com/jinzhu/copier v0.0.0-20190625015134-976e0346caa8 h1:mGIXW/lubQ4B+3bXTLxcTMTjUNDqoF6T/HUW9LbFx9s= +github.com/jinzhu/copier v0.0.0-20190625015134-976e0346caa8/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA= +github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f/go.mod h1:KDSfL7qe5ZfQqvlDMkVjCztbmcpp/c8M77vhQP8ZPvk= +github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570/go.mod h1:BLt8L9ld7wVsvEWQbuLrUZnCMnUmLZ+CGDzKtclrTlE= +github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f/go.mod h1:UGmTpUd3rjbtfIpwAPrcfmGf/Z1HS95TATB+m57TPB8= +github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042/go.mod h1:TPpsiPUEh0zFL1Snz4crhMlBe60PYxRHr5oFF3rRYg0= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/linode/linodego v0.7.1/go.mod h1:ga11n3ivecUrPCHN0rANxKmfWBJVkOXfLMZinAbj2sY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.14.0/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= +github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.2.3 h1:f/MjBEBDLttYCGfRaKBbKSRVF5aV2O6fnBpzknuE3jU= +github.com/mitchellh/mapstructure v1.2.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/pointerstructure v1.0.0/go.mod h1:k4XwG94++jLVsSiTxo7qdIfXA9pj9EAeo0QsNNJOLZ8= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nacos-group/nacos-sdk-go v1.0.0/go.mod h1:hlAPn3UdzlxIlSILAyOXKxjFSvDJ9oLzTJ9hLAK1KzA= +github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20180130162743-b8a9be070da4/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c/go.mod h1:otzZQXgoO96RTzDB/Hycg0qZcXZsWJGJRSXbmEIJ+4M= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= +github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4 v2.2.6+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rboyer/safeio v0.2.1/go.mod h1:Cq/cEPK+YXFn622lsQ0K4KsPZSPtaptHHEldsy7Fmig= +github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03/go.mod h1:gRAiPF5C5Nd0eyyRdqIu9qTiFSoZzpTq727b5B8fkkU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/zerolog v1.4.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= +github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/conswriter v0.0.0-20180208195008-f5ae3917a627/go.mod h1:7zjs06qF79/FKAJpBvFx3P8Ww4UTIMAe+lpNXDHziac= +github.com/sean-/pager v0.0.0-20180208200047-666be9bf53b5/go.mod h1:BeybITEsBEg6qbIiqJ6/Bqeq25bCLbL7YFmpaFfJDuM= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shirou/gopsutil v0.0.0-20181107111621-48177ef5f880/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil v2.19.9+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d/go.mod h1:Cw4GTlQccdRGSEf6KiMju767x0NEHE0YIVPJSaXjlsw= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIUJUJH6XQ= +github.com/tencentcloud/tencentcloud-sdk-go v3.0.83+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4= +github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9/go.mod h1:RHkNRtSLfOK7qBTHaeSX1D6BNpI3qw7NTxsmNr4RvN8= +github.com/tevid/gohamcrest v1.1.1/go.mod h1:3UvtWlqm8j5JbwYZh80D/PVBt0mJ1eJiYgZMibh0H/k= +github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU= +github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3/go.mod h1:QDlpd3qS71vYtakd2hmdpqhJ9nwv6mD6A30bQ1BPBFE= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= +github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/zouyx/agollo/v3 v3.4.4/go.mod h1:ag0XmE1r4iAgPd6PUnU9TJ0DMEjM1VKX1HUNqQJ2ywU= +github.com/zouyx/dubbo-go v1.0.1-0.20200907061547-94fe6c650944 h1:jofH2k/XoqKS2shPpSD77+4zLzDPZyD6Z6t7LN1/hYw= +github.com/zouyx/dubbo-go v1.0.1-0.20200907061547-94fe6c650944/go.mod h1:p+G854KEVwsBrDEasxKBEpICEzSttSo7zjNTXoocSqg= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= +go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190508220229-2d0786266e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +istio.io/gogo-genproto v0.0.0-20190124151557-6d926a6e6feb/go.mod h1:eIDJ6jNk/IeJz6ODSksHl5Aiczy5JUq6vFhJWI5OtiI= +k8s.io/api v0.16.9/go.mod h1:Y7dZNHs1Xy0mSwSlzL9QShi6qkljnN41yR8oWCRTDe8= +k8s.io/apimachinery v0.16.9/go.mod h1:Xk2vD2TRRpuWYLQNM6lT9R7DSFZUYG03SarNkbGrnKE= +k8s.io/client-go v0.16.9/go.mod h1:ThjPlh7Kx+XoBFOCt775vx5J7atwY7F/zaFzTco5gL0= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= From 04b35ea319b1f46ece1133e68da2167ec73454d0 Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Mon, 7 Sep 2020 18:51:32 +0800 Subject: [PATCH 235/242] Fix: integration testing problems --- test/integrate/dubbo/go-client/Dockerfile | 1 + test/integrate/dubbo/go-server/Dockerfile | 1 + 2 files changed, 2 insertions(+) diff --git a/test/integrate/dubbo/go-client/Dockerfile b/test/integrate/dubbo/go-client/Dockerfile index ab5bcd5037..0c614eed6b 100644 --- a/test/integrate/dubbo/go-client/Dockerfile +++ b/test/integrate/dubbo/go-client/Dockerfile @@ -29,6 +29,7 @@ ADD . /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-client # update dubbo-go to current commit id RUN test ${PR_ORIGIN_REPO} && echo "github.com/apache/dubbo-go will be replace to github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID}" || echo 'go get github.com/apache/dubbo-go@develop' +RUN test ${PR_ORIGIN_REPO} && echo "module github.com/apache/dubbo-go/test/integrate/dubbo/go-client" > go.mod RUN test ${PR_ORIGIN_REPO} && sed -n '2,$p' go.main.mod >> go.mod RUN test ${PR_ORIGIN_REPO} && go mod edit -replace=github.com/apache/dubbo-go=github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID} || go mod vendor diff --git a/test/integrate/dubbo/go-server/Dockerfile b/test/integrate/dubbo/go-server/Dockerfile index 39d7481bfb..1d6ea286c7 100644 --- a/test/integrate/dubbo/go-server/Dockerfile +++ b/test/integrate/dubbo/go-server/Dockerfile @@ -29,6 +29,7 @@ ADD . /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-server # update dubbo-go to current commit id RUN test ${PR_ORIGIN_REPO} && echo "github.com/apache/dubbo-go will be replace to github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID}" || echo 'go get github.com/apache/dubbo-go@develop' +RUN test ${PR_ORIGIN_REPO} && echo "module github.com/apache/dubbo-go/test/integrate/dubbo/go-server" > go.mod RUN test ${PR_ORIGIN_REPO} && sed -n '2,$p' go.main.mod >> go.mod RUN test ${PR_ORIGIN_REPO} && go mod edit -replace=github.com/apache/dubbo-go=github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID} || go mod vendor From a76745f75b53a167a005a613ecc2e055ac7355d5 Mon Sep 17 00:00:00 2001 From: "beiwei.ly" Date: Mon, 7 Sep 2020 19:53:55 +0800 Subject: [PATCH 236/242] fix review comment --- registry/directory/directory.go | 4 ++-- registry/protocol/protocol.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/registry/directory/directory.go b/registry/directory/directory.go index 79c90c1ceb..960855bc3c 100644 --- a/registry/directory/directory.go +++ b/registry/directory/directory.go @@ -128,7 +128,7 @@ func (dir *RegistryDirectory) refreshInvokers(events ...*registry.ServiceEvent) var oldInvokers []protocol.Invoker // in batch mode, it is safe to remove since we have the complete list of events. - if events != nil && len(events) > 1 { + if len(events) > 1 { dir.cacheInvokersMap.Range(func(k, v interface{}) bool { if !dir.eventMatched(k.(string), events) { if invoker := dir.uncacheInvokerWithKey(k.(string)); invoker != nil { @@ -145,7 +145,7 @@ func (dir *RegistryDirectory) refreshInvokers(events ...*registry.ServiceEvent) } } - if events != nil && len(events) > 0 { + if len(events) > 0 { dir.setNewInvokers() } diff --git a/registry/protocol/protocol.go b/registry/protocol/protocol.go index 00283a117c..d313e482a7 100644 --- a/registry/protocol/protocol.go +++ b/registry/protocol/protocol.go @@ -243,7 +243,7 @@ func newOverrideSubscribeListener(overriderUrl *common.URL, invoker protocol.Inv // Notify will be triggered when a service change notification is received. func (nl *overrideSubscribeListener) Notify(events ...*registry.ServiceEvent) { - if events == nil || len(events) == 0 { + if len(events) == 0 { return } From 9f50dc26d9e19213c41f789cb76d36810215a5b0 Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Mon, 7 Sep 2020 23:13:33 +0800 Subject: [PATCH 237/242] delete useless file --- test/integrate/dubbo/go-server/go.sum | 687 -------------------------- 1 file changed, 687 deletions(-) delete mode 100644 test/integrate/dubbo/go-server/go.sum diff --git a/test/integrate/dubbo/go-server/go.sum b/test/integrate/dubbo/go-server/go.sum deleted file mode 100644 index a4ded6c5a4..0000000000 --- a/test/integrate/dubbo/go-server/go.sum +++ /dev/null @@ -1,687 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/azure-sdk-for-go v40.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0= -github.com/Azure/go-autorest/autorest v0.10.0/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= -github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= -github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= -github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= -github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM= -github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw= -github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= -github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= -github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= -github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= -github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/Microsoft/go-winio v0.4.3/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= -github.com/Microsoft/go-winio v0.4.13/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/NYTimes/gziphandler v1.0.1/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/RoaringBitmap/roaring v0.4.23 h1:gpyfd12QohbqhFO4NVDUdoPOCXsyahYRQhINmlHxKeo= -github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/Workiva/go-datastructures v1.0.50 h1:slDmfW6KCHcC7U+LP3DDBbm4fqTwZGn1beOFPfGaLvo= -github.com/Workiva/go-datastructures v1.0.50/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA= -github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= -github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vajsk51NtYIcwSTkXr+JGrMd36kTDJw= -github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk= -github.com/apache/dubbo-getty v1.3.10 h1:ys5mwjPdxG/KwkPjS6EI0RzQtU6p6FCPoKpaFEzpAL0= -github.com/apache/dubbo-getty v1.3.10/go.mod h1:x6rraK01BL5C7jUM2fPl5KMkAxLVIx54ZB8/XEOik9Y= -github.com/apache/dubbo-go-hessian2 v1.6.0-rc1.0.20200906044240-6c1fb5c3bd44/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= -github.com/apache/dubbo-go-hessian2 v1.6.3 h1:HshZqqo2foC9OaFbTdLOvhTaf95uF3UL6x3UaJaLww8= -github.com/apache/dubbo-go-hessian2 v1.6.3/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= -github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go v1.25.41/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= -github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= -github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/coredns/coredns v1.1.2/go.mod h1:zASH/MVDgR6XZTbxvOnsZfffS+31vg6Ackf/wo1+AM0= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/creasty/defaults v1.3.0 h1:uG+RAxYbJgOPCOdKEcec9ZJXeva7Y6mj/8egdzwmLtw= -github.com/creasty/defaults v1.3.0/go.mod h1:CIEEvs7oIVZm30R8VxtFJs+4k201gReYyuYHJxZc68I= -github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/digitalocean/godo v1.1.1/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= -github.com/digitalocean/godo v1.10.0/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= -github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= -github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/dubbogo/go-zookeeper v1.0.1 h1:irLzvOsDOTNsN8Sv9tvYYxVu6DCQfLtziZQtUHmZgz8= -github.com/dubbogo/go-zookeeper v1.0.1/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c= -github.com/dubbogo/gost v1.9.0/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= -github.com/dubbogo/gost v1.9.1 h1:0/PPFo13zPbjt4Ia0zYWMFi3C6rAe9X7O1J2Iv+BHNM= -github.com/dubbogo/gost v1.9.1/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= -github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= -github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful/v3 v3.0.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/envoyproxy/go-control-plane v0.8.0/go.mod h1:GSSbY9P1neVhdY7G4wu+IK1rk/dqhiCC/4ExuWJZVuk= -github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/frankban/quicktest v1.4.1/go.mod h1:36zfPVQyHxymz4cH7wlDmVwDrJuljRB60qkgn7rorfQ= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= -github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 h1:Ujru1hufTHVb++eG6OuNDKMxZnGIvF6o/u8q/8h2+I4= -github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= -github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= -github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= -github.com/go-check/check v0.0.0-20140225173054-eb6ee6f84d0a/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= -github.com/go-co-op/gocron v0.1.1/go.mod h1:Y9PWlYqDChf2Nbgg7kfS+ZsXHDTZbMZYPEQ0MILqH+M= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= -github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= -github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= -github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= -github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= -github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= -github.com/go-redis/redis v6.15.5+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= -github.com/go-resty/resty/v2 v2.1.0/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2/go.mod h1:DavVbd41y+b7ukKDmlnPR4nGYmkWXR6vHUkjQNiHPBs= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= -github.com/hashicorp/consul v1.8.0/go.mod h1:Gg9/UgAQ9rdY3CTvzQZ6g2jcIb7NlIfjI+0pvLk5D1A= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.5.0/go.mod h1:LqwrLNW876eYSuUOo4ZLHBcdKc038txr/IMfbLPATa4= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/consul/sdk v0.5.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-bexpr v0.1.2/go.mod h1:ANbpTX1oAql27TZkKVeW8p1w8NTdnyzPe/0qqPCKohU= -github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de/go.mod h1:xIwEieBHERyEvaeKF/TcHh1Hu+lxPM+n2vT1+g9I4m4= -github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-connlimit v0.2.0/go.mod h1:OUj9FGL1tPIhl/2RCfzYHrIiWj+VVPGNyVPnUX8AqS0= -github.com/hashicorp/go-discover v0.0.0-20200501174627-ad1e96bde088/go.mod h1:vZu6Opqf49xX5lsFAu7iFNewkcVF1sn/wyapZh5ytlg= -github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= -github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-memdb v1.0.3/go.mod h1:LWQ8R70vPrS4OEY9k28D2z8/Zzyu34NVzeRibGAzHO0= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= -github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= -github.com/hashicorp/go-raftchunking v0.6.1/go.mod h1:cGlg3JtDy7qy6c/3Bu660Mic1JF+7lWqIwCFSb08fX0= -github.com/hashicorp/go-raftchunking v0.6.3-0.20191002164813-7e9e8525653a/go.mod h1:xbXnmKqX9/+RhPkJ4zrEx4738HacP72aaUPlT2RZ4sU= -github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-retryablehttp v0.6.2/go.mod h1:gEx6HMUGxYYhJScX7W1Il64m6cc2C1mDaW3NQ9sY1FY= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.2-0.20191001231223-f32f5fe8d6a8/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/hil v0.0.0-20160711231837-1e86c6b523c5/go.mod h1:KHvg/R2/dPtaePb16oW4qIyzkMxXOL38xjRN64adsts= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/memberlist v0.2.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/net-rpc-msgpackrpc v0.0.0-20151116020338-a14192a58a69/go.mod h1:/z+jUGRBlwVpUZfjute9jWaF6/HuhjuFQuL1YXzVD1Q= -github.com/hashicorp/raft v1.1.1/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= -github.com/hashicorp/raft v1.1.2-0.20191002163536-9c6bd3e3eb17/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= -github.com/hashicorp/raft v1.1.2/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= -github.com/hashicorp/raft-boltdb v0.0.0-20171010151810-6e5ba93211ea/go.mod h1:pNv7Wc3ycL6F5oOWn+tPGo2gWD4a5X+yp/ntwdKLjRk= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/serf v0.9.0/go.mod h1:YL0HO+FifKOW2u1ke99DGVu1zhcpZzNwrLIqBC7vbYU= -github.com/hashicorp/serf v0.9.2/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= -github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= -github.com/hashicorp/vault/api v1.0.5-0.20191108163347-bdd38fca2cff/go.mod h1:Uf8LaHyrYsgVgHzO2tMZKhqRGlL3UJ6XaSwW2EA1Iqo= -github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= -github.com/hashicorp/vault/sdk v0.1.14-0.20191108161836-82f2b5571044/go.mod h1:PcekaFGiPJyHnFy+NZhP6ll650zEw51Ag7g/YEa+EOU= -github.com/hashicorp/vault/sdk v0.1.14-0.20191112033314-390e96e22eb2/go.mod h1:PcekaFGiPJyHnFy+NZhP6ll650zEw51Ag7g/YEa+EOU= -github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443/go.mod h1:bEpDU35nTu0ey1EXjwNwPjI9xErAsoOCmcMb9GKvyxo= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= -github.com/jackc/pgx v3.3.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= -github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= -github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag= -github.com/jinzhu/copier v0.0.0-20190625015134-976e0346caa8 h1:mGIXW/lubQ4B+3bXTLxcTMTjUNDqoF6T/HUW9LbFx9s= -github.com/jinzhu/copier v0.0.0-20190625015134-976e0346caa8/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA= -github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f/go.mod h1:KDSfL7qe5ZfQqvlDMkVjCztbmcpp/c8M77vhQP8ZPvk= -github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570/go.mod h1:BLt8L9ld7wVsvEWQbuLrUZnCMnUmLZ+CGDzKtclrTlE= -github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f/go.mod h1:UGmTpUd3rjbtfIpwAPrcfmGf/Z1HS95TATB+m57TPB8= -github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042/go.mod h1:TPpsiPUEh0zFL1Snz4crhMlBe60PYxRHr5oFF3rRYg0= -github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/linode/linodego v0.7.1/go.mod h1:ga11n3ivecUrPCHN0rANxKmfWBJVkOXfLMZinAbj2sY= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-testing-interface v1.14.0/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= -github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.2.3 h1:f/MjBEBDLttYCGfRaKBbKSRVF5aV2O6fnBpzknuE3jU= -github.com/mitchellh/mapstructure v1.2.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/pointerstructure v1.0.0/go.mod h1:k4XwG94++jLVsSiTxo7qdIfXA9pj9EAeo0QsNNJOLZ8= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/nacos-group/nacos-sdk-go v1.0.0/go.mod h1:hlAPn3UdzlxIlSILAyOXKxjFSvDJ9oLzTJ9hLAK1KzA= -github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.0-20180130162743-b8a9be070da4/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c/go.mod h1:otzZQXgoO96RTzDB/Hycg0qZcXZsWJGJRSXbmEIJ+4M= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= -github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pierrec/lz4 v2.2.6+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= -github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rboyer/safeio v0.2.1/go.mod h1:Cq/cEPK+YXFn622lsQ0K4KsPZSPtaptHHEldsy7Fmig= -github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03/go.mod h1:gRAiPF5C5Nd0eyyRdqIu9qTiFSoZzpTq727b5B8fkkU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rs/zerolog v1.4.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= -github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/sean-/conswriter v0.0.0-20180208195008-f5ae3917a627/go.mod h1:7zjs06qF79/FKAJpBvFx3P8Ww4UTIMAe+lpNXDHziac= -github.com/sean-/pager v0.0.0-20180208200047-666be9bf53b5/go.mod h1:BeybITEsBEg6qbIiqJ6/Bqeq25bCLbL7YFmpaFfJDuM= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shirou/gopsutil v0.0.0-20181107111621-48177ef5f880/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shirou/gopsutil v2.19.9+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d/go.mod h1:Cw4GTlQccdRGSEf6KiMju767x0NEHE0YIVPJSaXjlsw= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIUJUJH6XQ= -github.com/tencentcloud/tencentcloud-sdk-go v3.0.83+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4= -github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9/go.mod h1:RHkNRtSLfOK7qBTHaeSX1D6BNpI3qw7NTxsmNr4RvN8= -github.com/tevid/gohamcrest v1.1.1/go.mod h1:3UvtWlqm8j5JbwYZh80D/PVBt0mJ1eJiYgZMibh0H/k= -github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU= -github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3/go.mod h1:QDlpd3qS71vYtakd2hmdpqhJ9nwv6mD6A30bQ1BPBFE= -github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= -github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/zouyx/agollo/v3 v3.4.4/go.mod h1:ag0XmE1r4iAgPd6PUnU9TJ0DMEjM1VKX1HUNqQJ2ywU= -github.com/zouyx/dubbo-go v1.0.1-0.20200907061547-94fe6c650944 h1:jofH2k/XoqKS2shPpSD77+4zLzDPZyD6Z6t7LN1/hYw= -github.com/zouyx/dubbo-go v1.0.1-0.20200907061547-94fe6c650944/go.mod h1:p+G854KEVwsBrDEasxKBEpICEzSttSo7zjNTXoocSqg= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= -go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= -golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190508220229-2d0786266e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -istio.io/gogo-genproto v0.0.0-20190124151557-6d926a6e6feb/go.mod h1:eIDJ6jNk/IeJz6ODSksHl5Aiczy5JUq6vFhJWI5OtiI= -k8s.io/api v0.16.9/go.mod h1:Y7dZNHs1Xy0mSwSlzL9QShi6qkljnN41yR8oWCRTDe8= -k8s.io/apimachinery v0.16.9/go.mod h1:Xk2vD2TRRpuWYLQNM6lT9R7DSFZUYG03SarNkbGrnKE= -k8s.io/client-go v0.16.9/go.mod h1:ThjPlh7Kx+XoBFOCt775vx5J7atwY7F/zaFzTco5gL0= -k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= -k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= -k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= -k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= From a87b814d9bd1910676aed1a716951951ed0f8a60 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Tue, 8 Sep 2020 10:23:55 +0800 Subject: [PATCH 238/242] enhance comments --- registry/registry.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/registry/registry.go b/registry/registry.go index e9210604bb..2225d2c1fc 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -68,7 +68,10 @@ type Registry interface { // nolint type NotifyListener interface { - // Notify supports notifications on the service interface and the dimension of the data type. + // Notify supports notifications on the service interface and the dimension of the data type. When a list of + // events are passed in, it's considered as a complete list, on the other side, if one single event is + // passed in, then it's a incremental event. Pls. note when a list (instead of single event) comes, + // the impl of NotifyListener may abandon the accumulated result from previous notifications. Notify(...*ServiceEvent) } From f146507ff7eda61fd3cbcfdcea9a9bb15f4e6294 Mon Sep 17 00:00:00 2001 From: Joe Zou Date: Tue, 8 Sep 2020 11:23:51 +0800 Subject: [PATCH 239/242] Revert "Fix: integration testing problems" --- integrate_test.sh | 4 ---- test/integrate/dubbo/go-client/Dockerfile | 4 +--- test/integrate/dubbo/go-client/go.mod | 4 ++++ test/integrate/dubbo/go-server/Dockerfile | 5 +---- test/integrate/dubbo/go-server/go.mod | 4 ++++ 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/integrate_test.sh b/integrate_test.sh index c54172ec5e..c9c2f23b5b 100644 --- a/integrate_test.sh +++ b/integrate_test.sh @@ -52,16 +52,12 @@ docker run -d --network host zookeeper echo "zookeeper listen in [:]2181" # build go-server image -# copy main go.mod to test dir -cp go.mod ./test/integrate/dubbo/go-server/go.main.mod cd ./test/integrate/dubbo/go-server docker build . -t ci-provider --build-arg PR_ORIGIN_REPO=${TRAVIS_PULL_REQUEST_SLUG} --build-arg PR_ORIGIN_COMMITID=${TRAVIS_PULL_REQUEST_SHA} cd ${ROOT_DIR} docker run -d --network host ci-provider # build go-client image -# copy main go.mod to test dir -cp go.mod ./test/integrate/dubbo/go-client/go.main.mod cd ./test/integrate/dubbo/go-client docker build . -t ci-consumer --build-arg PR_ORIGIN_REPO=${TRAVIS_PULL_REQUEST_SLUG} --build-arg PR_ORIGIN_COMMITID=${TRAVIS_PULL_REQUEST_SHA} cd ${ROOT_DIR} diff --git a/test/integrate/dubbo/go-client/Dockerfile b/test/integrate/dubbo/go-client/Dockerfile index 0c614eed6b..d48df36dc7 100644 --- a/test/integrate/dubbo/go-client/Dockerfile +++ b/test/integrate/dubbo/go-client/Dockerfile @@ -29,9 +29,7 @@ ADD . /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-client # update dubbo-go to current commit id RUN test ${PR_ORIGIN_REPO} && echo "github.com/apache/dubbo-go will be replace to github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID}" || echo 'go get github.com/apache/dubbo-go@develop' -RUN test ${PR_ORIGIN_REPO} && echo "module github.com/apache/dubbo-go/test/integrate/dubbo/go-client" > go.mod -RUN test ${PR_ORIGIN_REPO} && sed -n '2,$p' go.main.mod >> go.mod -RUN test ${PR_ORIGIN_REPO} && go mod edit -replace=github.com/apache/dubbo-go=github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID} || go mod vendor +RUN test ${PR_ORIGIN_REPO} && go mod edit -replace=github.com/apache/dubbo-go=github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID} || go get -u github.com/apache/dubbo-go@develop RUN go install github.com/apache/dubbo-go/test/integrate/dubbo/go-client diff --git a/test/integrate/dubbo/go-client/go.mod b/test/integrate/dubbo/go-client/go.mod index 4708eb1f0f..b0be45ae9c 100644 --- a/test/integrate/dubbo/go-client/go.mod +++ b/test/integrate/dubbo/go-client/go.mod @@ -1,3 +1,7 @@ module github.com/apache/dubbo-go/test/integrate/dubbo/go-client +require ( + github.com/apache/dubbo-go-hessian2 v1.6.0-rc1.0.20200906044240-6c1fb5c3bd44 +) + go 1.13 diff --git a/test/integrate/dubbo/go-server/Dockerfile b/test/integrate/dubbo/go-server/Dockerfile index 1d6ea286c7..c2f2d63462 100644 --- a/test/integrate/dubbo/go-server/Dockerfile +++ b/test/integrate/dubbo/go-server/Dockerfile @@ -26,12 +26,9 @@ ARG PR_ORIGIN_REPO ARG PR_ORIGIN_COMMITID ADD . /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-server - # update dubbo-go to current commit id RUN test ${PR_ORIGIN_REPO} && echo "github.com/apache/dubbo-go will be replace to github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID}" || echo 'go get github.com/apache/dubbo-go@develop' -RUN test ${PR_ORIGIN_REPO} && echo "module github.com/apache/dubbo-go/test/integrate/dubbo/go-server" > go.mod -RUN test ${PR_ORIGIN_REPO} && sed -n '2,$p' go.main.mod >> go.mod -RUN test ${PR_ORIGIN_REPO} && go mod edit -replace=github.com/apache/dubbo-go=github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID} || go mod vendor +RUN test ${PR_ORIGIN_REPO} && go mod edit -replace=github.com/apache/dubbo-go=github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID} || go get -u github.com/apache/dubbo-go@develop RUN go install github.com/apache/dubbo-go/test/integrate/dubbo/go-server diff --git a/test/integrate/dubbo/go-server/go.mod b/test/integrate/dubbo/go-server/go.mod index 9e1162327d..6c530f6a59 100644 --- a/test/integrate/dubbo/go-server/go.mod +++ b/test/integrate/dubbo/go-server/go.mod @@ -1,3 +1,7 @@ module github.com/apache/dubbo-go/test/integrate/dubbo/go-server +require ( + github.com/apache/dubbo-go-hessian2 v1.6.0-rc1.0.20200906044240-6c1fb5c3bd44 +) + go 1.13 From 6209b52eae127170798f4c060843cc112f97c750 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Tue, 8 Sep 2020 13:48:10 +0800 Subject: [PATCH 240/242] move debug() into refreshInvokers and remove update method --- registry/directory/directory.go | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/registry/directory/directory.go b/registry/directory/directory.go index 960855bc3c..5d4a890c6e 100644 --- a/registry/directory/directory.go +++ b/registry/directory/directory.go @@ -104,18 +104,8 @@ func (dir *RegistryDirectory) subscribe(url *common.URL) { } // Notify monitor changes from registry,and update the cacheServices -func (dir *RegistryDirectory) Notify(event ...*registry.ServiceEvent) { - go dir.update(event...) -} - -// update the cacheServices and subscribe service from registry -func (dir *RegistryDirectory) update(events ...*registry.ServiceEvent) { - for _, event := range events { - logger.Debugf("registry update, result{%s}", event) - logger.Debugf("update service name: %s!", event.Service) - } - - dir.refreshInvokers(events...) +func (dir *RegistryDirectory) Notify(events ...*registry.ServiceEvent) { + go dir.refreshInvokers(events...) } // refreshInvokers refreshes service's events. It supports two modes: incremental mode and batch mode. If a single @@ -140,6 +130,7 @@ func (dir *RegistryDirectory) refreshInvokers(events ...*registry.ServiceEvent) } for _, event := range events { + logger.Debugf("registry update, result{%s}", event) if oldInvoker, _ := dir.cacheInvokerByEvent(event); oldInvoker != nil { oldInvokers = append(oldInvokers, oldInvoker) } From 8ac1f91c97654ff0b7bf9adf7e43c390f7afc385 Mon Sep 17 00:00:00 2001 From: watermelo <80680489@qq.com> Date: Wed, 9 Sep 2020 23:02:40 +0800 Subject: [PATCH 241/242] Fix: rename SethealthChecker to SetHealthChecker --- common/extension/health_checker.go | 4 ++-- common/extension/health_checker_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common/extension/health_checker.go b/common/extension/health_checker.go index 8def727614..cec4c2defc 100644 --- a/common/extension/health_checker.go +++ b/common/extension/health_checker.go @@ -26,8 +26,8 @@ var ( healthCheckers = make(map[string]func(url *common.URL) router.HealthChecker) ) -// SethealthChecker sets the HealthChecker with @name -func SethealthChecker(name string, fcn func(_ *common.URL) router.HealthChecker) { +// SetHealthChecker sets the HealthChecker with @name +func SetHealthChecker(name string, fcn func(_ *common.URL) router.HealthChecker) { healthCheckers[name] = fcn } diff --git a/common/extension/health_checker_test.go b/common/extension/health_checker_test.go index 4e83a6f6e1..af6b114a61 100644 --- a/common/extension/health_checker_test.go +++ b/common/extension/health_checker_test.go @@ -32,7 +32,7 @@ import ( ) func TestGetHealthChecker(t *testing.T) { - SethealthChecker("mock", newMockhealthCheck) + SetHealthChecker("mock", newMockHealthCheck) checker := GetHealthChecker("mock", common.NewURLWithOptions()) assert.NotNil(t, checker) } @@ -44,6 +44,6 @@ func (m mockHealthChecker) IsHealthy(invoker protocol.Invoker) bool { return true } -func newMockhealthCheck(_ *common.URL) router.HealthChecker { +func newMockHealthCheck(_ *common.URL) router.HealthChecker { return &mockHealthChecker{} } From c9726e48bf4d20771704d9afe6c6e93348906168 Mon Sep 17 00:00:00 2001 From: watermelo <80680489@qq.com> Date: Wed, 9 Sep 2020 23:36:46 +0800 Subject: [PATCH 242/242] Fix: syntax error --- cluster/router/healthcheck/default_health_check.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cluster/router/healthcheck/default_health_check.go b/cluster/router/healthcheck/default_health_check.go index c693b86ecd..c522bdf0f9 100644 --- a/cluster/router/healthcheck/default_health_check.go +++ b/cluster/router/healthcheck/default_health_check.go @@ -31,7 +31,7 @@ import ( ) func init() { - extension.SethealthChecker(constant.DEFAULT_HEALTH_CHECKER, NewDefaultHealthChecker) + extension.SetHealthChecker(constant.DEFAULT_HEALTH_CHECKER, NewDefaultHealthChecker) } // DefaultHealthChecker is the default implementation of HealthChecker, which determines the health status of @@ -85,7 +85,7 @@ func (c *DefaultHealthChecker) getCircuitBreakerSleepWindowTime(status *protocol } else if diff > constant.DEFAULT_SUCCESSIVE_FAILED_REQUEST_MAX_DIFF { diff = constant.DEFAULT_SUCCESSIVE_FAILED_REQUEST_MAX_DIFF } - sleepWindow := (1 << diff) * c.GetCircuitTrippedTimeoutFactor() + sleepWindow := (1 << uint(diff)) * c.GetCircuitTrippedTimeoutFactor() if sleepWindow > constant.MAX_CIRCUIT_TRIPPED_TIMEOUT_IN_MS { sleepWindow = constant.MAX_CIRCUIT_TRIPPED_TIMEOUT_IN_MS }