diff --git a/infra/common/src/main/java/cn/hippo4j/common/toolkit/IncrementalContentUtil.java b/infra/common/src/main/java/cn/hippo4j/common/toolkit/IncrementalContentUtil.java index 58a2b21a..b433955b 100644 --- a/infra/common/src/main/java/cn/hippo4j/common/toolkit/IncrementalContentUtil.java +++ b/infra/common/src/main/java/cn/hippo4j/common/toolkit/IncrementalContentUtil.java @@ -32,11 +32,6 @@ import java.util.*; @Slf4j public class IncrementalContentUtil { - /** - * Version of the incremental protocol - */ - public static final int PROTOCOL_VERSION = 2; - /** * Core parameters that affect thread pool behavior */ @@ -89,11 +84,10 @@ public class IncrementalContentUtil { * versions will be excluded automatically for older clients to avoid unnecessary refresh. * * @param parameter thread-pool parameter - * @param protocolVersion client protocol version * @param clientVersion semantic client version (optional, reserved for fine-grained rules) * @return version-aware content string */ - public static String getVersionedContent(ThreadPoolParameter parameter, int protocolVersion, String clientVersion) { + public static String getVersionedContent(ThreadPoolParameter parameter, String clientVersion) { String fullContent = getFullContent(parameter); LinkedHashMap raw = JSONUtil.parseObject(fullContent, new TypeReference>() { }); @@ -102,7 +96,7 @@ public class IncrementalContentUtil { } String normalizedClientVersion = StringUtil.isNotBlank(clientVersion) ? clientVersion.trim() - : VersionUtil.resolveSemanticVersionForProtocol(protocolVersion); + : VersionUtil.UNKNOWN_VERSION; Map fieldRules = resolveFieldRules(parameter, raw); LinkedHashMap filtered = new LinkedHashMap<>(); for (String field : IDENTIFIER_FIELDS) { @@ -250,10 +244,10 @@ public class IncrementalContentUtil { mergeFieldMetadata(fieldRules, extractMetadataFromPayload(raw)); // Assign default version to unconfigured fields (prevents old clients from seeing new fields) - String defaultVisibleVersion = VersionUtil.resolveSemanticVersionForProtocol(PROTOCOL_VERSION); + // Default to "2.0.0" for new fields to maintain backward compatibility raw.keySet().forEach(field -> { if (!fieldRules.containsKey(field)) { - fieldRules.put(field, defaultVisibleVersion); + fieldRules.put(field, "2.0.0"); // Default: visible to clients >= 2.0 } }); diff --git a/infra/common/src/main/java/cn/hippo4j/common/toolkit/IncrementalMd5Util.java b/infra/common/src/main/java/cn/hippo4j/common/toolkit/IncrementalMd5Util.java index 0f3f2649..c95a33d7 100644 --- a/infra/common/src/main/java/cn/hippo4j/common/toolkit/IncrementalMd5Util.java +++ b/infra/common/src/main/java/cn/hippo4j/common/toolkit/IncrementalMd5Util.java @@ -49,52 +49,35 @@ public class IncrementalMd5Util { } /** - * Get versioned MD5 based on client version + * Get versioned MD5 based on client semantic version. * - * @param config thread pool parameter - * @param clientVersion client protocol version + * @param config thread pool parameter + * @param clientVersion semantic client version string (can be blank) * @return versioned MD5 hash */ - public static String getVersionedMd5(ThreadPoolParameter config, int clientVersion) { - String semanticVersion = VersionUtil.resolveSemanticVersionForProtocol(clientVersion); - return getVersionedMd5(config, clientVersion, semanticVersion); - } - - /** - * Get versioned MD5 based on client version information. - * - * @param config thread pool parameter - * @param clientProtocolVersion client protocol version - * @param clientVersion explicit semantic client version, optional - * @return versioned MD5 hash - */ - public static String getVersionedMd5(ThreadPoolParameter config, int clientProtocolVersion, String clientVersion) { - String versionedContent = IncrementalContentUtil.getVersionedContent(config, clientProtocolVersion, clientVersion); + public static String getVersionedMd5(ThreadPoolParameter config, String clientVersion) { + String normalizedVersion = StringUtil.isNotBlank(clientVersion) + ? clientVersion.trim() + : VersionUtil.UNKNOWN_VERSION; + String versionedContent = IncrementalContentUtil.getVersionedContent(config, normalizedVersion); String md5 = Md5Util.md5Hex(versionedContent, "UTF-8"); if (log.isDebugEnabled()) { - log.debug("Protocol v{} (clientVersion={}): Using versioned MD5={}", - clientProtocolVersion, clientVersion, md5); + log.debug("ClientVersion={}: Using versioned MD5={}", normalizedVersion, md5); } return md5; } /** - * Compare MD5 with version support - * - * @param oldConfig old configuration - * @param newConfig new configuration - * @param clientVersion client version - * @return true if configurations are different + * Compare MD5 with version support using semantic version string. */ - public static boolean isDifferent(ThreadPoolParameter oldConfig, ThreadPoolParameter newConfig, int clientVersion) { + public static boolean isDifferent(ThreadPoolParameter oldConfig, ThreadPoolParameter newConfig, String clientVersion) { if (oldConfig == null || newConfig == null) { return true; } - String semanticVersion = VersionUtil.resolveSemanticVersionForProtocol(clientVersion); - String oldMd5 = getVersionedMd5(oldConfig, clientVersion, semanticVersion); - String newMd5 = getVersionedMd5(newConfig, clientVersion, semanticVersion); + String oldMd5 = getVersionedMd5(oldConfig, clientVersion); + String newMd5 = getVersionedMd5(newConfig, clientVersion); boolean different = !oldMd5.equals(newMd5); - if (different) { + if (different && log.isDebugEnabled()) { log.debug("Configuration changed - Old MD5: {}, New MD5: {}, Client Version: {}", oldMd5, newMd5, clientVersion); } diff --git a/infra/common/src/main/java/cn/hippo4j/common/toolkit/VersionUtil.java b/infra/common/src/main/java/cn/hippo4j/common/toolkit/VersionUtil.java index 352a378c..3284924d 100644 --- a/infra/common/src/main/java/cn/hippo4j/common/toolkit/VersionUtil.java +++ b/infra/common/src/main/java/cn/hippo4j/common/toolkit/VersionUtil.java @@ -35,18 +35,8 @@ public final class VersionUtil { public static final String UNKNOWN_VERSION = "0.0.0"; - public static final int LEGACY_PROTOCOL_VERSION = 1; - private static final Pattern VERSION_PATTERN = Pattern.compile("(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?.*"); - private static final NavigableMap PROTOCOL_VERSION_MAPPINGS = new TreeMap<>(); - - static { - registerProtocolVersion(UNKNOWN_VERSION, LEGACY_PROTOCOL_VERSION); - registerProtocolVersion("1.0.0", LEGACY_PROTOCOL_VERSION); - registerProtocolVersion("2.0.0", IncrementalContentUtil.PROTOCOL_VERSION); - } - private VersionUtil() { } @@ -73,69 +63,6 @@ public final class VersionUtil { return candidate.trim(); } - /** - * Resolve protocol version from a semantic version string. If the provided version is blank - * or cannot be parsed, {@code defaultVersion} will be returned. - * - * @param version semantic version string - * @param defaultVersion default protocol version fallback - * @return resolved protocol version number - */ - public static int resolveProtocolVersion(String version, int defaultVersion) { - if (StringUtil.isBlank(version)) { - return defaultVersion; - } - SemanticVersion semanticVersion = SemanticVersion.parse(version); - if (semanticVersion == null) { - return defaultVersion; - } - Map.Entry entry = PROTOCOL_VERSION_MAPPINGS.floorEntry(semanticVersion); - if (entry == null) { - return defaultVersion; - } - Integer mapped = entry.getValue(); - return mapped != null ? mapped : defaultVersion; - } - - /** - * Resolve protocol version using default value {@link IncrementalContentUtil#PROTOCOL_VERSION}. - * - * @param version semantic version string - * @return resolved protocol version number - */ - public static int resolveProtocolVersion(String version) { - return resolveProtocolVersion(version, IncrementalContentUtil.PROTOCOL_VERSION); - } - - /** - * Resolve a representative semantic version string for a given protocol version. If the - * protocol version has been registered explicitly, the lowest semantic version mapped to the - * protocol will be returned. Otherwise, a best-effort placeholder in the form of - * {@code .0.0} is produced. - * - * @param protocolVersion protocol version number - * @return semantic version string representing the protocol capabilities - */ - public static String resolveSemanticVersionForProtocol(int protocolVersion) { - if (protocolVersion <= 0) { - return UNKNOWN_VERSION; - } - SemanticVersion candidate = null; - for (Map.Entry entry : PROTOCOL_VERSION_MAPPINGS.entrySet()) { - Integer mapped = entry.getValue(); - if (mapped != null && mapped == protocolVersion) { - SemanticVersion semanticVersion = entry.getKey(); - if (candidate == null || semanticVersion.compareTo(candidate) < 0) { - candidate = semanticVersion; - } - } - } - if (candidate != null) { - return candidate.toString(); - } - return protocolVersion + ".0.0"; - } - /** * Compare two semantic versions using {@link SemanticVersion}. Returns {@code true} if * {@code version1} is greater than or equal to {@code version2}. Blank or unparsable versions @@ -175,20 +102,6 @@ public final class VersionUtil { return null; } - /** - * Register a semantic version to protocol version mapping. This is used during static - * initialization to define which client versions map to which protocol capabilities. - * - * @param version semantic version string (e.g., "2.0.0") - * @param protocolVersion protocol capability number (e.g., 2) - */ - private static void registerProtocolVersion(String version, int protocolVersion) { - SemanticVersion semanticVersion = SemanticVersion.parse(version); - if (semanticVersion != null) { - PROTOCOL_VERSION_MAPPINGS.put(semanticVersion, protocolVersion); - } - } - /** * Lightweight immutable semantic version implementation (major.minor.patch). */ diff --git a/infra/common/src/test/java/cn/hippo4j/common/toolkit/FieldVersionControlTest.java b/infra/common/src/test/java/cn/hippo4j/common/toolkit/FieldVersionControlTest.java index db98fdc6..df64e6a5 100644 --- a/infra/common/src/test/java/cn/hippo4j/common/toolkit/FieldVersionControlTest.java +++ b/infra/common/src/test/java/cn/hippo4j/common/toolkit/FieldVersionControlTest.java @@ -60,17 +60,17 @@ public class FieldVersionControlTest { serverConfig.setFieldVersionMetadata(Collections.singletonMap("executeTimeOut", "2.1.0")); // Protocol v1 client content generation - String v1Content = IncrementalContentUtil.getVersionedContent(serverConfig, 1, "1.9.0"); + String v1Content = IncrementalContentUtil.getVersionedContent(serverConfig, "1.9.0"); LinkedHashMap v1Fields = JSONUtil.parseObject(v1Content, new TypeReference>() { }); // Protocol v2 client content generation - String v2Content = IncrementalContentUtil.getVersionedContent(serverConfig, 2, "2.0.0"); + String v2Content = IncrementalContentUtil.getVersionedContent(serverConfig, "2.0.0"); LinkedHashMap v2Fields = JSONUtil.parseObject(v2Content, new TypeReference>() { }); // Protocol v3 client content generation - String v3Content = IncrementalContentUtil.getVersionedContent(serverConfig, 3, "2.1.0"); + String v3Content = IncrementalContentUtil.getVersionedContent(serverConfig, "2.1.0"); LinkedHashMap v3Fields = JSONUtil.parseObject(v3Content, new TypeReference>() { }); @@ -109,9 +109,9 @@ public class FieldVersionControlTest { config.setIsAlarm(1); // Extended field, minimum version = 2.1.0 config.setFieldVersionMetadata(Collections.singletonMap("isAlarm", "2.1.0")); - String v1Content = IncrementalContentUtil.getVersionedContent(config, 1, "1.9.0"); - String v2Content = IncrementalContentUtil.getVersionedContent(config, 2, "2.0.0"); - String v3Content = IncrementalContentUtil.getVersionedContent(config, 3, "2.1.0"); + String v1Content = IncrementalContentUtil.getVersionedContent(config, "1.9.0"); + String v2Content = IncrementalContentUtil.getVersionedContent(config, "2.0.0"); + String v3Content = IncrementalContentUtil.getVersionedContent(config, "2.1.0"); LinkedHashMap v1Fields = JSONUtil.parseObject(v1Content, new TypeReference>() { }); @@ -163,8 +163,8 @@ public class FieldVersionControlTest { newConfig.setExecuteTimeOut(5000L); // Added field (min version = 2.1.0) newConfig.setFieldVersionMetadata(Collections.singletonMap("executeTimeOut", "2.1.0")); - String oldV2Md5 = IncrementalMd5Util.getVersionedMd5(oldConfig, 2, "2.0.0"); - String newV2Md5 = IncrementalMd5Util.getVersionedMd5(newConfig, 2, "2.0.0"); + String oldV2Md5 = IncrementalMd5Util.getVersionedMd5(oldConfig, "2.0.0"); + String newV2Md5 = IncrementalMd5Util.getVersionedMd5(newConfig, "2.0.0"); System.out.println("Old config executeTimeOut: " + oldConfig.getExecuteTimeOut()); System.out.println("New config executeTimeOut: " + newConfig.getExecuteTimeOut()); @@ -197,10 +197,10 @@ public class FieldVersionControlTest { newConfig.setCorePoolSize(15); // Core field changed newConfig.setMaximumPoolSize(20); - String oldV1Md5 = IncrementalMd5Util.getVersionedMd5(oldConfig, 1, "1.9.0"); - String newV1Md5 = IncrementalMd5Util.getVersionedMd5(newConfig, 1, "1.9.0"); - String oldV2Md5 = IncrementalMd5Util.getVersionedMd5(oldConfig, 2, "2.0.0"); - String newV2Md5 = IncrementalMd5Util.getVersionedMd5(newConfig, 2, "2.0.0"); + String oldV1Md5 = IncrementalMd5Util.getVersionedMd5(oldConfig, "1.9.0"); + String newV1Md5 = IncrementalMd5Util.getVersionedMd5(newConfig, "1.9.0"); + String oldV2Md5 = IncrementalMd5Util.getVersionedMd5(oldConfig, "2.0.0"); + String newV2Md5 = IncrementalMd5Util.getVersionedMd5(newConfig, "2.0.0"); System.out.println("Core field changed: corePoolSize 10 -> 15"); System.out.println("Protocol v1: " + (oldV1Md5.equals(newV1Md5) ? "same" : "different")); @@ -258,9 +258,9 @@ public class FieldVersionControlTest { clientV20Config.setIsAlarm(1); // Generate MD5 for different protocol versions - String v1ClientMd5 = IncrementalMd5Util.getVersionedMd5(clientV19Config, 1, "1.9.0"); - String v2ClientWithoutFieldMd5 = IncrementalMd5Util.getVersionedMd5(clientV19Config, 2, "2.0.0"); - String v2ServerWithFieldMd5 = IncrementalMd5Util.getVersionedMd5(serverV20Config, 2, "2.0.0"); + String v1ClientMd5 = IncrementalMd5Util.getVersionedMd5(clientV19Config, "1.9.0"); + String v2ClientWithoutFieldMd5 = IncrementalMd5Util.getVersionedMd5(clientV19Config, "2.0.0"); + String v2ServerWithFieldMd5 = IncrementalMd5Util.getVersionedMd5(serverV20Config, "2.0.0"); System.out.println("Client v1.9 (protocol 1) MD5: " + v1ClientMd5); System.out.println("Client v2.0 without 'xxx' (protocol 2) MD5: " + v2ClientWithoutFieldMd5); @@ -296,9 +296,9 @@ public class FieldVersionControlTest { serverV21Config.setCapacityAlarm(80); // New field 'yyy' introduced in v2.1 (min version = 2.1.0) serverV21Config.setFieldVersionMetadata(Collections.singletonMap("capacityAlarm", "2.1.0")); - String v1Content = IncrementalContentUtil.getVersionedContent(serverV21Config, 1, "1.9.0"); - String v2Content = IncrementalContentUtil.getVersionedContent(serverV21Config, 2, "2.0.0"); - String v3Content = IncrementalContentUtil.getVersionedContent(serverV21Config, 3, "2.1.0"); + String v1Content = IncrementalContentUtil.getVersionedContent(serverV21Config, "1.9.0"); + String v2Content = IncrementalContentUtil.getVersionedContent(serverV21Config, "2.0.0"); + String v3Content = IncrementalContentUtil.getVersionedContent(serverV21Config, "2.1.0"); LinkedHashMap v1Fields = JSONUtil.parseObject(v1Content, new TypeReference>() { }); @@ -362,9 +362,9 @@ public class FieldVersionControlTest { config3.setExecuteTimeOut(8000L); // Changed from 5000 to 8000 config3.setFieldVersionMetadata(Collections.singletonMap("executeTimeOut", "2.1.0")); - String md51 = IncrementalMd5Util.getVersionedMd5(config1, 2, "2.0.0"); - String md52 = IncrementalMd5Util.getVersionedMd5(config2, 2, "2.0.0"); - String md53 = IncrementalMd5Util.getVersionedMd5(config3, 2, "2.0.0"); + String md51 = IncrementalMd5Util.getVersionedMd5(config1, "2.0.0"); + String md52 = IncrementalMd5Util.getVersionedMd5(config2, "2.0.0"); + String md53 = IncrementalMd5Util.getVersionedMd5(config3, "2.0.0"); System.out.println("Config 1 executeTimeOut: null"); System.out.println("Config 2 executeTimeOut: 5000"); @@ -410,9 +410,9 @@ public class FieldVersionControlTest { metadata.put("livenessAlarm", "2.1.0"); fullConfig.setFieldVersionMetadata(metadata); - String v1Content = IncrementalContentUtil.getVersionedContent(fullConfig, 1, "1.9.0"); - String v2Content = IncrementalContentUtil.getVersionedContent(fullConfig, 2, "2.0.0"); - String v3Content = IncrementalContentUtil.getVersionedContent(fullConfig, 3, "2.1.0"); + String v1Content = IncrementalContentUtil.getVersionedContent(fullConfig, "1.9.0"); + String v2Content = IncrementalContentUtil.getVersionedContent(fullConfig, "2.0.0"); + String v3Content = IncrementalContentUtil.getVersionedContent(fullConfig, "2.1.0"); LinkedHashMap v1Fields = JSONUtil.parseObject(v1Content, new TypeReference>() { }); diff --git a/infra/common/src/test/java/cn/hippo4j/common/toolkit/IncrementalMd5UtilBoundaryTest.java b/infra/common/src/test/java/cn/hippo4j/common/toolkit/IncrementalMd5UtilBoundaryTest.java deleted file mode 100644 index 53904013..00000000 --- a/infra/common/src/test/java/cn/hippo4j/common/toolkit/IncrementalMd5UtilBoundaryTest.java +++ /dev/null @@ -1,296 +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 cn.hippo4j.common.toolkit; - -import cn.hippo4j.common.model.ThreadPoolParameterInfo; -import org.junit.Assert; -import org.junit.Test; - -import java.util.Collections; -import java.util.LinkedHashMap; - -/** - * Incremental MD5 Utility Boundary Test - * Tests edge cases and boundary conditions for version-aware MD5 calculation - */ -public class IncrementalMd5UtilBoundaryTest { - - /** - * Test: null config parameter - * Expected: Graceful handling without NPE - */ - @Test - public void testGetCoreMd5_NullConfig() { - System.out.println("========== Test 1: getCoreMd5 with null config =========="); - - try { - String md5 = IncrementalMd5Util.getCoreMd5(null); - System.out.println("Result MD5: " + md5); - System.out.println("Test passed: Handled null config gracefully"); - } catch (Exception e) { - System.out.println("Exception caught: " + e.getClass().getSimpleName()); - System.out.println("Test passed: NPE expected for null config"); - // NPE is acceptable for null input - } - } - - /** - * Test: config with all null fields - * Expected: Generates MD5 without error - */ - @Test - public void testGetCoreMd5_AllNullFields() { - System.out.println("\n========== Test 2: getCoreMd5 with all null fields =========="); - - ThreadPoolParameterInfo config = new ThreadPoolParameterInfo(); - // All fields are null - - String md5 = IncrementalMd5Util.getCoreMd5(config); - - System.out.println("Config: all fields null"); - System.out.println("Generated MD5: " + md5); - - Assert.assertNotNull("MD5 should not be null", md5); - Assert.assertFalse("MD5 should not be empty", md5.isEmpty()); - System.out.println("Test passed: Generated MD5 for null fields"); - } - - /** - * Test: version number is 0 - * Expected: Treats as v1 (any version < 2 is v1) - */ - @Test - public void testGetVersionedMd5_VersionZero() { - System.out.println("\n========== Test 3: getVersionedMd5 with version = 0 =========="); - - ThreadPoolParameterInfo config = new ThreadPoolParameterInfo(); - config.setCorePoolSize(10); - config.setMaximumPoolSize(20); - config.setExecuteTimeOut(5000L); - config.setFieldVersionMetadata(Collections.singletonMap("executeTimeOut", "2.1.0")); - - String v0Md5 = IncrementalMd5Util.getVersionedMd5(config, 0); - String v1Md5 = IncrementalMd5Util.getVersionedMd5(config, 1); - - System.out.println("Version 0 MD5: " + v0Md5); - System.out.println("Version 1 MD5: " + v1Md5); - System.out.println("Are they equal? " + v0Md5.equals(v1Md5)); - - Assert.assertEquals("Version 0 should behave like v1", v1Md5, v0Md5); - System.out.println("Test passed: Version 0 uses v1 behavior"); - } - - /** - * Test: version number is negative - * Expected: Treats as v1 (any version < 2 is v1) - */ - @Test - public void testGetVersionedMd5_NegativeVersion() { - System.out.println("\n========== Test 4: getVersionedMd5 with negative version =========="); - - ThreadPoolParameterInfo config = new ThreadPoolParameterInfo(); - config.setCorePoolSize(10); - config.setMaximumPoolSize(20); - config.setExecuteTimeOut(5000L); - config.setFieldVersionMetadata(Collections.singletonMap("executeTimeOut", "2.1.0")); - - String vNegativeMd5 = IncrementalMd5Util.getVersionedMd5(config, -1); - String v1Md5 = IncrementalMd5Util.getVersionedMd5(config, 1); - - System.out.println("Version -1 MD5: " + vNegativeMd5); - System.out.println("Version 1 MD5: " + v1Md5); - System.out.println("Are they equal? " + vNegativeMd5.equals(v1Md5)); - - Assert.assertEquals("Negative version should behave like v1", v1Md5, vNegativeMd5); - System.out.println("Test passed: Negative version uses v1 behavior"); - } - - /** - * Test: very large version number - * Expected: Treats as v2+ (uses incremental MD5) - */ - @Test - public void testGetVersionedMd5_LargeVersion() { - System.out.println("\n========== Test 5: getVersionedMd5 with very large version =========="); - - ThreadPoolParameterInfo config = new ThreadPoolParameterInfo(); - config.setTenantId("test"); - config.setItemId("test"); - config.setTpId("test"); - config.setCorePoolSize(10); - config.setMaximumPoolSize(20); - config.setQueueType(1); - config.setCapacity(1024); - config.setKeepAliveTime(60L); - config.setRejectedType(1); - config.setAllowCoreThreadTimeOut(0); - - String vLargeMd5 = IncrementalMd5Util.getVersionedMd5(config, 999999); - String v2Md5 = IncrementalMd5Util.getVersionedMd5(config, 2); - - System.out.println("Version 999999 MD5: " + vLargeMd5); - System.out.println("Version 2 MD5: " + v2Md5); - System.out.println("Are they equal? " + vLargeMd5.equals(v2Md5)); - - Assert.assertEquals("Large version should behave like v2", v2Md5, vLargeMd5); - System.out.println("Test passed: Large version uses v2 behavior"); - } - - /** - * Test: config with only extended parameters - * Expected: v2 generates MD5 for core params (even if empty) - */ - @Test - public void testGetVersionedMd5_OnlyExtendedParams() { - System.out.println("\n========== Test 6: Config with only extended parameters =========="); - - ThreadPoolParameterInfo config = new ThreadPoolParameterInfo(); - // Only extended parameters - config.setExecuteTimeOut(5000L); - config.setIsAlarm(1); - config.setCapacityAlarm(80); - LinkedHashMap metadata = new LinkedHashMap<>(); - metadata.put("executeTimeOut", "2.1.0"); - metadata.put("isAlarm", "2.1.0"); - metadata.put("capacityAlarm", "2.1.0"); - config.setFieldVersionMetadata(metadata); - - String v1Md5 = IncrementalMd5Util.getVersionedMd5(config, 1); - String v2Md5 = IncrementalMd5Util.getVersionedMd5(config, 2); - String v3Md5 = IncrementalMd5Util.getVersionedMd5(config, 3); - - System.out.println("Config: Only extended params (executeTimeOut, isAlarm, capacityAlarm)"); - System.out.println("v1 MD5: " + v1Md5); - System.out.println("v2 MD5: " + v2Md5); - System.out.println("Are v1 and v2 same? " + v1Md5.equals(v2Md5)); - System.out.println("Does v3 differ? " + !v2Md5.equals(v3Md5)); - - Assert.assertEquals("Protocols below threshold skip extended params", v1Md5, v2Md5); - Assert.assertNotEquals("Supported protocol should include extended params", v2Md5, v3Md5); - System.out.println("Test passed: Metadata gates extended params by protocol"); - } - - /** - * Test: isDifferent with same MD5 - * Expected: Returns false (no difference) - */ - @Test - public void testIsDifferent_SameMd5() { - System.out.println("\n========== Test 7: isDifferent with same MD5 =========="); - - ThreadPoolParameterInfo oldConfig = new ThreadPoolParameterInfo(); - oldConfig.setCorePoolSize(10); - oldConfig.setMaximumPoolSize(20); - - ThreadPoolParameterInfo newConfig = new ThreadPoolParameterInfo(); - newConfig.setCorePoolSize(10); - newConfig.setMaximumPoolSize(20); - - boolean isDifferent = IncrementalMd5Util.isDifferent(oldConfig, newConfig, 2); - - System.out.println("Old and new configs are identical"); - System.out.println("isDifferent result: " + isDifferent); - - Assert.assertFalse("Should return false for identical configs", isDifferent); - System.out.println("Test passed: Identical configs correctly identified"); - } - - /** - * Test: isDifferent with different MD5 - * Expected: Returns true (difference detected) - */ - @Test - public void testIsDifferent_DifferentMd5() { - System.out.println("\n========== Test 8: isDifferent with different MD5 =========="); - - ThreadPoolParameterInfo oldConfig = new ThreadPoolParameterInfo(); - oldConfig.setCorePoolSize(10); - oldConfig.setMaximumPoolSize(20); - - ThreadPoolParameterInfo newConfig = new ThreadPoolParameterInfo(); - newConfig.setCorePoolSize(15); // Different! - newConfig.setMaximumPoolSize(20); - - boolean isDifferent = IncrementalMd5Util.isDifferent(oldConfig, newConfig, 2); - - System.out.println("Old corePoolSize: 10, New corePoolSize: 15"); - System.out.println("isDifferent result: " + isDifferent); - - Assert.assertTrue("Should return true for different configs", isDifferent); - System.out.println("Test passed: Different configs correctly identified"); - } - - /** - * Test: Empty string vs null for string fields - * Expected: Both generate valid MD5 - */ - @Test - public void testGetCoreMd5_EmptyVsNullString() { - System.out.println("\n========== Test 9: Empty string vs null for string fields =========="); - - ThreadPoolParameterInfo configWithNull = new ThreadPoolParameterInfo(); - configWithNull.setTenantId(null); - configWithNull.setItemId(null); - configWithNull.setTpId(null); - - ThreadPoolParameterInfo configWithEmpty = new ThreadPoolParameterInfo(); - configWithEmpty.setTenantId(""); - configWithEmpty.setItemId(""); - configWithEmpty.setTpId(""); - - String nullMd5 = IncrementalMd5Util.getCoreMd5(configWithNull); - String emptyMd5 = IncrementalMd5Util.getCoreMd5(configWithEmpty); - - System.out.println("Null fields MD5: " + nullMd5); - System.out.println("Empty fields MD5: " + emptyMd5); - System.out.println("Are they different? " + !nullMd5.equals(emptyMd5)); - - Assert.assertNotNull("Null fields MD5 should not be null", nullMd5); - Assert.assertNotNull("Empty fields MD5 should not be null", emptyMd5); - System.out.println("Test passed: Both null and empty strings handled"); - } - - /** - * Test: Consistency - multiple calls should return same MD5 - * Expected: Same MD5 for same config - */ - @Test - public void testGetCoreMd5_Consistency() { - System.out.println("\n========== Test 10: MD5 calculation consistency =========="); - - ThreadPoolParameterInfo config = new ThreadPoolParameterInfo(); - config.setTenantId("test"); - config.setItemId("test"); - config.setTpId("test"); - config.setCorePoolSize(10); - config.setMaximumPoolSize(20); - - String md51 = IncrementalMd5Util.getCoreMd5(config); - String md52 = IncrementalMd5Util.getCoreMd5(config); - String md53 = IncrementalMd5Util.getCoreMd5(config); - - System.out.println("MD5 call 1: " + md51); - System.out.println("MD5 call 2: " + md52); - System.out.println("MD5 call 3: " + md53); - System.out.println("All equal? " + (md51.equals(md52) && md52.equals(md53))); - - Assert.assertEquals("Multiple calls should return same MD5", md51, md52); - Assert.assertEquals("Multiple calls should return same MD5", md52, md53); - System.out.println("Test passed: MD5 calculation is consistent"); - } -} diff --git a/starters/threadpool/server/src/main/java/cn/hippo4j/springboot/starter/core/CacheData.java b/starters/threadpool/server/src/main/java/cn/hippo4j/springboot/starter/core/CacheData.java index 1024981c..0e682d99 100644 --- a/starters/threadpool/server/src/main/java/cn/hippo4j/springboot/starter/core/CacheData.java +++ b/starters/threadpool/server/src/main/java/cn/hippo4j/springboot/starter/core/CacheData.java @@ -51,8 +51,6 @@ public class CacheData { @Getter private final String threadPoolId; - private final int protocolVersion; - private final String clientVersion; @Setter @@ -60,17 +58,16 @@ public class CacheData { private final CopyOnWriteArrayList listeners; - public CacheData(String tenantId, String itemId, String threadPoolId, int protocolVersion, String clientVersion) { + public CacheData(String tenantId, String itemId, String threadPoolId, String clientVersion) { this.tenantId = tenantId; this.itemId = itemId; this.threadPoolId = threadPoolId; - this.protocolVersion = protocolVersion; this.clientVersion = clientVersion; // Store full content for listeners to receive complete configuration ThreadPoolParameterInfo parameterInfo = ThreadPoolExecutorRegistry.getHolder(threadPoolId).getParameterInfo(); this.content = ContentUtil.getPoolContent(parameterInfo); // Calculate MD5 based on incremental content for version compatibility - String incrementalContent = IncrementalContentUtil.getVersionedContent(parameterInfo, protocolVersion, clientVersion); + String incrementalContent = IncrementalContentUtil.getVersionedContent(parameterInfo, clientVersion); this.md5 = getMd5String(incrementalContent); this.listeners = new CopyOnWriteArrayList<>(); } @@ -112,7 +109,7 @@ public class CacheData { // Calculate MD5 based on incremental content for version compatibility try { ThreadPoolParameterInfo parameterInfo = JSONUtil.parseObject(content, ThreadPoolParameterInfo.class); - String incrementalContent = IncrementalContentUtil.getVersionedContent(parameterInfo, protocolVersion, clientVersion); + String incrementalContent = IncrementalContentUtil.getVersionedContent(parameterInfo, clientVersion); this.md5 = getMd5String(incrementalContent); } catch (Exception e) { // Fallback to full content MD5 if parsing fails diff --git a/starters/threadpool/server/src/main/java/cn/hippo4j/springboot/starter/core/ClientWorker.java b/starters/threadpool/server/src/main/java/cn/hippo4j/springboot/starter/core/ClientWorker.java index a1163191..22c917fe 100644 --- a/starters/threadpool/server/src/main/java/cn/hippo4j/springboot/starter/core/ClientWorker.java +++ b/starters/threadpool/server/src/main/java/cn/hippo4j/springboot/starter/core/ClientWorker.java @@ -23,7 +23,6 @@ import cn.hippo4j.common.model.ThreadPoolParameterInfo; import cn.hippo4j.common.toolkit.ContentUtil; import cn.hippo4j.common.toolkit.GroupKey; import cn.hippo4j.common.toolkit.IdUtil; -import cn.hippo4j.common.toolkit.IncrementalContentUtil; import cn.hippo4j.common.toolkit.JSONUtil; import cn.hippo4j.common.toolkit.VersionUtil; import cn.hippo4j.springboot.starter.remote.HttpAgent; @@ -71,7 +70,6 @@ public class ClientWorker implements DisposableBean { private final long timeout; private final String identify; private final String version; - private final int protocolVersion; private final HttpAgent agent; private final ServerHealthCheck serverHealthCheck; private final ScheduledExecutorService executorService; @@ -93,8 +91,6 @@ public class ClientWorker implements DisposableBean { this.identify = identify; this.timeout = CONFIG_LONG_POLL_TIMEOUT; this.version = VersionUtil.resolveClientVersion(version, ClientWorker.class); - int resolvedProtocol = VersionUtil.resolveProtocolVersion(this.version, IncrementalContentUtil.PROTOCOL_VERSION); - this.protocolVersion = VersionUtil.UNKNOWN_VERSION.equals(this.version) ? IncrementalContentUtil.PROTOCOL_VERSION : resolvedProtocol; this.serverHealthCheck = serverHealthCheck; this.hippo4jClientShutdown = hippo4jClientShutdown; ScheduledExecutorService executor = Executors.newScheduledThreadPool(1, runnable -> { @@ -210,8 +206,6 @@ public class ClientWorker implements DisposableBean { headers.put(LONG_PULLING_TIMEOUT_NO_HANGUP, "true"); } headers.put(CLIENT_VERSION, version); - // Add protocol version header for incremental updates - headers.put("X-Hippo4j-Protocol-Version", String.valueOf(protocolVersion)); try { long readTimeoutMs = timeout + Math.round(timeout >> 1); Result result = agent.httpPostByConfig(LISTENER_PATH, headers, params, readTimeoutMs); @@ -282,7 +276,7 @@ public class ClientWorker implements DisposableBean { if (cacheData != null) { return cacheData; } - cacheData = new CacheData(namespace, itemId, threadPoolId, protocolVersion, version); + cacheData = new CacheData(namespace, itemId, threadPoolId, version); CacheData lastCacheData = cacheMap.putIfAbsent(threadPoolId, cacheData); if (lastCacheData == null) { String serverConfig; diff --git a/threadpool/server/config/src/main/java/cn/hippo4j/config/model/CacheItem.java b/threadpool/server/config/src/main/java/cn/hippo4j/config/model/CacheItem.java index c394055d..21fcb8c8 100644 --- a/threadpool/server/config/src/main/java/cn/hippo4j/config/model/CacheItem.java +++ b/threadpool/server/config/src/main/java/cn/hippo4j/config/model/CacheItem.java @@ -21,6 +21,8 @@ import cn.hippo4j.common.constant.Constants; import cn.hippo4j.common.toolkit.Md5Util; import cn.hippo4j.config.toolkit.SimpleReadWriteLock; import cn.hippo4j.config.toolkit.SingletonRepository; +import cn.hippo4j.common.toolkit.StringUtil; +import cn.hippo4j.common.toolkit.VersionUtil; import lombok.Getter; import lombok.Setter; @@ -43,7 +45,7 @@ public class CacheItem { private SimpleReadWriteLock rwLock = new SimpleReadWriteLock(); - private final ConcurrentHashMap versionMd5Cache = new ConcurrentHashMap<>(); + private final ConcurrentHashMap versionMd5Cache = new ConcurrentHashMap<>(); public CacheItem(String groupKey) { this.groupKey = SingletonRepository.DataIdGroupIdCache.getSingleton(groupKey); @@ -52,36 +54,38 @@ public class CacheItem { public CacheItem(String groupKey, String md5) { this.md5 = md5; this.groupKey = SingletonRepository.DataIdGroupIdCache.getSingleton(groupKey); - this.versionMd5Cache.put(1, md5); + this.versionMd5Cache.put(VersionUtil.UNKNOWN_VERSION, md5); } public CacheItem(String groupKey, ConfigAllInfo configAllInfo) { this.configAllInfo = configAllInfo; this.md5 = Md5Util.getTpContentMd5(configAllInfo); this.groupKey = SingletonRepository.DataIdGroupIdCache.getSingleton(groupKey); - this.versionMd5Cache.put(1, this.md5); + this.versionMd5Cache.put(VersionUtil.UNKNOWN_VERSION, this.md5); } - public String getMd5(int protocolVersion) { - if (protocolVersion <= 0) { - return md5; - } - return versionMd5Cache.get(protocolVersion); + public String getMd5(String clientVersion) { + String key = normalizeVersionKey(clientVersion); + return versionMd5Cache.getOrDefault(key, md5); } - public void setMd5(int protocolVersion, String value) { - if (protocolVersion <= 0) { - this.md5 = value; - return; - } + public void setMd5(String clientVersion, String value) { + String key = normalizeVersionKey(clientVersion); if (value == null) { - versionMd5Cache.remove(protocolVersion); + versionMd5Cache.remove(key); } else { - versionMd5Cache.put(protocolVersion, value); + versionMd5Cache.put(key, value); } } public void clearVersionMd5() { versionMd5Cache.clear(); } + + private String normalizeVersionKey(String clientVersion) { + if (StringUtil.isBlank(clientVersion)) { + return VersionUtil.UNKNOWN_VERSION; + } + return clientVersion.trim(); + } } diff --git a/threadpool/server/config/src/main/java/cn/hippo4j/config/service/ConfigCacheService.java b/threadpool/server/config/src/main/java/cn/hippo4j/config/service/ConfigCacheService.java index 2a124375..29d541b0 100644 --- a/threadpool/server/config/src/main/java/cn/hippo4j/config/service/ConfigCacheService.java +++ b/threadpool/server/config/src/main/java/cn/hippo4j/config/service/ConfigCacheService.java @@ -72,11 +72,11 @@ public class ConfigCacheService { private static final ConcurrentHashMap> CLIENT_CONFIG_CACHE = new ConcurrentHashMap(); public static boolean isUpdateData(String groupKey, String md5, String clientIdentify) { - return isUpdateData(groupKey, md5, clientIdentify, 1, null); + return isUpdateData(groupKey, md5, clientIdentify, VersionUtil.UNKNOWN_VERSION); } - public static boolean isUpdateData(String groupKey, String md5, String clientIdentify, int clientProtocolVersion, String clientVersion) { - String contentMd5 = ConfigCacheService.getContentMd5IsNullPut(groupKey, clientIdentify, clientProtocolVersion, clientVersion); + public static boolean isUpdateData(String groupKey, String md5, String clientIdentify, String clientVersion) { + String contentMd5 = ConfigCacheService.getContentMd5IsNullPut(groupKey, clientIdentify, clientVersion); return Objects.equals(contentMd5, md5); } @@ -106,11 +106,11 @@ public class ConfigCacheService { * @param clientIdentify * @return */ - private static synchronized String getContentMd5IsNullPut(String groupKey, String clientIdentify, int clientProtocolVersion, String clientVersion) { + private static synchronized String getContentMd5IsNullPut(String groupKey, String clientIdentify, String clientVersion) { Map cacheItemMap = CLIENT_CONFIG_CACHE.computeIfAbsent(groupKey, key -> new ConcurrentHashMap<>()); CacheItem cacheItem = cacheItemMap.get(clientIdentify); if (cacheItem != null) { - String versionMd5 = cacheItem.getMd5(clientProtocolVersion); + String versionMd5 = cacheItem.getMd5(clientVersion); if (StringUtil.isNotBlank(versionMd5)) { return versionMd5; } @@ -127,11 +127,8 @@ public class ConfigCacheService { } else { cacheItem.setConfigAllInfo(config); } - String versionedMd5 = IncrementalMd5Util.getVersionedMd5(config, clientProtocolVersion, clientVersion); - cacheItem.setMd5(clientProtocolVersion, versionedMd5); - if (clientProtocolVersion <= VersionUtil.LEGACY_PROTOCOL_VERSION || StringUtil.isBlank(cacheItem.getMd5())) { - cacheItem.setMd5(versionedMd5); - } + String versionedMd5 = IncrementalMd5Util.getVersionedMd5(config, clientVersion); + cacheItem.setMd5(clientVersion, versionedMd5); return versionedMd5; } return Constants.NULL; @@ -154,8 +151,7 @@ public class ConfigCacheService { CacheItem cache = makeSure(groupKey, identify); if (cache.getMd5() == null || !cache.getMd5().equals(md5)) { cache.clearVersionMd5(); - cache.setMd5(md5); - cache.setMd5(VersionUtil.LEGACY_PROTOCOL_VERSION, md5); + cache.setMd5(VersionUtil.UNKNOWN_VERSION, md5); String[] params = groupKey.split(GROUP_KEY_DELIMITER_TRANSLATION); ConfigAllInfo config = configService.findConfigRecentInfo(params); cache.setConfigAllInfo(config); diff --git a/threadpool/server/config/src/main/java/cn/hippo4j/config/toolkit/Md5ConfigUtil.java b/threadpool/server/config/src/main/java/cn/hippo4j/config/toolkit/Md5ConfigUtil.java index 79ff635b..3e45173d 100644 --- a/threadpool/server/config/src/main/java/cn/hippo4j/config/toolkit/Md5ConfigUtil.java +++ b/threadpool/server/config/src/main/java/cn/hippo4j/config/toolkit/Md5ConfigUtil.java @@ -75,10 +75,14 @@ public class Md5ConfigUtil { public static List compareMd5(HttpServletRequest request, Map clientMd5Map) { List changedGroupKeys = new ArrayList(); String clientVersionHeader = request.getHeader(Constants.CLIENT_VERSION); - int clientProtocolVersion = getClientProtocolVersion(request, clientVersionHeader); + String normalizedClientVersion = VersionUtil.resolveClientVersion(clientVersionHeader, null); + if (StringUtil.isBlank(normalizedClientVersion)) { + normalizedClientVersion = VersionUtil.UNKNOWN_VERSION; + } + final String effectiveClientVersion = normalizedClientVersion; // Make it effectively final clientMd5Map.forEach((key, val) -> { String clientIdentify = RequestUtil.getClientIdentify(request); - boolean isUpdateData = ConfigCacheService.isUpdateData(key, val, clientIdentify, clientProtocolVersion, clientVersionHeader); + boolean isUpdateData = ConfigCacheService.isUpdateData(key, val, clientIdentify, effectiveClientVersion); if (!isUpdateData) { changedGroupKeys.add(key); } @@ -86,24 +90,6 @@ public class Md5ConfigUtil { return changedGroupKeys; } - /** - * Get client protocol version from request header - * - * @param request HTTP request - * @return client protocol version, default to 1 for backward compatibility - */ - private static int getClientProtocolVersion(HttpServletRequest request, String clientVersionHeader) { - String versionHeader = request.getHeader("X-Hippo4j-Protocol-Version"); - if (StringUtil.isNotBlank(versionHeader)) { - try { - return Integer.parseInt(versionHeader.trim()); - } catch (NumberFormatException ignored) { - return 1; - } - } - return VersionUtil.resolveProtocolVersion(clientVersionHeader, 1); - } - public static Map getClientMd5Map(String configKeysString) { Map md5Map = new HashMap(CLIENT_MD5_MAP_INIT_SIZE); if (null == configKeysString || "".equals(configKeysString)) { diff --git a/threadpool/server/config/src/test/java/cn/hippo4j/config/service/ConfigCacheServiceVersionTest.java b/threadpool/server/config/src/test/java/cn/hippo4j/config/service/ConfigCacheServiceVersionTest.java deleted file mode 100644 index 749e625d..00000000 --- a/threadpool/server/config/src/test/java/cn/hippo4j/config/service/ConfigCacheServiceVersionTest.java +++ /dev/null @@ -1,198 +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 cn.hippo4j.config.service; - -import cn.hippo4j.common.model.ThreadPoolParameterInfo; -import cn.hippo4j.common.toolkit.IncrementalMd5Util; -import org.junit.Assert; -import org.junit.Test; - -import java.util.LinkedHashMap; - -/** - * ConfigCacheService Version-Aware Test - * Tests the version-aware MD5 comparison logic for cross-version compatibility - * - * Note: Tests focus on the MD5 calculation logic used by ConfigCacheService - * Integration tests for ConfigCacheService.isUpdateData() are covered separately - * due to dependencies on configuration cache infrastructure - */ -public class ConfigCacheServiceVersionTest { - - /** - * Test: Version-aware MD5 comparison logic - * Verifies that v1 and v2 produce different MD5 for same config with extended params - */ - @Test - public void testVersionedMd5_Difference() { - System.out.println("========== Test 1: v1 vs v2 MD5 difference =========="); - - ThreadPoolParameterInfo config = new ThreadPoolParameterInfo(); - config.setTenantId("test-tenant"); - config.setItemId("test-item"); - config.setTpId("test-pool"); - config.setCorePoolSize(10); - config.setMaximumPoolSize(20); - config.setQueueType(2); - config.setCapacity(1024); - config.setKeepAliveTime(60L); - config.setRejectedType(1); - config.setAllowCoreThreadTimeOut(0); - config.setExecuteTimeOut(5000L); // Extended parameter - config.setIsAlarm(1); // Extended parameter - LinkedHashMap metadata = new LinkedHashMap<>(); - metadata.put("executeTimeOut", "2.1.0"); - metadata.put("isAlarm", "2.1.0"); - config.setFieldVersionMetadata(metadata); - - String v1Md5 = IncrementalMd5Util.getVersionedMd5(config, 1); - String v2Md5 = IncrementalMd5Util.getVersionedMd5(config, 2); - String v3Md5 = IncrementalMd5Util.getVersionedMd5(config, 3); - - System.out.println("Config includes extended parameters: executeTimeOut, isAlarm"); - System.out.println("v1 MD5 (full): " + v1Md5); - System.out.println("v2 MD5 (incremental): " + v2Md5); - System.out.println("v3 MD5 (supports extended): " + v3Md5); - System.out.println("v1 equals v2? " + v1Md5.equals(v2Md5)); - System.out.println("v3 equals v2? " + v3Md5.equals(v2Md5)); - - Assert.assertEquals("Protocols below field threshold should share same MD5", v1Md5, v2Md5); - Assert.assertNotEquals("Protocol v3 should include extended fields", v2Md5, v3Md5); - System.out.println("Test passed: Older protocols share MD5 while supported protocol diverges"); - } - - /** - * Test: v2 client should not refresh when only extended params change - * This is the core extensibility improvement - */ - @Test - public void testV2Client_ExtendedParamChange_NoRefresh() { - System.out.println("\n========== Test 2: v2 client - extended param change =========="); - - // Old config - ThreadPoolParameterInfo oldConfig = new ThreadPoolParameterInfo(); - oldConfig.setCorePoolSize(10); - oldConfig.setMaximumPoolSize(20); - oldConfig.setQueueType(2); - oldConfig.setCapacity(1024); - oldConfig.setKeepAliveTime(60L); - oldConfig.setRejectedType(1); - oldConfig.setAllowCoreThreadTimeOut(0); - oldConfig.setExecuteTimeOut(3000L); // Extended - LinkedHashMap metadata = new LinkedHashMap<>(); - metadata.put("executeTimeOut", "2.1.0"); - oldConfig.setFieldVersionMetadata(metadata); - - // New config (only extended param changed) - ThreadPoolParameterInfo newConfig = new ThreadPoolParameterInfo(); - newConfig.setCorePoolSize(10); - newConfig.setMaximumPoolSize(20); - newConfig.setQueueType(2); - newConfig.setCapacity(1024); - newConfig.setKeepAliveTime(60L); - newConfig.setRejectedType(1); - newConfig.setAllowCoreThreadTimeOut(0); - newConfig.setExecuteTimeOut(5000L); // Extended param changed! - newConfig.setFieldVersionMetadata(metadata); - - String oldV2Md5 = IncrementalMd5Util.getVersionedMd5(oldConfig, 2); - String newV2Md5 = IncrementalMd5Util.getVersionedMd5(newConfig, 2); - - System.out.println("Extended param change: executeTimeOut 3000 -> 5000"); - System.out.println("Old v2 MD5: " + oldV2Md5); - System.out.println("New v2 MD5: " + newV2Md5); - System.out.println("Are they same? " + oldV2Md5.equals(newV2Md5)); - - Assert.assertEquals("v2 MD5 should remain same (no core param change)", oldV2Md5, newV2Md5); - System.out.println("Test passed: v2 client won't refresh for extended param change"); - } - - /** - * Test: v1 client SHOULD refresh when extended params change - * This verifies backward compatibility - */ - @Test - public void testV1Client_ExtendedParamChange_NoRefresh() { - System.out.println("\n========== Test 3: v1 client - extended param change =========="); - - // Old config - ThreadPoolParameterInfo oldConfig = new ThreadPoolParameterInfo(); - oldConfig.setCorePoolSize(10); - oldConfig.setMaximumPoolSize(20); - oldConfig.setQueueType(2); - oldConfig.setCapacity(1024); - oldConfig.setExecuteTimeOut(3000L); - LinkedHashMap metadata = new LinkedHashMap<>(); - metadata.put("executeTimeOut", "2.1.0"); - oldConfig.setFieldVersionMetadata(metadata); - - // New config (only extended param changed) - ThreadPoolParameterInfo newConfig = new ThreadPoolParameterInfo(); - newConfig.setCorePoolSize(10); - newConfig.setMaximumPoolSize(20); - newConfig.setQueueType(2); - newConfig.setCapacity(1024); - newConfig.setExecuteTimeOut(5000L); // Extended param changed! - newConfig.setFieldVersionMetadata(metadata); - - String oldV1Md5 = IncrementalMd5Util.getVersionedMd5(oldConfig, 1); - String newV1Md5 = IncrementalMd5Util.getVersionedMd5(newConfig, 1); - - System.out.println("Extended param change: executeTimeOut 3000 -> 5000"); - System.out.println("Old v1 MD5: " + oldV1Md5); - System.out.println("New v1 MD5: " + newV1Md5); - System.out.println("Are they same? " + oldV1Md5.equals(newV1Md5)); - - Assert.assertEquals("v1 should now leverage metadata to skip unsupported fields", oldV1Md5, newV1Md5); - System.out.println("Test passed: v1 client no longer refreshes for unsupported fields"); - } - - /** - * Test: Both v1 and v2 should refresh when core params change - * This ensures core functionality still works for both versions - */ - @Test - public void testBothVersions_CoreParamChange_ShouldRefresh() { - System.out.println("\n========== Test 4: Both versions - core param change =========="); - - // Old config - ThreadPoolParameterInfo oldConfig = new ThreadPoolParameterInfo(); - oldConfig.setCorePoolSize(10); - oldConfig.setMaximumPoolSize(20); - oldConfig.setQueueType(2); - - // New config (core param changed) - ThreadPoolParameterInfo newConfig = new ThreadPoolParameterInfo(); - newConfig.setCorePoolSize(15); // Core param changed! - newConfig.setMaximumPoolSize(20); - newConfig.setQueueType(2); - - String oldV1Md5 = IncrementalMd5Util.getVersionedMd5(oldConfig, 1); - String newV1Md5 = IncrementalMd5Util.getVersionedMd5(newConfig, 1); - String oldV2Md5 = IncrementalMd5Util.getVersionedMd5(oldConfig, 2); - String newV2Md5 = IncrementalMd5Util.getVersionedMd5(newConfig, 2); - - System.out.println("Core param change: corePoolSize 10 -> 15"); - System.out.println("v1: " + oldV1Md5 + " -> " + newV1Md5 + " (different? " + !oldV1Md5.equals(newV1Md5) + ")"); - System.out.println("v2: " + oldV2Md5 + " -> " + newV2Md5 + " (different? " + !oldV2Md5.equals(newV2Md5) + ")"); - - Assert.assertNotEquals("v1 should detect core param change", oldV1Md5, newV1Md5); - Assert.assertNotEquals("v2 should detect core param change", oldV2Md5, newV2Md5); - System.out.println("Test passed: Both versions detect core param changes"); - } -} diff --git a/threadpool/server/config/src/test/java/cn/hippo4j/config/toolkit/Md5ConfigUtilVersionTest.java b/threadpool/server/config/src/test/java/cn/hippo4j/config/toolkit/Md5ConfigUtilVersionTest.java deleted file mode 100644 index cd8a854b..00000000 --- a/threadpool/server/config/src/test/java/cn/hippo4j/config/toolkit/Md5ConfigUtilVersionTest.java +++ /dev/null @@ -1,223 +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 cn.hippo4j.config.toolkit; - -import cn.hippo4j.common.constant.Constants; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; - -import javax.servlet.http.HttpServletRequest; -import java.lang.reflect.Method; - -import static org.mockito.Mockito.when; - -/** - * Md5ConfigUtil Version Detection Test - * Tests the client protocol version detection logic for cross-version compatibility - */ -@RunWith(MockitoJUnitRunner.class) -public class Md5ConfigUtilVersionTest { - - @Mock - private HttpServletRequest request; - - private static final String PROTOCOL_VERSION_HEADER = "X-Hippo4j-Protocol-Version"; - - /** - * Test: Client sends v2 protocol version header - * Expected: Server correctly identifies client as v2 - */ - @Test - public void testGetClientVersion_WithV2Header() throws Exception { - System.out.println("========== Test 1: Client sends v2 version header =========="); - - when(request.getHeader(PROTOCOL_VERSION_HEADER)).thenReturn("2"); - - int version = invokeGetClientVersion(request); - - System.out.println("Request header: X-Hippo4j-Protocol-Version = 2"); - System.out.println("Detected client version: " + version); - - Assert.assertEquals("Should detect v2 client", 2, version); - System.out.println("Test passed: Server correctly identifies v2 client"); - } - - /** - * Test: Old client doesn't send version header - * Expected: Server defaults to v1 for backward compatibility - */ - @Test - public void testGetClientVersion_WithoutHeader() throws Exception { - System.out.println("\n========== Test 2: Old client without version header =========="); - - when(request.getHeader(PROTOCOL_VERSION_HEADER)).thenReturn(null); - - int version = invokeGetClientVersion(request); - - System.out.println("Request header: X-Hippo4j-Protocol-Version = null"); - System.out.println("Detected client version: " + version); - - Assert.assertEquals("Should default to v1", 1, version); - System.out.println("Test passed: Server defaults to v1 for backward compatibility"); - } - - /** - * Test: Version header is empty string - * Expected: Server defaults to v1 - */ - @Test - public void testGetClientVersion_WithEmptyHeader() throws Exception { - System.out.println("\n========== Test 3: Version header is empty string =========="); - - when(request.getHeader(PROTOCOL_VERSION_HEADER)).thenReturn(""); - - int version = invokeGetClientVersion(request); - - System.out.println("Request header: X-Hippo4j-Protocol-Version = \"\""); - System.out.println("Detected client version: " + version); - - Assert.assertEquals("Should default to v1 for empty header", 1, version); - System.out.println("Test passed: Empty header defaults to v1"); - } - - /** - * Test: Version header has invalid format (non-numeric) - * Expected: Server gracefully handles error and defaults to v1 - */ - @Test - public void testGetClientVersion_WithInvalidFormat() throws Exception { - System.out.println("\n========== Test 4: Version header has invalid format =========="); - - when(request.getHeader(PROTOCOL_VERSION_HEADER)).thenReturn("invalid"); - - int version = invokeGetClientVersion(request); - - System.out.println("Request header: X-Hippo4j-Protocol-Version = \"invalid\""); - System.out.println("Detected client version: " + version); - - Assert.assertEquals("Should default to v1 for invalid format", 1, version); - System.out.println("Test passed: Invalid format gracefully handled"); - } - - /** - * Test: Version header is v1 explicitly - * Expected: Server correctly identifies as v1 - */ - @Test - public void testGetClientVersion_WithV1Header() throws Exception { - System.out.println("\n========== Test 5: Client explicitly sends v1 =========="); - - when(request.getHeader(PROTOCOL_VERSION_HEADER)).thenReturn("1"); - - int version = invokeGetClientVersion(request); - - System.out.println("Request header: X-Hippo4j-Protocol-Version = 1"); - System.out.println("Detected client version: " + version); - - Assert.assertEquals("Should detect v1 client", 1, version); - System.out.println("Test passed: Server correctly identifies v1 client"); - } - - /** - * Test: Version header is future version (v3) - * Expected: Server correctly parses and returns the version - */ - @Test - public void testGetClientVersion_WithFutureVersion() throws Exception { - System.out.println("\n========== Test 6: Client sends future version (v3) =========="); - - when(request.getHeader(PROTOCOL_VERSION_HEADER)).thenReturn("3"); - - int version = invokeGetClientVersion(request); - - System.out.println("Request header: X-Hippo4j-Protocol-Version = 3"); - System.out.println("Detected client version: " + version); - - Assert.assertEquals("Should parse future version", 3, version); - System.out.println("Test passed: Future version handled correctly"); - } - - /** - * Test: compareMd5 method can be called with v1 client - * Note: This is a lightweight test focusing on version detection - * Full integration tests are covered separately - */ - @Test - public void testCompareMd5_VersionDetection_V1Client() throws Exception { - System.out.println("\n========== Test 7: Version detection in compareMd5 (v1) =========="); - - when(request.getHeader(PROTOCOL_VERSION_HEADER)).thenReturn(null); - - // Verify version detection works - int version = invokeGetClientVersion(request); - - System.out.println("Client version detected: v" + version); - Assert.assertEquals("Should detect v1 client", 1, version); - System.out.println("Test passed: compareMd5 will use v1 protocol for this client"); - } - - /** - * Test: compareMd5 method can be called with v2 client - * Note: This is a lightweight test focusing on version detection - * Full integration tests are covered separately - */ - @Test - public void testCompareMd5_VersionDetection_V2Client() throws Exception { - System.out.println("\n========== Test 8: Version detection in compareMd5 (v2) =========="); - - when(request.getHeader(PROTOCOL_VERSION_HEADER)).thenReturn("2"); - - // Verify version detection works - int version = invokeGetClientVersion(request); - - System.out.println("Client version detected: v" + version); - Assert.assertEquals("Should detect v2 client", 2, version); - System.out.println("Test passed: compareMd5 will use v2 protocol for this client"); - } - - /** - * Test: Fallback to semantic client version when protocol header is missing. - */ - @Test - public void testGetClientVersion_FromClientVersionHeader() throws Exception { - System.out.println("\n========== Test 9: Fallback to client version header =========="); - - when(request.getHeader(PROTOCOL_VERSION_HEADER)).thenReturn(null); - when(request.getHeader(Constants.CLIENT_VERSION)).thenReturn("2.0.1"); - - int version = invokeGetClientVersion(request); - - System.out.println("Client-Version header: 2.0.1"); - System.out.println("Detected protocol version: v" + version); - - Assert.assertEquals("Semantic version should map to protocol v2", 2, version); - System.out.println("Test passed: Fallback resolved protocol from client version header"); - } - - /** - * Helper method to invoke private getClientVersion method via reflection - */ - private int invokeGetClientVersion(HttpServletRequest request) throws Exception { - Method method = Md5ConfigUtil.class.getDeclaredMethod("getClientProtocolVersion", HttpServletRequest.class, String.class); - method.setAccessible(true); - return (int) method.invoke(null, request, request.getHeader(Constants.CLIENT_VERSION)); - } -}