diff --git a/polaris-common/polaris-config/src/main/java/com/tencent/polaris/api/config/configuration/ConnectorConfig.java b/polaris-common/polaris-config/src/main/java/com/tencent/polaris/api/config/configuration/ConnectorConfig.java index a236b7dd6..e3a7ebdfb 100644 --- a/polaris-common/polaris-config/src/main/java/com/tencent/polaris/api/config/configuration/ConnectorConfig.java +++ b/polaris-common/polaris-config/src/main/java/com/tencent/polaris/api/config/configuration/ConnectorConfig.java @@ -33,4 +33,18 @@ public interface ConnectorConfig extends ServerConnectorConfig { * @return 连接器类型 */ String getConnectorType(); + + /** + * 是否开启推空保护 + * + * @return 是否开启推空保护 + */ + Boolean isEmptyProtectionEnable(); + + /** + * 推空保护过期时间,单位毫秒 + * + * @return 推空保护过期时间 + */ + Long getEmptyProtectionExpiredInterval(); } diff --git a/polaris-common/polaris-config/src/main/java/com/tencent/polaris/factory/config/configuration/ConnectorConfigImpl.java b/polaris-common/polaris-config/src/main/java/com/tencent/polaris/factory/config/configuration/ConnectorConfigImpl.java index 9dabdf9d7..c740f9ec8 100644 --- a/polaris-common/polaris-config/src/main/java/com/tencent/polaris/factory/config/configuration/ConnectorConfigImpl.java +++ b/polaris-common/polaris-config/src/main/java/com/tencent/polaris/factory/config/configuration/ConnectorConfigImpl.java @@ -61,6 +61,13 @@ public class ConnectorConfigImpl extends ServerConnectorConfigImpl implements Co @JsonProperty private Integer configFileGroupThreadNum = 10; + @JsonProperty + private Boolean emptyProtectionEnable = true; + + @JsonProperty + @JsonDeserialize(using = TimeStrJsonDeserializer.class) + private Long emptyProtectionExpiredInterval = 7 * 24 * 3600 * 1000L; + @Override public void verify() { ConfigUtils.validateString(connectorType, "configConnectorType"); @@ -91,6 +98,12 @@ public void setDefault(Object defaultObject) { if (connectorType == null) { this.connectorType = connectorConfig.getConnectorType(); } + if (emptyProtectionEnable == null) { + this.emptyProtectionEnable = connectorConfig.isEmptyProtectionEnable(); + } + if (emptyProtectionExpiredInterval == null) { + this.emptyProtectionExpiredInterval = connectorConfig.getEmptyProtectionExpiredInterval(); + } } } @@ -159,4 +172,21 @@ public void setConfigFileGroupThreadNum(Integer configFileGroupThreadNum) { this.configFileGroupThreadNum = configFileGroupThreadNum; } + @Override + public Boolean isEmptyProtectionEnable() { + return emptyProtectionEnable; + } + + public void setEmptyProtectionEnable(Boolean emptyProtectionEnable) { + this.emptyProtectionEnable = emptyProtectionEnable; + } + + @Override + public Long getEmptyProtectionExpiredInterval() { + return emptyProtectionExpiredInterval; + } + + public void setEmptyProtectionExpiredInterval(Long emptyProtectionExpiredInterval) { + this.emptyProtectionExpiredInterval = emptyProtectionExpiredInterval; + } } diff --git a/polaris-configuration/polaris-configuration-client/src/main/java/com/tencent/polaris/configuration/client/internal/RemoteConfigFileRepo.java b/polaris-configuration/polaris-configuration-client/src/main/java/com/tencent/polaris/configuration/client/internal/RemoteConfigFileRepo.java index ebee6767d..b7ebd8aff 100644 --- a/polaris-configuration/polaris-configuration-client/src/main/java/com/tencent/polaris/configuration/client/internal/RemoteConfigFileRepo.java +++ b/polaris-configuration/polaris-configuration-client/src/main/java/com/tencent/polaris/configuration/client/internal/RemoteConfigFileRepo.java @@ -17,6 +17,7 @@ package com.tencent.polaris.configuration.client.internal; +import com.tencent.polaris.annonation.JustForTest; import com.tencent.polaris.api.control.Destroyable; import com.tencent.polaris.api.exception.ServerCodes; import com.tencent.polaris.api.plugin.configuration.ConfigFile; @@ -27,11 +28,11 @@ import com.tencent.polaris.client.api.SDKContext; import com.tencent.polaris.client.util.NamedThreadFactory; import com.tencent.polaris.configuration.api.core.ConfigFileMetadata; +import com.tencent.polaris.configuration.client.util.ConfigFileUtils; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; @@ -47,6 +48,8 @@ public class RemoteConfigFileRepo extends AbstractConfigFileRepo { private static ScheduledExecutorService pullExecutorService; + private static Set configFileInitSet = new HashSet<>(); + private final AtomicReference remoteConfigFile; //服务端通知的版本号,此版本号有可能落后于服务端 private final AtomicLong notifiedVersion; @@ -58,6 +61,17 @@ public class RemoteConfigFileRepo extends AbstractConfigFileRepo { private String token; + private final boolean emptyProtection; + + private final long emptyProtectionExpiredInterval; + + /** + * 淘汰线程 + */ + private final ScheduledExecutorService emptyProtectionExpireExecutor; + + private ScheduledFuture emptyProtectionExpireFuture; + static { createPullExecutorService(); } @@ -80,6 +94,9 @@ public RemoteConfigFileRepo(SDKContext sdkContext, //获取远程调用插件实现类 this.configFileConnector = connector; this.fallbackToLocalCache = sdkContext.getConfig().getConfigFile().getServerConnector().getFallbackToLocalCache(); + this.emptyProtection = sdkContext.getConfig().getConfigFile().getServerConnector().isEmptyProtectionEnable(); + this.emptyProtectionExpiredInterval = sdkContext.getConfig().getConfigFile().getServerConnector().getEmptyProtectionExpiredInterval(); + this.emptyProtectionExpireExecutor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("polaris-config-empty-protection")); //注册 destroy hook registerRepoDestroyHook(sdkContext); //同步从远程仓库拉取一次 @@ -163,7 +180,8 @@ protected void doPull() { } else { shouldUpdateLocalCache = remoteConfigFile.get() == null || pulledConfigFile.getVersion() != remoteConfigFile.get().getVersion(); } - if (shouldUpdateLocalCache) { + // 构造更新回调动作 + Runnable runnable = () -> { ConfigFile copiedConfigFile = deepCloneConfigFile(pulledConfigFile); remoteConfigFile.set(copiedConfigFile); //配置有更新,触发回调 @@ -171,24 +189,42 @@ protected void doPull() { // update local file cache this.configFilePersistHandler.asyncSaveConfigFile(pulledConfigFile); + }; + if (shouldUpdateLocalCache && checkEmptyProtect(response)) { + shouldUpdateLocalCache = false; + fallbackIfNecessaryWhenStartingUp(pullConfigFileReq); + submitEmptyProtectionExpireTask(runnable); + } + if (shouldUpdateLocalCache) { + runnable.run(); + cancelEmptyProtectionExpireTask(); } return; } //远端没有此配置文件 if (response.getCode() == ServerCodes.NOT_FOUND_RESOURCE) { - LOGGER.warn("[Config] config file not found, please check whether config file released. {}", - configFileMetadata); - //delete local file cache - this.configFilePersistHandler - .asyncDeleteConfigFile(new ConfigFile(configFileMetadata.getNamespace(), - configFileMetadata.getFileGroup(), configFileMetadata.getFileName())); - - //删除配置文件 - if (remoteConfigFile.get() != null) { - remoteConfigFile.set(null); - //删除配置文件也需要触发通知 - fireChangeEvent(null); + // 构造更新回调动作 + Runnable runnable = () -> { + //delete local file cache + this.configFilePersistHandler + .asyncDeleteConfigFile(new ConfigFile(configFileMetadata.getNamespace(), + configFileMetadata.getFileGroup(), configFileMetadata.getFileName())); + + //删除配置文件 + if (remoteConfigFile.get() != null) { + remoteConfigFile.set(null); + //删除配置文件也需要触发通知 + fireChangeEvent(null); + } + }; + if (checkEmptyProtect(response)) { + fallbackIfNecessaryWhenStartingUp(pullConfigFileReq); + submitEmptyProtectionExpireTask(runnable); + } else { + LOGGER.warn("[Config] config file not found, please check whether config file released. {}", + configFileMetadata); + runnable.run(); } return; } @@ -213,16 +249,39 @@ protected void doPull() { } private void fallbackIfNecessary(final int retryTimes, ConfigFile configFileReq) { - if (retryTimes >= PULL_CONFIG_RETRY_TIMES && fallbackToLocalCache) { + if (retryTimes >= PULL_CONFIG_RETRY_TIMES) { + LOGGER.info("[Config] failed to pull config file from remote."); + //重试次数超过上限,从本地缓存拉取 + loadLocalCache(configFileReq); + } + } + + private void fallbackIfNecessaryWhenStartingUp(ConfigFile configFileReq) { + String identifier = getIdentifier(); + boolean initFlag = false; + if (configFileInitSet.contains(identifier)) { + initFlag = true; + } else { + configFileInitSet.add(identifier); + } + if (!initFlag) { + // 第一次启动的时候,如果拉取到空配置,则尝试从缓存中获取 + LOGGER.info("[Config] load local cache because of empty config when starting up."); + loadLocalCache(configFileReq); + } + } + + private void loadLocalCache(ConfigFile configFileReq) { + if (fallbackToLocalCache) { ConfigFile configFileRes = configFilePersistHandler.loadPersistedConfigFile(configFileReq); if (configFileRes != null) { - LOGGER.info("[Config] failed to pull config file from remote,fallback to local cache success.{}.", configFileRes); + LOGGER.info("[Config] load local cache success.{}.", configFileRes); remoteConfigFile.set(configFileRes); //配置有更新,触发回调 fireChangeEvent(configFileRes); return; } - LOGGER.info("[Config] failed to pull config file from remote,fallback to local cache fail.{}.", configFileReq); + LOGGER.info("[Config] load local cache fail.{}.", configFileReq); } } @@ -293,4 +352,39 @@ protected void doDestroy() { static void destroyPullExecutor() { ThreadPoolUtils.waitAndStopThreadPools(new ExecutorService[]{pullExecutorService}); } + + /** + * 若配置为空,则推空保护开启,则不刷新配置 + * + * @param configFileResponse + * @return + */ + private boolean checkEmptyProtect(ConfigFileResponse configFileResponse) { + if (emptyProtection && ConfigFileUtils.checkConfigContentEmpty(configFileResponse)) { + LOGGER.warn("Empty response from remote with {}, will not refresh config.", getIdentifier()); + return true; + } + return false; + } + + private void submitEmptyProtectionExpireTask(Runnable runnable) { + if (emptyProtectionExpireFuture == null || emptyProtectionExpireFuture.isCancelled() || emptyProtectionExpireFuture.isDone()) { + LOGGER.info("Empty protection expire task of {} submit.", getIdentifier()); + emptyProtectionExpireFuture = emptyProtectionExpireExecutor.schedule(runnable, emptyProtectionExpiredInterval, TimeUnit.MILLISECONDS); + } + } + + private void cancelEmptyProtectionExpireTask() { + if (emptyProtectionExpireFuture != null && !emptyProtectionExpireFuture.isCancelled() && !emptyProtectionExpireFuture.isDone()) { + emptyProtectionExpireFuture.cancel(true); + LOGGER.info("Empty protection expire task of {} cancel.", getIdentifier()); + } + } + + @JustForTest + String getIdentifier() { + return configFileMetadata.getNamespace() + "." + + configFileMetadata.getFileGroup() + "." + + configFileMetadata.getFileName(); + } } diff --git a/polaris-configuration/polaris-configuration-client/src/main/java/com/tencent/polaris/configuration/client/util/ConfigFileUtils.java b/polaris-configuration/polaris-configuration-client/src/main/java/com/tencent/polaris/configuration/client/util/ConfigFileUtils.java index fe8ab4d08..1d9755611 100644 --- a/polaris-configuration/polaris-configuration-client/src/main/java/com/tencent/polaris/configuration/client/util/ConfigFileUtils.java +++ b/polaris-configuration/polaris-configuration-client/src/main/java/com/tencent/polaris/configuration/client/util/ConfigFileUtils.java @@ -18,8 +18,13 @@ package com.tencent.polaris.configuration.client.util; import com.google.common.collect.Maps; +import com.tencent.polaris.api.exception.ServerCodes; +import com.tencent.polaris.api.plugin.configuration.ConfigFile; +import com.tencent.polaris.api.plugin.configuration.ConfigFileResponse; import com.tencent.polaris.api.utils.StringUtils; import com.tencent.polaris.configuration.api.core.ConfigFileMetadata; +import com.tencent.polaris.logging.LoggerFactory; +import org.slf4j.Logger; import java.util.Collections; import java.util.Map; @@ -31,6 +36,8 @@ */ public class ConfigFileUtils { + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigFileUtils.class); + public static void checkConfigFileMetadata(ConfigFileMetadata configFileMetadata) { if (StringUtils.isBlank(configFileMetadata.getNamespace())) { throw new IllegalArgumentException("namespace cannot be empty."); @@ -57,4 +64,25 @@ public static Set stringPropertyNames(Properties properties) { } return map.keySet(); } + + public static boolean checkConfigContentEmpty(ConfigFileResponse configFileResponse) { + if (configFileResponse == null) { + LOGGER.debug("config file response is null."); + return true; + } + if (configFileResponse.getCode() == ServerCodes.NOT_FOUND_RESOURCE) { + LOGGER.debug("config file not found. maybe not exist or deleted."); + return true; + } + ConfigFile configFile = configFileResponse.getConfigFile(); + if (configFile == null) { + LOGGER.debug("config file is null."); + return true; + } + if (StringUtils.isBlank(configFile.getContent())) { + LOGGER.debug("config file content is empty."); + return true; + } + return false; + } } diff --git a/polaris-configuration/polaris-configuration-client/src/test/java/com/tencent/polaris/configuration/client/internal/RemoteConfigFileRepoTest.java b/polaris-configuration/polaris-configuration-client/src/test/java/com/tencent/polaris/configuration/client/internal/RemoteConfigFileRepoTest.java index 9930d21c1..5d312af05 100644 --- a/polaris-configuration/polaris-configuration-client/src/test/java/com/tencent/polaris/configuration/client/internal/RemoteConfigFileRepoTest.java +++ b/polaris-configuration/polaris-configuration-client/src/test/java/com/tencent/polaris/configuration/client/internal/RemoteConfigFileRepoTest.java @@ -40,6 +40,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @@ -104,7 +105,6 @@ public void testPullNotFoundConfigFile() { ConfigFileResponse configFileResponse = new ConfigFileResponse(ServerCodes.NOT_FOUND_RESOURCE, "", null); when(configFileFilterChain.execute(any(), any())).thenReturn(configFileResponse); - doNothing().when(configFilePersistHandler).asyncDeleteConfigFile(any()); RemoteConfigFileRepo remoteConfigFileRepo = new RemoteConfigFileRepo(sdkContext, configFileLongPollingService, configFileFilterChain, configFileConnector, @@ -234,4 +234,61 @@ public void testNotifyAndPullSecondTime() throws InterruptedException { Assert.assertEquals(newContent, remoteConfigFileRepo.getContent()); Assert.assertEquals(newVersion, remoteConfigFileRepo.getConfigFileVersion()); } + + @Test + public void testGetIdentifierWithNormalValues() { + // 准备 + ConfigFileMetadata configFileMetadata = new DefaultConfigFileMetadata("testNamespace", "testGroup", "testFile"); + RemoteConfigFileRepo remoteConfigFileRepo = + new RemoteConfigFileRepo(sdkContext, configFileLongPollingService, configFileFilterChain, configFileConnector, + configFileMetadata, configFilePersistHandler); + // 验证 + assertThat(remoteConfigFileRepo.getIdentifier()).isEqualTo("testNamespace.testGroup.testFile"); + } + + @Test + public void testGetIdentifierWithEmptyValues() { + // 准备 + ConfigFileMetadata configFileMetadata = new DefaultConfigFileMetadata("", "", ""); + RemoteConfigFileRepo remoteConfigFileRepo = + new RemoteConfigFileRepo(sdkContext, configFileLongPollingService, configFileFilterChain, configFileConnector, + configFileMetadata, configFilePersistHandler); + // 验证 + assertThat(remoteConfigFileRepo.getIdentifier()).isEqualTo(".."); + } + + @Test + public void testGetIdentifierWithSpecialCharacters() { + // 准备 + ConfigFileMetadata configFileMetadata = new DefaultConfigFileMetadata("test@namespace", "test-group", "test_file.properties"); + RemoteConfigFileRepo remoteConfigFileRepo = + new RemoteConfigFileRepo(sdkContext, configFileLongPollingService, configFileFilterChain, configFileConnector, + configFileMetadata, configFilePersistHandler); + // 验证 + assertThat(remoteConfigFileRepo.getIdentifier()).isEqualTo("test@namespace.test-group.test_file.properties"); + } + + @Test + public void testGetIdentifierWithNullValues() { + // 准备 + ConfigFileMetadata configFileMetadata = new DefaultConfigFileMetadata(null, null, null); + RemoteConfigFileRepo remoteConfigFileRepo = + new RemoteConfigFileRepo(sdkContext, configFileLongPollingService, configFileFilterChain, configFileConnector, + configFileMetadata, configFilePersistHandler); + + // 验证 + assertThat(remoteConfigFileRepo.getIdentifier()).isEqualTo("null.null.null"); + } + + @Test + public void testGetIdentifierWithMixedValues() { + // 准备 + ConfigFileMetadata configFileMetadata = new DefaultConfigFileMetadata("prod", "", "application.yaml"); + RemoteConfigFileRepo remoteConfigFileRepo = + new RemoteConfigFileRepo(sdkContext, configFileLongPollingService, configFileFilterChain, configFileConnector, + configFileMetadata, configFilePersistHandler); + + // 验证 + assertThat(remoteConfigFileRepo.getIdentifier()).isEqualTo("prod..application.yaml"); + } } diff --git a/polaris-configuration/polaris-configuration-client/src/test/java/com/tencent/polaris/configuration/client/util/ConfigFileUtilsTest.java b/polaris-configuration/polaris-configuration-client/src/test/java/com/tencent/polaris/configuration/client/util/ConfigFileUtilsTest.java new file mode 100644 index 000000000..ebd6c6bba --- /dev/null +++ b/polaris-configuration/polaris-configuration-client/src/test/java/com/tencent/polaris/configuration/client/util/ConfigFileUtilsTest.java @@ -0,0 +1,129 @@ +/* + * Tencent is pleased to support the open source community by making polaris-java available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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 com.tencent.polaris.configuration.client.util; + +import com.tencent.polaris.api.exception.ServerCodes; +import com.tencent.polaris.api.plugin.configuration.ConfigFile; +import com.tencent.polaris.api.plugin.configuration.ConfigFileResponse; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ConfigFileUtils}. + * + * @author Haotian Zhang + */ +public class ConfigFileUtilsTest { + + @Test + public void testCheckConfigContentEmpty_WhenResponseIsNull() { + // 准备:设置输入为null + ConfigFileResponse response = null; + + // 执行:调用待测方法 + boolean result = ConfigFileUtils.checkConfigContentEmpty(response); + + // 验证:预期返回true + assertThat(result).isTrue(); + } + + @Test + public void testCheckConfigContentEmpty_WhenResponseCodeIsNotFound() { + // 准备:创建响应对象,设置响应码为NOT_FOUND_RESOURCE + ConfigFileResponse response = new ConfigFileResponse(ServerCodes.NOT_FOUND_RESOURCE, "not found", null); + + // 执行:调用待测方法 + boolean result = ConfigFileUtils.checkConfigContentEmpty(response); + + // 验证:预期返回true + assertThat(result).isTrue(); + } + + @Test + public void testCheckConfigContentEmpty_WhenConfigFileIsNull() { + // 准备:创建响应对象,不设置configFile + ConfigFileResponse response = new ConfigFileResponse(ServerCodes.EXECUTE_SUCCESS, "success", null); + + // 执行:调用待测方法 + boolean result = ConfigFileUtils.checkConfigContentEmpty(response); + + // 验证:预期返回true + assertThat(result).isTrue(); + } + + @Test + public void testCheckConfigContentEmpty_WhenContentIsEmpty() { + // 准备:创建完整的响应对象,但内容为空 + ConfigFile configFile = new ConfigFile("namespace", "group", "name"); + configFile.setContent(""); + + ConfigFileResponse response = new ConfigFileResponse(ServerCodes.EXECUTE_SUCCESS, "success", configFile); + + // 执行:调用待测方法 + boolean result = ConfigFileUtils.checkConfigContentEmpty(response); + + // 验证:预期返回true + assertThat(result).isTrue(); + } + + @Test + public void testCheckConfigContentEmpty_WhenContentIsBlank() { + // 准备:创建完整的响应对象,但内容为空白字符 + ConfigFile configFile = new ConfigFile("namespace", "group", "name"); + configFile.setContent(" "); + + ConfigFileResponse response = new ConfigFileResponse(ServerCodes.EXECUTE_SUCCESS, "success", configFile); + + // 执行:调用待测方法 + boolean result = ConfigFileUtils.checkConfigContentEmpty(response); + + // 验证:预期返回true + assertThat(result).isTrue(); + } + + @Test + public void testCheckConfigContentEmpty_WhenContentIsValid() { + // 准备:创建完整的响应对象,且有有效内容 + ConfigFile configFile = new ConfigFile("namespace", "group", "name"); + configFile.setContent("valid content"); + + ConfigFileResponse response = new ConfigFileResponse(ServerCodes.EXECUTE_SUCCESS, "success", configFile); + + // 执行:调用待测方法 + boolean result = ConfigFileUtils.checkConfigContentEmpty(response); + + // 验证:预期返回false + assertThat(result).isFalse(); + } + + @Test + public void testCheckConfigContentEmpty_WhenResponseCodeIsSuccessButContentIsNull() { + // 准备:创建响应对象,设置成功响应码但内容为null + ConfigFile configFile = new ConfigFile("namespace", "group", "name"); + configFile.setContent(null); + + ConfigFileResponse response = new ConfigFileResponse(ServerCodes.EXECUTE_SUCCESS, "success", configFile); + + // 执行:调用待测方法 + boolean result = ConfigFileUtils.checkConfigContentEmpty(response); + + // 验证:预期返回true + assertThat(result).isTrue(); + } +} diff --git a/polaris-ratelimit/polaris-ratelimit-factory/src/test/java/com/tencent/polaris/ratelimit/test/core/MultiRuleTest.java b/polaris-ratelimit/polaris-ratelimit-factory/src/test/java/com/tencent/polaris/ratelimit/test/core/MultiRuleTest.java index cbfd347f4..387d377cc 100644 --- a/polaris-ratelimit/polaris-ratelimit-factory/src/test/java/com/tencent/polaris/ratelimit/test/core/MultiRuleTest.java +++ b/polaris-ratelimit/polaris-ratelimit-factory/src/test/java/com/tencent/polaris/ratelimit/test/core/MultiRuleTest.java @@ -40,14 +40,15 @@ import com.tencent.polaris.specification.api.v1.traffic.manage.RateLimitProto.Rule.Type; import com.tencent.polaris.test.common.TestUtils; import com.tencent.polaris.test.mock.discovery.NamingServer; -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + public class MultiRuleTest { private static final int PORT = 10092; @@ -152,7 +153,7 @@ public void testGetQuotaLayer2() { // first query header boolean hasLimited = false; boolean hasPassed = false; - for (int i = 0; i < 16; i++) { + for (int i = 0; i < 18; i++) { QuotaResponse quotaResponse = quotaAcquire(limitAPI, Consts.METHOD_PAY, Consts.HEADER_VALUE); QuotaResultCode code = quotaResponse.getCode(); if (code == QuotaResultCode.QuotaResultLimited) { diff --git a/pom.xml b/pom.xml index c2041d433..30537ace5 100644 --- a/pom.xml +++ b/pom.xml @@ -66,7 +66,7 @@ - 2.0.1.0-SNAPSHOT + 2.0.2.0-SNAPSHOT ${maven.build.timestamp} false