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 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/README.md b/README.md index a1c09fc3ca..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) @@ -34,7 +36,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/README_CN.md b/README_CN.md index 552685c7bb..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),以便我们知晓。
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/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 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/base_cluster_invoker.go b/cluster/cluster_impl/base_cluster_invoker.go index bbdfa715d7..ced5b15cb9 100644 --- a/cluster/cluster_impl/base_cluster_invoker.go +++ b/cluster/cluster_impl/base_cluster_invoker.go @@ -17,6 +17,10 @@ package cluster_impl +import ( + "context" +) + import ( gxnet "github.com/dubbogo/gost/net" perrors "github.com/pkg/errors" @@ -36,6 +40,7 @@ type baseClusterInvoker struct { availablecheck bool destroyed *atomic.Bool stickyInvoker protocol.Invoker + interceptor cluster.ClusterInterceptor } func newBaseClusterInvoker(directory cluster.Directory) baseClusterInvoker { @@ -146,6 +151,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/broadcast_cluster.go b/cluster/cluster_impl/broadcast_cluster.go index ba454af6a8..ea3dee9218 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 returns a baseClusterInvoker instance func (cluster *broadcastCluster) Join(directory cluster.Directory) protocol.Invoker { return newBroadcastClusterInvoker(directory) } 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/cluster/cluster_impl/failback_cluster.go b/cluster/cluster_impl/failback_cluster.go index 432e33122c..278ac5432e 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 returns a 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..a5ea7a0585 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 returns a 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..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 @@ -40,6 +39,7 @@ func NewFailoverCluster() cluster.Cluster { return &failoverCluster{} } +// Join returns a 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..4260a9324d 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" ) @@ -44,6 +45,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 @@ -91,8 +93,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/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..d9465c034c 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 returns a 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..8c99113275 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 returns a 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_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 63% rename from cluster/cluster_impl/registry_aware_cluster.go rename to cluster/cluster_impl/zone_aware_cluster.go index fcefa52862..7439db2d37 100644 --- a/cluster/cluster_impl/registry_aware_cluster.go +++ b/cluster/cluster_impl/zone_aware_cluster.go @@ -19,21 +19,26 @@ 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 registryAwareCluster struct{} +type zoneAwareCluster struct{} func init() { - extension.SetCluster("registryAware", NewRegistryAwareCluster) + extension.SetCluster(constant.ZONEAWARE_CLUSTER_NAME, NewZoneAwareCluster) } -// NewRegistryAwareCluster returns a registry aware cluster instance -func NewRegistryAwareCluster() cluster.Cluster { - return ®istryAwareCluster{} +// 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{} } -func (cluster *registryAwareCluster) Join(directory cluster.Directory) protocol.Invoker { - return newRegistryAwareClusterInvoker(directory) +// Join returns a zoneAwareClusterInvoker instance +func (cluster *zoneAwareCluster) Join(directory cluster.Directory) protocol.Invoker { + return newZoneAwareClusterInvoker(directory) } 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..0f52b0442c --- /dev/null +++ b/cluster/cluster_impl/zone_aware_cluster_invoker.go @@ -0,0 +1,133 @@ +/* + * 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" +) + +import ( + "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 { + invoke := &zoneAwareClusterInvoker{ + baseClusterInvoker: newBaseClusterInvoker(directory), + } + // add self to interceptor + invoke.interceptor = invoke + return invoke +} + +// nolint +func (invoker *zoneAwareClusterInvoker) DoInvoke(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 { + key := constant.REGISTRY_KEY + "." + constant.PREFERRED_KEY + if invoker.IsAvailable() && matchParam("true", key, "false", invoker) { + return invoker.Invoke(ctx, invocation) + } + } + + // providers in the registry with the same zone + key := constant.REGISTRY_KEY + "." + constant.ZONE_KEY + zone := invocation.AttachmentsByKey(key, "") + if "" != zone { + for _, invoker := range invokers { + if invoker.IsAvailable() && matchParam(zone, key, "", invoker) { + return invoker.Invoke(ctx, invocation) + } + } + + 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 "+ + "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), + } +} + +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 new file mode 100644 index 0000000000..7f77f33166 --- /dev/null +++ b/cluster/cluster_impl/zone_aware_cluster_invoker_test.go @@ -0,0 +1,206 @@ +/* + * 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/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" +) + +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(). + //defer ctrl.Finish() + + mockResult := &protocol.RPCResult{ + Attrs: map[string]interface{}{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 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(). + //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() + 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( + func(invocation protocol.Invocation) protocol.Result { + return &protocol.RPCResult{ + Attrs: map[string]interface{}{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]interface{}{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 height : %s | count : %d, w2 height : %s | count : %d", loop, + w1, w1Count, w2, w2Count) +} + +func TestZoneWareInvokerWithZoneSuccess(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]interface{}{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_KEY+"."+constant.ZONE_KEY, hz) + + result := clusterInvoker.Invoke(context.Background(), inv) + + assert.Equal(t, hz, result.Attachment(constant.ZONE_KEY, "")) +} + +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(). + //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_KEY+"."+constant.ZONE_KEY, "hangzhou") + // zone force + inv.SetAttachments(constant.REGISTRY_KEY+"."+constant.ZONE_FORCE_KEY, "true") + + result := clusterInvoker.Invoke(context.Background(), inv) + + assert.NotNil(t, result.Error()) +} diff --git a/cluster/cluster_interceptor.go b/cluster/cluster_interceptor.go new file mode 100644 index 0000000000..a627e81365 --- /dev/null +++ b/cluster/cluster_interceptor.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 cluster + +import ( + "context" +) + +import ( + "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/cluster/directory/base_directory.go b/cluster/directory/base_directory.go index 8a7e0c0e83..20db1f2b7d 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,25 @@ func (dir *BaseDirectory) SetRouters(urls []*common.URL) { rc.AddRouters(routers) } +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 len(serviceKey) == 0 { + serviceKey = dir.GetUrl().SubURL.ServiceKey() + } + if len(app) > 0 && app == dirApp { + return true + } + if url.ServiceKey() == serviceKey { + return true + } + return false +} + // 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..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) { @@ -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, anyURL) + routeURL.AddParam(constant.INTERFACE_KEY, "mock-app") routerURLs := make([]*common.URL, 0) routerURLs = append(routerURLs, routeURL) directory.SetRouters(routerURLs) @@ -63,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 &url +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) } diff --git a/cluster/directory/static_directory.go b/cluster/directory/static_directory.go index 87f5135649..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 @@ -69,7 +72,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 +95,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/loadbalance/consistent_hash.go b/cluster/loadbalance/consistent_hash.go index 84fbb268c7..27ce2369f9 100644 --- a/cluster/loadbalance/consistent_hash.go +++ b/cluster/loadbalance/consistent_hash.go @@ -38,11 +38,11 @@ import ( ) const ( - // ConsistentHash ... + // ConsistentHash consistent hash ConsistentHash = "consistenthash" - // HashNodes ... + // HashNodes hash nodes HashNodes = "hash.nodes" - // HashArguments key of hash arguments in url + // HashArguments key of hash arguments in url HashArguments = "hash.arguments" ) @@ -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..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 ) @@ -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/loadbalance/util.go b/cluster/loadbalance/util.go index b6c013852b..684ffe11a7 100644 --- a/cluster/loadbalance/util.go +++ b/cluster/loadbalance/util.go @@ -28,23 +28,35 @@ import ( // 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/cluster/router/chain.go b/cluster/router/chain.go new file mode 100644 index 0000000000..3614d0a5a3 --- /dev/null +++ b/cluster/router/chain.go @@ -0,0 +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 router + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/protocol" +) + +// Chain +type Chain interface { + 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..952aedf92d 100644 --- a/cluster/router/chain/chain.go +++ b/cluster/router/chain/chain.go @@ -18,9 +18,10 @@ package chain import ( - "math" "sort" "sync" + "sync/atomic" + "time" ) import ( @@ -30,11 +31,18 @@ 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" ) +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. @@ -48,20 +56,37 @@ type RouterChain struct { mutex sync.RWMutex url common.URL + + // The times of address notification since last update for address cache + count int64 + // The timestamp of last update for address cache + last time.Time + // Channel for notify to update the address cache + notify 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(invoker []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { - finalInvokers := invoker - 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() +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 + } + + bitmap := cache.bitmap + for _, r := range c.copyRouters() { + bitmap = r.Route(bitmap, cache, url, invocation) + } - for _, r := range rs { - finalInvokers = r.Route(finalInvokers, url, invocation) + indexes := bitmap.ToArray() + finalInvokers := make([]protocol.Invoker, len(indexes)) + for i, index := range indexes { + finalInvokers[i] = cache.invokers[index] } + return finalInvokers } @@ -79,6 +104,116 @@ 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.mutex.Lock() + c.invokers = invokers + c.mutex.Unlock() + + c.count++ + now := time.Now() + if c.count >= countThreshold && now.Sub(c.last) >= timeThreshold { + c.last = now + c.count = 0 + go func() { + c.notify <- struct{}{} + }() + } +} + +// 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 { + ticker := time.NewTicker(timeInterval) + select { + case <-ticker.C: + c.buildCache() + case <-c.notify: + c.buildCache() + } + } +} + +// copyRouters make a snapshot copy from RouterChain's router list. +func (c *RouterChain) copyRouters() []router.PriorityRouter { + c.mutex.RLock() + defer c.mutex.RUnlock() + ret := make([]router.PriorityRouter, 0, len(c.routers)) + ret = append(ret, c.routers...) + return ret +} + +// 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 := 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 + } + + 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() { + origin := c.loadCache() + invokers := c.copyInvokerIfNecessary(origin) + if invokers == nil || len(invokers) == 0 { + return + } + + 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) + 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 + }(p) + } + } + wg.Wait() + + c.cache.Store(cache) +} + // URL Return URL in RouterChain func (c *RouterChain) URL() common.URL { return c.url @@ -109,14 +244,62 @@ func NewRouterChain(url *common.URL) (*RouterChain, error) { chain := &RouterChain{ builtinRouters: routers, routers: newRouters, + last: time.Now(), + notify: make(chan struct{}), } if url != nil { chain.url = *url } + go chain.loop() return chain, nil } +// 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.ShouldPool() || &(origin.invokers) != &invokers { + logger.Debugf("build address cache for router %q", name) + return p.Pool(invokers) + } + + 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. +// 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 +} + +// 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 + } + + for _, r := range right { + found := false + for _, l := range left { + if common.IsEquals(l.GetUrl(), r.GetUrl(), constant.TIMESTAMP_KEY, constant.REMOTE_TIMESTAMP_KEY) { + found = true + break + } + } + if !found { + return true + } + } + return false +} + // sortRouter Sort router instance by priority with stable algorithm func sortRouter(routers []router.PriorityRouter) { sort.Stable(byPriority(routers)) diff --git a/cluster/router/chain/chain_test.go b/cluster/router/chain/chain_test.go index c1f723525f..5a9f2d1c74 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: @@ -168,10 +172,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)) } @@ -182,7 +188,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: @@ -205,10 +213,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 +242,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/chain/invoker_cache.go b/cluster/router/chain/invoker_cache.go new file mode 100644 index 0000000000..43cdfa5067 --- /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, 8), + metadatas: make(map[string]router.AddrMetadata, 8), + } +} + +// 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/app_router_test.go b/cluster/router/condition/app_router_test.go index 8b38f2dd61..cce96b12c9 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" ) @@ -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/factory_test.go b/cluster/router/condition/factory_test.go index 0f61b39fc7..b31f6c7a82 100644 --- a/cluster/router/condition/factory_test.go +++ b/cluster/router/condition/factory_test.go @@ -32,6 +32,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/common/logger" @@ -180,30 +183,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 +228,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 +240,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 +256,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 +273,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 +292,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 +311,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 +330,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 +349,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 +366,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 +380,7 @@ func TestNewAppRouterFactory(t *testing.T) { factory := newAppRouterFactory() assert.NotNil(t, factory) } + +func setUpAddrCache(addrs []protocol.Invoker) router.Cache { + return chain.BuildCache(addrs) +} diff --git a/cluster/router/condition/file.go b/cluster/router/condition/file.go index eabdf1c263..996db7443f 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" @@ -71,11 +72,53 @@ 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), + ) + if routerRule.Scope == constant.RouterApplicationScope { + f.url.AddParam(constant.APPLICATION_KEY, routerRule.Key) + 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 } +// 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 + } + 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") + } + 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], ":")) + 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 3092b12ff8..bd19a0d18c 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,78 @@ 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:?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/listenable_router.go b/cluster/router/condition/listenable_router.go index 4ccc19e955..19e3a00bf6 100644 --- a/cluster/router/condition/listenable_router.go +++ b/cluster/router/condition/listenable_router.go @@ -22,10 +22,12 @@ import ( ) 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" @@ -64,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) @@ -85,7 +87,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 { @@ -129,13 +131,13 @@ func (l *listenableRouter) generateConditions(rule *RouterRule) { } // 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.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, url, invocation) + invokers = r.Route(invokers, cache, url, invocation) } return invokers } diff --git a/cluster/router/condition/router.go b/cluster/router/condition/router.go index 40a251573f..4267f5b405 100644 --- a/cluster/router/condition/router.go +++ b/cluster/router/condition/router.go @@ -23,20 +23,23 @@ 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]+)` ) @@ -117,7 +120,13 @@ func NewConditionRouter(url *common.URL) (*ConditionRouter, error) { } router.url = url - router.priority = url.GetParamInt(constant.RouterPriority, 0) + var defaultPriority int64 = 0 + if url.GetParam(constant.APPLICATION_KEY, "") != "" { + defaultPriority = 150 + } else if url.GetParam(constant.INTERFACE_KEY, "") != "" { + defaultPriority = 140 + } + router.priority = url.GetParamInt(constant.RouterPriority, defaultPriority) router.Force = url.GetParamBool(constant.RouterForce, false) router.enabled = url.GetParamBool(constant.RouterEnabled, true) @@ -142,29 +151,36 @@ 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.Cache, 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 + return utils.EmptyAddr } - for _, invoker := range invokers { + + result := roaring.NewBitmap() + for iter := invokers.Iterator(); iter.HasNext(); { + index := iter.Next() + invoker := cache.GetInvokers()[index] invokerUrl := invoker.GetUrl() isMatchThen := c.MatchThen(&invokerUrl, url) if isMatchThen { - result = append(result, invoker) + result.Add(index) } } - if len(result) > 0 { + + if !result.IsEmpty() { return result } else if c.Force { rule, _ := url.GetParamAndDecoded(constant.RULE_KEY) @@ -172,6 +188,7 @@ func (c *ConditionRouter) Route(invokers []protocol.Invoker, url *common.URL, in logger.Warnf("The route result is empty and force execute. consumer: %s, service: %s, router: %s", localIP, url.Service(), rule) return result } + return invokers } @@ -288,7 +305,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 +331,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/condition/router_rule.go b/cluster/router/condition/router_rule.go index ce397d6cc0..9f2bf2d41a 100644 --- a/cluster/router/condition/router_rule.go +++ b/cluster/router/condition/router_rule.go @@ -28,12 +28,13 @@ 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/yaml" ) // 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 && len(r.Key) > 0 && (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..369b14f08a 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..c27a1d9552 --- /dev/null +++ b/cluster/router/condition/router_test.go @@ -0,0 +1,88 @@ +/* + * 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/apache/dubbo-go/common" + "github.com/dubbogo/gost/container/set" + "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`)) +} + +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/cluster/router/healthcheck/default_health_check.go b/cluster/router/healthcheck/default_health_check.go index a26f86ddac..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 @@ -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 } @@ -85,25 +85,25 @@ 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 } 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/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/healthcheck/health_check_route.go b/cluster/router/healthcheck/health_check_route.go index ee42e47e3b..75ad189967 100644 --- a/cluster/router/healthcheck/health_check_route.go +++ b/cluster/router/healthcheck/health_check_route.go @@ -17,8 +17,13 @@ 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" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" @@ -28,6 +33,8 @@ import ( const ( HEALTH_ROUTE_ENABLED_KEY = "health.route.enabled" + healthy = "healthy" + name = "health-check-router" ) // HealthCheckRouter provides a health-first routing mechanism through HealthChecker @@ -51,25 +58,48 @@ 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.Cache, 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 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 } return healthyInvokers } +// 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, 8) + rb[healthy] = roaring.NewBitmap() + for i, invoker := range invokers { + if r.checker.IsHealthy(invoker) { + rb[healthy].Add(uint32(i)) + } + } + + return rb, nil +} + +// ShouldPool will always return true to make sure healthy check constantly. +func (r *HealthCheckRouter) ShouldPool() bool { + return r.enabled +} + +func (r *HealthCheckRouter) Name() string { + return name +} + // Priority func (r *HealthCheckRouter) Priority() int64 { return 0 diff --git a/cluster/router/healthcheck/health_check_route_test.go b/cluster/router/healthcheck/health_check_route_test.go index d5862fb884..c321b56156 100644 --- a/cluster/router/healthcheck/health_check_route_test.go +++ b/cluster/router/healthcheck/health_check_route_test.go @@ -29,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" @@ -59,25 +62,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 +88,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 +137,11 @@ 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.Cache { + pool, info := r.Pool(addrs) + 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 1d1f79d277..ddca42a01d 100644 --- a/cluster/router/router.go +++ b/cluster/router/router.go @@ -17,6 +17,10 @@ package router +import ( + "github.com/RoaringBitmap/roaring" +) + import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/protocol" @@ -38,7 +42,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, Cache, *common.URL, protocol.Invocation) *roaring.Bitmap + // URL Return URL in router URL() common.URL } @@ -50,3 +55,39 @@ type PriorityRouter interface { // 0 to ^int(0) is better 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 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 +} + +// 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 + + // FindAddrPool returns address pool associated with the given Poolable instance. + FindAddrPool(Poolable) AddrPool + + // FindAddrMeta returns address metadata associated with the given Poolable instance. + FindAddrMeta(Poolable) AddrMetadata +} diff --git a/cluster/router/tag/file.go b/cluster/router/tag/file.go index 8144c83203..0ee4311647 100644 --- a/cluster/router/tag/file.go +++ b/cluster/router/tag/file.go @@ -24,10 +24,12 @@ import ( ) 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" @@ -42,7 +44,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)) @@ -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.Cache, 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/router_rule.go b/cluster/router/tag/router_rule.go index 926446dcb2..c0a2d763ae 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""` + router.BaseRouterRule `yaml:",inline"` + Tags []Tag + AddressToTagNames map[string][]string + TagNameToAddresses map[string][]string } func getRule(rawRule string) (*RouterRule, error) { @@ -34,5 +52,54 @@ func getRule(rawRule string) (*RouterRule, error) { return r, err } r.RawRule = rawRule + r.parseTags() return r, nil } + +// 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 { + 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, 2*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 { + return len(t.TagNameToAddresses[tag]) > 0 +} + +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/chan.go b/cluster/router/tag/tag.go similarity index 87% rename from cluster/router/chan.go rename to cluster/router/tag/tag.go index 6904e1734a..468b426a4c 100644 --- a/cluster/router/chan.go +++ b/cluster/router/tag/tag.go @@ -15,11 +15,9 @@ * limitations under the License. */ -package router +package tag -// Chain -type Chain interface { - router - // AddRouters Add routers - AddRouters([]PriorityRouter) +type Tag struct { + Name string + Addresses []string } diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index 87da418943..9f92e83b21 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -19,24 +19,66 @@ package tag import ( "strconv" + "strings" + "sync" ) import ( + "github.com/RoaringBitmap/roaring" + gxnet "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/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" ) +const ( + name = "tag-router" + staticPrefix = "static-" + dynamicPrefix = "dynamic-" +) + +// addrMetadata keeps snapshot data for app name, and some infos extracted from dynamic tag rule in order to make +// Route() method lock-free. +type addrMetadata struct { + // application name + application string + // is rule a runtime rule + ruleRuntime bool + // is rule a force rule + ruleForce bool + // is rule a valid rule + ruleValid bool + // is rule an enabled rule + ruleEnabled bool +} + +// Source indicates where the metadata comes from. +func (m *addrMetadata) Source() string { + return name +} + +// tagRouter defines url, enable and the priority type tagRouter struct { - url *common.URL - enabled bool - priority int64 + url *common.URL + tagRouterRule *RouterRule + enabled bool + priority int64 + application string + ruleChanged bool + mutex sync.RWMutex } +// 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!") @@ -48,47 +90,280 @@ func NewTagRouter(url *common.URL) (*tagRouter, error) { }, nil } +// nolint 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() { +// Route gets a list of invoker +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 } - if len(invokers) == 0 { + + if shouldUseDynamicTag(cache.FindAddrMeta(c)) { + return c.routeWithDynamicTag(invokers, cache, url, invocation) + } + return c.routeWithStaticTag(invokers, cache, url, invocation) +} + +// routeWithStaticTag routes with static tag rule +func (c *tagRouter) routeWithStaticTag(invokers *roaring.Bitmap, cache router.Cache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap { + tag := findTag(invocation, url) + if tag == "" { return invokers } - return filterUsingStaticTag(invokers, url, invocation) + + ret, _ := c.filterWithTag(invokers, cache, staticPrefix+tag) + if ret.IsEmpty() && !isForceUseTag(url, invocation) { + return invokers + } + + return ret +} + +// routeWithDynamicTag routes with dynamic tag rule +func (c *tagRouter) routeWithDynamicTag(invokers *roaring.Bitmap, cache router.Cache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap { + tag := findTag(invocation, url) + if tag == "" { + return c.filterNotInDynamicTag(invokers, cache) + } + + ret, ok := c.filterWithTag(invokers, cache, dynamicPrefix+tag) + if ok && (!ret.IsEmpty() || isTagRuleForce(cache.FindAddrMeta(c))) { + return ret + } + + // 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 + if ret.IsEmpty() { + ret, _ = c.filterWithTag(invokers, cache, staticPrefix+tag) + // 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 !ret.IsEmpty() || isForceUseTag(url, invocation) { + return ret + } + return c.filterNotInDynamicTag(invokers, cache) + } + return ret +} + +// filterWithTag filters incoming invokers with the given tag +func (c *tagRouter) filterWithTag(invokers *roaring.Bitmap, cache router.Cache, tag string) (*roaring.Bitmap, bool) { + if target, ok := cache.FindAddrPool(c)[tag]; ok { + return utils.JoinIfNotEqual(target, invokers), true + } + return utils.EmptyAddr, false } +// filterNotInDynamicTag filters incoming invokers not applied to dynamic tag rule +func (c *tagRouter) filterNotInDynamicTag(invokers *roaring.Bitmap, cache router.Cache) *roaring.Bitmap { + // FAILOVER: return all Providers without any tags. + invokers = invokers.Clone() + for k, v := range cache.FindAddrPool(c) { + if strings.HasPrefix(k, dynamicPrefix) { + invokers.AndNot(v) + } + } + return invokers +} + +// Process parses dynamic tag rule +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 + } + 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.mutex.Lock() + defer c.mutex.Unlock() + c.tagRouterRule = routerRule + c.ruleChanged = true +} + +// 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 } -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) +// Pool divided invokers into different address pool by tag. +func (c *tagRouter) Pool(invokers []protocol.Invoker) (router.AddrPool, router.AddrMetadata) { + c.fetchRuleIfNecessary(invokers) + + rb := make(router.AddrPool, 8) + poolWithStaticTag(invokers, rb) + + c.mutex.Lock() + defer c.mutex.Unlock() + poolWithDynamicTag(invokers, c.tagRouterRule, rb) + c.ruleChanged = false + // create metadata in order to avoid lock in route() + meta := addrMetadata{application: c.application} + if c.tagRouterRule != nil { + meta.ruleForce = c.tagRouterRule.Force + meta.ruleEnabled = c.tagRouterRule.Enabled + meta.ruleValid = c.tagRouterRule.Valid + } + + return rb, &meta +} + +// fetchRuleIfNecessary fetches, parses rule and register listener for the further change +func (c *tagRouter) fetchRuleIfNecessary(invokers []protocol.Invoker) { + if invokers == nil || len(invokers) == 0 { + return + } + + url := invokers[0].GetUrl() + providerApplication := url.GetParam(constant.RemoteApplicationKey, "") + 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 + } + 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) + } else { + // if app name from URL is as same as the current app name, then it is safe to jump out + return + } + + c.application = providerApplication + 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 len(rule) > 0 { + c.Process(&config_center.ConfigChangeEvent{ + Key: routerKey, + Value: rule, + ConfigType: remoting.EventTypeUpdate}) + } +} + +// ShouldPool returns false, to make sure address cache for tag router happens once and only once. +func (c *tagRouter) ShouldPool() bool { + c.mutex.RLock() + defer c.mutex.RUnlock() + return c.ruleChanged +} + +// Name returns pool's name +func (c *tagRouter) Name() string { + return name +} + +// poolWithDynamicTag pools addresses with the tags defined in dynamic tag rule, all keys have prefix "dynamic-" +func poolWithDynamicTag(invokers []protocol.Invoker, rule *RouterRule, pool router.AddrPool) { + if rule == nil { + return + } + + tagNameToAddresses := rule.getTagNameToAddresses() + for tag, addrs := range tagNameToAddresses { + pool[dynamicPrefix+tag] = addrsToBitmap(addrs, invokers) + } +} + +// poolWithStaticTag pools addresses with tags found from incoming URLs, all keys have prefix "static-" +func poolWithStaticTag(invokers []protocol.Invoker, pool router.AddrPool) { + for i, invoker := range invokers { + url := invoker.GetUrl() + tag := url.GetParam(constant.Tagkey, "") + if len(tag) > 0 { + if _, ok := pool[staticPrefix+tag]; !ok { + pool[staticPrefix+tag] = roaring.NewBitmap() } + pool[staticPrefix+tag].AddInt(i) } - if len(result) == 0 && !isForceUseTag(url, invocation) { - return invokers - } - return result } - return invokers } +// shouldUseDynamicTag uses the snapshot data from the parsed rule to decide if dynamic tag rule should be used or not +func shouldUseDynamicTag(meta router.AddrMetadata) bool { + return meta.(*addrMetadata).ruleValid && meta.(*addrMetadata).ruleEnabled +} + +// isTagRuleForce uses the snapshot data from the parsed rule to decide if dynamic tag rule is forced or not +func isTagRuleForce(meta router.AddrMetadata) bool { + return meta.(*addrMetadata).ruleForce +} + +// 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 } return false } + +// addrsToBitmap finds indexes for the given IP addresses in the target URL list, if any '0.0.0.0' IP address is met, +// then returns back all indexes of the URLs list. +func addrsToBitmap(addrs []string, invokers []protocol.Invoker) *roaring.Bitmap { + ret := roaring.NewBitmap() + for _, addr := range addrs { + if isAnyHost(addr) { + ret.AddRange(0, uint64(len(invokers))) + return ret + } + + index := findIndexWithIp(addr, invokers) + if index != -1 { + ret.AddInt(index) + } + } + return ret +} + +// findIndexWithIp finds index for one particular IP +func findIndexWithIp(addr string, invokers []protocol.Invoker) int { + for i, invoker := range invokers { + if gxnet.MatchIP(addr, invoker.GetUrl().Ip, invoker.GetUrl().Port) { + return i + } + } + return -1 +} + +// isAnyHost checks if an IP is '0.0.0.0' +func isAnyHost(addr string) bool { + return strings.HasPrefix(addr, constant.ANYHOST_VALUE) +} + +// findTag finds tag, first from invocation's attachment, then from URL +func findTag(invocation protocol.Invocation, consumerUrl *common.URL) string { + tag, ok := invocation.Attachments()[constant.Tagkey] + if !ok { + return consumerUrl.GetParam(constant.Tagkey, "") + } else if v, t := tag.(string); t { + return v + } + return "" +} diff --git a/cluster/router/tag/tag_router_test.go b/cluster/router/tag/tag_router_test.go index 000b3ec672..92611ac2c5 100644 --- a/cluster/router/tag/tag_router_test.go +++ b/cluster/router/tag/tag_router_test.go @@ -19,25 +19,46 @@ package tag import ( "context" + "fmt" "testing" + "time" ) import ( + "github.com/RoaringBitmap/roaring" + "github.com/dubbogo/go-zookeeper/zk" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" ) 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/config" + "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" - 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" @@ -45,6 +66,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 @@ -116,17 +146,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 +178,217 @@ 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.Cache { + pool, info := r.Pool(addrs) + cache := chain.BuildCache(addrs) + cache.SetAddrPool(r.Name(), pool) + cache.SetAddrMeta(r.Name(), info) + return cache +} + +func setUpAddrCacheWithRuleDisabled(r router.Poolable, addrs []protocol.Invoker) router.Cache { + pool, info := r.Pool(addrs) + info.(*addrMetadata).ruleEnabled = false + cache := chain.BuildCache(addrs) + cache.SetAddrPool(r.Name(), pool) + cache.SetAddrMeta(r.Name(), info) + return cache +} + +func TestRouteBeijingInvoker(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) + + url, _ := common.NewURL(tagRouterTestBeijingUrl) + tagRouter, _ := NewTagRouter(&url) + + rb := roaring.NewBitmap() + rb.AddRange(0, uint64(len(invokers))) + cache := setUpAddrCache(tagRouter, invokers) + inv := &invocation.RPCInvocation{} + res := tagRouter.Route(rb, cache, &url, inv) + // inv4 and inv5 + assert.Equal(t, []uint32{2, 3}, res.ToArray()) +} + +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 + rb := roaring.NewBitmap() + rb.AddRange(0, uint64(len(invokers))) + cache := setUpAddrCache(suite.route, invokers) + suite.NotNil(suite.route.tagRouterRule) + + consumer := &invocation.RPCInvocation{} + consumer.SetAttachments(tagRouterTestDubboTag, "tag1") + targetInvokers := suite.route.Route(rb, cache, suite.url, consumer) + suite.Equal(uint64(1), targetInvokers.GetCardinality()) + suite.Equal(targetInvokers.ToArray()[0], uint32(0)) + + consumer.SetAttachments(tagRouterTestDubboTag, "tag3") + targetInvokers = suite.route.Route(rb, cache, suite.url, consumer) + suite.Equal(uint64(2), targetInvokers.GetCardinality()) + suite.True(targetInvokers.Contains(2) && targetInvokers.Contains(3)) +} + +func (suite *DynamicTagRouter) TestDynamicTagRouterStaticTag() { + invokers := suite.invokers + rb := roaring.NewBitmap() + rb.AddRange(0, uint64(len(invokers))) + cache := setUpAddrCacheWithRuleDisabled(suite.route, invokers) + consumer := &invocation.RPCInvocation{} + consumer.SetAttachments(tagRouterTestDubboTag, "tag4") + targetInvokers := suite.route.Route(rb, cache, suite.url, consumer) + suite.Equal(uint64(1), targetInvokers.GetCardinality()) + suite.Equal(targetInvokers.ToArray()[0], uint32(3)) +} + +// Teas no tag and return a address are not in dynamic tag group +func (suite *DynamicTagRouter) TestDynamicTagRouterByNoTagAndAddressMatch() { + invokers := suite.invokers + rb := roaring.NewBitmap() + rb.AddRange(0, uint64(len(invokers))) + cache := setUpAddrCache(suite.route, invokers) + suite.NotNil(suite.route.tagRouterRule) + consumer := &invocation.RPCInvocation{} + targetInvokers := suite.route.Route(rb, cache, suite.url, consumer) + suite.Equal(uint64(1), targetInvokers.GetCardinality()) + suite.Equal(targetInvokers.ToArray()[0], uint32(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(rb, cache, suite.url, consumer) + suite.Equal(uint64(1), targetInvokers.GetCardinality()) + suite.Equal(targetInvokers.ToArray()[0], uint32(4)) +} + +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/cluster/router/utils/bitmap_util.go b/cluster/router/utils/bitmap_util.go new file mode 100644 index 0000000000..8b4ee5538f --- /dev/null +++ b/cluster/router/utils/bitmap_util.go @@ -0,0 +1,50 @@ +/* + * 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() + 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 + } + return ret +} + +func ToBitmap(invokers []protocol.Invoker) *roaring.Bitmap { + bitmap := roaring.NewBitmap() + bitmap.AddRange(0, uint64(len(invokers))) + return bitmap +} 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/default.go b/common/constant/default.go index c1c404e089..629aa32392 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 = "15m" DEFAULT_CLUSTER = "failover" DEFAULT_FAILBACK_TIMES = "3" DEFAULT_FAILBACK_TIMES_INT = 3 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/common/constant/key.go b/common/constant/key.go index cd23dd0f1a..7c45a1397d 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 ( @@ -97,6 +98,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" + ZONE_FORCE_KEY = "zone.force" + REGISTRY_TTL_KEY = "registry.ttl" ) const ( @@ -187,6 +193,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" @@ -196,7 +205,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" 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{} } 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/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/common/logger/logger.go b/common/logger/logger.go index 9bc6a46100..63eda231dd 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" @@ -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 { @@ -100,7 +104,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 +116,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 diff --git a/common/proxy/proxy.go b/common/proxy/proxy.go index abcf87cd9d..d51ce1cc1b 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" @@ -140,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) @@ -154,7 +164,18 @@ 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 some error happened, it should be log some info in the seperate file. + if throwabler, ok := err.(java_exception.Throwabler); ok { + logger.Warnf("invoke service throw exception: %v , stackTraceElements: %v", err.Error(), throwabler.GetStackTrace()) + } else { + logger.Warnf("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()} } 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/common/rpc_service.go b/common/rpc_service.go index 9ef2b956aa..5ed4df6d71 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() @@ -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 807d0ed5ef..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) @@ -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 } @@ -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 { 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/base_config.go b/config/base_config.go index 0ba5bc7ef9..336bb03c7b 100644 --- a/config/base_config.go +++ b/config/base_config.go @@ -29,11 +29,8 @@ 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 +49,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,74 +68,6 @@ 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 - } - - 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) - } - } - // 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 -} - func getKeyPrefix(val reflect.Value) []string { var ( prefix string @@ -150,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 { @@ -288,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/base_config_test.go b/config/base_config_test.go index 15b468753d..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" ) @@ -95,14 +93,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 +256,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", }, }, }, @@ -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 c9133dc26d..0fc4007940 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,94 @@ func (c *ConfigCenterConfig) GetUrlMap() url.Values { urlMap.Set(constant.CONFIG_LOG_DIR_KEY, c.LogDir) return urlMap } + +type configCenter struct { +} + +// 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 { + return common.NewURL(baseConfig.ConfigCenterConfig.Address, + common.WithProtocol(baseConfig.ConfigCenterConfig.Protocol), common.WithParams(baseConfig.ConfigCenterConfig.GetUrlMap())) + } + + remoteRef := baseConfig.ConfigCenterConfig.RemoteRef + rc, ok := baseConfig.GetRemoteConfig(remoteRef) + + if !ok { + return common.URL{}, perrors.New("Could not find out the remote ref config, name: " + remoteRef) + } + + newURL, err := rc.toURL() + if err == nil { + newURL.SetParams(baseConfig.ConfigCenterConfig.GetUrlMap()) + } + return newURL, err +} + +// startConfigCenter will start the config center. +// it will prepare the environment +func (b *configCenter) startConfigCenter(baseConfig BaseConfig) error { + url, err := b.toURL(baseConfig) + if err != nil { + return err + } + if err = b.prepareEnvironment(baseConfig, &url); err != nil { + return perrors.WithMessagef(err, "start config center error!") + } + // c.fresh() + return nil +} + +func (b *configCenter) prepareEnvironment(baseConfig BaseConfig, configCenterUrl *common.URL) error { + factory := extension.GetConfigCenterFactory(configCenterUrl.Protocol) + dynamicConfig, err := factory.GetDynamicConfiguration(configCenterUrl) + 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) + 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/config_center_config_test.go b/config/config_center_config_test.go new file mode 100644 index 0000000000..2299167bb6 --- /dev/null +++ b/config/config_center_config_test.go @@ -0,0 +1,93 @@ +/* + * 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) +} + +func TestStartConfigCenterWithRemoteRef(t *testing.T) { + extension.SetConfigCenterFactory("mock", func() config_center.DynamicConfigurationFactory { + return &config_center.MockDynamicConfigurationFactory{} + }) + m := make(map[string]*RemoteConfig) + m["mock"] = &RemoteConfig{Protocol: "mock", Address: "172.0.0.1"} + baseConfig := &BaseConfig{ + Remotes: m, + ConfigCenterConfig: &ConfigCenterConfig{ + 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) +} + +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) +} diff --git a/config/config_loader.go b/config/config_loader.go index d5f8c68c1b..c66e526921 100644 --- a/config/config_loader.go +++ b/config/config_loader.go @@ -21,11 +21,14 @@ import ( "fmt" "log" "os" + "reflect" + "strconv" "sync" "time" ) import ( + gxnet "github.com/dubbogo/gost/net" perrors "github.com/pkg/errors" ) @@ -35,6 +38,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 ( @@ -42,6 +46,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 @@ -136,19 +141,18 @@ 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) || (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) } @@ -156,14 +160,13 @@ 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) } } } if checkok { break } - checkok = true } } @@ -205,6 +208,97 @@ 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{" + reflect.TypeOf(p).String() + "} is invalid") + } + rs := rp.GetRegistries() + for _, r := range rs { + var sdr registry.ServiceDiscoveryHolder + if sdr, ok = r.(registry.ServiceDiscoveryHolder); !ok { + continue + } + err := sdr.GetServiceDiscovery().Register(instance) + if err != nil { + panic(err) + } + } +} + +// nolint +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 { + logger.Warn(err) + return nil + } + 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 + } + selectedUrl = url + // rest first + if url.Protocol == "rest" { + break + } + } + return &selectedUrl } func initRouter() { @@ -325,6 +419,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/config_loader_test.go b/config/config_loader_test.go index 2cbf526a70..461e607c1e 100644 --- a/config/config_loader_test.go +++ b/config/config_loader_test.go @@ -19,10 +19,16 @@ package config import ( "path/filepath" + "sort" + "sync" "testing" ) 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" ) @@ -33,8 +39,11 @@ 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" ) const mockConsumerConfigPath = "./testdata/consumer_config.yml" @@ -72,9 +81,19 @@ func TestLoad(t *testing.T) { SetProviderService(ms) extension.SetProtocol("registry", GetProtocol) - extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster) + 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())) @@ -101,9 +120,19 @@ func TestLoadWithSingleReg(t *testing.T) { SetProviderService(ms) extension.SetProtocol("registry", GetProtocol) - extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster) + 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())) @@ -130,9 +159,19 @@ func TestWithNoRegLoad(t *testing.T) { SetProviderService(ms) extension.SetProtocol("registry", GetProtocol) - extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster) + 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())) @@ -278,13 +317,13 @@ func mockInitProviderWithSingleRegistry() { { Name: "GetUser", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", Weight: 200, }, { Name: "GetUser1", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", Weight: 200, }, }, @@ -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/consumer_config.go b/config/consumer_config.go index 48f29f0e70..c8083603e1 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 ///////////////////////// @@ -41,7 +44,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 @@ -56,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"` } @@ -106,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 != "" { @@ -125,13 +129,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(); 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) @@ -142,5 +139,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 } 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/provider_config.go b/config/provider_config.go index 7cd3c1e98b..fcb429b640 100644 --- a/config/provider_config.go +++ b/config/provider_config.go @@ -37,14 +37,15 @@ 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"` 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"` @@ -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() diff --git a/config/reference_config.go b/config/reference_config.go index 748b2d403f..cd10f89eb7 100644 --- a/config/reference_config.go +++ b/config/reference_config.go @@ -147,13 +147,26 @@ func (c *ReferenceConfig) Refer(_ interface{}) { } // TODO(decouple from directory, config should not depend on directory module) + var hitClu string if regUrl != nil { - cluster := extension.GetCluster("registryAware") - c.invoker = cluster.Join(directory.NewStaticDirectory(invokers)) + // for multi-subscription scenario, use 'zone-aware' policy by default + hitClu = constant.ZONEAWARE_CLUSTER_NAME } else { - cluster := extension.GetCluster(c.Cluster) - c.invoker = cluster.Join(directory.NewStaticDirectory(invokers)) + // not a registry url, must be direct invoke. + hitClu = constant.FAILOVER_CLUSTER_NAME + if len(invokers) > 0 { + u := invokers[0].GetUrl() + if nil != &u { + hitClu = u.GetParam(constant.CLUSTER_KEY, constant.ZONEAWARE_CLUSTER_NAME) + } + } } + + 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 @@ -220,7 +233,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 { @@ -238,5 +251,4 @@ func (c *ReferenceConfig) GenericLoad(id string) { c.id = id c.Refer(genericService) c.Implement(genericService) - return } diff --git a/config/reference_config_test.go b/config/reference_config_test.go index 3fbf8da44c..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 @@ -103,12 +104,12 @@ func doInitConsumer() { { Name: "GetUser", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", }, { Name: "GetUser1", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", Sticky: true, }, }, @@ -174,12 +175,12 @@ func doInitConsumerWithSingleRegistry() { { Name: "GetUser", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", }, { Name: "GetUser1", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", }, }, }, @@ -187,10 +188,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(constant.ZONEAWARE_CLUSTER_NAME, cluster_impl.NewZoneAwareCluster) for _, reference := range consumerConfig.References { reference.Refer(nil) @@ -203,7 +204,7 @@ func TestReferMultireg(t *testing.T) { func TestRefer(t *testing.T) { doInitConsumer() extension.SetProtocol("registry", GetProtocol) - extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster) + extension.SetCluster(constant.ZONEAWARE_CLUSTER_NAME, cluster_impl.NewZoneAwareCluster) for _, reference := range consumerConfig.References { reference.Refer(nil) @@ -217,7 +218,7 @@ func TestRefer(t *testing.T) { func TestReferAsync(t *testing.T) { doInitConsumerAsync() extension.SetProtocol("registry", GetProtocol) - extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster) + extension.SetCluster(constant.ZONEAWARE_CLUSTER_NAME, cluster_impl.NewZoneAwareCluster) for _, reference := range consumerConfig.References { reference.Refer(nil) @@ -275,7 +276,7 @@ func TestReferMultiP2PWithReg(t *testing.T) { func TestImplement(t *testing.T) { doInitConsumer() extension.SetProtocol("registry", GetProtocol) - extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster) + extension.SetCluster(constant.ZONEAWARE_CLUSTER_NAME, cluster_impl.NewZoneAwareCluster) for _, reference := range consumerConfig.References { reference.Refer(nil) reference.Implement(&MockService{}) @@ -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/registry_config.go b/config/registry_config.go index ef527c827e..89566c428e 100644 --- a/config/registry_config.go +++ b/config/registry_config.go @@ -39,12 +39,22 @@ 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"` - 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:"preferred,omitempty" property:"preferred"` + // The region where the registry belongs, usually used to isolate traffics + 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"` + Params map[string]string `yaml:"params" json:"params,omitempty" property:"params"` } // UnmarshalYAML unmarshals the RegistryConfig by @unmarshal function @@ -118,6 +128,13 @@ 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.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 { urlMap.Set(k, v) } diff --git a/config/remote_config.go b/config/remote_config.go index 5e0330c571..55380dd5a0 100644 --- a/config/remote_config.go +++ b/config/remote_config.go @@ -22,6 +22,11 @@ import ( ) import ( + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/logger" ) @@ -30,6 +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 `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"` @@ -56,3 +62,15 @@ func (rc *RemoteConfig) GetParam(key string, def string) string { } return param } + +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), + common.WithLocation(rc.Address), + common.WithProtocol(rc.Protocol), + ) +} diff --git a/config/router_config.go b/config/router_config.go index 16a2bec918..ed42577ed3 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,37 @@ 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 { - 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) @@ -52,6 +74,7 @@ func RouterInit(confRouterFile string) error { 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 72e51c1c82..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" ) @@ -31,6 +32,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 +65,10 @@ func TestRouterInit(t *testing.T) { assert.NoError(t, errPro) assert.Equal(t, 1, routerURLSet.Size()) + + routerURLSet = gxset.NewSet() + errPro = RouterInit(testMultiRouterYML) + assert.NoError(t, errPro) + + assert.Equal(t, 2, routerURLSet.Size()) } diff --git a/config/service_config.go b/config/service_config.go index 57fce028fa..8968bac88f 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]) @@ -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,23 +160,23 @@ 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 } 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) 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 } port := proto.Port - if len(proto.Port) == 0 { port = nextPort.Value.(string) nextPort = nextPort.Next() @@ -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(GetSslEnabled())), common.WithMethods(strings.Split(methods, ",")), common.WithToken(c.Token), ) @@ -195,33 +196,31 @@ 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 { + 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) - exporter = c.cacheProtocol.Export(invoker) + invoker := 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)) } + c.exporters = append(c.exporters, exporter) } } else { - invoker := extension.GetProxyFactory(providerConfig.ProxyFactory).GetInvoker(*ivkURL) - exporter = extension.GetProtocol(protocolwrapper.FILTER).Export(invoker) + invoker := 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) } - c.exporters = append(c.exporters, exporter) } c.exported.Store(true) return nil @@ -303,7 +302,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)) @@ -313,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/config/service_config_test.go b/config/service_config_test.go index e7d55077be..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", @@ -56,13 +76,13 @@ func doInitProvider() { { Name: "GetUser", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", Weight: 200, }, { Name: "GetUser1", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", Weight: 200, }, }, @@ -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", @@ -81,13 +101,13 @@ func doInitProvider() { { Name: "GetUser", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", Weight: 200, }, { Name: "GetUser1", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", Weight: 200, }, }, @@ -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{ @@ -153,7 +181,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/ssl_config.go b/config/ssl_config.go new file mode 100644 index 0000000000..8576930367 --- /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/apache/dubbo-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/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 diff --git a/config_center/apollo/factory.go b/config_center/apollo/factory.go index f975ce13d8..c52d942c4f 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 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/config_center/apollo/impl.go b/config_center/apollo/impl.go index b049d334bc..8030a2c800 100644 --- a/config_center/apollo/impl.go +++ b/config_center/apollo/impl.go @@ -27,7 +27,9 @@ import ( import ( gxset "github.com/dubbogo/gost/container/set" perrors "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 ( @@ -35,19 +37,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 } @@ -60,31 +61,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 { @@ -92,7 +82,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) } @@ -110,10 +100,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) } @@ -148,7 +138,7 @@ func (c *apolloConfiguration) GetProperties(key string, opts ...cc.Option) (stri if config == nil { return "", perrors.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/impl_test.go b/config_center/apollo/impl_test.go index 335fb71045..50c4e689de 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, "registries.hangzhouzk.username", listener.event) + 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(_, 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) } diff --git a/config_center/apollo/listener.go b/config_center/apollo/listener.go index 1cf65ed22b..ace5ed0268 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 creates a new apolloListener -func NewApolloListener() *apolloListener { +// nolint +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, - }) - } +// OnChange process each listener +func (a *apolloListener) OnChange(changeEvent *storage.ChangeEvent) { + +} + +// OnNewestChange process each listener by all changes +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/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/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/nacos/client.go b/config_center/nacos/client.go index 6fe5c4d7df..acfe2609ce 100644 --- a/config_center/nacos/client.go +++ b/config_center/nacos/client.go @@ -98,6 +98,7 @@ func ValidateNacosClient(container nacosClientFacade, opts ...option) error { } nacosAddresses := strings.Split(url.Location, ",") if container.NacosClient() == nil { + //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.Errorf("newNacosClient(name{%s}, nacos address{%v}, timeout{%d}) = error{%v}", @@ -115,6 +116,7 @@ func ValidateNacosClient(container nacosClientFacade, opts ...option) error { return perrors.WithMessagef(err, "newNacosClient(address:%+v)", url.Location) } container.NacosClient().SetClient(&configClient) + } return perrors.WithMessagef(nil, "newNacosClient(address:%+v)", url.PrimitiveURL) @@ -169,7 +171,7 @@ func initNacosConfigClient(nacosAddrs []string, timeout time.Duration, url commo TimeoutMs: uint64(int32(timeout / time.Millisecond)), ListenInterval: uint64(int32(timeout / time.Millisecond)), NotLoadCacheAtStart: true, - LogDir: url.GetParam(constant.NACOS_LOG_DIR_KEY, 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, ""), 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/config_center/nacos/listener.go b/config_center/nacos/listener.go index 3118a9d052..57df0363a1 100644 --- a/config_center/nacos/listener.go +++ b/config_center/nacos/listener.go @@ -48,6 +48,7 @@ func (n *nacosDynamicConfiguration) addListener(key string, listener config_cent }) if err != nil { logger.Errorf("nacos : listen config fail, error:%v ", err) + return } newListener := make(map[config_center.ConfigurationListener]context.CancelFunc) newListener[listener] = cancel 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/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 diff --git a/doc/pic/arch/dubbo-go-arch.png b/doc/pic/arch/dubbo-go-arch.png index e5f1927152..0a28cb6a3a 100644 Binary files a/doc/pic/arch/dubbo-go-arch.png and b/doc/pic/arch/dubbo-go-arch.png differ diff --git a/doc/pic/arch/dubbo-go-ext.png b/doc/pic/arch/dubbo-go-ext.png index d065ef4d8e..b60ecdf47e 100644 Binary files a/doc/pic/arch/dubbo-go-ext.png and b/doc/pic/arch/dubbo-go-ext.png differ 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..6eaf9cb00b 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" ) @@ -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/echo_filter.go b/filter/filter_impl/echo_filter.go index 7da5ec7029..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 ... +// GetFilter gets 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/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/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/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/hystrix_filter.go b/filter/filter_impl/hystrix_filter.go index 711ef71c44..d13e02c06f 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" ) @@ -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) @@ -85,14 +81,54 @@ func NewHystrixFilterError(err error, failByHystrix bool) error { } } -// HystrixFilter ... +/** + * 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 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()) @@ -213,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 { @@ -232,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.GetConsumerConfig().FilterConf.(map[interface{}]interface{})[HYSTRIX] + 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 { @@ -256,7 +292,7 @@ func initHystrixConfigProvider() error { // return initHystrixConfig() //} -// CommandConfigWithError ... +// nolint type CommandConfigWithError struct { Timeout int `yaml:"timeout"` MaxConcurrentRequests int `yaml:"max_concurrent_requests"` @@ -274,14 +310,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/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.go b/filter/filter_impl/seata_filter.go new file mode 100644 index 0000000000..7722d2954f --- /dev/null +++ b/filter/filter_impl/seata_filter.go @@ -0,0 +1,64 @@ +/* + * 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" + "strings" +) + +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 when use seata-golang, use this filter to transfer xid +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 strings.TrimSpace(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 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 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 new file mode 100644 index 0000000000..45817e95cb --- /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]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 23742c66e9..b5e05605c2 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" ) @@ -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 ", @@ -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/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_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..be76466092 100644 --- a/filter/filter_impl/tps/tps_limit_strategy_mock.go +++ b/filter/filter_impl/tps/tps_limit_strategy_mock.go @@ -23,6 +23,9 @@ package tps import ( gomock "github.com/golang/mock/gomock" +) + +import ( reflect "reflect" ) 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 15dc32e7ec..e159b7400d 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" ) @@ -45,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 e82a04b279..9e18cc276a 100644 --- a/go.mod +++ b/go.mod @@ -1,61 +1,66 @@ module github.com/apache/dubbo-go require ( + github.com/Microsoft/go-winio v0.4.13 // indirect + github.com/NYTimes/gziphandler v1.1.1 // indirect + github.com/RoaringBitmap/roaring v0.4.23 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/apache/dubbo-getty v1.3.10 + 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-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.7 + github.com/docker/go-connections v0.4.0 // indirect 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 + 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/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 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/shirou/gopsutil v2.19.9+incompatible // 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 + 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 - 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/grpc v1.23.0 + 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 launchpad.net/gocheck => github.com/go-check/check v0.0.0-20140225173054-eb6ee6f84d0a diff --git a/go.sum b/go.sum index 8c8c4ef132..f744fc313b 100644 --- a/go.sum +++ b/go.sum @@ -1,30 +1,65 @@ -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/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 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/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/Jeffail/gabs v1.1.0 h1:kw5zCcl9tlJNHTDme7qbi21fDHZmXrnjMoXos3Jw/NI= -github.com/Jeffail/gabs v1.1.0/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc= -github.com/Microsoft/go-winio v0.4.3 h1:M3NHMuPgMSUPdE5epwNUHlRPSVzHs8HpRTrVXhR0myo= +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/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/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 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= @@ -35,97 +70,94 @@ 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/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 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= 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-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26FmByPp2eC2qla6E1Tw+scpcg/to= +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/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/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= +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/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= 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/docker/go-connections v0.3.0 h1:3lOnM9cSzgGwx8VfK/NGOW5fLQ0GjIlCkaktF+n1M6o= +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/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/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/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= -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= +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 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= @@ -134,28 +166,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/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/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 h1:gclg6gY70GLy3PbkQ1AERPfmLMMagS60DKF78eWwLn8= +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 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-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 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/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= @@ -165,24 +208,25 @@ 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= @@ -190,169 +234,197 @@ 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= 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-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-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0= +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/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/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 h1:AtvtonGEH/fZK0XPNNBdB6swgy7Iudfx88wzyIpwqJ8= +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 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= -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 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= +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.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 v1.8.0 h1:yRKMKZyPLqUxl37t4nFt5OuGmTXoFhTJrakhfnYKCYA= +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/sdk v0.1.1 h1:LnuDWGNsoajlhGyHJvuWW6FVqRl8JOTPqS6CPTsYjhY= +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.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +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/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-hclog v0.9.1 h1:9PZfAcVEvez4yhLH2TBU64/h/z4xlFI80cWXRrxuKuM= +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/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= +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/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-raftchunking v0.6.1 h1:moEnaG3gcwsWNyIBJoD5PCByE+Ewkqxh6N05CT+MbwA= +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/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= github.com/hashicorp/go-raftchunking v0.6.1/go.mod h1:cGlg3JtDy7qy6c/3Bu660Mic1JF+7lWqIwCFSb08fX0= -github.com/hashicorp/go-retryablehttp v0.5.3 h1:QlWt0KvWT0lq8MFppF9tsJGF+ynG7ztc2KIPhzRGk7s= +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/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI= +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.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= +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/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-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/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/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/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/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/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= -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/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= @@ -362,34 +434,53 @@ 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/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-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= +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/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/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y= +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/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-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= +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/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/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -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/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 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= @@ -400,184 +491,251 @@ 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 h1:VeRdUYdCw49yizlSbMEn2SZ+gT+3IUKx8BqxyQdz+BY= +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.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/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +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 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/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/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +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 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +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 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= +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/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/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= +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/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +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/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +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/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/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= +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= 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/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= +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= 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.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 h1:f7QxSmP7meCtoAmiKZogvVbLInT+CZx6Px6K5rYsJZo= github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= +github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc= +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= -github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8/go.mod h1:S1cAa98KMFv4Sa8SbJ6ZtvOmf0VlgH0QJ1gXI0lBfBY= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +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.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.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 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= +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-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 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/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 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= 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 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-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= +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 h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -589,88 +747,153 @@ 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-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-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= +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-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= +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 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-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 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= -google.golang.org/api v0.0.0-20180829000535-087779f1d2c9/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= +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/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= +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 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= +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 h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4= +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.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.21.1/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= 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= 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= 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/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +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.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 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= +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 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 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/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{ 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/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 6d1b771bf4..b6e4618bb5 100644 --- a/protocol/dubbo/client.go +++ b/protocol/dubbo/client.go @@ -25,8 +25,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" @@ -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 create a new Response. -func NewResponse(reply interface{}, atta map[string]string) *Response { +// NewResponse creates a new Response. +func NewResponse(reply interface{}, atta map[string]interface{}) *Response { return &Response{ reply: reply, atta: atta, @@ -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}, "."), "") @@ -239,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 ( @@ -322,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 1f7d107544..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 @@ -54,13 +57,13 @@ const ( // dubbo package //////////////////////////////////////////// -// SequenceType ... +// SequenceType sequence type type SequenceType int64 -// DubboPackage ... +// 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 { @@ -82,15 +85,15 @@ func (p *DubboPackage) Marshal() (*bytes.Buffer, error) { return bytes.NewBuffer(pkg), nil } -// Unmarshal dncode 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() - 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/config.go b/protocol/dubbo/config.go index 635d12109a..b47ec1cc34 100644 --- a/protocol/dubbo/config.go +++ b/protocol/dubbo/config.go @@ -22,10 +22,13 @@ import ( ) 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 { @@ -178,9 +181,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 +201,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()) 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 4834459390..180fd176f9 100644 --- a/protocol/dubbo/listener.go +++ b/protocol/dubbo/listener.go @@ -27,9 +27,7 @@ import ( ) import ( - "github.com/apache/dubbo-go-hessian2" - "github.com/dubbogo/getty" - "github.com/opentracing/opentracing-go" + "github.com/apache/dubbo-getty" 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/pool.go b/protocol/dubbo/pool.go index c9f5e34fad..6a7d211b49 100644 --- a/protocol/dubbo/pool.go +++ b/protocol/dubbo/pool.go @@ -18,6 +18,7 @@ package dubbo import ( + "crypto/tls" "fmt" "math/rand" "net" @@ -27,12 +28,13 @@ import ( ) import ( - "github.com/dubbogo/getty" + "github.com/apache/dubbo-getty" perrors "github.com/pkg/errors" ) import ( "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config" ) type gettyRPCClient struct { @@ -53,15 +55,31 @@ 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 + sslEnabled bool + ) + sslEnabled = pool.sslEnabled + if sslEnabled { + gettyClient = getty.NewTCPClient( + 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 { + 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 @@ -94,16 +112,34 @@ 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 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())) } @@ -288,9 +324,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/readwriter.go b/protocol/dubbo/readwriter.go index 9cc7ea25cd..a7b37aae76 100644 --- a/protocol/dubbo/readwriter.go +++ b/protocol/dubbo/readwriter.go @@ -23,8 +23,7 @@ import ( ) import ( - "github.com/apache/dubbo-go-hessian2" - "github.com/dubbogo/getty" + "github.com/apache/dubbo-getty" 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/dubbo/server.go b/protocol/dubbo/server.go index 8de353a0b3..4ad4796c54 100644 --- a/protocol/dubbo/server.go +++ b/protocol/dubbo/server.go @@ -18,18 +18,20 @@ package dubbo import ( + "crypto/tls" "fmt" "net" ) import ( - "github.com/dubbogo/getty" + "github.com/apache/dubbo-getty" "github.com/dubbogo/gost/sync" "gopkg.in/yaml.v2" ) 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" ) @@ -126,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("server 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())) } @@ -163,9 +178,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 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/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.go b/protocol/invocation.go index ba5949794c..452f619e2d 100644 --- a/protocol/invocation.go +++ b/protocol/invocation.go @@ -34,13 +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 interface{}) // Invoker gets the invoker in current context. Invoker() Invoker } diff --git a/protocol/invocation/rpcinvocation.go b/protocol/invocation/rpcinvocation.go index b8b5b50970..35d12965e8 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 @@ -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,11 +99,11 @@ 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 } -// 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() @@ -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 } @@ -157,6 +171,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 } @@ -219,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/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/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/protocol/jsonrpc/server.go b/protocol/jsonrpc/server.go index aa458a1614..9755a481fd 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) } @@ -345,7 +343,7 @@ func serveRequest(ctx context.Context, 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/protocolwrapper/protocol_filter_wrapper.go b/protocol/protocolwrapper/protocol_filter_wrapper.go index cba1d5d5bc..4b2702b99f 100644 --- a/protocol/protocolwrapper/protocol_filter_wrapper.go +++ b/protocol/protocolwrapper/protocol_filter_wrapper.go @@ -68,25 +68,23 @@ func (pfw *ProtocolFilterWrapper) Destroy() { } func buildInvokerChain(invoker protocol.Invoker, key string) protocol.Invoker { - filtName := invoker.GetUrl().GetParam(key, "") - if filtName == "" { + filterName := invoker.GetUrl().GetParam(key, "") + if filterName == "" { return invoker } - filtNames := strings.Split(filtName, ",") - next := invoker + 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 } -// GetProtocol ... +// nolint func GetProtocol() protocol.Protocol { return &ProtocolFilterWrapper{} } @@ -95,30 +93,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..359506a842 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, "") @@ -46,5 +49,4 @@ func (re *RestExporter) Unexport() { if err != nil { logger.Errorf("[RestExporter.Unexport] error: %v", err) } - return } 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/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/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..b1e66063bf --- /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() +} diff --git a/protocol/result.go b/protocol/result.go index 2e7a6e492a..a36b16d1cc 100644 --- a/protocol/result.go +++ b/protocol/result.go @@ -28,22 +28,23 @@ 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{} } ///////////////////////////// -// Result Impletment of RPC +// Result Implement of RPC ///////////////////////////// // 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/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) 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() 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/registry/directory/directory.go b/registry/directory/directory.go index 2fbf9410f7..5d4a890c6e 100644 --- a/registry/directory/directory.go +++ b/registry/directory/directory.go @@ -18,10 +18,14 @@ package directory import ( + "fmt" + "net/url" + "os" "sync" ) import ( + gxnet "github.com/dubbogo/gost/net" perrors "github.com/pkg/errors" "go.uber.org/atomic" ) @@ -29,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" @@ -55,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 @@ -75,6 +81,15 @@ func NewRegistryDirectory(url *common.URL, registry registry.Registry) (cluster. serviceType: url.SubURL.Service(), registry: registry, } + + dir.consumerURL = dir.getConsumerUrl(url.SubURL) + + 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) + } + dir.consumerConfigurationListener = newConsumerConfigurationListener(dir) go dir.subscribe(url.SubURL) @@ -89,69 +104,120 @@ 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(events ...*registry.ServiceEvent) { + go dir.refreshInvokers(events...) } -// update the cacheServices and subscribe service from registry -func (dir *RegistryDirectory) update(res *registry.ServiceEvent) { - if res == nil { - return +// 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 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 + }) } - logger.Debugf("registry update, result{%s}", res) - logger.Debugf("update service name: %s!", res.Service) - dir.refreshInvokers(res) -} - -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 + for _, event := range events { + logger.Debugf("registry update, result{%s}", event) + 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 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 - dir.listenerLock.Unlock() - // After dir.cacheInvokers is updated,destroy the oldInvoker - // Ensure that no request will enter the oldInvoker - if oldInvoker != nil { - oldInvoker.Destroy() + 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) + } + } + 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 { @@ -197,11 +263,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 @@ -232,6 +302,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 { @@ -251,7 +327,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.consumerURL, invocation) } // IsAvailable whether the directory is available @@ -287,6 +363,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) @@ -312,7 +406,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 { @@ -338,5 +433,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/etcdv3/listener_test.go b/registry/etcdv3/listener_test.go index f27e7ce8ba..1cf06d17dc 100644 --- a/registry/etcdv3/listener_test.go +++ b/registry/etcdv3/listener_test.go @@ -24,8 +24,8 @@ import ( ) import ( + "github.com/apache/dubbo-getty" "github.com/coreos/etcd/embed" - "github.com/dubbogo/getty" "github.com/stretchr/testify/suite" ) @@ -63,7 +63,6 @@ func (suite *RegistryTestSuite) SetupSuite() { } suite.etcd = e - return } // stop etcd server diff --git a/registry/etcdv3/service_discovery.go b/registry/etcdv3/service_discovery.go index 10396049fb..dceaa99df8 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" ) @@ -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/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/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/registry/nacos/listener.go b/registry/nacos/listener.go index 36f733df5a..cf6a73d38f 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 exsit 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 + // 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 exsit 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) } } @@ -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 { diff --git a/registry/nacos/registry.go b/registry/nacos/registry.go index 51d3e2f56a..411090820c 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 } diff --git a/registry/nacos/service_discovery_test.go b/registry/nacos/service_discovery_test.go index 720c44a6f9..119be0b3aa 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 @@ -100,23 +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) - page := serviceDiscovry.GetHealthyInstancesByPage(serviceName, 0, 10, true) + //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()) assert.Equal(t, 10, page.GetPageSize()) assert.Equal(t, 1, page.GetDataSize()) @@ -130,12 +136,13 @@ 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) + time.Sleep(5 * 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())) @@ -145,11 +152,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{ServiceName: serviceName}) assert.Nil(t, err) } diff --git a/registry/protocol/protocol.go b/registry/protocol/protocol.go index 4c669b2cee..d313e482a7 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,10 +102,9 @@ 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() { + for k := range url.GetParams() { if strings.HasPrefix(k, ".") { removeSet.Add(k) } @@ -117,6 +118,18 @@ 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 { + 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 { var registryUrl = url @@ -127,7 +140,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 +150,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 +163,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 @@ -178,6 +189,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) } @@ -191,7 +203,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!") @@ -204,7 +216,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) { @@ -216,7 +227,6 @@ func (proto *registryProtocol) reExport(invoker protocol.Invoker, newUrl *common proto.bounds.Delete(key) proto.Export(wrappedNewInvoker) // TODO: unregister & unsubscribe - } } @@ -232,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 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() @@ -334,14 +349,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() { @@ -355,7 +368,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 @@ -373,7 +386,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/registry.go b/registry/registry.go index bb09ead7ef..2225d2c1fc 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -30,38 +30,49 @@ 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. + + // 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 // 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 relace mode1 in dubbogo version v1.1.0 - //mode2 : callback mode, subscribe with notify(notify listener). + // 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 + // 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 } // nolint type NotifyListener interface { - // Notify supports notifications on the service interface and the dimension of the data type. - Notify(*ServiceEvent) + // 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) } // Listener Deprecated! diff --git a/registry/registry_factory.go b/registry/registry_factory.go new file mode 100644 index 0000000000..caefbce2eb --- /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_holder.go b/registry/service_discovery_holder.go new file mode 100644 index 0000000000..3bcf72612a --- /dev/null +++ b/registry/service_discovery_holder.go @@ -0,0 +1,25 @@ +/* + * 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 + +// ServiceDiscoveryHolder we can get a service discovery +// it always be a service discovery registry +type ServiceDiscoveryHolder 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..7576804eb5 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 { @@ -675,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) } } 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/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/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) } diff --git a/remoting/etcdv3/client.go b/remoting/etcdv3/client.go index 7632a7cd04..4e7436e445 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) } @@ -145,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 } @@ -171,7 +166,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 +178,6 @@ func NewClient(name string, endpoints []string, timeout time.Duration, heartbeat } c := &Client{ - name: name, timeout: timeout, endpoints: endpoints, @@ -205,7 +198,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 +209,6 @@ func (c *Client) clean() { } func (c *Client) stop() bool { - select { case <-c.exit: return true @@ -229,7 +220,6 @@ func (c *Client) stop() bool { // nolint func (c *Client) Close() { - if c == nil { return } @@ -241,15 +231,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 +251,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 +276,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 +287,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 +304,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 +316,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 +341,6 @@ func (c *Client) get(k string) (string, error) { // nolint func (c *Client) CleanKV() error { - c.lock.RLock() defer c.lock.RUnlock() @@ -378,15 +349,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 +369,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 +390,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 +401,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 +416,15 @@ 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 + return perrors.WithMessage(err, "put k/v with lease") } // nolint @@ -481,92 +441,54 @@ func (c *Client) Valid() bool { } c.lock.RLock() - if c.rawClient == nil { - c.lock.RUnlock() - return false - } - c.lock.RUnlock() - return true + defer c.lock.RUnlock() + return c.rawClient != nil } // 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 + 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) - if err != nil { - return perrors.WithMessagef(err, "Update k/v (key: %s value %s)", k, v) - } - return nil + 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) - if err != nil { - return perrors.WithMessagef(err, "delete k/v (key %s)", k) - } - - return nil + 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) - if err != nil { - return perrors.WithMessagef(err, "keepalive kv (key %s)", k) - } - - return nil + 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) - if err != nil { - return nil, nil, perrors.WithMessagef(err, "get key children (key %s)", k) - } - return kList, vList, nil + 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) - if err != nil { - return "", perrors.WithMessagef(err, "get key value (key %s)", k) - } - - return v, nil + 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) - if err != nil { - return nil, perrors.WithMessagef(err, "watch prefix (key %s)", k) - } - return wc, nil + 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) - if err != nil { - return nil, perrors.WithMessagef(err, "watch prefix (key %s)", prefix) - } - return wc, nil + return wc, perrors.WithMessagef(err, "watch prefix (key %s)", prefix) } 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/kubernetes/listener_test.go b/remoting/kubernetes/listener_test.go index 1f398485b2..0b05b6e6e0 100644 --- a/remoting/kubernetes/listener_test.go +++ b/remoting/kubernetes/listener_test.go @@ -19,6 +19,7 @@ package kubernetes import ( "testing" + "time" ) import ( @@ -87,6 +88,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 { 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/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) } diff --git a/remoting/zookeeper/client.go b/remoting/zookeeper/client.go index 4ca34a6aec..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 @@ -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 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 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 9a4874db24..b6c6d78106 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" ) @@ -37,6 +37,10 @@ import ( "github.com/apache/dubbo-go/remoting" ) +var ( + defaultTTL = 15 * time.Minute +) + // nolint type ZkEventListener struct { client *ZookeeperClient @@ -197,10 +201,20 @@ 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 + 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, using default value %v instead", err, defaultTTL) + } + } defer close(event) for { // get current children for a zkPath @@ -302,18 +316,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(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) + 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 } + } } 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-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/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 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: 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")