From 94e6f9cc9dd486c2a257dfe56ea7eedc2f5a23bb Mon Sep 17 00:00:00 2001 From: 0027011133 Date: Mon, 19 Oct 2026 21:33:42 +0800 Subject: [PATCH 1/2] Fix metadata service compatibility with mixed protocols --- .../ConfigurableMetadataServiceExporter.java | 7 +- ...ataServiceURLParamsMetadataCustomizer.java | 11 +- .../metadata/MetadataServiceProtocolTest.java | 176 ++++++++++++++++++ .../util/MetadataServiceVersionUtils.java | 13 +- 4 files changed, 198 insertions(+), 9 deletions(-) create mode 100644 dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/metadata/MetadataServiceProtocolTest.java diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java index caa5b3f55db6..9a99485d252c 100644 --- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java @@ -116,9 +116,14 @@ private void exportV1() { .getBeanFactory() .getBean(FrameworkExecutorRepository.class) .getInternalServiceExecutor(); + // Both metadata versions must be reachable at the endpoint advertised to consumers. this.serviceConfig = InternalServiceConfigBuilder.newBuilder(applicationModel) .interfaceClass(MetadataService.class) - .protocol(getApplicationConfig().getMetadataServiceProtocol(), METADATA_SERVICE_PROTOCOL_KEY) + .protocol( + MetadataServiceVersionUtils.needExportV2(applicationModel) + ? TRIPLE + : getApplicationConfig().getMetadataServiceProtocol(), + METADATA_SERVICE_PROTOCOL_KEY) .port(getApplicationConfig().getMetadataServicePort(), METADATA_SERVICE_PORT_KEY) .registryId(INTERNAL_METADATA_REGISTRY_ID) .executor(internalServiceExecutor) diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/MetadataServiceURLParamsMetadataCustomizer.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/MetadataServiceURLParamsMetadataCustomizer.java index 3ac99915efb9..1b113261e6f8 100644 --- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/MetadataServiceURLParamsMetadataCustomizer.java +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/MetadataServiceURLParamsMetadataCustomizer.java @@ -78,16 +78,15 @@ private String resolveMetadataPropertyValue(ApplicationModel applicationModel) { String key; - if (MetadataServiceVersionUtils.needExportV2(applicationModel)) { + if (MetadataServiceVersionUtils.needExportV1(applicationModel)) { + // Advertise the V1 endpoint for legacy consumers when both versions are exported. + key = BaseServiceMetadata.buildServiceKey( + MetadataService.class.getName(), applicationModel.getApplicationName(), MetadataService.VERSION); + } else { key = BaseServiceMetadata.buildServiceKey( MetadataServiceV2.class.getName(), applicationModel.getApplicationName(), MetadataServiceDelegationV2.VERSION); - } else { - // If MetadataService and MetadataServiceV2 are both exported, use v1 path for capacity. - // Client will use version and protocol to judge if it needs to refer v2 path. - key = BaseServiceMetadata.buildServiceKey( - MetadataService.class.getName(), applicationModel.getApplicationName(), MetadataService.VERSION); } ProviderModel providerModel = serviceRepository.lookupExportedService(key); String metadataValue = ""; diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/metadata/MetadataServiceProtocolTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/metadata/MetadataServiceProtocolTest.java new file mode 100644 index 000000000000..7fc1f4057295 --- /dev/null +++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/metadata/MetadataServiceProtocolTest.java @@ -0,0 +1,176 @@ +/* + * 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 org.apache.dubbo.config.metadata; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.config.ApplicationConfig; +import org.apache.dubbo.config.ProtocolConfig; +import org.apache.dubbo.config.RegistryConfig; +import org.apache.dubbo.config.ServiceConfig; +import org.apache.dubbo.config.api.DemoService; +import org.apache.dubbo.config.bootstrap.DubboBootstrap; +import org.apache.dubbo.config.provider.impl.DemoServiceImpl; +import org.apache.dubbo.metadata.MetadataInfo; +import org.apache.dubbo.metadata.MetadataService; +import org.apache.dubbo.metadata.MetadataServiceV2; +import org.apache.dubbo.registry.client.DefaultServiceInstance; +import org.apache.dubbo.registry.client.ServiceDiscovery; +import org.apache.dubbo.registry.client.ServiceDiscoveryRegistry; +import org.apache.dubbo.registry.client.metadata.MetadataServiceDelegation; +import org.apache.dubbo.registry.client.metadata.MetadataServiceDelegationV2; +import org.apache.dubbo.registry.client.metadata.MetadataUtils; +import org.apache.dubbo.registry.client.metadata.StandardMetadataServiceURLBuilder; +import org.apache.dubbo.registry.support.RegistryManager; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.Protocol; +import org.apache.dubbo.rpc.ProxyFactory; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.rpc.model.FrameworkModel; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.METADATA_SERVICE_VERSION_NAME; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class MetadataServiceProtocolTest { + + static Stream metadataProtocols() { + return Stream.of( + Arguments.of(Arrays.asList("dubbo", "tri"), false, null, false, "tri", true), + Arguments.of(Arrays.asList("tri", "dubbo"), false, null, false, "tri", true), + Arguments.of(Collections.singletonList("tri"), false, null, false, "tri", true), + Arguments.of(Collections.singletonList("tri"), true, null, false, "tri", true), + Arguments.of(Collections.singletonList("dubbo"), false, null, false, "dubbo", false), + Arguments.of(Arrays.asList("dubbo", "tri"), false, "dubbo", false, "dubbo", false), + Arguments.of(Arrays.asList("dubbo", "tri"), false, "dubbo", true, "dubbo", false)); + } + + @ParameterizedTest + @MethodSource("metadataProtocols") + void advertisedMetadataSupportsConsumers( + List protocols, + boolean onlyV2, + String metadataProtocol, + boolean protocolParameter, + String expectedProtocol, + boolean supportsV2) { + FrameworkModel frameworkModel = new FrameworkModel(); + try { + DubboBootstrap provider = DubboBootstrap.newInstance(frameworkModel); + ApplicationConfig config = new ApplicationConfig("mixed-protocol-metadata-provider"); + config.setQosEnable(false); + config.setOnlyUseMetadataV2(onlyV2); + if (protocolParameter) { + config.setParameters(Collections.singletonMap("metadata-service-protocol", metadataProtocol)); + } else { + config.setMetadataServiceProtocol(metadataProtocol); + } + config.setMetadataType("local"); + + ServiceConfig service = new ServiceConfig<>(); + service.setInterface(DemoService.class); + service.setRef(new DemoServiceImpl()); + List protocolConfigs = new ArrayList<>(); + for (String protocol : protocols) { + ProtocolConfig protocolConfig = new ProtocolConfig(protocol, -1); + protocolConfig.setId(protocol); + protocolConfigs.add(protocolConfig); + } + provider.application(config) + .registry(new RegistryConfig("N/A")) + .protocols(protocolConfigs) + .service(service) + .start(); + + ApplicationModel providerModel = provider.getApplicationModel(); + MetadataInfo expected = new MetadataInfo(config.getName(), "metadata-revision", Collections.emptyMap()); + ServiceDiscovery discovery = mock(ServiceDiscovery.class); + when(discovery.getLocalMetadata(expected.getRevision())).thenReturn(expected); + ServiceDiscoveryRegistry registry = mock(ServiceDiscoveryRegistry.class); + when(registry.getServiceDiscovery()).thenReturn(discovery); + RegistryManager.getInstance(providerModel).putRegistry("metadata-test", registry); + + DubboBootstrap consumer = DubboBootstrap.newInstance(frameworkModel); + ApplicationConfig consumerConfig = new ApplicationConfig("metadata-consumer"); + consumerConfig.setQosEnable(false); + consumer.application(consumerConfig).initialize(); + ApplicationModel consumerModel = consumer.getApplicationModel(); + DefaultServiceInstance instance = + new DefaultServiceInstance(config.getName(), "127.0.0.1", 20880, consumerModel); + new MetadataServiceURLParamsMetadataCustomizer().customize(instance, providerModel); + + // Legacy consumers use the advertised URL without negotiating MetadataServiceV2. + URL legacyUrl = new StandardMetadataServiceURLBuilder(consumerModel) + .build(instance) + .get(0); + if (!onlyV2) { + legacyUrl = legacyUrl.setServiceModel( + consumerModel.getInternalModule().registerInternalConsumer(MetadataService.class, legacyUrl)); + Invoker invoker = consumerModel + .getExtensionLoader(Protocol.class) + .getExtension(legacyUrl.getProtocol()) + .refer(MetadataService.class, legacyUrl); + try { + MetadataService legacyClient = consumerModel + .getExtensionLoader(ProxyFactory.class) + .getAdaptiveExtension() + .getProxy(invoker); + assertEquals( + expected.getRevision(), + legacyClient.getMetadataInfo(expected.getRevision()).getRevision()); + } finally { + invoker.destroy(); + } + } + + MetadataUtils.RemoteMetadataService currentClient = MetadataUtils.referMetadataService(instance); + try { + if (supportsV2) { + assertInstanceOf(MetadataServiceV2.class, currentClient.getInternalProxy()); + } else { + assertInstanceOf(MetadataService.class, currentClient.getInternalProxy()); + } + assertEquals( + expected.getRevision(), + currentClient.getRemoteMetadata(expected.getRevision()).getRevision()); + } finally { + MetadataUtils.destroyProxy(currentClient); + } + + assertEquals(expectedProtocol, legacyUrl.getProtocol()); + assertEquals( + onlyV2 ? MetadataServiceDelegationV2.VERSION : MetadataServiceDelegation.VERSION, + legacyUrl.getVersion()); + assertEquals( + supportsV2 ? MetadataServiceDelegationV2.VERSION : MetadataServiceDelegation.VERSION, + instance.getMetadata().get(METADATA_SERVICE_VERSION_NAME)); + } finally { + frameworkModel.destroy(); + } + } +} diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/util/MetadataServiceVersionUtils.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/util/MetadataServiceVersionUtils.java index 13d1ccc7c29c..5f2f53482e9c 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/util/MetadataServiceVersionUtils.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/util/MetadataServiceVersionUtils.java @@ -17,6 +17,7 @@ package org.apache.dubbo.metadata.util; import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.config.ApplicationConfig; import org.apache.dubbo.config.ProtocolConfig; import org.apache.dubbo.config.context.ConfigManager; @@ -32,6 +33,7 @@ import java.util.Map; import java.util.Optional; +import static org.apache.dubbo.common.constants.CommonConstants.METADATA_SERVICE_PROTOCOL_KEY; import static org.apache.dubbo.common.constants.CommonConstants.TRIPLE; public class MetadataServiceVersionUtils { @@ -127,8 +129,15 @@ public static boolean tripleConfigured(ApplicationModel applicationModel) { Optional appConfig = getApplicationConfig(applicationModel); // if user configured MetadataService protocol - if (appConfig.isPresent() && appConfig.get().getMetadataServiceProtocol() != null) { - return TRIPLE.equals(appConfig.get().getMetadataServiceProtocol()); + if (appConfig.isPresent()) { + String metadataServiceProtocol = appConfig.get().getMetadataServiceProtocol(); + Map parameters = appConfig.get().getParameters(); + if (StringUtils.isEmpty(metadataServiceProtocol) && CollectionUtils.isNotEmptyMap(parameters)) { + metadataServiceProtocol = parameters.get(METADATA_SERVICE_PROTOCOL_KEY); + } + if (StringUtils.isNotEmpty(metadataServiceProtocol)) { + return TRIPLE.equals(metadataServiceProtocol); + } } // if not specified, check all protocol configs if (configManager.isPresent() From de142e98d2f2ed1f17ad8303e68508169375d527 Mon Sep 17 00:00:00 2001 From: 0027011133 Date: Mon, 19 Oct 2026 22:38:43 +0800 Subject: [PATCH 2/2] Isolate metadata protocol test ports and cover configuration precedence --- .../metadata/MetadataServiceProtocolTest.java | 3 +- .../util/MetadataServiceVersionUtilsTest.java | 81 +++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/util/MetadataServiceVersionUtilsTest.java diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/metadata/MetadataServiceProtocolTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/metadata/MetadataServiceProtocolTest.java index 7fc1f4057295..fe77adcc88c5 100644 --- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/metadata/MetadataServiceProtocolTest.java +++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/metadata/MetadataServiceProtocolTest.java @@ -17,6 +17,7 @@ package org.apache.dubbo.config.metadata; import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.utils.NetUtils; import org.apache.dubbo.config.ApplicationConfig; import org.apache.dubbo.config.ProtocolConfig; import org.apache.dubbo.config.RegistryConfig; @@ -97,7 +98,7 @@ void advertisedMetadataSupportsConsumers( service.setRef(new DemoServiceImpl()); List protocolConfigs = new ArrayList<>(); for (String protocol : protocols) { - ProtocolConfig protocolConfig = new ProtocolConfig(protocol, -1); + ProtocolConfig protocolConfig = new ProtocolConfig(protocol, NetUtils.getAvailablePort()); protocolConfig.setId(protocol); protocolConfigs.add(protocolConfig); } diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/util/MetadataServiceVersionUtilsTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/util/MetadataServiceVersionUtilsTest.java new file mode 100644 index 000000000000..bc83dfa7438c --- /dev/null +++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/util/MetadataServiceVersionUtilsTest.java @@ -0,0 +1,81 @@ +/* + * 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 org.apache.dubbo.metadata.util; + +import org.apache.dubbo.config.ApplicationConfig; +import org.apache.dubbo.config.ProtocolConfig; +import org.apache.dubbo.config.context.ConfigManager; +import org.apache.dubbo.rpc.model.ApplicationModel; + +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.apache.dubbo.common.constants.CommonConstants.METADATA_SERVICE_PROTOCOL_KEY; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class MetadataServiceVersionUtilsTest { + + static Stream metadataProtocolOverrides() { + return Stream.of( + Arguments.of(null, null, true), + Arguments.of(null, Collections.emptyMap(), true), + Arguments.of(null, Collections.singletonMap("unrelated", "value"), true), + Arguments.of(null, Collections.singletonMap(METADATA_SERVICE_PROTOCOL_KEY, ""), true), + Arguments.of(null, Collections.singletonMap(METADATA_SERVICE_PROTOCOL_KEY, "tri"), true), + Arguments.of(null, Collections.singletonMap(METADATA_SERVICE_PROTOCOL_KEY, "dubbo"), false), + Arguments.of("", Collections.singletonMap(METADATA_SERVICE_PROTOCOL_KEY, "dubbo"), false), + Arguments.of("tri", Collections.singletonMap(METADATA_SERVICE_PROTOCOL_KEY, "dubbo"), true), + Arguments.of("dubbo", Collections.singletonMap(METADATA_SERVICE_PROTOCOL_KEY, "tri"), false)); + } + + @ParameterizedTest + @MethodSource("metadataProtocolOverrides") + void metadataProtocolOverridesConfiguredTriple( + String metadataProtocol, Map parameters, boolean expectedTriple) { + ApplicationConfig application = new ApplicationConfig("metadata-protocol-configuration"); + application.setMetadataServiceProtocol(metadataProtocol); + application.setParameters(parameters); + ConfigManager configManager = mock(ConfigManager.class); + when(configManager.getApplication()).thenReturn(Optional.of(application)); + when(configManager.getProtocols()).thenReturn(Collections.singletonList(new ProtocolConfig("tri"))); + ApplicationModel applicationModel = mock(ApplicationModel.class); + when(applicationModel.getApplicationConfigManager()).thenReturn(configManager); + + assertEquals(expectedTriple, MetadataServiceVersionUtils.tripleConfigured(applicationModel)); + } + + @Test + void configuredProtocolIsUsedWithoutApplicationConfig() { + ConfigManager configManager = mock(ConfigManager.class); + when(configManager.getApplication()).thenReturn(Optional.empty()); + when(configManager.getProtocols()).thenReturn(Collections.singletonList(new ProtocolConfig("tri"))); + ApplicationModel applicationModel = mock(ApplicationModel.class); + when(applicationModel.getApplicationConfigManager()).thenReturn(configManager); + + assertTrue(MetadataServiceVersionUtils.tripleConfigured(applicationModel)); + } +}