From cefda66da7e0f97547a7142d8499e1b822782e18 Mon Sep 17 00:00:00 2001 From: foghost Date: Mon, 29 Apr 2024 14:38:33 +0800 Subject: [PATCH] add metadata unit test (#2665) --- global/application_config.go | 19 +- metadata/client.go | 41 +- metadata/client_test.go | 280 +++++++++++++ metadata/info/metadata_info.go | 29 +- metadata/info/metadata_info_test.go | 94 ++++- .../mapping/metadata/service_name_mapping.go | 2 - .../metadata/service_name_mapping_test.go | 182 +++++++++ metadata/metadata.go | 18 +- metadata/metadata_service.go | 34 +- metadata/metadata_service_test.go | 377 ++++++++++++++++++ metadata/metadata_test.go | 141 +++++++ metadata/options.go | 37 +- metadata/report/report_factory.go | 2 - metadata/report_instance.go | 33 +- metadata/report_instance_test.go | 264 ++++++++++++ options.go | 13 +- .../service_instance_host_port_customizer.go | 2 +- 17 files changed, 1443 insertions(+), 125 deletions(-) create mode 100644 metadata/client_test.go create mode 100644 metadata/mapping/metadata/service_name_mapping_test.go create mode 100644 metadata/metadata_service_test.go create mode 100644 metadata/metadata_test.go create mode 100644 metadata/report_instance_test.go diff --git a/global/application_config.go b/global/application_config.go index dbb35a9acc..977899753d 100644 --- a/global/application_config.go +++ b/global/application_config.go @@ -44,14 +44,15 @@ func (c *ApplicationConfig) Clone() *ApplicationConfig { } return &ApplicationConfig{ - Organization: c.Organization, - Name: c.Name, - Module: c.Module, - Group: c.Group, - Version: c.Version, - Owner: c.Owner, - Environment: c.Environment, - MetadataType: c.MetadataType, - Tag: c.Tag, + Organization: c.Organization, + Name: c.Name, + Module: c.Module, + Group: c.Group, + Version: c.Version, + Owner: c.Owner, + Environment: c.Environment, + MetadataType: c.MetadataType, + Tag: c.Tag, + MetadataServicePort: c.MetadataServicePort, } } diff --git a/metadata/client.go b/metadata/client.go index 6b9120e1ea..a3e7dd425f 100644 --- a/metadata/client.go +++ b/metadata/client.go @@ -20,7 +20,6 @@ package metadata import ( "context" "encoding/json" - "time" ) import ( @@ -37,47 +36,33 @@ import ( "dubbo.apache.org/dubbo-go/v3/registry" ) -const metadataProxyDefaultTimeout = 5000 +const defaultTimeout = "5s" // s -// GetMetadataFromMetadataReport test depends on dubbo protocol, if dubbo not dependent on config package, can move to metadata dir func GetMetadataFromMetadataReport(revision string, instance registry.ServiceInstance) (*info.MetadataInfo, error) { report := GetMetadataReport() + if report == nil { + return nil, perrors.New("no metadata report instance found,please check ") + } return report.GetAppMetadata(instance.GetServiceName(), revision) } func GetMetadataFromRpc(revision string, instance registry.ServiceInstance) (*info.MetadataInfo, error) { - service, destroy, err := createRpcClient(instance) - if err != nil { - return nil, err - } - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(metadataProxyDefaultTimeout)) - defer cancel() - defer destroy() - return service.GetMetadataInfo(ctx, revision) -} - -type remoteMetadataService struct { - GetMetadataInfo func(context context.Context, revision string) (*info.MetadataInfo, error) `dubbo:"getMetadataInfo"` -} - -func createRpcClient(instance registry.ServiceInstance) (*remoteMetadataService, func(), error) { params := getMetadataServiceUrlParams(instance.GetMetadata()[constant.MetadataServiceURLParamsPropertyName]) url := buildMetadataServiceURL(instance.GetServiceName(), instance.GetHost(), params) - return createRpcClientByUrl(url) -} - -func createRpcClientByUrl(url *common.URL) (*remoteMetadataService, func(), error) { + url.SetParam(constant.TimeoutKey, defaultTimeout) rpcService := &remoteMetadataService{} invoker := extension.GetProtocol(constant.Dubbo).Refer(url) if invoker == nil { - return nil, nil, perrors.New("create invoker error, can not connect to the metadata report server: " + url.Ip + ":" + url.Port) + return nil, perrors.New("create invoker error, can not connect to the metadata report server: " + url.Ip + ":" + url.Port) } proxy := extension.GetProxyFactory(constant.DefaultKey).GetProxy(invoker, url) proxy.Implement(rpcService) - destroy := func() { - invoker.Destroy() - } - return rpcService, destroy, nil + defer invoker.Destroy() + return rpcService.GetMetadataInfo(context.TODO(), revision) +} + +type remoteMetadataService struct { + GetMetadataInfo func(context context.Context, revision string) (*info.MetadataInfo, error) `dubbo:"getMetadataInfo"` } // buildMetadataServiceURL will use standard format to build the metadata service url. @@ -108,7 +93,7 @@ func getMetadataServiceUrlParams(jsonStr string) map[string]string { if len(jsonStr) > 0 { err := json.Unmarshal([]byte(jsonStr), &res) if err != nil { - logger.Errorf("could not parse the metadata service url parameters to map", err) + logger.Errorf("could not parse the metadata service url parameters '%s' to map", jsonStr) } } return res diff --git a/metadata/client_test.go b/metadata/client_test.go new file mode 100644 index 0000000000..aa9fe7691b --- /dev/null +++ b/metadata/client_test.go @@ -0,0 +1,280 @@ +/* + * 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 metadata + +import ( + "context" + "testing" +) + +import ( + "github.com/pkg/errors" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +import ( + "dubbo.apache.org/dubbo-go/v3/common" + "dubbo.apache.org/dubbo-go/v3/common/constant" + "dubbo.apache.org/dubbo-go/v3/common/extension" + "dubbo.apache.org/dubbo-go/v3/metadata/info" + "dubbo.apache.org/dubbo-go/v3/protocol" + _ "dubbo.apache.org/dubbo-go/v3/proxy/proxy_factory" + "dubbo.apache.org/dubbo-go/v3/registry" +) + +var ( + ins = ®istry.DefaultServiceInstance{ + ID: "1", + Metadata: map[string]string{ + constant.MetadataServiceURLParamsPropertyName: `{ + "application": "dubbo-go", + "group": "BDTService", + "port": "64658", + "protocol": "dubbo", + "version": "1.0.0" + }`, + }, + Host: "dubbo.io", + ServiceName: "dubbo-app", + } + metadataInfo = &info.MetadataInfo{ + App: "dubbo-app", + } +) + +func TestGetMetadataFromMetadataReport(t *testing.T) { + t.Run("no report instance", func(t *testing.T) { + _, err := GetMetadataFromMetadataReport("1", ins) + assert.NotNil(t, err) + }) + mockReport := new(mockMetadataReport) + defer mockReport.AssertExpectations(t) + instances["default"] = mockReport + t.Run("normal", func(t *testing.T) { + mockReport.On("GetAppMetadata").Return(metadataInfo, nil).Once() + got, err := GetMetadataFromMetadataReport("1", ins) + assert.Nil(t, err) + assert.Equal(t, metadataInfo, got) + }) + t.Run("error", func(t *testing.T) { + mockReport.On("GetAppMetadata").Return(metadataInfo, errors.New("mock error")).Once() + _, err := GetMetadataFromMetadataReport("1", ins) + assert.NotNil(t, err) + }) +} + +func TestGetMetadataFromRpc(t *testing.T) { + mockInvoker := new(mockInvoker) + defer mockInvoker.AssertExpectations(t) + mockProtocol := new(mockProtocol) + defer mockProtocol.AssertExpectations(t) + extension.SetProtocol("dubbo", func() protocol.Protocol { + return mockProtocol + }) + + result := &protocol.RPCResult{ + Attrs: map[string]interface{}{}, + Err: nil, + Rest: metadataInfo, + } + t.Run("normal", func(t *testing.T) { + mockProtocol.On("Refer").Return(mockInvoker).Once() + mockInvoker.On("Invoke").Return(result).Once() + mockInvoker.On("Destroy").Once() + metadata, err := GetMetadataFromRpc("111", ins) + assert.Nil(t, err) + assert.Equal(t, metadata, result.Rest) + }) + t.Run("refer error", func(t *testing.T) { + mockProtocol.On("Refer").Return(nil).Once() + _, err := GetMetadataFromRpc("111", ins) + assert.NotNil(t, err) + }) + t.Run("invoke timeout", func(t *testing.T) { + mockProtocol.On("Refer").Return(mockInvoker).Once() + mockInvoker.On("Invoke").Return(&protocol.RPCResult{ + Attrs: map[string]interface{}{}, + Err: errors.New("timeout error"), + Rest: metadataInfo, + }).Once() + mockInvoker.On("Destroy").Once() + _, err := GetMetadataFromRpc("111", ins) + assert.NotNil(t, err) + }) +} + +func Test_buildMetadataServiceURL(t *testing.T) { + type args struct { + serviceName string + host string + params map[string]string + } + tests := []struct { + name string + args args + want *common.URL + }{ + { + name: "normal", + args: args{ + serviceName: "dubbo-app", + host: "dubbo.io", + params: map[string]string{ + constant.ProtocolKey: "dubbo", + constant.PortKey: "3000", + }, + }, + want: common.NewURLWithOptions( + common.WithIp("dubbo.io"), + common.WithProtocol("dubbo"), + common.WithPath(constant.MetadataServiceName), + common.WithProtocol("dubbo"), + common.WithPort("3000"), + common.WithParams(map[string][]string{ + constant.ProtocolKey: {"dubbo"}, + constant.PortKey: {"3000"}, + }), + common.WithParamsValue(constant.GroupKey, "dubbo-app"), + common.WithParamsValue(constant.InterfaceKey, constant.MetadataServiceName), + ), + }, + { + name: "no protocol", + args: args{ + serviceName: "dubbo-app", + host: "dubbo.io", + params: map[string]string{}, + }, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, buildMetadataServiceURL(tt.args.serviceName, tt.args.host, tt.args.params), "buildMetadataServiceURL(%v, %v, %v)", tt.args.serviceName, tt.args.host, tt.args.params) + }) + } +} + +func Test_getMetadataServiceUrlParams(t *testing.T) { + type args struct { + jsonStr string + } + tests := []struct { + name string + args args + want map[string]string + }{ + { + name: "normal", + args: args{ + jsonStr: `{ + "application": "BDTService", + "group": "BDTService", + "port": "64658", + "protocol": "dubbo", + "release": "dubbo-golang-3.0.0", + "timestamp": "1713432877", + "version": "1.0.0" + }`, + }, + want: map[string]string{ + "application": "BDTService", + "group": "BDTService", + "port": "64658", + "protocol": "dubbo", + "release": "dubbo-golang-3.0.0", + "timestamp": "1713432877", + "version": "1.0.0", + }, + }, + { + name: "wrong format", + args: args{ + jsonStr: "xxx", + }, + want: map[string]string{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, getMetadataServiceUrlParams(tt.args.jsonStr), "getMetadataServiceUrlParams(%v)", tt.args.jsonStr) + }) + } +} + +type mockProtocol struct { + mock.Mock +} + +func (m *mockProtocol) Export(invoker protocol.Invoker) protocol.Exporter { + args := m.Called() + return args.Get(0).(protocol.Exporter) +} + +func (m *mockProtocol) Refer(url *common.URL) protocol.Invoker { + args := m.Called() + if args.Get(0) == nil { + return nil + } + return args.Get(0).(protocol.Invoker) +} + +func (m *mockProtocol) Destroy() { +} + +type mockInvoker struct { + mock.Mock +} + +func (m *mockInvoker) GetURL() *common.URL { + return nil +} + +func (m *mockInvoker) IsAvailable() bool { + return true +} + +func (m *mockInvoker) Destroy() { + m.Called() +} + +func (m *mockInvoker) Invoke(ctx context.Context, inv protocol.Invocation) protocol.Result { + args := m.Mock.Called() + meta := args.Get(0).(protocol.Result).Result().(*info.MetadataInfo) + reply := inv.Reply().(*info.MetadataInfo) + reply.App = meta.App + reply.Tag = meta.Tag + reply.Revision = meta.Revision + reply.Services = meta.Services + return args.Get(0).(protocol.Result) +} + +type mockExporter struct { + mock.Mock +} + +func (m *mockExporter) GetInvoker() protocol.Invoker { + args := m.Called() + return args.Get(0).(protocol.Invoker) +} + +func (m *mockExporter) UnExport() { + m.Called() +} diff --git a/metadata/info/metadata_info.go b/metadata/info/metadata_info.go index 471a97d81e..126d27c91d 100644 --- a/metadata/info/metadata_info.go +++ b/metadata/info/metadata_info.go @@ -22,6 +22,7 @@ import ( "hash/crc32" "net/url" "sort" + "strconv" "strings" ) @@ -66,8 +67,18 @@ type MetadataInfo struct { subscribedServiceURLs map[string][]*common.URL `hessian:"-"` // client subscribed service urls } +func NewAppMetadataInfo(app string) *MetadataInfo { + return NewMetadataInfo(app, "") +} + func NewMetadataInfo(app, tag string) *MetadataInfo { - return NewMetadataInfoWithParams(app, "", make(map[string]*ServiceInfo)) + return &MetadataInfo{ + App: app, + Tag: tag, + Services: make(map[string]*ServiceInfo), + exportedServiceURLs: make(map[string][]*common.URL), + subscribedServiceURLs: make(map[string][]*common.URL), + } } func NewMetadataInfoWithParams(app string, revision string, services map[string]*ServiceInfo) *MetadataInfo { @@ -207,9 +218,9 @@ type ServiceInfo struct { URL *common.URL `json:"-" hessian:"-"` } -// nolint func NewServiceInfoWithURL(url *common.URL) *ServiceInfo { service := NewServiceInfo(url.Service(), url.Group(), url.Version(), url.Protocol, url.Path, nil) + service.Port, _ = strconv.Atoi(url.Port) service.URL = url // TODO includeKeys load dynamic p := make(map[string]string, 8) @@ -231,7 +242,6 @@ func NewServiceInfoWithURL(url *common.URL) *ServiceInfo { return service } -// nolint func NewServiceInfo(name, group, version, protocol, path string, params map[string]string) *ServiceInfo { serviceKey := common.ServiceKey(name, group, version) matchKey := common.MatchKey(serviceKey, protocol) @@ -256,16 +266,13 @@ func (si *ServiceInfo) GetMethods() []string { return strings.Split(s, ",") } -// nolint func (si *ServiceInfo) GetParams() url.Values { v := url.Values{} - methodNames := si.Params[constant.MethodsKey] - if len(methodNames) == 0 { - return v - } methods := gxset.NewSet() - for _, method := range strings.Split(si.Params[constant.MethodsKey], ",") { - methods.Add(method) + if methodNames, ok := si.Params[constant.MethodsKey]; ok { + for _, method := range strings.Split(methodNames, ",") { + methods.Add(method) + } } for k, p := range si.Params { ms := strings.Index(k, ".") @@ -278,7 +285,6 @@ func (si *ServiceInfo) GetParams() url.Values { return v } -// nolint func (si *ServiceInfo) GetMatchKey() string { if si.MatchKey != "" { return si.MatchKey @@ -288,7 +294,6 @@ func (si *ServiceInfo) GetMatchKey() string { return si.MatchKey } -// nolint func (si *ServiceInfo) GetServiceKey() string { if si.ServiceKey != "" { return si.ServiceKey diff --git a/metadata/info/metadata_info_test.go b/metadata/info/metadata_info_test.go index 41889bfdc6..94cd6e0256 100644 --- a/metadata/info/metadata_info_test.go +++ b/metadata/info/metadata_info_test.go @@ -19,6 +19,8 @@ package info import ( "encoding/json" + "strconv" + "strings" "testing" ) @@ -32,6 +34,19 @@ import ( "dubbo.apache.org/dubbo-go/v3/common" ) +var ( + serviceUrl = common.NewURLWithOptions( + common.WithProtocol("tri"), + common.WithIp("127.0.0.1"), + common.WithPort("20035"), + common.WithPath("/org.apache.dubbo.samples.proto.GreetService"), + common.WithInterface("org.apache.dubbo.samples.proto.GreetService"), + common.WithMethods([]string{"Greet", "SayHello"}), + common.WithParamsValue("loadbalance", "random"), + common.WithParamsValue("methods.Greet.timeout", "1000"), + ) +) + func TestMetadataInfoAddService(t *testing.T) { metadataInfo := &MetadataInfo{ Services: make(map[string]*ServiceInfo), @@ -42,11 +57,11 @@ func TestMetadataInfoAddService(t *testing.T) { url, _ := common.NewURL("dubbo://127.0.0.1:20000?application=foo&category=providers&check=false&dubbo=dubbo-go+v1.5.0&interface=com.foo.Bar&methods=GetPetByID%2CGetPetTypes&organization=Apache&owner=foo&revision=1.0.0&side=provider&version=1.0.0") metadataInfo.AddService(url) assert.True(t, len(metadataInfo.Services) > 0) - assert.True(t, len(metadataInfo.exportedServiceURLs) > 0) + assert.True(t, len(metadataInfo.GetExportedServiceURLs()) > 0) metadataInfo.RemoveService(url) assert.True(t, len(metadataInfo.Services) == 0) - assert.True(t, len(metadataInfo.exportedServiceURLs) == 0) + assert.True(t, len(metadataInfo.GetExportedServiceURLs()) == 0) } func TestHessian(t *testing.T) { @@ -67,3 +82,78 @@ func TestHessian(t *testing.T) { metaJson, _ := json.Marshal(metadataInfo) assert.Equal(t, objJson, metaJson) } + +func TestMetadataInfoAddSubscribeURL(t *testing.T) { + info := NewMetadataInfo("dubbo", "tag") + info.AddSubscribeURL(serviceUrl) + assert.True(t, len(info.GetSubscribedURLs()) > 0) + info.RemoveSubscribeURL(serviceUrl) + assert.True(t, len(info.GetSubscribedURLs()) == 0) +} + +func TestMetadataInfoCalAndGetRevision(t *testing.T) { + metadata := NewAppMetadataInfo("dubbo") + assert.Equalf(t, "0", metadata.CalAndGetRevision(), "CalAndGetRevision()") + metadata.AddService(serviceUrl) + assert.True(t, metadata.CalAndGetRevision() != "0") + + v := metadata.Revision + assert.Equal(t, v, metadata.CalAndGetRevision(), "CalAndGetRevision() test cache") + + metadata = NewAppMetadataInfo("dubbo") + url1 := serviceUrl.Clone() + url1.Methods = []string{} + metadata.AddService(url1) + assert.True(t, metadata.CalAndGetRevision() != "0", "CalAndGetRevision() test empty methods") +} + +func TestNewMetadataInfo(t *testing.T) { + info := NewMetadataInfo("dubbo", "tag") + assert.Equal(t, info.App, "dubbo") + assert.Equal(t, info.Tag, "tag") +} + +func TestNewMetadataInfoWithParams(t *testing.T) { + info := NewMetadataInfoWithParams("dubbo", "", + map[string]*ServiceInfo{"org.apache.dubbo.samples.proto.GreetService": NewServiceInfoWithURL(serviceUrl)}) + assert.Equal(t, info.App, "dubbo") + assert.Equal(t, info.Revision, "") + assert.Equal(t, info.Services, map[string]*ServiceInfo{"org.apache.dubbo.samples.proto.GreetService": NewServiceInfoWithURL(serviceUrl)}) +} + +func TestNewServiceInfoWithURL(t *testing.T) { + info := NewServiceInfoWithURL(serviceUrl) + assert.True(t, info.URL == serviceUrl) + assert.Equal(t, info.Protocol, serviceUrl.Protocol) + assert.Equal(t, info.Name, serviceUrl.Interface()) + assert.Equal(t, info.Group, serviceUrl.Group()) + assert.Equal(t, info.Version, serviceUrl.Version()) + assert.Equal(t, strconv.Itoa(info.Port), serviceUrl.Port) + assert.Equal(t, info.Path, strings.TrimPrefix(serviceUrl.Path, "/")) + assert.Equal(t, info.Params["Greet.timeout"], "1000") +} + +func TestServiceInfoGetMethods(t *testing.T) { + service := NewServiceInfoWithURL(serviceUrl) + assert.Equal(t, service.GetMethods(), []string{"Greet", "SayHello"}) +} + +func TestServiceInfoGetParams(t *testing.T) { + service := NewServiceInfoWithURL(serviceUrl) + assert.Equal(t, service.GetParams()["loadbalance"], []string{"random"}) +} + +func TestServiceInfoGetMatchKey(t *testing.T) { + si := NewServiceInfoWithURL(serviceUrl) + matchKey := si.MatchKey + assert.Equal(t, si.GetMatchKey(), matchKey) + si.MatchKey = "" + assert.True(t, si.GetMatchKey() != "") + si.MatchKey = "" + si.ServiceKey = "" + assert.True(t, si.GetMatchKey() != "") +} + +func TestServiceInfoJavaClassName(t *testing.T) { + assert.Equalf(t, "org.apache.dubbo.metadata.MetadataInfo", NewAppMetadataInfo("dubbo").JavaClassName(), "JavaClassName()") +} diff --git a/metadata/mapping/metadata/service_name_mapping.go b/metadata/mapping/metadata/service_name_mapping.go index 95e588ef91..1346436b2c 100644 --- a/metadata/mapping/metadata/service_name_mapping.go +++ b/metadata/mapping/metadata/service_name_mapping.go @@ -23,7 +23,6 @@ import ( import ( gxset "github.com/dubbogo/gost/container/set" - "github.com/dubbogo/gost/log/logger" perrors "github.com/pkg/errors" ) @@ -81,7 +80,6 @@ func (d *ServiceNameMapping) Map(url *common.URL) error { } } if err != nil { - logger.Errorf("Failed registering mapping to remote, &v", err) return err } } diff --git a/metadata/mapping/metadata/service_name_mapping_test.go b/metadata/mapping/metadata/service_name_mapping_test.go new file mode 100644 index 0000000000..01b25f569b --- /dev/null +++ b/metadata/mapping/metadata/service_name_mapping_test.go @@ -0,0 +1,182 @@ +/* + * 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 metadata + +import ( + "errors" + "testing" +) + +import ( + gxset "github.com/dubbogo/gost/container/set" + "github.com/dubbogo/gost/gof/observer" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +import ( + "dubbo.apache.org/dubbo-go/v3/common" + "dubbo.apache.org/dubbo-go/v3/common/constant" + "dubbo.apache.org/dubbo-go/v3/common/extension" + "dubbo.apache.org/dubbo-go/v3/metadata" + "dubbo.apache.org/dubbo-go/v3/metadata/info" + "dubbo.apache.org/dubbo-go/v3/metadata/mapping" + "dubbo.apache.org/dubbo-go/v3/metadata/report" +) + +func TestGetNameMappingInstance(t *testing.T) { + ins := GetNameMappingInstance() + assert.NotNil(t, ins) +} + +func TestNoReportInstance(t *testing.T) { + ins := GetNameMappingInstance() + lis := &listener{} + serviceUrl := common.NewURLWithOptions( + common.WithInterface("org.apache.dubbo.samples.proto.GreetService"), + common.WithParamsValue(constant.ApplicationKey, "dubbo"), + ) + _, err := ins.Get(serviceUrl, lis) + assert.NotNil(t, err, "test Get no report instance") + err = ins.Map(serviceUrl) + assert.NotNil(t, err, "test Map with no report instance") + err = ins.Remove(serviceUrl) + assert.NotNil(t, err, "test Remove with no report instance") +} + +func TestServiceNameMappingGet(t *testing.T) { + ins := GetNameMappingInstance() + lis := &listener{} + serviceUrl := common.NewURLWithOptions( + common.WithInterface("org.apache.dubbo.samples.proto.GreetService"), + common.WithParamsValue(constant.ApplicationKey, "dubbo"), + ) + mockReport, err := initMock() + assert.Nil(t, err) + t.Run("test normal", func(t *testing.T) { + mockReport.On("GetServiceAppMapping").Return(gxset.NewSet("dubbo"), nil).Once() + apps, er := ins.Get(serviceUrl, lis) + assert.Nil(t, er) + assert.True(t, !apps.Empty()) + }) + t.Run("test error", func(t *testing.T) { + mockReport.On("GetServiceAppMapping").Return(gxset.NewSet(), errors.New("mock error")).Once() + _, err = ins.Get(serviceUrl, lis) + assert.NotNil(t, err) + }) + mockReport.AssertExpectations(t) +} + +func TestServiceNameMappingMap(t *testing.T) { + ins := GetNameMappingInstance() + serviceUrl := common.NewURLWithOptions( + common.WithInterface("org.apache.dubbo.samples.proto.GreetService"), + common.WithParamsValue(constant.ApplicationKey, "dubbo"), + ) + mockReport, err := initMock() + assert.Nil(t, err) + t.Run("test normal", func(t *testing.T) { + mockReport.On("RegisterServiceAppMapping").Return(nil).Once() + err = ins.Map(serviceUrl) + assert.Nil(t, err) + }) + t.Run("test error", func(t *testing.T) { + mockReport.On("RegisterServiceAppMapping").Return(errors.New("mock error")).Times(retryTimes) + err = ins.Map(serviceUrl) + assert.NotNil(t, err, "test mapping error") + }) + mockReport.AssertExpectations(t) +} + +func TestServiceNameMappingRemove(t *testing.T) { + ins := GetNameMappingInstance() + serviceUrl := common.NewURLWithOptions( + common.WithInterface("org.apache.dubbo.samples.proto.GreetService"), + common.WithParamsValue(constant.ApplicationKey, "dubbo"), + ) + mockReport, err := initMock() + assert.Nil(t, err) + t.Run("test normal", func(t *testing.T) { + mockReport.On("RemoveServiceAppMappingListener").Return(nil).Once() + err = ins.Remove(serviceUrl) + assert.Nil(t, err) + }) + t.Run("test error", func(t *testing.T) { + mockReport.On("RemoveServiceAppMappingListener").Return(errors.New("mock error")).Once() + err = ins.Remove(serviceUrl) + assert.NotNil(t, err) + }) + mockReport.AssertExpectations(t) +} + +func initMock() (*mockMetadataReport, error) { + metadataReport := new(mockMetadataReport) + extension.SetMetadataReportFactory("mock", func() report.MetadataReportFactory { + return metadataReport + }) + opts := metadata.NewReportOptions( + metadata.WithProtocol("mock"), + metadata.WithAddress("127.0.0.1"), + ) + err := opts.Init() + return metadataReport, err +} + +type listener struct { +} + +func (l listener) OnEvent(e observer.Event) error { + return nil +} + +func (l listener) Stop() { +} + +type mockMetadataReport struct { + mock.Mock +} + +func (m *mockMetadataReport) CreateMetadataReport(*common.URL) report.MetadataReport { + return m +} + +func (m *mockMetadataReport) GetAppMetadata(string, string) (*info.MetadataInfo, error) { + args := m.Called() + return args.Get(0).(*info.MetadataInfo), args.Error(1) +} + +func (m *mockMetadataReport) PublishAppMetadata(string, string, *info.MetadataInfo) error { + args := m.Called() + return args.Error(0) +} + +func (m *mockMetadataReport) RegisterServiceAppMapping(string, string, string) error { + args := m.Called() + return args.Error(0) +} + +func (m *mockMetadataReport) GetServiceAppMapping(string, string, mapping.MappingListener) (*gxset.HashSet, error) { + args := m.Called() + return args.Get(0).(*gxset.HashSet), args.Error(1) +} + +func (m *mockMetadataReport) RemoveServiceAppMappingListener(string, string) error { + args := m.Called() + return args.Error(0) +} diff --git a/metadata/metadata.go b/metadata/metadata.go index 6fbbfcd8ca..03077bbeb6 100644 --- a/metadata/metadata.go +++ b/metadata/metadata.go @@ -25,8 +25,8 @@ import ( ) var ( - metadataService MetadataService = &DefaultMetadataService{} - appMetadataInfoMap = make(map[string]*info.MetadataInfo) + registryMetadataInfo = make(map[string]*info.MetadataInfo) + metadataService MetadataService = &DefaultMetadataService{metadataMap: registryMetadataInfo} ) func GetMetadataService() MetadataService { @@ -34,25 +34,25 @@ func GetMetadataService() MetadataService { } func GetMetadataInfo(registryId string) *info.MetadataInfo { - return appMetadataInfoMap[registryId] + return registryMetadataInfo[registryId] } func AddService(registryId string, url *common.URL) { - if _, exist := appMetadataInfoMap[registryId]; !exist { - appMetadataInfoMap[registryId] = info.NewMetadataInfo( + if _, exist := registryMetadataInfo[registryId]; !exist { + registryMetadataInfo[registryId] = info.NewMetadataInfo( url.GetParam(constant.ApplicationKey, ""), url.GetParam(constant.ApplicationTagKey, ""), ) } - appMetadataInfoMap[registryId].AddService(url) + registryMetadataInfo[registryId].AddService(url) } func AddSubscribeURL(registryId string, url *common.URL) { - if _, exist := appMetadataInfoMap[registryId]; !exist { - appMetadataInfoMap[registryId] = info.NewMetadataInfo( + if _, exist := registryMetadataInfo[registryId]; !exist { + registryMetadataInfo[registryId] = info.NewMetadataInfo( url.GetParam(constant.ApplicationKey, ""), url.GetParam(constant.ApplicationTagKey, ""), ) } - appMetadataInfoMap[registryId].AddSubscribeURL(url) + registryMetadataInfo[registryId].AddSubscribeURL(url) } diff --git a/metadata/metadata_service.go b/metadata/metadata_service.go index 5a3591c54c..80eb159d51 100644 --- a/metadata/metadata_service.go +++ b/metadata/metadata_service.go @@ -40,8 +40,8 @@ import ( // version will be used by Version func const ( - version = "1.0.0" - allServiceInterfaces = "*" + version = "1.0.0" + allMatch = "*" ) // MetadataService is used to define meta data related behaviors @@ -58,12 +58,11 @@ type MetadataService interface { GetMetadataInfo(revision string) (*info.MetadataInfo, error) // GetMetadataServiceURL will return the url of metadata service GetMetadataServiceURL() (*common.URL, error) - // SetMetadataServiceURL exporter to set url of metadata service, will not be exported by exporter,cause no error return - SetMetadataServiceURL(*common.URL) } // DefaultMetadataService is store and query the metadata info in memory when each service registry type DefaultMetadataService struct { + metadataMap map[string]*info.MetadataInfo metadataUrl *common.URL } @@ -73,19 +72,16 @@ func (mts *DefaultMetadataService) SetMetadataServiceURL(url *common.URL) { // GetExportedURLs get all exported urls func (mts *DefaultMetadataService) GetExportedURLs(serviceInterface string, group string, version string, protocol string) ([]*common.URL, error) { - if allServiceInterfaces == serviceInterface { - return mts.GetExportedServiceURLs() - } all, err := mts.GetExportedServiceURLs() if err != nil { return nil, err } urls := make([]*common.URL, 0) for _, url := range all { - if url.GetParam(constant.InterfaceKey, "") == serviceInterface && - url.GetParam(constant.GroupKey, "") == group && - url.GetParam(constant.ProtocolKey, "") == protocol && - url.GetParam(constant.VersionKey, "") == version { + if (url.Interface() == serviceInterface || serviceInterface == allMatch) && + (url.Group() == group || group == allMatch) && + (url.Protocol == protocol || protocol == allMatch) && + (url.Version() == version || version == allMatch) { urls = append(urls, url) } } @@ -97,7 +93,7 @@ func (mts *DefaultMetadataService) GetMetadataInfo(revision string) (*info.Metad if revision == "" { return nil, nil } - for _, metadataInfo := range appMetadataInfoMap { + for _, metadataInfo := range mts.metadataMap { if metadataInfo.Revision == revision { return metadataInfo, nil } @@ -109,7 +105,7 @@ func (mts *DefaultMetadataService) GetMetadataInfo(revision string) (*info.Metad // GetExportedServiceURLs get exported service urls func (mts *DefaultMetadataService) GetExportedServiceURLs() ([]*common.URL, error) { urls := make([]*common.URL, 0) - for _, metadataInfo := range appMetadataInfoMap { + for _, metadataInfo := range mts.metadataMap { urls = append(urls, metadataInfo.GetExportedServiceURLs()...) } return urls, nil @@ -127,7 +123,7 @@ func (mts *DefaultMetadataService) GetMetadataServiceURL() (*common.URL, error) func (mts *DefaultMetadataService) GetSubscribedURLs() ([]*common.URL, error) { urls := make([]*common.URL, 0) - for _, metadataInfo := range appMetadataInfoMap { + for _, metadataInfo := range mts.metadataMap { urls = append(urls, metadataInfo.GetSubscribedURLs()...) } return urls, nil @@ -141,15 +137,15 @@ func (mts *DefaultMetadataService) MethodMapper() map[string]string { } } -// ServiceExporter is the ConfigurableMetadataServiceExporter which implement MetadataServiceExporter interface -type ServiceExporter struct { +// serviceExporter export MetadataService with dubbo protocol +type serviceExporter struct { opts *Options service MetadataService protocolExporter protocol.Exporter } // Export will export the metadataService -func (e *ServiceExporter) Export() error { +func (e *serviceExporter) Export() error { version, _ := e.service.Version() var port string if e.opts.port == 0 { @@ -181,7 +177,7 @@ func (e *ServiceExporter) Export() error { proxyFactory := extension.GetProxyFactory("") invoker := proxyFactory.GetInvoker(ivkURL) e.protocolExporter = extension.GetProtocol(ivkURL.Protocol).Export(invoker) - e.service.SetMetadataServiceURL(ivkURL) + e.service.(*DefaultMetadataService).SetMetadataServiceURL(ivkURL) logger.Infof("[Metadata Service] The MetadataService exports urls : %v ", ivkURL) return nil } @@ -199,6 +195,6 @@ func randomPort() string { } // UnExport will unExport the metadataService -func (e *ServiceExporter) UnExport() { +func (e *serviceExporter) UnExport() { e.protocolExporter.UnExport() } diff --git a/metadata/metadata_service_test.go b/metadata/metadata_service_test.go new file mode 100644 index 0000000000..0939955775 --- /dev/null +++ b/metadata/metadata_service_test.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 metadata + +import ( + "strconv" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "dubbo.apache.org/dubbo-go/v3/common" + "dubbo.apache.org/dubbo-go/v3/common/constant" + "dubbo.apache.org/dubbo-go/v3/common/extension" + "dubbo.apache.org/dubbo-go/v3/metadata/info" + "dubbo.apache.org/dubbo-go/v3/protocol" + _ "dubbo.apache.org/dubbo-go/v3/proxy/proxy_factory" +) + +var ( + url, _ = common.NewURL("dubbo://127.0.0.1:20000?application=foo&category=providers&check=false&dubbo=dubbo-go+v1.5.0&interface=com.foo.Bar&methods=GetPetByID%2CGetPetTypes&organization=Apache&owner=foo&revision=1.0.0&side=provider&version=1.0.0") +) + +func newMetadataMap() map[string]*info.MetadataInfo { + metadataInfo := info.NewAppMetadataInfo("dubbo-app") + metadataInfo.Revision = "1" + metadataInfo.AddService(url) + metadataInfo.AddSubscribeURL(url) + registryMetadataInfo["default"] = metadataInfo + return map[string]*info.MetadataInfo{ + "default": metadataInfo, + } +} + +func TestDefaultMetadataServiceGetExportedServiceURLs(t *testing.T) { + mts := &DefaultMetadataService{ + metadataMap: newMetadataMap(), + } + got, err := mts.GetExportedServiceURLs() + assert.Nil(t, err) + assert.True(t, len(got) == 1) + assert.Equal(t, url, got[0]) +} + +func TestDefaultMetadataServiceGetExportedURLs(t *testing.T) { + type args struct { + serviceInterface string + group string + version string + protocol string + } + tests := []struct { + name string + args args + want []*common.URL + }{ + { + name: "all exact", + args: args{ + serviceInterface: url.Interface(), + group: url.Group(), + version: url.Version(), + protocol: url.Protocol, + }, + want: []*common.URL{url}, + }, + { + name: "interface *", + args: args{ + serviceInterface: "*", + group: url.Group(), + version: url.Version(), + protocol: url.Protocol, + }, + want: []*common.URL{url}, + }, + { + name: "group *", + args: args{ + serviceInterface: url.Interface(), + group: "*", + version: url.Version(), + protocol: url.Protocol, + }, + want: []*common.URL{url}, + }, + { + name: "version *", + args: args{ + serviceInterface: url.Interface(), + group: url.Group(), + version: "*", + protocol: url.Protocol, + }, + want: []*common.URL{url}, + }, + { + name: "protocol *", + args: args{ + serviceInterface: url.Interface(), + group: url.Group(), + version: url.Version(), + protocol: "*", + }, + want: []*common.URL{url}, + }, + { + name: "all *", + args: args{ + serviceInterface: "*", + group: "*", + version: "*", + protocol: "*", + }, + want: []*common.URL{url}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mts := &DefaultMetadataService{ + metadataMap: newMetadataMap(), + } + got, err := mts.GetExportedURLs(tt.args.serviceInterface, tt.args.group, tt.args.version, tt.args.protocol) + assert.Nil(t, err) + assert.Equalf(t, tt.want, got, "GetExportedURLs(%v, %v, %v, %v)", tt.args.serviceInterface, tt.args.group, tt.args.version, tt.args.protocol) + }) + } +} + +func TestDefaultMetadataServiceGetMetadataInfo(t *testing.T) { + type args struct { + revision string + } + tests := []struct { + name string + args args + want *info.MetadataInfo + }{ + { + name: "normal", + args: args{ + revision: "1", + }, + want: newMetadataMap()["default"], + }, + { + name: "empty revision", + args: args{ + revision: "", + }, + want: nil, + }, + { + name: "revision not match", + args: args{ + revision: "2", + }, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mts := &DefaultMetadataService{ + metadataMap: newMetadataMap(), + } + got, err := mts.GetMetadataInfo(tt.args.revision) + assert.Nil(t, err) + assert.Equalf(t, tt.want, got, "GetMetadataInfo(%v)", tt.args.revision) + }) + } +} + +func TestDefaultMetadataServiceGetMetadataServiceURL(t *testing.T) { + type fields struct { + metadataUrl *common.URL + } + tests := []struct { + name string + fields fields + want *common.URL + }{ + { + name: "normal", + fields: fields{ + metadataUrl: url, + }, + want: url, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mts := &DefaultMetadataService{ + metadataUrl: tt.fields.metadataUrl, + } + got, err := mts.GetMetadataServiceURL() + assert.Nil(t, err) + assert.Equalf(t, tt.want, got, "GetMetadataServiceURL()") + }) + } +} + +func TestDefaultMetadataServiceGetSubscribedURLs(t *testing.T) { + tests := []struct { + name string + want []*common.URL + }{ + { + name: "normal", + want: []*common.URL{url}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mts := &DefaultMetadataService{ + metadataMap: newMetadataMap(), + } + got, err := mts.GetSubscribedURLs() + assert.Nil(t, err) + assert.Equalf(t, tt.want, got, "GetSubscribedURLs()") + }) + } +} + +func TestDefaultMetadataServiceMethodMapper(t *testing.T) { + tests := []struct { + name string + want map[string]string + }{ + { + name: "normal", + want: map[string]string{ + "GetExportedURLs": "getExportedURLs", + "GetMetadataInfo": "getMetadataInfo", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mts := &DefaultMetadataService{ + metadataMap: newMetadataMap(), + } + assert.Equalf(t, tt.want, mts.MethodMapper(), "MethodMapper()") + }) + } +} + +func TestDefaultMetadataServiceSetMetadataServiceURL(t *testing.T) { + type args struct { + url *common.URL + } + tests := []struct { + name string + args args + want *common.URL + }{ + { + name: "normal", + args: args{ + url: url, + }, + want: url, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mts := &DefaultMetadataService{ + metadataMap: map[string]*info.MetadataInfo{}, + } + mts.SetMetadataServiceURL(tt.args.url) + assert.Equal(t, tt.want, mts.metadataUrl) + }) + } +} + +func TestDefaultMetadataServiceVersion(t *testing.T) { + mts := &DefaultMetadataService{} + got, err := mts.Version() + assert.Nil(t, err) + assert.Equal(t, version, got) +} + +func Test_randomPort(t *testing.T) { + port := randomPort() + assert.True(t, port != "") +} + +func Test_serviceExporterExport(t *testing.T) { + mockExporter := new(mockExporter) + defer mockExporter.AssertExpectations(t) + mockProtocol := new(mockProtocol) + defer mockProtocol.AssertExpectations(t) + extension.SetProtocol("dubbo", func() protocol.Protocol { + return mockProtocol + }) + t.Run("normal", func(t *testing.T) { + port := randomPort() + p, err := strconv.Atoi(port) + assert.Nil(t, err) + opts := &Options{ + appName: "dubbo-app", + metadataType: constant.RemoteMetadataStorageType, + port: p, + } + mockProtocol.On("Export").Return(mockExporter).Once() + mockExporter.On("UnExport").Once() + e := &serviceExporter{ + opts: opts, + service: &DefaultMetadataService{}, + } + err = e.Export() + assert.Nil(t, err) + e.UnExport() + }) + // first t.Run has called commom.ServiceMap.Register ,second will fail + t.Run("get methods error", func(t *testing.T) { + port := randomPort() + p, err := strconv.Atoi(port) + assert.Nil(t, err) + opts := &Options{ + appName: "dubbo-app", + metadataType: constant.RemoteMetadataStorageType, + port: p, + } + e := &serviceExporter{ + opts: opts, + service: &DefaultMetadataService{}, + } + err = e.Export() + assert.NotNil(t, err) + }) + t.Run("port == 0", func(t *testing.T) { + opts := &Options{ + appName: "dubbo-app", + metadataType: constant.RemoteMetadataStorageType, + port: 0, + } + // UnRegister first otherwise will fail + err := common.ServiceMap.UnRegister(constant.MetadataServiceName, constant.DefaultProtocol, + common.ServiceKey(constant.MetadataServiceName, opts.appName, version)) + assert.Nil(t, err) + mockProtocol.On("Export").Return(mockExporter).Once() + mockExporter.On("UnExport").Once() + e := &serviceExporter{ + opts: opts, + service: &DefaultMetadataService{}, + } + err = e.Export() + assert.Nil(t, err) + e.UnExport() + }) +} + +func Test_serviceExporterUnExport(t *testing.T) { + mockExporter := new(mockExporter) + defer mockExporter.AssertExpectations(t) + serviceExporter := &serviceExporter{protocolExporter: mockExporter} + mockExporter.On("UnExport").Once() + serviceExporter.UnExport() +} diff --git a/metadata/metadata_test.go b/metadata/metadata_test.go new file mode 100644 index 0000000000..ae890773a3 --- /dev/null +++ b/metadata/metadata_test.go @@ -0,0 +1,141 @@ +/* + * 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 metadata + +import ( + "reflect" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "dubbo.apache.org/dubbo-go/v3/common" + "dubbo.apache.org/dubbo-go/v3/common/constant" + "dubbo.apache.org/dubbo-go/v3/metadata/info" +) + +func TestAddService(t *testing.T) { + type args struct { + registryId string + url *common.URL + } + tests := []struct { + name string + args args + }{ + { + name: "add", + args: args{ + registryId: "reg1", + url: common.NewURLWithOptions( + common.WithProtocol("dubbo"), + common.WithParamsValue(constant.ApplicationKey, "dubbo"), + common.WithParamsValue(constant.ApplicationTagKey, "v1"), + ), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + AddService(tt.args.registryId, tt.args.url) + assert.True(t, registryMetadataInfo[tt.args.registryId] != nil) + meta := registryMetadataInfo[tt.args.registryId] + meta.App = tt.args.url.GetParam(constant.ApplicationKey, "") + meta.Tag = tt.args.url.GetParam(constant.ApplicationTagKey, "") + assert.True(t, reflect.DeepEqual(meta.GetExportedServiceURLs()[0], tt.args.url)) + }) + } +} + +func TestAddSubscribeURL(t *testing.T) { + type args struct { + registryId string + url *common.URL + } + tests := []struct { + name string + args args + }{ + { + name: "addSub", + args: args{ + registryId: "reg2", + url: common.NewURLWithOptions( + common.WithProtocol("dubbo"), + common.WithParamsValue(constant.ApplicationKey, "dubbo"), + common.WithParamsValue(constant.ApplicationTagKey, "v1"), + ), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + AddSubscribeURL(tt.args.registryId, tt.args.url) + assert.True(t, registryMetadataInfo[tt.args.registryId] != nil) + meta := registryMetadataInfo[tt.args.registryId] + meta.App = tt.args.url.GetParam(constant.ApplicationKey, "") + meta.Tag = tt.args.url.GetParam(constant.ApplicationTagKey, "") + assert.True(t, reflect.DeepEqual(meta.GetSubscribedURLs()[0], tt.args.url)) + }) + } +} + +func TestGetMetadataInfo(t *testing.T) { + type args struct { + registryId string + } + tests := []struct { + name string + args args + want *info.MetadataInfo + }{ + { + name: "get", + args: args{ + registryId: "reg3", + }, + want: info.NewMetadataInfo("dubbo", "v2"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + registryMetadataInfo[tt.args.registryId] = tt.want + assert.Equalf(t, tt.want, GetMetadataInfo(tt.args.registryId), "GetMetadataInfo(%v)", tt.args.registryId) + }) + } +} + +func TestGetMetadataService(t *testing.T) { + tests := []struct { + name string + want MetadataService + }{ + { + name: "getMetadataService", + want: metadataService, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, GetMetadataService(), "GetMetadataService()") + }) + } +} diff --git a/metadata/options.go b/metadata/options.go index 30e866abee..5a5eb5b429 100644 --- a/metadata/options.go +++ b/metadata/options.go @@ -26,11 +26,13 @@ import ( import ( "github.com/dubbogo/gost/log/logger" + + perrors "github.com/pkg/errors" ) import ( + "dubbo.apache.org/dubbo-go/v3/common" "dubbo.apache.org/dubbo-go/v3/common/constant" - "dubbo.apache.org/dubbo-go/v3/common/extension" "dubbo.apache.org/dubbo-go/v3/global" ) @@ -62,7 +64,7 @@ func (opts *Options) Init() error { var err error exportOnce.Do(func() { if opts.metadataType != constant.RemoteMetadataStorageType { - exporter := &ServiceExporter{service: metadataService, opts: opts} + exporter := &serviceExporter{service: metadataService, opts: opts} defer func() { // TODO remove this recover func,this just to avoid some unit test failed,this will not happen in user side mostly // config test -> metadata exporter -> dubbo protocol/remoting -> config,cycle import will occur @@ -103,18 +105,33 @@ type ReportOptions struct { } func (opts *ReportOptions) Init() error { - fac := extension.GetMetadataReportFactory(opts.Protocol) - if fac == nil { - logger.Errorf("no metadata report factory of protocol %s found!", opts.Protocol) - return nil - } - url, err := toUrl(opts) + url, err := opts.toUrl() if err != nil { logger.Errorf("metadata report create error %v", err) return err } - instances[opts.registryId] = &DelegateMetadataReport{instance: fac.CreateMetadataReport(url)} - return nil + return addMetadataReport(opts.registryId, url) +} + +func (opts *ReportOptions) toUrl() (*common.URL, error) { + res, err := common.NewURL(opts.Address, + common.WithUsername(opts.Username), + common.WithPassword(opts.Password), + common.WithLocation(opts.Address), + common.WithProtocol(opts.Protocol), + common.WithParamsValue(constant.TimeoutKey, opts.Timeout), + common.WithParamsValue(constant.MetadataReportGroupKey, opts.Group), + common.WithParamsValue(constant.MetadataReportNamespaceKey, opts.Namespace), + common.WithParamsValue(constant.ClientNameKey, strings.Join([]string{constant.MetadataReportPrefix, opts.Protocol, opts.Address}, "-")), + ) + if err != nil || len(res.Protocol) == 0 { + return nil, perrors.New("Invalid MetadataReport Config.") + } + res.SetParam("metadata", res.Protocol) + for key, val := range opts.Params { + res.SetParam(key, val) + } + return res, nil } func defaultReportOptions() *ReportOptions { diff --git a/metadata/report/report_factory.go b/metadata/report/report_factory.go index 2c2af7349f..cd2cf7c7aa 100644 --- a/metadata/report/report_factory.go +++ b/metadata/report/report_factory.go @@ -25,5 +25,3 @@ import ( type MetadataReportFactory interface { CreateMetadataReport(*common.URL) MetadataReport } - -type BaseMetadataReportFactory struct{} diff --git a/metadata/report_instance.go b/metadata/report_instance.go index 2238144f09..f7bceb4584 100644 --- a/metadata/report_instance.go +++ b/metadata/report_instance.go @@ -18,19 +18,18 @@ package metadata import ( - "strings" "time" ) import ( "github.com/dubbogo/gost/container/set" - - perrors "github.com/pkg/errors" + "github.com/dubbogo/gost/log/logger" ) import ( "dubbo.apache.org/dubbo-go/v3/common" "dubbo.apache.org/dubbo-go/v3/common/constant" + "dubbo.apache.org/dubbo-go/v3/common/extension" "dubbo.apache.org/dubbo-go/v3/metadata/info" "dubbo.apache.org/dubbo-go/v3/metadata/mapping" "dubbo.apache.org/dubbo-go/v3/metadata/report" @@ -42,25 +41,14 @@ var ( instances = make(map[string]report.MetadataReport) ) -func toUrl(opts *ReportOptions) (*common.URL, error) { - res, err := common.NewURL(opts.Address, - common.WithUsername(opts.Username), - common.WithPassword(opts.Password), - common.WithLocation(opts.Address), - common.WithProtocol(opts.Protocol), - common.WithParamsValue(constant.TimeoutKey, opts.Timeout), - common.WithParamsValue(constant.MetadataReportGroupKey, opts.Group), - common.WithParamsValue(constant.MetadataReportNamespaceKey, opts.Namespace), - common.WithParamsValue(constant.ClientNameKey, strings.Join([]string{constant.MetadataReportPrefix, opts.Protocol, opts.Address}, "-")), - ) - if err != nil || len(res.Protocol) == 0 { - return nil, perrors.New("Invalid MetadataReport Config.") - } - res.SetParam("metadata", res.Protocol) - for key, val := range opts.Params { - res.SetParam(key, val) +func addMetadataReport(registryId string, url *common.URL) error { + fac := extension.GetMetadataReportFactory(url.Protocol) + if fac == nil { + logger.Warnf("no metadata report factory of protocol %s found, please check if the metadata report factory is imported", url.Protocol) + return nil } - return res, nil + instances[registryId] = &DelegateMetadataReport{instance: fac.CreateMetadataReport(url)} + return nil } func GetMetadataReport() report.MetadataReport { @@ -91,6 +79,9 @@ func GetMetadataReports() []report.MetadataReport { } func GetMetadataType() string { + if metadataOptions == nil || metadataOptions.metadataType == "" { + return constant.DefaultMetadataStorageType + } return metadataOptions.metadataType } diff --git a/metadata/report_instance_test.go b/metadata/report_instance_test.go new file mode 100644 index 0000000000..4b89cdec9c --- /dev/null +++ b/metadata/report_instance_test.go @@ -0,0 +1,264 @@ +/* + * 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 metadata + +import ( + "reflect" + "testing" +) + +import ( + gxset "github.com/dubbogo/gost/container/set" + "github.com/dubbogo/gost/gof/observer" + + "github.com/pkg/errors" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +import ( + "dubbo.apache.org/dubbo-go/v3/common" + "dubbo.apache.org/dubbo-go/v3/common/constant" + "dubbo.apache.org/dubbo-go/v3/common/extension" + "dubbo.apache.org/dubbo-go/v3/metadata/info" + "dubbo.apache.org/dubbo-go/v3/metadata/mapping" + "dubbo.apache.org/dubbo-go/v3/metadata/report" + "dubbo.apache.org/dubbo-go/v3/metrics" + metricsMetadata "dubbo.apache.org/dubbo-go/v3/metrics/metadata" +) + +func TestDelegateMetadataReportGetAppMetadata(t *testing.T) { + mockReport := new(mockMetadataReport) + defer mockReport.AssertExpectations(t) + delegate := &DelegateMetadataReport{instance: mockReport} + metadataInfo := info.NewAppMetadataInfo("dubbo") + var ch = make(chan metrics.MetricsEvent, 10) + metrics.Subscribe(constant.MetricsMetadata, ch) + defer close(ch) + t.Run("normal", func(t *testing.T) { + mockReport.On("GetAppMetadata").Return(metadataInfo, nil).Once() + got, err := delegate.GetAppMetadata("dubbo", "1") + assert.Nil(t, err) + if !reflect.DeepEqual(got, metadataInfo) { + t.Errorf("GetAppMetadata() got = %v, want %v", got, metadataInfo) + } + assert.True(t, len(ch) == 1) + metricEvent := <-ch + assert.Equal(t, metricEvent.Type(), constant.MetricsMetadata) + event, ok := metricEvent.(*metricsMetadata.MetadataMetricEvent) + assert.True(t, ok) + assert.NotNil(t, event.Name, metricsMetadata.MetadataSub) + assert.NotNil(t, event.Start) + assert.NotNil(t, event.End) + assert.True(t, event.Succ) + }) + t.Run("error", func(t *testing.T) { + mockReport.On("GetAppMetadata").Return(info.NewAppMetadataInfo("dubbo"), errors.New("mock error")).Once() + _, err := delegate.GetAppMetadata("dubbo", "1111") + assert.NotNil(t, err) + assert.True(t, len(ch) == 1) + metricEvent := <-ch + assert.Equal(t, metricEvent.Type(), constant.MetricsMetadata) + event, ok := metricEvent.(*metricsMetadata.MetadataMetricEvent) + assert.True(t, ok) + assert.NotNil(t, event.Name, metricsMetadata.MetadataSub) + assert.NotNil(t, event.Start) + assert.NotNil(t, event.End) + assert.True(t, !event.Succ) + }) +} + +func TestDelegateMetadataReportPublishAppMetadata(t *testing.T) { + mockReport := new(mockMetadataReport) + defer mockReport.AssertExpectations(t) + delegate := &DelegateMetadataReport{instance: mockReport} + metadataInfo := info.NewAppMetadataInfo("dubbo") + var ch = make(chan metrics.MetricsEvent, 10) + metrics.Subscribe(constant.MetricsMetadata, ch) + defer close(ch) + t.Run("normal", func(t *testing.T) { + mockReport.On("PublishAppMetadata").Return(nil).Once() + err := delegate.PublishAppMetadata("application", "revision", metadataInfo) + assert.Nil(t, err) + assert.True(t, len(ch) == 1) + metricEvent := <-ch + assert.Equal(t, metricEvent.Type(), constant.MetricsMetadata) + event, ok := metricEvent.(*metricsMetadata.MetadataMetricEvent) + assert.True(t, ok) + assert.NotNil(t, event.Name, metricsMetadata.MetadataPush) + assert.NotNil(t, event.Start) + assert.NotNil(t, event.End) + assert.True(t, event.Succ) + }) + t.Run("error", func(t *testing.T) { + mockReport.On("PublishAppMetadata").Return(errors.New("mock error")).Once() + err := delegate.PublishAppMetadata("application", "revision", metadataInfo) + assert.NotNil(t, err) + assert.True(t, len(ch) == 1) + metricEvent := <-ch + assert.Equal(t, metricEvent.Type(), constant.MetricsMetadata) + event, ok := metricEvent.(*metricsMetadata.MetadataMetricEvent) + assert.True(t, ok) + assert.NotNil(t, event.Name, metricsMetadata.MetadataPush) + assert.NotNil(t, event.Start) + assert.NotNil(t, event.End) + assert.True(t, !event.Succ) + }) +} + +func TestDelegateMetadataReportGetServiceAppMapping(t *testing.T) { + mockReport := new(mockMetadataReport) + defer mockReport.AssertExpectations(t) + delegate := &DelegateMetadataReport{instance: mockReport} + t.Run("normal", func(t *testing.T) { + mockReport.On("GetServiceAppMapping").Return(gxset.NewSet(), nil).Once() + got, err := delegate.GetServiceAppMapping("dubbo", "dev", &listener{}) + assert.Nil(t, err) + assert.True(t, got.Empty()) + }) + t.Run("error", func(t *testing.T) { + mockReport.On("GetServiceAppMapping").Return(gxset.NewSet(), errors.New("mock error")).Once() + _, err := delegate.GetServiceAppMapping("dubbo", "dev", &listener{}) + assert.NotNil(t, err) + }) +} + +func TestDelegateMetadataReportRegisterServiceAppMapping(t *testing.T) { + mockReport := new(mockMetadataReport) + defer mockReport.AssertExpectations(t) + delegate := &DelegateMetadataReport{instance: mockReport} + t.Run("normal", func(t *testing.T) { + mockReport.On("RegisterServiceAppMapping").Return(nil).Once() + err := delegate.RegisterServiceAppMapping("interfaceName", "group", "application") + assert.Nil(t, err) + }) + t.Run("error", func(t *testing.T) { + mockReport.On("RegisterServiceAppMapping").Return(errors.New("mock error")).Once() + err := delegate.RegisterServiceAppMapping("interfaceName", "group", "application") + assert.NotNil(t, err) + }) +} + +func TestDelegateMetadataReportRemoveServiceAppMappingListener(t *testing.T) { + mockReport := new(mockMetadataReport) + defer mockReport.AssertExpectations(t) + delegate := &DelegateMetadataReport{instance: mockReport} + t.Run("normal", func(t *testing.T) { + mockReport.On("RemoveServiceAppMappingListener").Return(nil).Once() + err := delegate.RemoveServiceAppMappingListener("interfaceName", "group") + assert.Nil(t, err) + }) + t.Run("error", func(t *testing.T) { + mockReport.On("RemoveServiceAppMappingListener").Return(errors.New("mock error")).Once() + err := delegate.RemoveServiceAppMappingListener("interfaceName", "group") + assert.NotNil(t, err) + }) +} + +func TestGetMetadataReport(t *testing.T) { + instances = make(map[string]report.MetadataReport) + assert.Nil(t, GetMetadataReport()) + instances["default"] = new(mockMetadataReport) + assert.NotNil(t, GetMetadataReport()) +} + +func TestGetMetadataReportByRegistry(t *testing.T) { + instances = make(map[string]report.MetadataReport) + assert.Nil(t, GetMetadataReportByRegistry("reg")) + instances["default"] = new(mockMetadataReport) + assert.NotNil(t, GetMetadataReportByRegistry("default")) + assert.NotNil(t, GetMetadataReportByRegistry("reg")) + assert.NotNil(t, GetMetadataReportByRegistry("")) +} + +func TestGetMetadataReports(t *testing.T) { + instances = make(map[string]report.MetadataReport) + assert.True(t, len(GetMetadataReports()) == 0) + instances["default"] = new(mockMetadataReport) + assert.True(t, len(GetMetadataReports()) == 1) +} + +func TestGetMetadataType(t *testing.T) { + assert.Equal(t, GetMetadataType(), constant.DefaultMetadataStorageType) + metadataOptions = &Options{} + assert.Equal(t, GetMetadataType(), constant.DefaultMetadataStorageType) + metadataOptions = &Options{ + metadataType: constant.RemoteMetadataStorageType, + } + assert.Equal(t, GetMetadataType(), constant.RemoteMetadataStorageType) +} + +func TestAddMetadataReport(t *testing.T) { + url := common.NewURLWithOptions( + common.WithProtocol("registryId"), + ) + err := addMetadataReport("registryId", url) + assert.Nil(t, err) + assert.True(t, instances["registryId"] == nil) + mockReport := new(mockMetadataReport) + extension.SetMetadataReportFactory("registryId", func() report.MetadataReportFactory { + return mockReport + }) + err = addMetadataReport("registryId", url) + assert.Nil(t, err) + assert.True(t, instances["registryId"] != nil) +} + +type mockMetadataReport struct { + mock.Mock +} + +func (m *mockMetadataReport) CreateMetadataReport(*common.URL) report.MetadataReport { + return m +} + +func (m *mockMetadataReport) GetAppMetadata(string, string) (*info.MetadataInfo, error) { + args := m.Called() + return args.Get(0).(*info.MetadataInfo), args.Error(1) +} + +func (m *mockMetadataReport) PublishAppMetadata(string, string, *info.MetadataInfo) error { + args := m.Called() + return args.Error(0) +} + +func (m *mockMetadataReport) RegisterServiceAppMapping(string, string, string) error { + args := m.Called() + return args.Error(0) +} + +func (m *mockMetadataReport) GetServiceAppMapping(string, string, mapping.MappingListener) (*gxset.HashSet, error) { + args := m.Called() + return args.Get(0).(*gxset.HashSet), args.Error(1) +} + +func (m *mockMetadataReport) RemoveServiceAppMappingListener(string, string) error { + args := m.Called() + return args.Error(0) +} + +type listener struct { +} + +func (l *listener) OnEvent(e observer.Event) error { + return nil +} + +func (l *listener) Stop() { +} diff --git a/options.go b/options.go index b4bff1da27..5fd162db16 100644 --- a/options.go +++ b/options.go @@ -94,7 +94,6 @@ func (rc *InstanceOptions) init(opts ...InstanceOption) error { log.Infof("[Config Center] Config center doesn't start") log.Debugf("config center doesn't start because %s", err) } else { - compatInstanceOptions(rcCompat, rc) if err = rcCompat.Logger.Init(); err != nil { // init logger using config from config center again return err } @@ -110,15 +109,8 @@ func (rc *InstanceOptions) init(opts ...InstanceOption) error { } // init protocol - protocols := rcCompat.Protocols - if len(protocols) <= 0 { - protocol := &config.ProtocolConfig{} - protocols = make(map[string]*config.ProtocolConfig, 1) - protocols[constant.Dubbo] = protocol - rcCompat.Protocols = protocols - } - for _, protocol := range protocols { - if err := protocol.Init(); err != nil { + for _, protocolConfig := range rcCompat.Protocols { + if err := protocolConfig.Init(); err != nil { return err } } @@ -169,6 +161,7 @@ func (rc *InstanceOptions) init(opts ...InstanceOption) error { return err } + compatInstanceOptions(rcCompat, rc) // overrider options config because some config are changed after init return nil } diff --git a/registry/servicediscovery/customizer/service_instance_host_port_customizer.go b/registry/servicediscovery/customizer/service_instance_host_port_customizer.go index f33023f45b..bd918d6acd 100644 --- a/registry/servicediscovery/customizer/service_instance_host_port_customizer.go +++ b/registry/servicediscovery/customizer/service_instance_host_port_customizer.go @@ -39,7 +39,7 @@ func (e *hostPortCustomizer) GetPriority() int { // Customize calculate the revision for exported urls and then put it into instance metadata func (e *hostPortCustomizer) Customize(instance registry.ServiceInstance) { - if instance.GetPort() > 0 { + if instance.GetPort() > 0 { // has set, avoid reset return } if instance.GetServiceMetadata() == nil || len(instance.GetServiceMetadata().GetExportedServiceURLs()) == 0 {