提交 2a834828 编写于 作者: 张乐 提交者: GitHub

Merge pull request #719 from nobodyiam/configservice-cache-merge

Config service cache and namespace name normalization
......@@ -15,6 +15,7 @@ import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.common.utils.GrayReleaseRuleItemTransformer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
......@@ -70,6 +71,7 @@ public class NamespaceBranchController {
return ruleDTO;
}
@Transactional
@RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}/rules", method = RequestMethod.PUT)
public void updateBranchGrayRules(@PathVariable String appId, @PathVariable String clusterName,
@PathVariable String namespaceName, @PathVariable String branchName,
......@@ -87,6 +89,7 @@ public class NamespaceBranchController {
Topics.APOLLO_RELEASE_TOPIC);
}
@Transactional
@RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}", method = RequestMethod.DELETE)
public void deleteBranch(@PathVariable String appId, @PathVariable String clusterName,
@PathVariable String namespaceName, @PathVariable String branchName,
......
......@@ -94,6 +94,7 @@ public class ReleaseController {
return BeanUtils.transfrom(ReleaseDTO.class, release);
}
@Transactional
@RequestMapping(path = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases", method = RequestMethod.POST)
public ReleaseDTO publish(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName,
......@@ -160,6 +161,7 @@ public class ReleaseController {
}
@Transactional
@RequestMapping(path = "/releases/{releaseId}/rollback", method = RequestMethod.PUT)
public void rollback(@PathVariable("releaseId") long releaseId,
@RequestParam("operator") String operator) {
......
......@@ -21,6 +21,16 @@ import java.util.concurrent.TimeUnit;
@Component
public class BizConfig extends RefreshableConfig {
private static final int DEFAULT_ITEM_KEY_LENGTH = 128;
private static final int DEFAULT_ITEM_VALUE_LENGTH = 20000;
private static final int DEFAULT_APPNAMESPACE_CACHE_REBUILD_INTERVAL = 60; //60s
private static final int DEFAULT_GRAY_RELEASE_RULE_SCAN_INTERVAL = 60; //60s
private static final int DEFAULT_APPNAMESPACE_CACHE_SCAN_INTERVAL = 1; //1s
private static final int DEFAULT_RELEASE_MESSAGE_CACHE_SCAN_INTERVAL = 1; //1s
private static final int DEFAULT_RELEASE_MESSAGE_SCAN_INTERVAL_IN_MS = 1000; //1000ms
private static final int DEFAULT_RELEASE_MESSAGE_NOTIFICATION_BATCH = 100;
private static final int DEFAULT_RELEASE_MESSAGE_NOTIFICATION_BATCH_INTERVAL_IN_MILLI = 100;//100ms
private Gson gson = new Gson();
private static final Type namespaceValueLengthOverrideTypeReference =
new TypeToken<Map<Long, Integer>>() {
......@@ -44,18 +54,18 @@ public class BizConfig extends RefreshableConfig {
}
public int grayReleaseRuleScanInterval() {
int interval = getIntProperty("apollo.gray-release-rule-scan.interval", 60);
return checkInt(interval, 1, Integer.MAX_VALUE, 60);
int interval = getIntProperty("apollo.gray-release-rule-scan.interval", DEFAULT_GRAY_RELEASE_RULE_SCAN_INTERVAL);
return checkInt(interval, 1, Integer.MAX_VALUE, DEFAULT_GRAY_RELEASE_RULE_SCAN_INTERVAL);
}
public int itemKeyLengthLimit() {
int limit = getIntProperty("item.key.length.limit", 128);
return checkInt(limit, 5, Integer.MAX_VALUE, 128);
int limit = getIntProperty("item.key.length.limit", DEFAULT_ITEM_KEY_LENGTH);
return checkInt(limit, 5, Integer.MAX_VALUE, DEFAULT_ITEM_KEY_LENGTH);
}
public int itemValueLengthLimit() {
int limit = getIntProperty("item.value.length.limit", 20000);
return checkInt(limit, 5, Integer.MAX_VALUE, 20000);
int limit = getIntProperty("item.value.length.limit", DEFAULT_ITEM_VALUE_LENGTH);
return checkInt(limit, 5, Integer.MAX_VALUE, DEFAULT_ITEM_VALUE_LENGTH);
}
public Map<Long, Integer> namespaceValueLengthLimitOverride() {
......@@ -85,8 +95,8 @@ public class BizConfig extends RefreshableConfig {
}
public int appNamespaceCacheScanInterval() {
int interval = getIntProperty("apollo.app-namespace-cache-scan.interval", 1);
return checkInt(interval, 1, Integer.MAX_VALUE, 1);
int interval = getIntProperty("apollo.app-namespace-cache-scan.interval", DEFAULT_APPNAMESPACE_CACHE_SCAN_INTERVAL);
return checkInt(interval, 1, Integer.MAX_VALUE, DEFAULT_APPNAMESPACE_CACHE_SCAN_INTERVAL);
}
public TimeUnit appNamespaceCacheScanIntervalTimeUnit() {
......@@ -94,8 +104,8 @@ public class BizConfig extends RefreshableConfig {
}
public int appNamespaceCacheRebuildInterval() {
int interval = getIntProperty("apollo.app-namespace-cache-rebuild.interval", 60);
return checkInt(interval, 1, Integer.MAX_VALUE, 60);
int interval = getIntProperty("apollo.app-namespace-cache-rebuild.interval", DEFAULT_APPNAMESPACE_CACHE_REBUILD_INTERVAL);
return checkInt(interval, 1, Integer.MAX_VALUE, DEFAULT_APPNAMESPACE_CACHE_REBUILD_INTERVAL);
}
public TimeUnit appNamespaceCacheRebuildIntervalTimeUnit() {
......@@ -103,22 +113,31 @@ public class BizConfig extends RefreshableConfig {
}
public int releaseMessageCacheScanInterval() {
int interval = getIntProperty("apollo.release-message-cache-scan.interval", 1);
return checkInt(interval, 1, Integer.MAX_VALUE, 1);
int interval = getIntProperty("apollo.release-message-cache-scan.interval", DEFAULT_RELEASE_MESSAGE_CACHE_SCAN_INTERVAL);
return checkInt(interval, 1, Integer.MAX_VALUE, DEFAULT_RELEASE_MESSAGE_CACHE_SCAN_INTERVAL);
}
public TimeUnit releaseMessageCacheScanIntervalTimeUnit() {
return TimeUnit.SECONDS;
}
public int releaseMessageScanIntervalInMilli() {
int interval = getIntProperty("apollo.message-scan.interval", DEFAULT_RELEASE_MESSAGE_SCAN_INTERVAL_IN_MS);
return checkInt(interval, 100, Integer.MAX_VALUE, DEFAULT_RELEASE_MESSAGE_SCAN_INTERVAL_IN_MS);
}
public int releaseMessageNotificationBatch() {
int batch = getIntProperty("apollo.release-message.notification.batch", 100);
return checkInt(batch, 1, Integer.MAX_VALUE, 100);
int batch = getIntProperty("apollo.release-message.notification.batch", DEFAULT_RELEASE_MESSAGE_NOTIFICATION_BATCH);
return checkInt(batch, 1, Integer.MAX_VALUE, DEFAULT_RELEASE_MESSAGE_NOTIFICATION_BATCH);
}
public int releaseMessageNotificationBatchIntervalInMilli() {
int interval = getIntProperty("apollo.release-message.notification.batch.interval", 100);
return checkInt(interval, 1, Integer.MAX_VALUE, 100);
int interval = getIntProperty("apollo.release-message.notification.batch.interval", DEFAULT_RELEASE_MESSAGE_NOTIFICATION_BATCH_INTERVAL_IN_MILLI);
return checkInt(interval, 10, Integer.MAX_VALUE, DEFAULT_RELEASE_MESSAGE_NOTIFICATION_BATCH_INTERVAL_IN_MILLI);
}
public boolean isConfigServiceCacheEnabled() {
return getBooleanProperty("config-service.cache.enabled", false);
}
int checkInt(int value, int min, int max, int defaultValue) {
......
......@@ -61,6 +61,7 @@ public class DatabaseMessageSender implements MessageSender {
} catch (Throwable ex) {
logger.error("Sending message to database failed", ex);
transaction.setStatus(ex);
throw ex;
} finally {
transaction.complete();
}
......
package com.ctrip.framework.apollo.biz.message;
import com.google.common.collect.Lists;
import com.ctrip.framework.apollo.biz.entity.ReleaseMessage;
import com.ctrip.framework.apollo.biz.repository.ReleaseMessageRepository;
import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory;
import com.ctrip.framework.apollo.tracer.Tracer;
import com.ctrip.framework.apollo.tracer.spi.Transaction;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.ctrip.framework.apollo.biz.config.BizConfig;
import com.ctrip.framework.apollo.biz.entity.ReleaseMessage;
import com.ctrip.framework.apollo.biz.repository.ReleaseMessageRepository;
import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory;
import com.ctrip.framework.apollo.tracer.Tracer;
import com.ctrip.framework.apollo.tracer.spi.Transaction;
import com.google.common.collect.Lists;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class ReleaseMessageScanner implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(ReleaseMessageScanner.class);
private static final int DEFAULT_SCAN_INTERVAL_IN_MS = 1000;
@Autowired
private Environment env;
private BizConfig bizConfig;
@Autowired
private ReleaseMessageRepository releaseMessageRepository;
private int databaseScanInterval;
......@@ -44,7 +41,7 @@ public class ReleaseMessageScanner implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
populateDataBaseInterval();
databaseScanInterval = bizConfig.releaseMessageScanIntervalInMilli();
maxIdScanned = loadLargestMessageId();
executorService.scheduleWithFixedDelay((Runnable) () -> {
Transaction transaction = Tracer.newTransaction("Apollo.ReleaseMessageScanner", "scanMessage");
......@@ -57,7 +54,7 @@ public class ReleaseMessageScanner implements InitializingBean {
} finally {
transaction.complete();
}
}, getDatabaseScanIntervalMs(), getDatabaseScanIntervalMs(), TimeUnit.MILLISECONDS);
}, databaseScanInterval, databaseScanInterval, TimeUnit.MILLISECONDS);
}
......@@ -124,21 +121,4 @@ public class ReleaseMessageScanner implements InitializingBean {
}
}
}
private void populateDataBaseInterval() {
databaseScanInterval = DEFAULT_SCAN_INTERVAL_IN_MS;
try {
String interval = env.getProperty("apollo.message-scan.interval");
if (!Objects.isNull(interval)) {
databaseScanInterval = Integer.parseInt(interval);
}
} catch (Throwable ex) {
Tracer.logError(ex);
logger.error("Load apollo message scan interval from system property failed", ex);
}
}
private int getDatabaseScanIntervalMs() {
return databaseScanInterval;
}
}
......@@ -8,7 +8,10 @@ import com.ctrip.framework.apollo.biz.entity.Cluster;
import com.ctrip.framework.apollo.biz.entity.Item;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.message.MessageSender;
import com.ctrip.framework.apollo.biz.message.Topics;
import com.ctrip.framework.apollo.biz.repository.NamespaceRepository;
import com.ctrip.framework.apollo.biz.utils.ReleaseMessageKeyGenerator;
import com.ctrip.framework.apollo.common.constants.GsonType;
import com.ctrip.framework.apollo.common.constants.NamespaceBranchStatus;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
......@@ -59,6 +62,8 @@ public class NamespaceService {
private NamespaceLockService namespaceLockService;
@Autowired
private InstanceService instanceService;
@Autowired
private MessageSender messageSender;
public Namespace findOne(Long namespaceId) {
......@@ -282,7 +287,13 @@ public class NamespaceService {
auditService.audit(Namespace.class.getSimpleName(), namespace.getId(), Audit.OP.DELETE, operator);
return namespaceRepository.save(namespace);
Namespace deleted = namespaceRepository.save(namespace);
//Publish release message to do some clean up in config service, such as updating the cache
messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, clusterName, namespaceName),
Topics.APOLLO_RELEASE_TOPIC);
return deleted;
}
@Transactional
......
......@@ -12,9 +12,11 @@ import org.springframework.test.util.ReflectionTestUtils;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* @author Jason Song(song_s@ctrip.com)
......@@ -33,6 +35,11 @@ public class DatabaseMessageSenderTest extends AbstractUnitTest{
@Test
public void testSendMessage() throws Exception {
String someMessage = "some-message";
long someId = 1;
ReleaseMessage someReleaseMessage = mock(ReleaseMessage.class);
when(someReleaseMessage.getId()).thenReturn(someId);
when(releaseMessageRepository.save(any(ReleaseMessage.class))).thenReturn(someReleaseMessage);
ArgumentCaptor<ReleaseMessage> captor = ArgumentCaptor.forClass(ReleaseMessage.class);
messageSender.sendMessage(someMessage, Topics.APOLLO_RELEASE_TOPIC);
......@@ -50,4 +57,12 @@ public class DatabaseMessageSenderTest extends AbstractUnitTest{
verify(releaseMessageRepository, never()).save(any(ReleaseMessage.class));
}
@Test(expected = RuntimeException.class)
public void testSendMessageFailed() throws Exception {
String someMessage = "some-message";
when(releaseMessageRepository.save(any(ReleaseMessage.class))).thenThrow(new RuntimeException());
messageSender.sendMessage(someMessage, Topics.APOLLO_RELEASE_TOPIC);
}
}
package com.ctrip.framework.apollo.biz.message;
import com.ctrip.framework.apollo.biz.config.BizConfig;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.SettableFuture;
......@@ -26,7 +27,7 @@ public class ReleaseMessageScannerTest extends AbstractUnitTest {
@Mock
private ReleaseMessageRepository releaseMessageRepository;
@Mock
private Environment env;
private BizConfig bizConfig;
private int databaseScanInterval;
@Before
......@@ -34,9 +35,9 @@ public class ReleaseMessageScannerTest extends AbstractUnitTest {
releaseMessageScanner = new ReleaseMessageScanner();
ReflectionTestUtils
.setField(releaseMessageScanner, "releaseMessageRepository", releaseMessageRepository);
ReflectionTestUtils.setField(releaseMessageScanner, "env", env);
ReflectionTestUtils.setField(releaseMessageScanner, "bizConfig", bizConfig);
databaseScanInterval = 100; //100 ms
when(env.getProperty("apollo.message-scan.interval")).thenReturn(String.valueOf(databaseScanInterval));
when(bizConfig.releaseMessageScanIntervalInMilli()).thenReturn(databaseScanInterval);
releaseMessageScanner.afterPropertiesSet();
}
......
package com.ctrip.framework.apollo.internals;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.escape.Escaper;
import com.google.common.net.UrlEscapers;
import com.google.common.reflect.TypeToken;
import com.google.common.util.concurrent.RateLimiter;
import com.google.gson.Gson;
import com.ctrip.framework.apollo.build.ApolloInjector;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.dto.ApolloConfigNotification;
import com.ctrip.framework.apollo.core.dto.ApolloNotificationMessages;
import com.ctrip.framework.apollo.core.dto.ServiceDTO;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.core.schedule.ExponentialSchedulePolicy;
......@@ -29,18 +31,19 @@ import com.ctrip.framework.apollo.util.ExceptionUtil;
import com.ctrip.framework.apollo.util.http.HttpRequest;
import com.ctrip.framework.apollo.util.http.HttpResponse;
import com.ctrip.framework.apollo.util.http.HttpUtil;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.escape.Escaper;
import com.google.common.net.UrlEscapers;
import com.google.common.reflect.TypeToken;
import com.google.common.util.concurrent.RateLimiter;
import com.google.gson.Gson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @author Jason Song(song_s@ctrip.com)
......@@ -50,7 +53,7 @@ public class RemoteConfigLongPollService {
private static final Joiner STRING_JOINER = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR);
private static final Joiner.MapJoiner MAP_JOINER = Joiner.on("&").withKeyValueSeparator("=");
private static final Escaper queryParamEscaper = UrlEscapers.urlFormParameterEscaper();
private static final long INIT_NOTIFICATION_ID = -1;
private static final long INIT_NOTIFICATION_ID = ConfigConsts.NOTIFICATION_ID_PLACEHOLDER;
private final ExecutorService m_longPollingService;
private final AtomicBoolean m_longPollingStopped;
private SchedulePolicy m_longPollFailSchedulePolicyInSecond;
......@@ -58,6 +61,7 @@ public class RemoteConfigLongPollService {
private final AtomicBoolean m_longPollStarted;
private final Multimap<String, RemoteConfigRepository> m_longPollNamespaces;
private final ConcurrentMap<String, Long> m_notifications;
private final Map<String, ApolloNotificationMessages> m_remoteNotificationMessages;//namespaceName -> watchedKey -> notificationId
private Type m_responseType;
private Gson gson;
private ConfigUtil m_configUtil;
......@@ -76,6 +80,7 @@ public class RemoteConfigLongPollService {
m_longPollNamespaces =
Multimaps.synchronizedSetMultimap(HashMultimap.<String, RemoteConfigRepository>create());
m_notifications = Maps.newConcurrentMap();
m_remoteNotificationMessages = Maps.newConcurrentMap();
m_responseType = new TypeToken<List<ApolloConfigNotification>>() {
}.getType();
gson = new Gson();
......@@ -143,13 +148,14 @@ public class RemoteConfigLongPollService {
}
}
Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "pollNotification");
String url = null;
try {
if (lastServiceDto == null) {
List<ServiceDTO> configServices = getConfigServices();
lastServiceDto = configServices.get(random.nextInt(configServices.size()));
}
String url =
url =
assembleLongPollRefreshUrl(lastServiceDto.getHomepageUrl(), appId, cluster, dataCenter,
m_notifications);
......@@ -166,6 +172,7 @@ public class RemoteConfigLongPollService {
logger.debug("Long polling response: {}, url: {}", response.getStatusCode(), url);
if (response.getStatusCode() == 200 && response.getBody() != null) {
updateNotifications(response.getBody());
updateRemoteNotifications(response.getBody());
transaction.addData("Result", response.getBody().toString());
notify(lastServiceDto, response.getBody());
}
......@@ -184,9 +191,8 @@ public class RemoteConfigLongPollService {
transaction.setStatus(ex);
long sleepTimeInSecond = m_longPollFailSchedulePolicyInSecond.fail();
logger.warn(
"Long polling failed, will retry in {} seconds. appId: {}, cluster: {}, namespaces: {}, reason: {}",
sleepTimeInSecond, appId, cluster, assembleNamespaces(),
ExceptionUtil.getDetailMessage(ex));
"Long polling failed, will retry in {} seconds. appId: {}, cluster: {}, namespaces: {}, long polling url: {}, reason: {}",
sleepTimeInSecond, appId, cluster, assembleNamespaces(), url, ExceptionUtil.getDetailMessage(ex));
try {
TimeUnit.SECONDS.sleep(sleepTimeInSecond);
} catch (InterruptedException ie) {
......@@ -207,12 +213,14 @@ public class RemoteConfigLongPollService {
//create a new list to avoid ConcurrentModificationException
List<RemoteConfigRepository> toBeNotified =
Lists.newArrayList(m_longPollNamespaces.get(namespaceName));
ApolloNotificationMessages originalMessages = m_remoteNotificationMessages.get(namespaceName);
ApolloNotificationMessages remoteMessages = originalMessages == null ? null : originalMessages.clone();
//since .properties are filtered out by default, so we need to check if there is any listener for it
toBeNotified.addAll(m_longPollNamespaces
.get(String.format("%s.%s", namespaceName, ConfigFileFormat.Properties.getValue())));
for (RemoteConfigRepository remoteConfigRepository : toBeNotified) {
try {
remoteConfigRepository.onLongPollNotified(lastServiceDto);
remoteConfigRepository.onLongPollNotified(lastServiceDto, remoteMessages);
} catch (Throwable ex) {
Tracer.logError(ex);
}
......@@ -238,6 +246,27 @@ public class RemoteConfigLongPollService {
}
}
private void updateRemoteNotifications(List<ApolloConfigNotification> deltaNotifications) {
for (ApolloConfigNotification notification : deltaNotifications) {
if (Strings.isNullOrEmpty(notification.getNamespaceName())) {
continue;
}
if (notification.getMessages() == null || notification.getMessages().isEmpty()) {
continue;
}
ApolloNotificationMessages localRemoteMessages =
m_remoteNotificationMessages.get(notification.getNamespaceName());
if (localRemoteMessages == null) {
localRemoteMessages = new ApolloNotificationMessages();
m_remoteNotificationMessages.put(notification.getNamespaceName(), localRemoteMessages);
}
localRemoteMessages.mergeFrom(notification.getMessages());
}
}
private String assembleNamespaces() {
return STRING_JOINER.join(m_longPollNamespaces.keySet());
}
......@@ -269,9 +298,7 @@ public class RemoteConfigLongPollService {
String assembleNotifications(Map<String, Long> notificationsMap) {
List<ApolloConfigNotification> notifications = Lists.newArrayList();
for (Map.Entry<String, Long> entry : notificationsMap.entrySet()) {
ApolloConfigNotification notification = new ApolloConfigNotification();
notification.setNamespaceName(entry.getKey());
notification.setNotificationId(entry.getValue());
ApolloConfigNotification notification = new ApolloConfigNotification(entry.getKey(), entry.getValue());
notifications.add(notification);
}
return gson.toJson(notifications);
......
......@@ -17,6 +17,7 @@ import com.ctrip.framework.apollo.Apollo;
import com.ctrip.framework.apollo.build.ApolloInjector;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.dto.ApolloConfig;
import com.ctrip.framework.apollo.core.dto.ApolloNotificationMessages;
import com.ctrip.framework.apollo.core.dto.ServiceDTO;
import com.ctrip.framework.apollo.core.schedule.ExponentialSchedulePolicy;
import com.ctrip.framework.apollo.core.schedule.SchedulePolicy;
......@@ -37,6 +38,7 @@ import com.google.common.collect.Maps;
import com.google.common.escape.Escaper;
import com.google.common.net.UrlEscapers;
import com.google.common.util.concurrent.RateLimiter;
import com.google.gson.Gson;
/**
* @author Jason Song(song_s@ctrip.com)
......@@ -53,9 +55,11 @@ public class RemoteConfigRepository extends AbstractConfigRepository {
private final String m_namespace;
private final static ScheduledExecutorService m_executorService;
private AtomicReference<ServiceDTO> m_longPollServiceDto;
private AtomicReference<ApolloNotificationMessages> m_remoteMessages;
private RateLimiter m_loadConfigRateLimiter;
private AtomicBoolean m_configNeedForceRefresh;
private SchedulePolicy m_loadConfigFailSchedulePolicy;
private Gson gson;
private static final Escaper pathEscaper = UrlEscapers.urlPathSegmentEscaper();
private static final Escaper queryParamEscaper = UrlEscapers.urlFormParameterEscaper();
......@@ -77,10 +81,12 @@ public class RemoteConfigRepository extends AbstractConfigRepository {
m_serviceLocator = ApolloInjector.getInstance(ConfigServiceLocator.class);
remoteConfigLongPollService = ApolloInjector.getInstance(RemoteConfigLongPollService.class);
m_longPollServiceDto = new AtomicReference<>();
m_remoteMessages = new AtomicReference<>();
m_loadConfigRateLimiter = RateLimiter.create(m_configUtil.getLoadConfigQPS());
m_configNeedForceRefresh = new AtomicBoolean(true);
m_loadConfigFailSchedulePolicy = new ExponentialSchedulePolicy(m_configUtil.getOnErrorRetryInterval(),
m_configUtil.getOnErrorRetryInterval() * 8);
gson = new Gson();
this.trySync();
this.schedulePeriodicRefresh();
this.scheduleLongPollingRefresh();
......@@ -167,6 +173,7 @@ public class RemoteConfigRepository extends AbstractConfigRepository {
Throwable exception = null;
List<ServiceDTO> configServices = getConfigServices();
String url = null;
for (int i = 0; i < maxRetries; i++) {
List<ServiceDTO> randomConfigServices = Lists.newLinkedList(configServices);
Collections.shuffle(randomConfigServices);
......@@ -188,9 +195,8 @@ public class RemoteConfigRepository extends AbstractConfigRepository {
}
}
String url =
assembleQueryConfigUrl(configService.getHomepageUrl(), appId, cluster, m_namespace,
dataCenter, m_configCache.get());
url = assembleQueryConfigUrl(configService.getHomepageUrl(), appId, cluster, m_namespace,
dataCenter, m_remoteMessages.get(), m_configCache.get());
logger.debug("Loading config from {}", url);
HttpRequest request = new HttpRequest(url);
......@@ -245,13 +251,13 @@ public class RemoteConfigRepository extends AbstractConfigRepository {
}
String message = String.format(
"Load Apollo Config failed - appId: %s, cluster: %s, namespace: %s",
appId, cluster, m_namespace);
"Load Apollo Config failed - appId: %s, cluster: %s, namespace: %s, url: %s",
appId, cluster, m_namespace, url);
throw new ApolloConfigException(message, exception);
}
String assembleQueryConfigUrl(String uri, String appId, String cluster, String namespace,
String dataCenter, ApolloConfig previousConfig) {
String dataCenter, ApolloNotificationMessages remoteMessages, ApolloConfig previousConfig) {
String path = "configs/%s/%s/%s";
List<String> pathParams =
......@@ -272,6 +278,10 @@ public class RemoteConfigRepository extends AbstractConfigRepository {
queryParams.put("ip", queryParamEscaper.escape(localIp));
}
if (remoteMessages != null) {
queryParams.put("messages", queryParamEscaper.escape(gson.toJson(remoteMessages)));
}
String pathExpanded = String.format(path, pathParams.toArray());
if (!queryParams.isEmpty()) {
......@@ -287,8 +297,9 @@ public class RemoteConfigRepository extends AbstractConfigRepository {
remoteConfigLongPollService.submit(m_namespace, this);
}
public void onLongPollNotified(ServiceDTO longPollNotifiedServiceDto) {
public void onLongPollNotified(ServiceDTO longPollNotifiedServiceDto, ApolloNotificationMessages remoteMessages) {
m_longPollServiceDto.set(longPollNotifiedServiceDto);
m_remoteMessages.set(remoteMessages);
m_executorService.submit(new Runnable() {
@Override
public void run() {
......
package com.ctrip.framework.apollo.internals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
......@@ -10,9 +12,11 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.ctrip.framework.apollo.Apollo;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
......@@ -21,6 +25,7 @@ import javax.servlet.http.HttpServletResponse;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
......@@ -29,6 +34,7 @@ import org.springframework.test.util.ReflectionTestUtils;
import com.ctrip.framework.apollo.build.MockInjector;
import com.ctrip.framework.apollo.core.dto.ApolloConfigNotification;
import com.ctrip.framework.apollo.core.dto.ApolloNotificationMessages;
import com.ctrip.framework.apollo.core.dto.ServiceDTO;
import com.ctrip.framework.apollo.util.ConfigUtil;
import com.ctrip.framework.apollo.util.http.HttpRequest;
......@@ -114,7 +120,7 @@ public class RemoteConfigLongPollServiceTest {
remoteConfigLongPollService.stopLongPollingRefresh();
verify(someRepository, never()).onLongPollNotified(any(ServiceDTO.class));
verify(someRepository, never()).onLongPollNotified(any(ServiceDTO.class), any(ApolloNotificationMessages.class));
}
@Test
......@@ -122,8 +128,17 @@ public class RemoteConfigLongPollServiceTest {
RemoteConfigRepository someRepository = mock(RemoteConfigRepository.class);
final String someNamespace = "someNamespace";
ApolloNotificationMessages notificationMessages = new ApolloNotificationMessages();
String someKey = "someKey";
long someNotificationId = 1;
String anotherKey = "anotherKey";
long anotherNotificationId = 2;
notificationMessages.put(someKey, someNotificationId);
notificationMessages.put(anotherKey, anotherNotificationId);
ApolloConfigNotification someNotification = mock(ApolloConfigNotification.class);
when(someNotification.getNamespaceName()).thenReturn(someNamespace);
when(someNotification.getMessages()).thenReturn(notificationMessages);
when(pollResponse.getStatusCode()).thenReturn(HttpServletResponse.SC_OK);
when(pollResponse.getBody()).thenReturn(Lists.newArrayList(someNotification));
......@@ -148,7 +163,7 @@ public class RemoteConfigLongPollServiceTest {
onNotified.set(true);
return null;
}
}).when(someRepository).onLongPollNotified(any(ServiceDTO.class));
}).when(someRepository).onLongPollNotified(any(ServiceDTO.class), any(ApolloNotificationMessages.class));
remoteConfigLongPollService.submit(someNamespace, someRepository);
......@@ -156,7 +171,14 @@ public class RemoteConfigLongPollServiceTest {
remoteConfigLongPollService.stopLongPollingRefresh();
verify(someRepository, times(1)).onLongPollNotified(any(ServiceDTO.class));
final ArgumentCaptor<ApolloNotificationMessages> captor = ArgumentCaptor.forClass(ApolloNotificationMessages.class);
verify(someRepository, times(1)).onLongPollNotified(any(ServiceDTO.class), captor.capture());
ApolloNotificationMessages captured = captor.getValue();
assertEquals(2, captured.getDetails().size());
assertEquals(someNotificationId, captured.get(someKey).longValue());
assertEquals(anotherNotificationId, captured.get(anotherKey).longValue());
}
@Test
......@@ -221,7 +243,7 @@ public class RemoteConfigLongPollServiceTest {
onAnotherRepositoryNotified.set(true);
return null;
}
}).when(anotherRepository).onLongPollNotified(any(ServiceDTO.class));
}).when(anotherRepository).onLongPollNotified(any(ServiceDTO.class), any(ApolloNotificationMessages.class));
remoteConfigLongPollService.submit(someNamespace, someRepository);
......@@ -233,8 +255,8 @@ public class RemoteConfigLongPollServiceTest {
remoteConfigLongPollService.stopLongPollingRefresh();
verify(someRepository, times(1)).onLongPollNotified(any(ServiceDTO.class));
verify(anotherRepository, times(1)).onLongPollNotified(any(ServiceDTO.class));
verify(someRepository, times(1)).onLongPollNotified(any(ServiceDTO.class), any(ApolloNotificationMessages.class));
verify(anotherRepository, times(1)).onLongPollNotified(any(ServiceDTO.class), any(ApolloNotificationMessages.class));
}
@Test
......@@ -244,11 +266,22 @@ public class RemoteConfigLongPollServiceTest {
final String someNamespace = "someNamespace";
final String anotherNamespace = "anotherNamespace";
ApolloNotificationMessages notificationMessages = new ApolloNotificationMessages();
String someKey = "someKey";
long someNotificationId = 1;
notificationMessages.put(someKey, someNotificationId);
ApolloNotificationMessages anotherNotificationMessages = new ApolloNotificationMessages();
String anotherKey = "anotherKey";
long anotherNotificationId = 2;
anotherNotificationMessages.put(anotherKey, anotherNotificationId);
final ApolloConfigNotification someNotification = mock(ApolloConfigNotification.class);
when(someNotification.getNamespaceName()).thenReturn(someNamespace);
when(someNotification.getMessages()).thenReturn(notificationMessages);
final ApolloConfigNotification anotherNotification = mock(ApolloConfigNotification.class);
when(anotherNotification.getNamespaceName()).thenReturn(anotherNamespace);
when(anotherNotification.getMessages()).thenReturn(anotherNotificationMessages);
when(pollResponse.getStatusCode()).thenReturn(HttpServletResponse.SC_OK);
when(pollResponse.getBody()).thenReturn(Lists.newArrayList(someNotification, anotherNotification));
......@@ -273,7 +306,7 @@ public class RemoteConfigLongPollServiceTest {
someRepositoryNotified.set(true);
return null;
}
}).when(someRepository).onLongPollNotified(any(ServiceDTO.class));
}).when(someRepository).onLongPollNotified(any(ServiceDTO.class), any(ApolloNotificationMessages.class));
final SettableFuture<Boolean> anotherRepositoryNotified = SettableFuture.create();
doAnswer(new Answer<Void>() {
@Override
......@@ -281,7 +314,7 @@ public class RemoteConfigLongPollServiceTest {
anotherRepositoryNotified.set(true);
return null;
}
}).when(anotherRepository).onLongPollNotified(any(ServiceDTO.class));
}).when(anotherRepository).onLongPollNotified(any(ServiceDTO.class), any(ApolloNotificationMessages.class));
remoteConfigLongPollService.submit(someNamespace, someRepository);
remoteConfigLongPollService.submit(anotherNamespace, anotherRepository);
......@@ -291,8 +324,101 @@ public class RemoteConfigLongPollServiceTest {
remoteConfigLongPollService.stopLongPollingRefresh();
verify(someRepository, times(1)).onLongPollNotified(any(ServiceDTO.class));
verify(anotherRepository, times(1)).onLongPollNotified(any(ServiceDTO.class));
final ArgumentCaptor<ApolloNotificationMessages> captor = ArgumentCaptor.forClass(ApolloNotificationMessages.class);
final ArgumentCaptor<ApolloNotificationMessages> anotherCaptor = ArgumentCaptor.forClass(ApolloNotificationMessages.class);
verify(someRepository, times(1)).onLongPollNotified(any(ServiceDTO.class), captor.capture());
verify(anotherRepository, times(1)).onLongPollNotified(any(ServiceDTO.class), anotherCaptor.capture());
ApolloNotificationMessages result = captor.getValue();
assertEquals(1, result.getDetails().size());
assertEquals(someNotificationId, result.get(someKey).longValue());
ApolloNotificationMessages anotherResult = anotherCaptor.getValue();
assertEquals(1, anotherResult.getDetails().size());
assertEquals(anotherNotificationId, anotherResult.get(anotherKey).longValue());
}
@Test
public void testSubmitLongPollNamespaceWithMessagesUpdated() throws Exception {
RemoteConfigRepository someRepository = mock(RemoteConfigRepository.class);
final String someNamespace = "someNamespace";
ApolloNotificationMessages notificationMessages = new ApolloNotificationMessages();
String someKey = "someKey";
long someNotificationId = 1;
notificationMessages.put(someKey, someNotificationId);
ApolloConfigNotification someNotification = mock(ApolloConfigNotification.class);
when(someNotification.getNamespaceName()).thenReturn(someNamespace);
when(someNotification.getMessages()).thenReturn(notificationMessages);
when(pollResponse.getStatusCode()).thenReturn(HttpServletResponse.SC_OK);
when(pollResponse.getBody()).thenReturn(Lists.newArrayList(someNotification));
doAnswer(new Answer<HttpResponse<List<ApolloConfigNotification>>>() {
@Override
public HttpResponse<List<ApolloConfigNotification>> answer(InvocationOnMock invocation)
throws Throwable {
try {
TimeUnit.MILLISECONDS.sleep(50);
} catch (InterruptedException e) {
}
return pollResponse;
}
}).when(httpUtil).doGet(any(HttpRequest.class), eq(responseType));
final SettableFuture<Boolean> onNotified = SettableFuture.create();
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
onNotified.set(true);
return null;
}
}).when(someRepository).onLongPollNotified(any(ServiceDTO.class), any(ApolloNotificationMessages.class));
remoteConfigLongPollService.submit(someNamespace, someRepository);
onNotified.get(5000, TimeUnit.MILLISECONDS);
//reset to 304
when(pollResponse.getStatusCode()).thenReturn(HttpServletResponse.SC_NOT_MODIFIED);
final ArgumentCaptor<ApolloNotificationMessages> captor = ArgumentCaptor.forClass(ApolloNotificationMessages.class);
verify(someRepository, times(1)).onLongPollNotified(any(ServiceDTO.class), captor.capture());
ApolloNotificationMessages captured = captor.getValue();
assertEquals(1, captured.getDetails().size());
assertEquals(someNotificationId, captured.get(someKey).longValue());
final SettableFuture<Boolean> anotherOnNotified = SettableFuture.create();
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
anotherOnNotified.set(true);
return null;
}
}).when(someRepository).onLongPollNotified(any(ServiceDTO.class), any(ApolloNotificationMessages.class));
String anotherKey = "anotherKey";
long anotherNotificationId = 2;
notificationMessages.put(anotherKey, anotherNotificationId);
//send notifications
when(pollResponse.getStatusCode()).thenReturn(HttpServletResponse.SC_OK);
anotherOnNotified.get(5000, TimeUnit.MILLISECONDS);
remoteConfigLongPollService.stopLongPollingRefresh();
verify(someRepository, times(2)).onLongPollNotified(any(ServiceDTO.class), captor.capture());
captured = captor.getValue();
assertEquals(2, captured.getDetails().size());
assertEquals(someNotificationId, captured.get(someKey).longValue());
assertEquals(anotherNotificationId, captured.get(anotherKey).longValue());
}
@Test
......
......@@ -4,8 +4,10 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
......@@ -30,6 +32,7 @@ import org.mockito.stubbing.Answer;
import com.ctrip.framework.apollo.build.MockInjector;
import com.ctrip.framework.apollo.core.dto.ApolloConfig;
import com.ctrip.framework.apollo.core.dto.ApolloConfigNotification;
import com.ctrip.framework.apollo.core.dto.ApolloNotificationMessages;
import com.ctrip.framework.apollo.core.dto.ServiceDTO;
import com.ctrip.framework.apollo.exceptions.ApolloConfigException;
import com.ctrip.framework.apollo.util.ConfigUtil;
......@@ -39,7 +42,9 @@ import com.ctrip.framework.apollo.util.http.HttpUtil;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.net.UrlEscapers;
import com.google.common.util.concurrent.SettableFuture;
import com.google.gson.Gson;
/**
* Created by Jason on 4/9/16.
......@@ -49,6 +54,9 @@ public class RemoteConfigRepositoryTest {
@Mock
private ConfigServiceLocator configServiceLocator;
private String someNamespace;
private String someServerUrl;
private ConfigUtil configUtil;
private HttpUtil httpUtil;
@Mock
private static HttpResponse<ApolloConfig> someResponse;
@Mock
......@@ -62,9 +70,10 @@ public class RemoteConfigRepositoryTest {
when(pollResponse.getStatusCode()).thenReturn(HttpServletResponse.SC_NOT_MODIFIED);
MockInjector.reset();
MockInjector.setInstance(ConfigUtil.class, new MockConfigUtil());
configUtil = new MockConfigUtil();
MockInjector.setInstance(ConfigUtil.class, configUtil);
String someServerUrl = "http://someServer";
someServerUrl = "http://someServer";
ServiceDTO serviceDTO = mock(ServiceDTO.class);
......@@ -72,7 +81,8 @@ public class RemoteConfigRepositoryTest {
when(configServiceLocator.getConfigServices()).thenReturn(Lists.newArrayList(serviceDTO));
MockInjector.setInstance(ConfigServiceLocator.class, configServiceLocator);
MockInjector.setInstance(HttpUtil.class, new MockHttpUtil());
httpUtil = spy(new MockHttpUtil());
MockInjector.setInstance(HttpUtil.class, httpUtil);
remoteConfigLongPollService = new RemoteConfigLongPollService();
......@@ -164,8 +174,15 @@ public class RemoteConfigRepositoryTest {
Map<String, String> newConfigurations = ImmutableMap.of("someKey", "anotherValue");
ApolloConfig newApolloConfig = assembleApolloConfig(newConfigurations);
ApolloNotificationMessages notificationMessages = new ApolloNotificationMessages();
String someKey = "someKey";
long someNotificationId = 1;
notificationMessages.put(someKey, someNotificationId);
ApolloConfigNotification someNotification = mock(ApolloConfigNotification.class);
when(someNotification.getNamespaceName()).thenReturn(someNamespace);
when(someNotification.getMessages()).thenReturn(notificationMessages);
when(pollResponse.getStatusCode()).thenReturn(HttpServletResponse.SC_OK);
when(pollResponse.getBody()).thenReturn(Lists.newArrayList(someNotification));
......@@ -177,21 +194,37 @@ public class RemoteConfigRepositoryTest {
verify(someListener, times(1)).onRepositoryChange(eq(someNamespace), captor.capture());
assertEquals(newConfigurations, captor.getValue());
final ArgumentCaptor<HttpRequest> httpRequestArgumentCaptor = ArgumentCaptor.forClass(HttpRequest.class);
verify(httpUtil, atLeast(2)).doGet(httpRequestArgumentCaptor.capture(), eq(ApolloConfig.class));
HttpRequest request = httpRequestArgumentCaptor.getValue();
assertTrue(request.getUrl().contains("messages=%7B%22details%22%3A%7B%22someKey%22%3A1%7D%7D"));
}
@Test
public void testAssembleQueryConfigUrl() throws Exception {
Gson gson = new Gson();
String someUri = "http://someServer";
String someAppId = "someAppId";
String someCluster = "someCluster+ &.-_someSign";
String someReleaseKey = "20160705193346-583078ef5716c055+20160705193308-31c471ddf9087c3f";
ApolloNotificationMessages notificationMessages = new ApolloNotificationMessages();
String someKey = "someKey";
long someNotificationId = 1;
String anotherKey = "anotherKey";
long anotherNotificationId = 2;
notificationMessages.put(someKey, someNotificationId);
notificationMessages.put(anotherKey, anotherNotificationId);
RemoteConfigRepository remoteConfigRepository = new RemoteConfigRepository(someNamespace);
ApolloConfig someApolloConfig = mock(ApolloConfig.class);
when(someApolloConfig.getReleaseKey()).thenReturn(someReleaseKey);
String queryConfigUrl = remoteConfigRepository
.assembleQueryConfigUrl(someUri, someAppId, someCluster, someNamespace, null,
.assembleQueryConfigUrl(someUri, someAppId, someCluster, someNamespace, null, notificationMessages,
someApolloConfig);
remoteConfigLongPollService.stopLongPollingRefresh();
......@@ -200,7 +233,8 @@ public class RemoteConfigRepositoryTest {
"http://someServer/configs/someAppId/someCluster+%20&.-_someSign/" + someNamespace));
assertTrue(queryConfigUrl
.contains("releaseKey=20160705193346-583078ef5716c055%2B20160705193308-31c471ddf9087c3f"));
assertTrue(queryConfigUrl
.contains("messages=" + UrlEscapers.urlFormParameterEscaper().escape(gson.toJson(notificationMessages))));
}
private ApolloConfig assembleApolloConfig(Map<String, String> configurations) {
......
package com.ctrip.framework.apollo.configservice;
import com.ctrip.framework.apollo.biz.config.BizConfig;
import com.ctrip.framework.apollo.biz.grayReleaseRule.GrayReleaseRulesHolder;
import com.ctrip.framework.apollo.biz.message.ReleaseMessageScanner;
import com.ctrip.framework.apollo.configservice.controller.ConfigFileController;
......@@ -7,6 +8,9 @@ import com.ctrip.framework.apollo.configservice.controller.NotificationControlle
import com.ctrip.framework.apollo.configservice.controller.NotificationControllerV2;
import com.ctrip.framework.apollo.configservice.service.ReleaseMessageServiceWithCache;
import com.ctrip.framework.apollo.configservice.service.config.ConfigService;
import com.ctrip.framework.apollo.configservice.service.config.ConfigServiceWithCache;
import com.ctrip.framework.apollo.configservice.service.config.DefaultConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
......@@ -16,11 +20,23 @@ import org.springframework.context.annotation.Configuration;
*/
@Configuration
public class ConfigServiceAutoConfiguration {
@Autowired
private BizConfig bizConfig;
@Bean
public GrayReleaseRulesHolder grayReleaseRulesHolder() {
return new GrayReleaseRulesHolder();
}
@Bean
public ConfigService configService() {
if (bizConfig.isConfigServiceCacheEnabled()) {
return new ConfigServiceWithCache();
}
return new DefaultConfigService();
}
@Configuration
static class MessageScannerConfiguration {
@Autowired
......@@ -33,6 +49,8 @@ public class ConfigServiceAutoConfiguration {
private GrayReleaseRulesHolder grayReleaseRulesHolder;
@Autowired
private ReleaseMessageServiceWithCache releaseMessageServiceWithCache;
@Autowired
private ConfigService configService;
@Bean
public ReleaseMessageScanner releaseMessageScanner() {
......@@ -42,6 +60,7 @@ public class ConfigServiceAutoConfiguration {
//1. handle gray release rule
releaseMessageScanner.addMessageListener(grayReleaseRulesHolder);
//2. handle server cache
releaseMessageScanner.addMessageListener(configService);
releaseMessageScanner.addMessageListener(configFileController);
//3. notify clients
releaseMessageScanner.addMessageListener(notificationControllerV2);
......
package com.ctrip.framework.apollo.configservice.controller;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.grayReleaseRule.GrayReleaseRulesHolder;
import com.ctrip.framework.apollo.biz.service.AppNamespaceService;
import com.ctrip.framework.apollo.biz.service.ReleaseService;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.configservice.util.InstanceConfigAuditUtil;
import com.ctrip.framework.apollo.configservice.util.NamespaceUtil;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.dto.ApolloConfig;
import com.ctrip.framework.apollo.tracer.Tracer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
......@@ -27,14 +16,24 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.configservice.service.AppNamespaceServiceWithCache;
import com.ctrip.framework.apollo.configservice.service.config.ConfigService;
import com.ctrip.framework.apollo.configservice.util.InstanceConfigAuditUtil;
import com.ctrip.framework.apollo.configservice.util.NamespaceUtil;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.dto.ApolloConfig;
import com.ctrip.framework.apollo.core.dto.ApolloNotificationMessages;
import com.ctrip.framework.apollo.tracer.Tracer;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
/**
* @author Jason Song(song_s@ctrip.com)
......@@ -45,46 +44,46 @@ public class ConfigController {
private static final Splitter X_FORWARDED_FOR_SPLITTER = Splitter.on(",").omitEmptyStrings()
.trimResults();
@Autowired
private ReleaseService releaseService;
private ConfigService configService;
@Autowired
private AppNamespaceService appNamespaceService;
private AppNamespaceServiceWithCache appNamespaceService;
@Autowired
private NamespaceUtil namespaceUtil;
@Autowired
private InstanceConfigAuditUtil instanceConfigAuditUtil;
@Autowired
private GrayReleaseRulesHolder grayReleaseRulesHolder;
private Gson gson;
private static final Gson gson = new Gson();
private static final Type configurationTypeReference =
new TypeToken<Map<java.lang.String, java.lang.String>>() {
private static final Type configurationTypeReference = new TypeToken<Map<String, String>>() {
}.getType();
private static final Joiner STRING_JOINER = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR);
@RequestMapping(value = "/{appId}/{clusterName}/{namespace:.+}", method = RequestMethod.GET)
public ApolloConfig queryConfig(@PathVariable String appId, @PathVariable String clusterName,
@PathVariable String namespace,
@RequestParam(value = "dataCenter", required = false) String
dataCenter,
@RequestParam(value = "releaseKey", defaultValue = "-1") String
clientSideReleaseKey,
@RequestParam(value = "dataCenter", required = false) String dataCenter,
@RequestParam(value = "releaseKey", defaultValue = "-1") String clientSideReleaseKey,
@RequestParam(value = "ip", required = false) String clientIp,
HttpServletRequest request,
HttpServletResponse response) throws IOException {
@RequestParam(value = "messages", required = false) String messagesAsString,
HttpServletRequest request, HttpServletResponse response) throws IOException {
String originalNamespace = namespace;
//strip out .properties suffix
namespace = namespaceUtil.filterNamespaceName(namespace);
//fix the character case issue, such as FX.apollo <-> fx.apollo
namespace = namespaceUtil.normalizeNamespace(appId, namespace);
if (Strings.isNullOrEmpty(clientIp)) {
clientIp = tryToGetClientIp(request);
}
ApolloNotificationMessages clientMessages = transformMessages(messagesAsString);
List<Release> releases = Lists.newLinkedList();
String appClusterNameLoaded = clusterName;
if (!ConfigConsts.NO_APPID_PLACEHOLDER.equalsIgnoreCase(appId)) {
Release currentAppRelease = loadConfig(appId, clientIp, appId, clusterName, namespace,
dataCenter);
Release currentAppRelease = configService.loadConfig(appId, clientIp, appId, clusterName, namespace,
dataCenter, clientMessages);
if (currentAppRelease != null) {
releases.add(currentAppRelease);
......@@ -96,7 +95,7 @@ public class ConfigController {
//if namespace does not belong to this appId, should check if there is a public configuration
if (!namespaceBelongsToAppId(appId, namespace)) {
Release publicRelease = this.findPublicConfig(appId, clientIp, clusterName, namespace,
dataCenter);
dataCenter, clientMessages);
if (!Objects.isNull(publicRelease)) {
releases.add(publicRelease);
}
......@@ -145,7 +144,7 @@ public class ConfigController {
return false;
}
AppNamespace appNamespace = appNamespaceService.findOne(appId, namespaceName);
AppNamespace appNamespace = appNamespaceService.findByAppIdAndNamespace(appId, namespaceName);
return appNamespace != null;
}
......@@ -156,8 +155,7 @@ public class ConfigController {
* @param dataCenter the datacenter
*/
private Release findPublicConfig(String clientAppId, String clientIp, String clusterName,
String namespace,
String dataCenter) {
String namespace, String dataCenter, ApolloNotificationMessages clientMessages) {
AppNamespace appNamespace = appNamespaceService.findPublicNamespaceByName(namespace);
//check whether the namespace's appId equals to current one
......@@ -167,52 +165,8 @@ public class ConfigController {
String publicConfigAppId = appNamespace.getAppId();
return loadConfig(clientAppId, clientIp, publicConfigAppId, clusterName, namespace, dataCenter);
}
private Release loadConfig(String clientAppId, String clientIp, String configAppId, String
configClusterName, String configNamespace, String dataCenter) {
//load from specified cluster fist
if (!Objects.equals(ConfigConsts.CLUSTER_NAME_DEFAULT, configClusterName)) {
Release clusterRelease = findRelease(clientAppId, clientIp, configAppId, configClusterName,
configNamespace);
if (!Objects.isNull(clusterRelease)) {
return clusterRelease;
}
}
//try to load via data center
if (!Strings.isNullOrEmpty(dataCenter) && !Objects.equals(dataCenter, configClusterName)) {
Release dataCenterRelease = findRelease(clientAppId, clientIp, configAppId, dataCenter,
configNamespace);
if (!Objects.isNull(dataCenterRelease)) {
return dataCenterRelease;
}
}
//fallback to default release
return findRelease(clientAppId, clientIp, configAppId, ConfigConsts.CLUSTER_NAME_DEFAULT,
configNamespace);
}
private Release findRelease(String clientAppId, String clientIp, String configAppId, String
configClusterName, String configNamespace) {
Long grayReleaseId = grayReleaseRulesHolder.findReleaseIdFromGrayReleaseRule(clientAppId,
clientIp, configAppId, configClusterName, configNamespace);
Release release = null;
if (grayReleaseId != null) {
release = releaseService.findActiveOne(grayReleaseId);
}
if (release == null) {
release = releaseService.findLatestActiveRelease(configAppId, configClusterName,
configNamespace);
}
return release;
return configService.loadConfig(clientAppId, clientIp, publicConfigAppId, clusterName, namespace, dataCenter,
clientMessages);
}
/**
......@@ -227,22 +181,22 @@ public class ConfigController {
return result;
}
private String assembleKey(String appId, String cluster, String namespace, String datacenter) {
private String assembleKey(String appId, String cluster, String namespace, String dataCenter) {
List<String> keyParts = Lists.newArrayList(appId, cluster, namespace);
if (!Strings.isNullOrEmpty(datacenter)) {
keyParts.add(datacenter);
if (!Strings.isNullOrEmpty(dataCenter)) {
keyParts.add(dataCenter);
}
return STRING_JOINER.join(keyParts);
}
private void auditReleases(String appId, String cluster, String datacenter, String clientIp,
private void auditReleases(String appId, String cluster, String dataCenter, String clientIp,
List<Release> releases) {
if (Strings.isNullOrEmpty(clientIp)) {
//no need to audit instance config when there is no ip
return;
}
for (Release release : releases) {
instanceConfigAuditUtil.audit(appId, cluster, datacenter, clientIp, release.getAppId(),
instanceConfigAuditUtil.audit(appId, cluster, dataCenter, clientIp, release.getAppId(),
release.getClusterName(),
release.getNamespaceName(), release.getReleaseKey());
}
......@@ -256,4 +210,16 @@ public class ConfigController {
return request.getRemoteAddr();
}
ApolloNotificationMessages transformMessages(String messagesAsString) {
ApolloNotificationMessages notificationMessages = null;
if (!Strings.isNullOrEmpty(messagesAsString)) {
try {
notificationMessages = gson.fromJson(messagesAsString, ApolloNotificationMessages.class);
} catch (Throwable ex) {
Tracer.logError(ex);
}
}
return notificationMessages;
}
}
......@@ -163,6 +163,8 @@ public class ConfigFileController implements ReleaseMessageListener {
HttpServletResponse response) throws IOException {
//strip out .properties suffix
namespace = namespaceUtil.filterNamespaceName(namespace);
//fix the character case issue, such as FX.apollo <-> fx.apollo
namespace = namespaceUtil.normalizeNamespace(appId, namespace);
if (Strings.isNullOrEmpty(clientIp)) {
clientIp = tryToGetClientIp(request);
......@@ -225,7 +227,7 @@ public class ConfigFileController implements ReleaseMessageListener {
HttpServletRequest request,
HttpServletResponse response) throws IOException {
ApolloConfig apolloConfig = configController.queryConfig(appId, clusterName, namespace,
dataCenter, "-1", clientIp, request, response);
dataCenter, "-1", clientIp, null, request, response);
if (apolloConfig == null || apolloConfig.getConfigurations() == null) {
return null;
......
......@@ -35,6 +35,7 @@ import java.util.Set;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@Deprecated
@RestController
@RequestMapping("/notifications")
public class NotificationController implements ReleaseMessageListener {
......
......@@ -21,6 +21,7 @@ import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.configservice.service.ReleaseMessageServiceWithCache;
import com.ctrip.framework.apollo.configservice.util.NamespaceUtil;
import com.ctrip.framework.apollo.configservice.util.WatchKeysUtil;
import com.ctrip.framework.apollo.configservice.wrapper.DeferredResultWrapper;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.dto.ApolloConfigNotification;
import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory;
......@@ -29,7 +30,6 @@ import com.ctrip.framework.apollo.tracer.Tracer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
......@@ -42,6 +42,7 @@ import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
......@@ -54,14 +55,10 @@ import java.util.concurrent.TimeUnit;
@RequestMapping("/notifications/v2")
public class NotificationControllerV2 implements ReleaseMessageListener {
private static final Logger logger = LoggerFactory.getLogger(NotificationControllerV2.class);
private static final long TIMEOUT = 30 * 1000;//30 seconds
private final Multimap<String, DeferredResult<ResponseEntity<List<ApolloConfigNotification>>>>
deferredResults = Multimaps.synchronizedSetMultimap(HashMultimap.create());
private static final ResponseEntity<List<ApolloConfigNotification>>
NOT_MODIFIED_RESPONSE_LIST = new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
private final Multimap<String, DeferredResultWrapper> deferredResults =
Multimaps.synchronizedSetMultimap(HashMultimap.create());
private static final Splitter STRING_SPLITTER =
Splitter.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).omitEmptyStrings();
private static final long NOTIFICATION_ID_PLACEHOLDER = -1;
private static final Type notificationsTypeReference =
new TypeToken<List<ApolloConfigNotification>>() {
}.getType();
......@@ -111,16 +108,19 @@ public class NotificationControllerV2 implements ReleaseMessageListener {
throw new BadRequestException("Invalid format of notifications: " + notificationsAsString);
}
DeferredResultWrapper deferredResultWrapper = new DeferredResultWrapper();
Set<String> namespaces = Sets.newHashSet();
Map<String, Long> clientSideNotifications = Maps.newHashMap();
for (ApolloConfigNotification notification : notifications) {
if (Strings.isNullOrEmpty(notification.getNamespaceName())) {
continue;
Map<String, ApolloConfigNotification> filteredNotifications = filterNotifications(appId, notifications);
for (Map.Entry<String, ApolloConfigNotification> notificationEntry : filteredNotifications.entrySet()) {
String normalizedNamespace = notificationEntry.getKey();
ApolloConfigNotification notification = notificationEntry.getValue();
namespaces.add(normalizedNamespace);
clientSideNotifications.put(normalizedNamespace, notification.getNotificationId());
if (!Objects.equals(notification.getNamespaceName(), normalizedNamespace)) {
deferredResultWrapper.recordNamespaceNameNormalizedResult(notification.getNamespaceName(), normalizedNamespace);
}
//strip out .properties suffix
String namespace = namespaceUtil.filterNamespaceName(notification.getNamespaceName());
namespaces.add(namespace);
clientSideNotifications.put(namespace, notification.getNotificationId());
}
if (CollectionUtils.isEmpty(namespaces)) {
......@@ -130,9 +130,6 @@ public class NotificationControllerV2 implements ReleaseMessageListener {
Multimap<String, String> watchedKeysMap =
watchKeysUtil.assembleAllWatchKeys(appId, cluster, namespaces, dataCenter);
DeferredResult<ResponseEntity<List<ApolloConfigNotification>>> deferredResult =
new DeferredResult<>(TIMEOUT, NOT_MODIFIED_RESPONSE_LIST);
Set<String> watchedKeys = Sets.newHashSet(watchedKeysMap.values());
List<ReleaseMessage> latestReleaseMessages =
......@@ -151,20 +148,20 @@ public class NotificationControllerV2 implements ReleaseMessageListener {
latestReleaseMessages);
if (!CollectionUtils.isEmpty(newNotifications)) {
deferredResult.setResult(new ResponseEntity<>(newNotifications, HttpStatus.OK));
deferredResultWrapper.setResult(newNotifications);
} else {
//register all keys
for (String key : watchedKeys) {
this.deferredResults.put(key, deferredResult);
this.deferredResults.put(key, deferredResultWrapper);
}
deferredResult
deferredResultWrapper
.onTimeout(() -> logWatchedKeys(watchedKeys, "Apollo.LongPoll.TimeOutKeys"));
deferredResult.onCompletion(() -> {
deferredResultWrapper.onCompletion(() -> {
//unregister all keys
for (String key : watchedKeys) {
deferredResults.remove(key, deferredResult);
deferredResults.remove(key, deferredResultWrapper);
}
logWatchedKeys(watchedKeys, "Apollo.LongPoll.CompletedKeys");
});
......@@ -174,7 +171,33 @@ public class NotificationControllerV2 implements ReleaseMessageListener {
watchedKeys, appId, cluster, namespaces, dataCenter);
}
return deferredResult;
return deferredResultWrapper.getResult();
}
private Map<String, ApolloConfigNotification> filterNotifications(String appId,
List<ApolloConfigNotification> notifications) {
Map<String, ApolloConfigNotification> filteredNotifications = Maps.newHashMap();
for (ApolloConfigNotification notification : notifications) {
if (Strings.isNullOrEmpty(notification.getNamespaceName())) {
continue;
}
//strip out .properties suffix
String originalNamespace = namespaceUtil.filterNamespaceName(notification.getNamespaceName());
notification.setNamespaceName(originalNamespace);
//fix the character case issue, such as FX.apollo <-> fx.apollo
String normalizedNamespace = namespaceUtil.normalizeNamespace(appId, originalNamespace);
// in case client side namespace name has character case issue and has difference notification ids
// such as FX.apollo = 1 but fx.apollo = 2, we should let FX.apollo have the chance to update its notification id
// which means we should record FX.apollo = 1 here and ignore fx.apollo = 2
if (filteredNotifications.containsKey(normalizedNamespace) &&
filteredNotifications.get(normalizedNamespace).getNotificationId() < notification.getNotificationId()) {
continue;
}
filteredNotifications.put(normalizedNamespace, notification);
}
return filteredNotifications;
}
private List<ApolloConfigNotification> getApolloConfigNotifications(Set<String> namespaces,
......@@ -190,17 +213,20 @@ public class NotificationControllerV2 implements ReleaseMessageListener {
for (String namespace : namespaces) {
long clientSideId = clientSideNotifications.get(namespace);
long latestId = NOTIFICATION_ID_PLACEHOLDER;
long latestId = ConfigConsts.NOTIFICATION_ID_PLACEHOLDER;
Collection<String> namespaceWatchedKeys = watchedKeysMap.get(namespace);
for (String namespaceWatchedKey : namespaceWatchedKeys) {
long namespaceNotificationId =
latestNotifications.getOrDefault(namespaceWatchedKey, NOTIFICATION_ID_PLACEHOLDER);
latestNotifications.getOrDefault(namespaceWatchedKey, ConfigConsts.NOTIFICATION_ID_PLACEHOLDER);
if (namespaceNotificationId > latestId) {
latestId = namespaceNotificationId;
}
}
if (latestId > clientSideId) {
newNotifications.add(new ApolloConfigNotification(namespace, latestId));
ApolloConfigNotification notification = new ApolloConfigNotification(namespace, latestId);
namespaceWatchedKeys.stream().filter(latestNotifications::containsKey).forEach(namespaceWatchedKey ->
notification.addMessage(namespaceWatchedKey, latestNotifications.get(namespaceWatchedKey)));
newNotifications.add(notification);
}
}
}
......@@ -224,17 +250,15 @@ public class NotificationControllerV2 implements ReleaseMessageListener {
return;
}
ResponseEntity<List<ApolloConfigNotification>> notification =
new ResponseEntity<>(
Lists.newArrayList(new ApolloConfigNotification(changedNamespace, message.getId())),
HttpStatus.OK);
if (!deferredResults.containsKey(content)) {
return;
}
//create a new list to avoid ConcurrentModificationException
List<DeferredResult<ResponseEntity<List<ApolloConfigNotification>>>> results =
Lists.newArrayList(deferredResults.get(content));
List<DeferredResultWrapper> results = Lists.newArrayList(deferredResults.get(content));
ApolloConfigNotification configNotification = new ApolloConfigNotification(changedNamespace, message.getId());
configNotification.addMessage(content, message.getId());
//do async notification if too many clients
if (results.size() > bizConfig.releaseMessageNotificationBatch()) {
......@@ -250,7 +274,7 @@ public class NotificationControllerV2 implements ReleaseMessageListener {
}
}
logger.debug("Async notify {}", results.get(i));
results.get(i).setResult(notification);
results.get(i).setResult(configNotification);
}
});
return;
......@@ -258,8 +282,8 @@ public class NotificationControllerV2 implements ReleaseMessageListener {
logger.debug("Notify {} clients for key {}", results.size(), content);
for (DeferredResult<ResponseEntity<List<ApolloConfigNotification>>> result : results) {
result.setResult(notification);
for (DeferredResultWrapper result : results) {
result.setResult(configNotification);
}
logger.debug("Notification completed");
}
......
package com.ctrip.framework.apollo.configservice.service;
import com.ctrip.framework.apollo.configservice.wrapper.CaseInsensitiveMapWrapper;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
......@@ -52,29 +54,37 @@ public class AppNamespaceServiceWithCache implements InitializingBean {
private long maxIdScanned;
//store namespaceName -> AppNamespace
private Map<String, AppNamespace> publicAppNamespaceCache;
private CaseInsensitiveMapWrapper<AppNamespace> publicAppNamespaceCache;
//store appId+namespaceName -> AppNamespace
private Map<String, AppNamespace> appNamespaceCache;
private CaseInsensitiveMapWrapper<AppNamespace> appNamespaceCache;
//store id -> AppNamespace
private Map<Long, AppNamespace> appNamespaceIdCache;
public AppNamespaceServiceWithCache() {
initialize();
}
private void initialize() {
maxIdScanned = 0;
publicAppNamespaceCache = Maps.newConcurrentMap();
appNamespaceCache = Maps.newConcurrentMap();
publicAppNamespaceCache = new CaseInsensitiveMapWrapper<>(Maps.newConcurrentMap());
appNamespaceCache = new CaseInsensitiveMapWrapper<>(Maps.newConcurrentMap());
appNamespaceIdCache = Maps.newConcurrentMap();
scheduledExecutorService = Executors.newScheduledThreadPool(1, ApolloThreadFactory
.create("AppNamespaceServiceWithCache", true));
}
public AppNamespace findByAppIdAndNamespace(String appId, String namespaceName) {
Preconditions.checkArgument(!StringUtils.isContainEmpty(appId, namespaceName), "appId and namespaceName must not be empty");
return appNamespaceCache.get(STRING_JOINER.join(appId, namespaceName));
}
public List<AppNamespace> findByAppIdAndNamespaces(String appId, Set<String> namespaceNames) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(appId), "appId must not be null");
if (namespaceNames == null || namespaceNames.isEmpty()) {
return Collections.emptyList();
}
// return appNamespaceRepository.findByAppIdAndNameIn(appId, namespaceNames);
List<AppNamespace> result = Lists.newArrayList();
for (String namespaceName : namespaceNames) {
AppNamespace appNamespace = appNamespaceCache.get(STRING_JOINER.join(appId, namespaceName));
......@@ -85,12 +95,16 @@ public class AppNamespaceServiceWithCache implements InitializingBean {
return result;
}
public AppNamespace findPublicNamespaceByName(String namespaceName) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(namespaceName), "namespaceName must not be empty");
return publicAppNamespaceCache.get(namespaceName);
}
public List<AppNamespace> findPublicNamespacesByNames(Set<String> namespaceNames) {
if (namespaceNames == null || namespaceNames.isEmpty()) {
return Collections.emptyList();
}
// return appNamespaceRepository.findByNameInAndIsPublicTrue(namespaceNames);
List<AppNamespace> result = Lists.newArrayList();
for (String namespaceName : namespaceNames) {
AppNamespace appNamespace = publicAppNamespaceCache.get(namespaceName);
......@@ -249,4 +263,11 @@ public class AppNamespaceServiceWithCache implements InitializingBean {
rebuildInterval = bizConfig.appNamespaceCacheRebuildInterval();
rebuildIntervalTimeUnit = bizConfig.appNamespaceCacheRebuildIntervalTimeUnit();
}
//only for test use
private void reset() throws Exception {
scheduledExecutorService.shutdownNow();
initialize();
afterPropertiesSet();
}
}
package com.ctrip.framework.apollo.configservice.service.config;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.grayReleaseRule.GrayReleaseRulesHolder;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.dto.ApolloNotificationMessages;
import com.google.common.base.Strings;
import java.util.Map;
import java.util.Objects;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public abstract class AbstractConfigService implements ConfigService {
@Autowired
private GrayReleaseRulesHolder grayReleaseRulesHolder;
@Override
public Release loadConfig(String clientAppId, String clientIp, String configAppId, String configClusterName,
String configNamespace, String dataCenter, ApolloNotificationMessages clientMessages) {
// load from specified cluster fist
if (!Objects.equals(ConfigConsts.CLUSTER_NAME_DEFAULT, configClusterName)) {
Release clusterRelease = findRelease(clientAppId, clientIp, configAppId, configClusterName, configNamespace,
clientMessages);
if (!Objects.isNull(clusterRelease)) {
return clusterRelease;
}
}
// try to load via data center
if (!Strings.isNullOrEmpty(dataCenter) && !Objects.equals(dataCenter, configClusterName)) {
Release dataCenterRelease = findRelease(clientAppId, clientIp, configAppId, dataCenter, configNamespace,
clientMessages);
if (!Objects.isNull(dataCenterRelease)) {
return dataCenterRelease;
}
}
// fallback to default release
return findRelease(clientAppId, clientIp, configAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, configNamespace,
clientMessages);
}
/**
* Find release
*
* @param clientAppId the client's app id
* @param clientIp the client ip
* @param configAppId the requested config's app id
* @param configClusterName the requested config's cluster name
* @param configNamespace the requested config's namespace name
* @param clientMessages the messages received in client side
* @return the release
*/
private Release findRelease(String clientAppId, String clientIp, String configAppId, String configClusterName,
String configNamespace, ApolloNotificationMessages clientMessages) {
Long grayReleaseId = grayReleaseRulesHolder.findReleaseIdFromGrayReleaseRule(clientAppId, clientIp, configAppId,
configClusterName, configNamespace);
Release release = null;
if (grayReleaseId != null) {
release = findActiveOne(grayReleaseId, clientMessages);
}
if (release == null) {
release = findLatestActiveRelease(configAppId, configClusterName, configNamespace, clientMessages);
}
return release;
}
/**
* Find active release by id
*/
protected abstract Release findActiveOne(long id, ApolloNotificationMessages clientMessages);
/**
* Find active release by app id, cluster name and namespace name
*/
protected abstract Release findLatestActiveRelease(String configAppId, String configClusterName,
String configNamespaceName, ApolloNotificationMessages clientMessages);
}
package com.ctrip.framework.apollo.configservice.service.config;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.message.ReleaseMessageListener;
import com.ctrip.framework.apollo.core.dto.ApolloNotificationMessages;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public interface ConfigService extends ReleaseMessageListener {
/**
* Load config
*
* @param clientAppId the client's app id
* @param clientIp the client ip
* @param configAppId the requested config's app id
* @param configClusterName the requested config's cluster name
* @param configNamespace the requested config's namespace name
* @param dataCenter the client data center
* @param clientMessages the messages received in client side
* @return the Release
*/
Release loadConfig(String clientAppId, String clientIp, String configAppId, String
configClusterName, String configNamespace, String dataCenter, ApolloNotificationMessages clientMessages);
}
package com.ctrip.framework.apollo.configservice.service.config;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.entity.ReleaseMessage;
import com.ctrip.framework.apollo.biz.message.Topics;
import com.ctrip.framework.apollo.biz.service.ReleaseMessageService;
import com.ctrip.framework.apollo.biz.service.ReleaseService;
import com.ctrip.framework.apollo.biz.utils.ReleaseMessageKeyGenerator;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.dto.ApolloNotificationMessages;
import com.ctrip.framework.apollo.tracer.Tracer;
import com.ctrip.framework.apollo.tracer.spi.Transaction;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
/**
* config service with guava cache
*
* @author Jason Song(song_s@ctrip.com)
*/
public class ConfigServiceWithCache extends AbstractConfigService {
private static final Logger logger = LoggerFactory.getLogger(ConfigServiceWithCache.class);
private static final long DEFAULT_EXPIRED_AFTER_ACCESS_IN_MINUTES = 60;//1 hour
private static final String TRACER_EVENT_CACHE_INVALIDATE = "ConfigCache.Invalidate";
private static final String TRACER_EVENT_CACHE_LOAD = "ConfigCache.LoadFromDB";
private static final String TRACER_EVENT_CACHE_LOAD_ID = "ConfigCache.LoadFromDBById";
private static final String TRACER_EVENT_CACHE_GET = "ConfigCache.Get";
private static final String TRACER_EVENT_CACHE_GET_ID = "ConfigCache.GetById";
private static final Splitter STRING_SPLITTER =
Splitter.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).omitEmptyStrings();
@Autowired
private ReleaseService releaseService;
@Autowired
private ReleaseMessageService releaseMessageService;
private LoadingCache<String, ConfigCacheEntry> configCache;
private LoadingCache<Long, Optional<Release>> configIdCache;
private ConfigCacheEntry nullConfigCacheEntry;
public ConfigServiceWithCache() {
nullConfigCacheEntry = new ConfigCacheEntry(ConfigConsts.NOTIFICATION_ID_PLACEHOLDER, null);
}
@PostConstruct
void initialize() {
configCache = CacheBuilder.newBuilder()
.expireAfterAccess(DEFAULT_EXPIRED_AFTER_ACCESS_IN_MINUTES, TimeUnit.MINUTES)
.build(new CacheLoader<String, ConfigCacheEntry>() {
@Override
public ConfigCacheEntry load(String key) throws Exception {
List<String> namespaceInfo = STRING_SPLITTER.splitToList(key);
if (namespaceInfo.size() != 3) {
Tracer.logError(
new IllegalArgumentException(String.format("Invalid cache load key %s", key)));
return nullConfigCacheEntry;
}
Transaction transaction = Tracer.newTransaction(TRACER_EVENT_CACHE_LOAD, key);
try {
ReleaseMessage latestReleaseMessage = releaseMessageService.findLatestReleaseMessageForMessages(Lists
.newArrayList(key));
Release latestRelease = releaseService.findLatestActiveRelease(namespaceInfo.get(0), namespaceInfo.get(1),
namespaceInfo.get(2));
transaction.setStatus(Transaction.SUCCESS);
long notificationId = latestReleaseMessage == null ? ConfigConsts.NOTIFICATION_ID_PLACEHOLDER : latestReleaseMessage
.getId();
if (notificationId == ConfigConsts.NOTIFICATION_ID_PLACEHOLDER && latestRelease == null) {
return nullConfigCacheEntry;
}
return new ConfigCacheEntry(notificationId, latestRelease);
} catch (Throwable ex) {
transaction.setStatus(ex);
throw ex;
} finally {
transaction.complete();
}
}
});
configIdCache = CacheBuilder.newBuilder()
.expireAfterAccess(DEFAULT_EXPIRED_AFTER_ACCESS_IN_MINUTES, TimeUnit.MINUTES)
.build(new CacheLoader<Long, Optional<Release>>() {
@Override
public Optional<Release> load(Long key) throws Exception {
Transaction transaction = Tracer.newTransaction(TRACER_EVENT_CACHE_LOAD_ID, String.valueOf(key));
try {
Release release = releaseService.findActiveOne(key);
transaction.setStatus(Transaction.SUCCESS);
return Optional.ofNullable(release);
} catch (Throwable ex) {
transaction.setStatus(ex);
throw ex;
} finally {
transaction.complete();
}
}
});
}
@Override
protected Release findActiveOne(long id, ApolloNotificationMessages clientMessages) {
Tracer.logEvent(TRACER_EVENT_CACHE_GET_ID, String.valueOf(id));
return configIdCache.getUnchecked(id).orElse(null);
}
@Override
protected Release findLatestActiveRelease(String appId, String clusterName, String namespaceName,
ApolloNotificationMessages clientMessages) {
String key = ReleaseMessageKeyGenerator.generate(appId, clusterName, namespaceName);
Tracer.logEvent(TRACER_EVENT_CACHE_GET, key);
ConfigCacheEntry cacheEntry = configCache.getUnchecked(key);
//cache is out-dated
if (clientMessages != null && clientMessages.has(key) &&
clientMessages.get(key) > cacheEntry.getNotificationId()) {
//invalidate the cache and try to load from db again
invalidate(key);
cacheEntry = configCache.getUnchecked(key);
}
return cacheEntry.getRelease();
}
private void invalidate(String key) {
configCache.invalidate(key);
Tracer.logEvent(TRACER_EVENT_CACHE_INVALIDATE, key);
}
@Override
public void handleMessage(ReleaseMessage message, String channel) {
logger.info("message received - channel: {}, message: {}", channel, message);
if (!Topics.APOLLO_RELEASE_TOPIC.equals(channel) || Strings.isNullOrEmpty(message.getMessage())) {
return;
}
try {
invalidate(message.getMessage());
//warm up the cache
configCache.getUnchecked(message.getMessage());
} catch (Throwable ex) {
//ignore
}
}
private static class ConfigCacheEntry {
private final long notificationId;
private final Release release;
public ConfigCacheEntry(long notificationId, Release release) {
this.notificationId = notificationId;
this.release = release;
}
public long getNotificationId() {
return notificationId;
}
public Release getRelease() {
return release;
}
}
}
package com.ctrip.framework.apollo.configservice.service.config;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.entity.ReleaseMessage;
import com.ctrip.framework.apollo.biz.service.ReleaseService;
import com.ctrip.framework.apollo.core.dto.ApolloNotificationMessages;
import org.springframework.beans.factory.annotation.Autowired;
/**
* config service with no cache
*
* @author Jason Song(song_s@ctrip.com)
*/
public class DefaultConfigService extends AbstractConfigService {
@Autowired
private ReleaseService releaseService;
@Override
protected Release findActiveOne(long id, ApolloNotificationMessages clientMessages) {
return releaseService.findActiveOne(id);
}
@Override
protected Release findLatestActiveRelease(String configAppId, String configClusterName, String configNamespace,
ApolloNotificationMessages clientMessages) {
return releaseService.findLatestActiveRelease(configAppId, configClusterName,
configNamespace);
}
@Override
public void handleMessage(ReleaseMessage message, String channel) {
// since there is no cache, so do nothing
}
}
package com.ctrip.framework.apollo.configservice.util;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.configservice.service.AppNamespaceServiceWithCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
......@@ -8,6 +12,9 @@ import org.springframework.stereotype.Component;
@Component
public class NamespaceUtil {
@Autowired
private AppNamespaceServiceWithCache appNamespaceServiceWithCache;
public String filterNamespaceName(String namespaceName) {
if (namespaceName.toLowerCase().endsWith(".properties")) {
int dotIndex = namespaceName.lastIndexOf(".");
......@@ -16,4 +23,18 @@ public class NamespaceUtil {
return namespaceName;
}
public String normalizeNamespace(String appId, String namespaceName) {
AppNamespace appNamespace = appNamespaceServiceWithCache.findByAppIdAndNamespace(appId, namespaceName);
if (appNamespace != null) {
return appNamespace.getName();
}
appNamespace = appNamespaceServiceWithCache.findPublicNamespaceByName(namespaceName);
if (appNamespace != null) {
return appNamespace.getName();
}
return namespaceName;
}
}
package com.ctrip.framework.apollo.configservice.wrapper;
import java.util.Map;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class CaseInsensitiveMapWrapper<T> {
private final Map<String, T> delegate;
public CaseInsensitiveMapWrapper(Map<String, T> delegate) {
this.delegate = delegate;
}
public T get(String key) {
return delegate.get(key.toLowerCase());
}
public T put(String key, T value) {
return delegate.put(key.toLowerCase(), value);
}
public T remove(String key) {
return delegate.remove(key.toLowerCase());
}
}
package com.ctrip.framework.apollo.configservice.wrapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.ctrip.framework.apollo.core.dto.ApolloConfig;
import com.ctrip.framework.apollo.core.dto.ApolloConfigNotification;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.context.request.async.DeferredResult;
import java.util.List;
import java.util.Map;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class DeferredResultWrapper {
private static final long TIMEOUT = 30 * 1000;//30 seconds
private static final ResponseEntity<List<ApolloConfigNotification>>
NOT_MODIFIED_RESPONSE_LIST = new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
private Map<String, String> normalizedNamespaceNameToOriginalNamespaceName;
private DeferredResult<ResponseEntity<List<ApolloConfigNotification>>> result;
public DeferredResultWrapper() {
result = new DeferredResult<>(TIMEOUT, NOT_MODIFIED_RESPONSE_LIST);
}
public void recordNamespaceNameNormalizedResult(String originalNamespaceName, String normalizedNamespaceName) {
if (normalizedNamespaceNameToOriginalNamespaceName == null) {
normalizedNamespaceNameToOriginalNamespaceName = Maps.newHashMap();
}
normalizedNamespaceNameToOriginalNamespaceName.put(normalizedNamespaceName, originalNamespaceName);
}
public void onTimeout(Runnable timeoutCallback) {
result.onTimeout(timeoutCallback);
}
public void onCompletion(Runnable completionCallback) {
result.onCompletion(completionCallback);
}
public void setResult(ApolloConfigNotification notification) {
setResult(Lists.newArrayList(notification));
}
/**
* The namespace name is used as a key in client side, so we have to return the original one instead of the correct one
*/
public void setResult(List<ApolloConfigNotification> notifications) {
if (normalizedNamespaceNameToOriginalNamespaceName != null) {
notifications.stream().filter(notification -> normalizedNamespaceNameToOriginalNamespaceName.containsKey
(notification.getNamespaceName())).forEach(notification -> notification.setNamespaceName(
normalizedNamespaceNameToOriginalNamespaceName.get(notification.getNamespaceName())));
}
result.setResult(new ResponseEntity<>(notifications, HttpStatus.OK));
}
public DeferredResult<ResponseEntity<List<ApolloConfigNotification>>> getResult() {
return result;
}
}
......@@ -10,9 +10,12 @@ import com.ctrip.framework.apollo.configservice.integration.NotificationControll
import com.ctrip.framework.apollo.configservice.integration.NotificationControllerV2IntegrationTest;
import com.ctrip.framework.apollo.configservice.service.AppNamespaceServiceWithCacheTest;
import com.ctrip.framework.apollo.configservice.service.ReleaseMessageServiceWithCacheTest;
import com.ctrip.framework.apollo.configservice.service.config.ConfigServiceWithCacheTest;
import com.ctrip.framework.apollo.configservice.service.config.DefaultConfigServiceTest;
import com.ctrip.framework.apollo.configservice.util.InstanceConfigAuditUtilTest;
import com.ctrip.framework.apollo.configservice.util.NamespaceUtilTest;
import com.ctrip.framework.apollo.configservice.util.WatchKeysUtilTest;
import com.ctrip.framework.apollo.configservice.wrapper.CaseInsensitiveMapWrapperTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
......@@ -25,7 +28,8 @@ import org.junit.runners.Suite.SuiteClasses;
ConfigFileControllerIntegrationTest.class, WatchKeysUtilTest.class,
NotificationControllerV2Test.class, NotificationControllerV2IntegrationTest.class,
InstanceConfigAuditUtilTest.class, AppNamespaceServiceWithCacheTest.class,
ReleaseMessageServiceWithCacheTest.class
ReleaseMessageServiceWithCacheTest.class, DefaultConfigServiceTest.class, ConfigServiceWithCacheTest.class,
CaseInsensitiveMapWrapperTest.class
})
public class AllTests {
......
......@@ -80,6 +80,7 @@ public class ConfigFileControllerTest {
someClientIp = "10.1.1.1";
when(namespaceUtil.filterNamespaceName(someNamespace)).thenReturn(someNamespace);
when(namespaceUtil.normalizeNamespace(someAppId, someNamespace)).thenReturn(someNamespace);
when(grayReleaseRulesHolder.hasGrayReleaseRule(anyString(), anyString(), anyString()))
.thenReturn(false);
......@@ -111,7 +112,7 @@ public class ConfigFileControllerTest {
ApolloConfig someApolloConfig = mock(ApolloConfig.class);
when(someApolloConfig.getConfigurations()).thenReturn(configurations);
when(configController
.queryConfig(someAppId, someClusterName, someNamespace, someDataCenter, "-1", someClientIp,
.queryConfig(someAppId, someClusterName, someNamespace, someDataCenter, "-1", someClientIp, null,
someRequest, someResponse)).thenReturn(someApolloConfig);
when(watchKeysUtil
.assembleAllWatchKeys(someAppId, someClusterName, someNamespace, someDataCenter))
......@@ -141,7 +142,7 @@ public class ConfigFileControllerTest {
assertEquals(response, anotherResponse);
verify(configController, times(1))
.queryConfig(someAppId, someClusterName, someNamespace, someDataCenter, "-1", someClientIp,
.queryConfig(someAppId, someClusterName, someNamespace, someDataCenter, "-1", someClientIp, null,
someRequest, someResponse);
}
......@@ -159,7 +160,7 @@ public class ConfigFileControllerTest {
ImmutableMap.of(someKey, someValue);
ApolloConfig someApolloConfig = mock(ApolloConfig.class);
when(configController
.queryConfig(someAppId, someClusterName, someNamespace, someDataCenter, "-1", someClientIp,
.queryConfig(someAppId, someClusterName, someNamespace, someDataCenter, "-1", someClientIp, null,
someRequest, someResponse)).thenReturn(someApolloConfig);
when(someApolloConfig.getConfigurations()).thenReturn(configurations);
when(watchKeysUtil
......@@ -191,7 +192,7 @@ public class ConfigFileControllerTest {
ApolloConfig someApolloConfig = mock(ApolloConfig.class);
when(someApolloConfig.getConfigurations()).thenReturn(configurations);
when(configController
.queryConfig(someAppId, someClusterName, someNamespace, someDataCenter, "-1", someClientIp,
.queryConfig(someAppId, someClusterName, someNamespace, someDataCenter, "-1", someClientIp, null,
someRequest, someResponse)).thenReturn(someApolloConfig);
ResponseEntity<String> response =
......@@ -205,7 +206,7 @@ public class ConfigFileControllerTest {
someClientIp, someRequest, someResponse);
verify(configController, times(2))
.queryConfig(someAppId, someClusterName, someNamespace, someDataCenter, "-1", someClientIp,
.queryConfig(someAppId, someClusterName, someNamespace, someDataCenter, "-1", someClientIp, null,
someRequest, someResponse);
assertEquals(HttpStatus.OK, response.getStatusCode());
......
package com.ctrip.framework.apollo.configservice.controller;
import com.ctrip.framework.apollo.configservice.wrapper.DeferredResultWrapper;
import com.ctrip.framework.apollo.core.dto.ApolloNotificationMessages;
import com.google.common.base.Joiner;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
......@@ -27,7 +29,9 @@ import org.springframework.http.ResponseEntity;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.context.request.async.DeferredResult;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
......@@ -64,8 +68,7 @@ public class NotificationControllerV2Test {
private Gson gson;
private Multimap<String, DeferredResult<ResponseEntity<List<ApolloConfigNotification>>>>
deferredResults;
private Multimap<String, DeferredResultWrapper> deferredResults;
@Before
public void setUp() throws Exception {
......@@ -93,10 +96,11 @@ public class NotificationControllerV2Test {
when(namespaceUtil.filterNamespaceName(defaultNamespace)).thenReturn(defaultNamespace);
when(namespaceUtil.filterNamespaceName(somePublicNamespace)).thenReturn(somePublicNamespace);
when(namespaceUtil.normalizeNamespace(someAppId, defaultNamespace)).thenReturn(defaultNamespace);
when(namespaceUtil.normalizeNamespace(someAppId, somePublicNamespace)).thenReturn(somePublicNamespace);
deferredResults =
(Multimap<String, DeferredResult<ResponseEntity<List<ApolloConfigNotification>>>>) ReflectionTestUtils
.getField(controller, "deferredResults");
(Multimap<String, DeferredResultWrapper>) ReflectionTestUtils.getField(controller, "deferredResults");
}
@Test
......@@ -122,9 +126,7 @@ public class NotificationControllerV2Test {
assertEquals(watchKeysMap.size(), deferredResults.size());
for (String watchKey : watchKeysMap.values()) {
assertTrue(deferredResults.get(watchKey).contains(deferredResult));
}
assertWatchKeys(watchKeysMap, deferredResult);
}
@Test
......@@ -153,9 +155,7 @@ public class NotificationControllerV2Test {
assertEquals(watchKeysMap.size(), deferredResults.size());
for (String watchKey : watchKeysMap.values()) {
assertTrue(deferredResults.get(watchKey).contains(deferredResult));
}
assertWatchKeys(watchKeysMap, deferredResult);
}
......@@ -165,8 +165,8 @@ public class NotificationControllerV2Test {
String somePublicNamespaceAsFile = somePublicNamespace + ".xml";
when(namespaceUtil.filterNamespaceName(defaultNamespaceAsFile)).thenReturn(defaultNamespace);
when(namespaceUtil.filterNamespaceName(somePublicNamespaceAsFile))
.thenReturn(somePublicNamespaceAsFile);
when(namespaceUtil.filterNamespaceName(somePublicNamespaceAsFile)).thenReturn(somePublicNamespaceAsFile);
when(namespaceUtil.normalizeNamespace(someAppId, somePublicNamespaceAsFile)).thenReturn(somePublicNamespaceAsFile);
String someWatchKey = "someKey";
String anotherWatchKey = "anotherKey";
......@@ -199,9 +199,7 @@ public class NotificationControllerV2Test {
assertEquals(watchKeysMap.size(), deferredResults.size());
for (String watchKey : watchKeysMap.values()) {
assertTrue(deferredResults.get(watchKey).contains(deferredResult));
}
assertWatchKeys(watchKeysMap, deferredResult);
verify(watchKeysUtil, times(1)).assembleAllWatchKeys(someAppId, someCluster,
Sets.newHashSet(defaultNamespace, somePublicNamespace, somePublicNamespaceAsFile),
......@@ -214,12 +212,15 @@ public class NotificationControllerV2Test {
String someWatchKey = "someKey";
String anotherWatchKey = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR)
.join(someAppId, someCluster, somePublicNamespace);
String yetAnotherWatchKey = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR)
.join(someAppId, defaultCluster, somePublicNamespace);
long notificationId = someNotificationId + 1;
long yetAnotherNotificationId = someNotificationId;
Multimap<String, String> watchKeysMap =
assembleMultiMap(defaultNamespace, Lists.newArrayList(someWatchKey));
watchKeysMap
.putAll(assembleMultiMap(somePublicNamespace, Lists.newArrayList(anotherWatchKey)));
.putAll(assembleMultiMap(somePublicNamespace, Lists.newArrayList(anotherWatchKey, yetAnotherWatchKey)));
when(watchKeysUtil
.assembleAllWatchKeys(someAppId, someCluster,
......@@ -229,9 +230,12 @@ public class NotificationControllerV2Test {
ReleaseMessage someReleaseMessage = mock(ReleaseMessage.class);
when(someReleaseMessage.getId()).thenReturn(notificationId);
when(someReleaseMessage.getMessage()).thenReturn(anotherWatchKey);
ReleaseMessage yetAnotherReleaseMessage = mock(ReleaseMessage.class);
when(yetAnotherReleaseMessage.getId()).thenReturn(yetAnotherNotificationId);
when(yetAnotherReleaseMessage.getMessage()).thenReturn(yetAnotherWatchKey);
when(releaseMessageService
.findLatestReleaseMessagesGroupByMessages(Sets.newHashSet(watchKeysMap.values())))
.thenReturn(Lists.newArrayList(someReleaseMessage));
.thenReturn(Lists.newArrayList(someReleaseMessage, yetAnotherReleaseMessage));
String notificationAsString =
transformApolloConfigNotificationsToString(defaultNamespace, someNotificationId,
......@@ -249,6 +253,11 @@ public class NotificationControllerV2Test {
assertEquals(1, result.getBody().size());
assertEquals(somePublicNamespace, result.getBody().get(0).getNamespaceName());
assertEquals(notificationId, result.getBody().get(0).getNotificationId());
ApolloNotificationMessages notificationMessages = result.getBody().get(0).getMessages();
assertEquals(2, notificationMessages.getDetails().size());
assertEquals(notificationId, notificationMessages.get(anotherWatchKey).longValue());
assertEquals(yetAnotherNotificationId, notificationMessages.get(yetAnotherWatchKey).longValue());
}
@Test
......@@ -286,11 +295,16 @@ public class NotificationControllerV2Test {
ResponseEntity<List<ApolloConfigNotification>> response =
(ResponseEntity<List<ApolloConfigNotification>>) deferredResult.getResult();
assertEquals(1, response.getBody().size());
ApolloConfigNotification notification = response.getBody().get(0);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(somePublicNamespace, notification.getNamespaceName());
assertEquals(someId, notification.getNotificationId());
ApolloNotificationMessages notificationMessages = response.getBody().get(0).getMessages();
assertEquals(1, notificationMessages.getDetails().size());
assertEquals(someId, notificationMessages.get(anotherWatchKey).longValue());
}
@Test
......@@ -365,9 +379,7 @@ public class NotificationControllerV2Test {
private ApolloConfigNotification assembleApolloConfigNotification(String namespace,
long notificationId) {
ApolloConfigNotification notification = new ApolloConfigNotification();
notification.setNamespaceName(namespace);
notification.setNotificationId(notificationId);
ApolloConfigNotification notification = new ApolloConfigNotification(namespace, notificationId);
return notification;
}
......@@ -376,4 +388,17 @@ public class NotificationControllerV2Test {
multimap.putAll(key, values);
return multimap;
}
private void assertWatchKeys(Multimap<String, String> watchKeysMap, DeferredResult deferredResult) {
for (String watchKey : watchKeysMap.values()) {
Collection<DeferredResultWrapper> deferredResultWrappers = deferredResults.get(watchKey);
boolean found = false;
for (DeferredResultWrapper wrapper: deferredResultWrappers) {
if (Objects.equals(wrapper.getResult(), deferredResult)) {
found = true;
}
}
assertTrue(found);
}
}
}
......@@ -3,6 +3,7 @@ package com.ctrip.framework.apollo.configservice.integration;
import com.google.gson.Gson;
import com.ctrip.framework.apollo.ConfigServiceTestConfiguration;
import com.ctrip.framework.apollo.biz.config.BizConfig;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.entity.ReleaseMessage;
......@@ -16,6 +17,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.boot.test.WebIntegrationTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
......@@ -61,6 +63,10 @@ public abstract class AbstractBaseIntegrationTest {
@Configuration
@Import(ConfigServiceTestConfiguration.class)
protected static class TestConfiguration {
@Bean
public BizConfig bizConfig() {
return new TestBizConfig();
}
}
protected void sendReleaseMessage(String message) {
......@@ -105,4 +111,15 @@ public abstract class AbstractBaseIntegrationTest {
});
}
private static class TestBizConfig extends BizConfig {
@Override
public int appNamespaceCacheScanInterval() {
return 50;
}
@Override
public TimeUnit appNamespaceCacheScanIntervalTimeUnit() {
return TimeUnit.MILLISECONDS;
}
}
}
package com.ctrip.framework.apollo.configservice.integration;
import com.ctrip.framework.apollo.configservice.service.AppNamespaceServiceWithCache;
import com.google.common.base.Joiner;
import com.ctrip.framework.apollo.core.ConfigConsts;
......@@ -7,9 +8,11 @@ import com.ctrip.framework.apollo.core.dto.ApolloConfig;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.client.HttpStatusCodeException;
import java.util.concurrent.ExecutorService;
......@@ -33,8 +36,13 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest
private String someClientIp;
private ExecutorService executorService;
@Autowired
private AppNamespaceServiceWithCache appNamespaceServiceWithCache;
@Before
public void setUp() throws Exception {
ReflectionTestUtils.invokeMethod(appNamespaceServiceWithCache, "reset");
someAppId = "someAppId";
someCluster = "someCluster";
someNamespace = "someNamespace";
......@@ -60,6 +68,21 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest
assertEquals("v1", result.getConfigurations().get("k1"));
}
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testQueryConfigWithDefaultClusterAndDefaultNamespaceAndIncorrectCase() throws Exception {
ResponseEntity<ApolloConfig> response = restTemplate
.getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}", ApolloConfig.class,
getHostUrl(), someAppId, ConfigConsts.CLUSTER_NAME_DEFAULT,
ConfigConsts.NAMESPACE_APPLICATION.toUpperCase());
ApolloConfig result = response.getBody();
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals("TEST-RELEASE-KEY1", result.getReleaseKey());
assertEquals("v1", result.getConfigurations().get("k1"));
}
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/test-gray-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
......@@ -83,6 +106,30 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest
assertEquals("v1-gray", result.getConfigurations().get("k1"));
}
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/test-gray-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testQueryGrayConfigWithDefaultClusterAndDefaultNamespaceAndIncorrectCase() throws Exception {
AtomicBoolean stop = new AtomicBoolean();
periodicSendMessage(executorService, assembleKey(someAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, ConfigConsts.NAMESPACE_APPLICATION),
stop);
TimeUnit.MILLISECONDS.sleep(500);
stop.set(true);
ResponseEntity<ApolloConfig> response = restTemplate
.getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}?ip={clientIp}", ApolloConfig.class,
getHostUrl(), someAppId, ConfigConsts.CLUSTER_NAME_DEFAULT,
ConfigConsts.NAMESPACE_APPLICATION.toUpperCase(), someClientIp);
ApolloConfig result = response.getBody();
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals("TEST-GRAY-RELEASE-KEY1", result.getReleaseKey());
assertEquals("v1-gray", result.getConfigurations().get("k1"));
}
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
......@@ -218,6 +265,24 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest
assertEquals("someDC-v2", result.getConfigurations().get("k2"));
}
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/test-release-public-dc-override.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testQueryPublicConfigWithIncorrectCaseAndDataCenterFoundAndOverride() throws Exception {
ResponseEntity<ApolloConfig> response = restTemplate
.getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}?dataCenter={dateCenter}",
ApolloConfig.class,
getHostUrl(), someAppId, someDefaultCluster, somePublicNamespace.toUpperCase(), someDC);
ApolloConfig result = response.getBody();
assertEquals(
"TEST-RELEASE-KEY6" + ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR + "TEST-RELEASE-KEY4",
result.getReleaseKey());
assertEquals("override-someDC-v1", result.getConfigurations().get("k1"));
assertEquals("someDC-v2", result.getConfigurations().get("k2"));
}
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
......@@ -286,6 +351,33 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest
assertEquals("gray-v2", result.getConfigurations().get("k2"));
}
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/test-release-public-default-override.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/test-gray-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testQueryPublicGrayConfigWithIncorrectCaseAndOverride() throws Exception {
AtomicBoolean stop = new AtomicBoolean();
periodicSendMessage(executorService, assembleKey(somePublicAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, somePublicNamespace),
stop);
TimeUnit.MILLISECONDS.sleep(500);
stop.set(true);
ResponseEntity<ApolloConfig> response = restTemplate
.getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}?ip={clientIp}", ApolloConfig.class,
getHostUrl(), someAppId, someDefaultCluster, somePublicNamespace.toUpperCase(), someClientIp);
ApolloConfig result = response.getBody();
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(
"TEST-RELEASE-KEY5" + ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR + "TEST-GRAY-RELEASE-KEY2",
result.getReleaseKey());
assertEquals("override-v1", result.getConfigurations().get("k1"));
assertEquals("gray-v2", result.getConfigurations().get("k2"));
}
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
......
package com.ctrip.framework.apollo.configservice.integration;
import com.ctrip.framework.apollo.configservice.service.AppNamespaceServiceWithCache;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
......@@ -12,6 +13,7 @@ import com.netflix.servo.util.Strings;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.jdbc.Sql;
......@@ -23,6 +25,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.springframework.test.util.ReflectionTestUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
......@@ -45,8 +48,12 @@ public class ConfigFileControllerIntegrationTest extends AbstractBaseIntegration
private ExecutorService executorService;
private Type mapResponseType = new TypeToken<Map<String, String>>(){}.getType();
@Autowired
private AppNamespaceServiceWithCache appNamespaceServiceWithCache;
@Before
public void setUp() throws Exception {
ReflectionTestUtils.invokeMethod(appNamespaceServiceWithCache, "reset");
someDefaultCluster = ConfigConsts.CLUSTER_NAME_DEFAULT;
someAppId = "someAppId";
somePublicAppId = "somePublicAppId";
......@@ -142,6 +149,21 @@ public class ConfigFileControllerIntegrationTest extends AbstractBaseIntegration
assertEquals("v2", configs.get("k2"));
}
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testQueryConfigAsJsonWithIncorrectCase() throws Exception {
ResponseEntity<String> response =
restTemplate
.getForEntity("{baseurl}/configfiles/json/{appId}/{clusterName}/{namespace}", String.class,
getHostUrl(), someAppId, someCluster, someNamespace.toUpperCase());
Map<String, String> configs = gson.fromJson(response.getBody(), mapResponseType);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals("v2", configs.get("k2"));
}
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/test-release-public-dc-override.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
......@@ -161,6 +183,25 @@ public class ConfigFileControllerIntegrationTest extends AbstractBaseIntegration
assertEquals("someDC-v2", configs.get("k2"));
}
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/test-release-public-dc-override.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testQueryPublicConfigAsJsonWithIncorrectCase() throws Exception {
ResponseEntity<String> response =
restTemplate
.getForEntity(
"{baseurl}/configfiles/json/{appId}/{clusterName}/{namespace}?dataCenter={dateCenter}",
String.class,
getHostUrl(), someAppId, someDefaultCluster, somePublicNamespace.toUpperCase(), someDC);
Map<String, String> configs = gson.fromJson(response.getBody(), mapResponseType);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals("override-someDC-v1", configs.get("k1"));
assertEquals("someDC-v2", configs.get("k2"));
}
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/test-release-public-default-override.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
......@@ -202,6 +243,47 @@ public class ConfigFileControllerIntegrationTest extends AbstractBaseIntegration
assertEquals("default-v2", anotherConfigs.get("k2"));
}
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/test-release-public-default-override.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/test-gray-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testQueryPublicConfigAsJsonWithGrayReleaseAndIncorrectCase() throws Exception {
AtomicBoolean stop = new AtomicBoolean();
periodicSendMessage(executorService, assembleKey(somePublicAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, somePublicNamespace),
stop);
TimeUnit.MILLISECONDS.sleep(500);
stop.set(true);
ResponseEntity<String> response =
restTemplate
.getForEntity(
"{baseurl}/configfiles/json/{appId}/{clusterName}/{namespace}?ip={clientIp}",
String.class,
getHostUrl(), someAppId, someDefaultCluster, somePublicNamespace.toUpperCase(), grayClientIp);
ResponseEntity<String> anotherResponse =
restTemplate
.getForEntity(
"{baseurl}/configfiles/json/{appId}/{clusterName}/{namespace}?ip={clientIp}",
String.class,
getHostUrl(), someAppId, someDefaultCluster, somePublicNamespace.toUpperCase(), nonGrayClientIp);
Map<String, String> configs = gson.fromJson(response.getBody(), mapResponseType);
Map<String, String> anotherConfigs = gson.fromJson(anotherResponse.getBody(), mapResponseType);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(HttpStatus.OK, anotherResponse.getStatusCode());
assertEquals("override-v1", configs.get("k1"));
assertEquals("gray-v2", configs.get("k2"));
assertEquals("override-v1", anotherConfigs.get("k1"));
assertEquals("default-v2", anotherConfigs.get("k2"));
}
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
......
package com.ctrip.framework.apollo.configservice.integration;
import com.ctrip.framework.apollo.configservice.service.AppNamespaceServiceWithCache;
import com.google.common.base.Joiner;
import com.ctrip.framework.apollo.configservice.service.ReleaseMessageServiceWithCache;
......@@ -35,10 +36,13 @@ public class NotificationControllerIntegrationTest extends AbstractBaseIntegrati
@Autowired
private ReleaseMessageServiceWithCache releaseMessageServiceWithCache;
@Autowired
private AppNamespaceServiceWithCache appNamespaceServiceWithCache;
@Before
public void setUp() throws Exception {
ReflectionTestUtils.invokeMethod(releaseMessageServiceWithCache, "reset");
ReflectionTestUtils.invokeMethod(appNamespaceServiceWithCache, "reset");
someAppId = "someAppId";
someCluster = ConfigConsts.CLUSTER_NAME_DEFAULT;
defaultNamespace = ConfigConsts.NAMESPACE_APPLICATION;
......
......@@ -23,6 +23,7 @@ import java.util.Set;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
......@@ -62,6 +63,7 @@ public class AppNamespaceServiceWithCacheTest {
public void testAppNamespace() throws Exception {
String someAppId = "someAppId";
String somePrivateNamespace = "somePrivateNamespace";
String somePrivateNamespaceWithIncorrectCase = somePrivateNamespace.toUpperCase();
long somePrivateNamespaceId = 1;
String yetAnotherPrivateNamespace = "anotherPrivateNamespace";
long yetAnotherPrivateNamespaceId = 4;
......@@ -70,6 +72,7 @@ public class AppNamespaceServiceWithCacheTest {
String somePublicAppId = "somePublicAppId";
String somePublicNamespace = "somePublicNamespace";
String somePublicNamespaceWithIncorrectCase = somePublicNamespace.toUpperCase();
long somePublicNamespaceId = 2;
String anotherPrivateNamespace = "anotherPrivateNamespace";
long anotherPrivateNamespaceId = 3;
......@@ -89,9 +92,13 @@ public class AppNamespaceServiceWithCacheTest {
Set<String> someAppIdNamespaces = Sets.newHashSet
(somePrivateNamespace, yetAnotherPrivateNamespace, anotherPublicNamespace);
Set<String> someAppIdNamespacesWithIncorrectCase = Sets.newHashSet
(somePrivateNamespaceWithIncorrectCase, yetAnotherPrivateNamespace, anotherPublicNamespace);
Set<String> somePublicAppIdNamespaces = Sets.newHashSet(somePublicNamespace,
anotherPrivateNamespace);
Set<String> publicNamespaces = Sets.newHashSet(somePublicNamespace, anotherPublicNamespace);
Set<String> publicNamespacesWithIncorrectCase = Sets.newHashSet(somePublicNamespaceWithIncorrectCase,
anotherPublicNamespace);
List<Long> appNamespaceIds = Lists.newArrayList(somePrivateNamespaceId,
somePublicNamespaceId, anotherPrivateNamespaceId, yetAnotherPrivateNamespaceId,
......@@ -104,12 +111,24 @@ public class AppNamespaceServiceWithCacheTest {
appNamespaceServiceWithCache.afterPropertiesSet();
// Should have no record now
assertTrue(appNamespaceServiceWithCache.findByAppIdAndNamespaces(someAppId, someAppIdNamespaces)
assertNull(appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppId, somePrivateNamespace));
assertNull(appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppId, somePrivateNamespaceWithIncorrectCase));
assertNull(appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppId, yetAnotherPrivateNamespace));
assertNull(appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppId, anotherPublicNamespace));
assertTrue(appNamespaceServiceWithCache.findByAppIdAndNamespaces(someAppId, someAppIdNamespaces).isEmpty());
assertTrue(appNamespaceServiceWithCache.findByAppIdAndNamespaces(someAppId, someAppIdNamespacesWithIncorrectCase)
.isEmpty());
assertNull(appNamespaceServiceWithCache.findByAppIdAndNamespace(somePublicAppId, somePublicNamespace));
assertNull(appNamespaceServiceWithCache.findByAppIdAndNamespace(somePublicAppId,
somePublicNamespaceWithIncorrectCase));
assertNull(appNamespaceServiceWithCache.findByAppIdAndNamespace(somePublicAppId, anotherPrivateNamespace));
assertTrue(appNamespaceServiceWithCache.findByAppIdAndNamespaces(somePublicAppId,
somePublicAppIdNamespaces).isEmpty());
assertTrue(appNamespaceServiceWithCache.findPublicNamespacesByNames(publicNamespaces).isEmpty
());
assertNull(appNamespaceServiceWithCache.findPublicNamespaceByName(somePublicNamespace));
assertNull(appNamespaceServiceWithCache.findPublicNamespaceByName(somePublicNamespaceWithIncorrectCase));
assertNull(appNamespaceServiceWithCache.findPublicNamespaceByName(anotherPublicNamespace));
assertTrue(appNamespaceServiceWithCache.findPublicNamespacesByNames(publicNamespaces).isEmpty());
assertTrue(appNamespaceServiceWithCache.findPublicNamespacesByNames(publicNamespacesWithIncorrectCase).isEmpty());
// Add 1 private namespace and 1 public namespace
when(appNamespaceRepository.findFirst500ByIdGreaterThanOrderByIdAsc(0)).thenReturn(Lists
......@@ -120,12 +139,27 @@ public class AppNamespaceServiceWithCacheTest {
scanIntervalTimeUnit.sleep(sleepInterval);
assertEquals(somePrivateAppNamespace,
appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppId, somePrivateNamespace));
assertEquals(somePrivateAppNamespace,
appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppId, somePrivateNamespaceWithIncorrectCase));
check(Lists.newArrayList(somePrivateAppNamespace), appNamespaceServiceWithCache
.findByAppIdAndNamespaces(someAppId, someAppIdNamespaces));
check(Lists.newArrayList(somePrivateAppNamespace), appNamespaceServiceWithCache
.findByAppIdAndNamespaces(someAppId, someAppIdNamespacesWithIncorrectCase));
assertEquals(somePublicAppNamespace, appNamespaceServiceWithCache.findByAppIdAndNamespace(somePublicAppId,
somePublicNamespace));
assertEquals(somePublicAppNamespace, appNamespaceServiceWithCache.findByAppIdAndNamespace(somePublicAppId,
somePublicNamespaceWithIncorrectCase));
check(Lists.newArrayList(somePublicAppNamespace), appNamespaceServiceWithCache
.findByAppIdAndNamespaces(somePublicAppId, somePublicAppIdNamespaces));
check(Lists.newArrayList(somePublicAppNamespace), appNamespaceServiceWithCache
.findPublicNamespacesByNames(publicNamespaces));
assertEquals(somePublicAppNamespace, appNamespaceServiceWithCache.findPublicNamespaceByName(somePublicNamespace));
assertEquals(somePublicAppNamespace, appNamespaceServiceWithCache.findPublicNamespaceByName
(somePublicNamespaceWithIncorrectCase));
check(Lists.newArrayList(somePublicAppNamespace), appNamespaceServiceWithCache.findPublicNamespacesByNames
(publicNamespaces));
check(Lists.newArrayList(somePublicAppNamespace), appNamespaceServiceWithCache.findPublicNamespacesByNames
(publicNamespacesWithIncorrectCase));
// Add 2 private namespaces and 1 public namespace
when(appNamespaceRepository.findFirst500ByIdGreaterThanOrderByIdAsc(somePublicNamespaceId))
......@@ -135,12 +169,23 @@ public class AppNamespaceServiceWithCacheTest {
scanIntervalTimeUnit.sleep(sleepInterval);
check(Lists.newArrayList(somePrivateAppNamespace, yetAnotherPrivateAppNamespace,
anotherPublicAppNamespace), Lists
.newArrayList(appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppId, somePrivateNamespace),
appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppId, yetAnotherPrivateNamespace),
appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppId, anotherPublicNamespace)));
check(Lists.newArrayList(somePrivateAppNamespace, yetAnotherPrivateAppNamespace,
anotherPublicAppNamespace), appNamespaceServiceWithCache.findByAppIdAndNamespaces
(someAppId, someAppIdNamespaces));
check(Lists.newArrayList(somePublicAppNamespace, anotherPrivateAppNamespace),
Lists.newArrayList(appNamespaceServiceWithCache.findByAppIdAndNamespace(somePublicAppId, somePublicNamespace),
appNamespaceServiceWithCache.findByAppIdAndNamespace(somePublicAppId, anotherPrivateNamespace)));
check(Lists.newArrayList(somePublicAppNamespace, anotherPrivateAppNamespace),
appNamespaceServiceWithCache.findByAppIdAndNamespaces(somePublicAppId,
somePublicAppIdNamespaces));
check(Lists.newArrayList(somePublicAppNamespace, anotherPublicAppNamespace),
Lists.newArrayList(appNamespaceServiceWithCache.findPublicNamespaceByName(somePublicNamespace),
appNamespaceServiceWithCache.findPublicNamespaceByName(anotherPublicNamespace)));
check(Lists.newArrayList(somePublicAppNamespace, anotherPublicAppNamespace),
appNamespaceServiceWithCache.findPublicNamespacesByNames(publicNamespaces));
......@@ -173,16 +218,27 @@ public class AppNamespaceServiceWithCacheTest {
scanIntervalTimeUnit.sleep(sleepInterval);
assertNull(appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppId, somePrivateNamespace));
assertNull(appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppId, yetAnotherPrivateNamespace));
assertNull(appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppId, anotherPublicNamespace));
check(Collections.emptyList(), appNamespaceServiceWithCache
.findByAppIdAndNamespaces(someAppId, someAppIdNamespaces));
assertEquals(somePublicAppNamespaceNew,
appNamespaceServiceWithCache.findByAppIdAndNamespace(somePublicAppId, somePublicNamespace));
check(Lists.newArrayList(somePublicAppNamespaceNew),
appNamespaceServiceWithCache.findByAppIdAndNamespaces(somePublicAppId,
somePublicAppIdNamespaces));
assertNull(appNamespaceServiceWithCache.findPublicNamespaceByName(somePublicNamespace));
assertNull(appNamespaceServiceWithCache.findPublicNamespaceByName(anotherPublicNamespace));
check(Collections.emptyList(),
appNamespaceServiceWithCache.findPublicNamespacesByNames(publicNamespaces));
assertEquals(somePrivateAppNamespaceNew,
appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppId, somePrivateNamespaceNew));
check(Lists.newArrayList(somePrivateAppNamespaceNew), appNamespaceServiceWithCache
.findByAppIdAndNamespaces(someAppId, Sets.newHashSet(somePrivateNamespaceNew)));
assertEquals(yetAnotherPrivateAppNamespaceNew,
appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppIdNew, yetAnotherPrivateNamespace));
check(Lists.newArrayList(yetAnotherPrivateAppNamespaceNew), appNamespaceServiceWithCache
.findByAppIdAndNamespaces(someAppIdNew, Sets.newHashSet(yetAnotherPrivateNamespace)));
}
......@@ -210,4 +266,4 @@ public class AppNamespaceServiceWithCacheTest {
appNamespace.setDataChangeLastModifiedTime(new Date());
return appNamespace;
}
}
\ No newline at end of file
}
package com.ctrip.framework.apollo.configservice.service.config;
import com.ctrip.framework.apollo.core.dto.ApolloNotificationMessages;
import com.google.common.collect.Lists;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.entity.ReleaseMessage;
import com.ctrip.framework.apollo.biz.message.Topics;
import com.ctrip.framework.apollo.biz.service.ReleaseMessageService;
import com.ctrip.framework.apollo.biz.service.ReleaseService;
import com.ctrip.framework.apollo.biz.utils.ReleaseMessageKeyGenerator;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@RunWith(MockitoJUnitRunner.class)
public class ConfigServiceWithCacheTest {
private ConfigServiceWithCache configServiceWithCache;
@Mock
private ReleaseService releaseService;
@Mock
private ReleaseMessageService releaseMessageService;
@Mock
private Release someRelease;
@Mock
private ReleaseMessage someReleaseMessage;
private String someAppId;
private String someClusterName;
private String someNamespaceName;
private String someKey;
private long someNotificationId;
private ApolloNotificationMessages someNotificationMessages;
@Before
public void setUp() throws Exception {
configServiceWithCache = new ConfigServiceWithCache();
ReflectionTestUtils.setField(configServiceWithCache, "releaseService", releaseService);
ReflectionTestUtils.setField(configServiceWithCache, "releaseMessageService", releaseMessageService);
configServiceWithCache.initialize();
someAppId = "someAppId";
someClusterName = "someClusterName";
someNamespaceName = "someNamespaceName";
someNotificationId = 1;
someKey = ReleaseMessageKeyGenerator.generate(someAppId, someClusterName, someNamespaceName);
someNotificationMessages = new ApolloNotificationMessages();
}
@Test
public void testFindActiveOne() throws Exception {
long someId = 1;
when(releaseService.findActiveOne(someId)).thenReturn(someRelease);
assertEquals(someRelease, configServiceWithCache.findActiveOne(someId, someNotificationMessages));
verify(releaseService, times(1)).findActiveOne(someId);
}
@Test
public void testFindActiveOneWithSameIdMultipleTimes() throws Exception {
long someId = 1;
when(releaseService.findActiveOne(someId)).thenReturn(someRelease);
assertEquals(someRelease, configServiceWithCache.findActiveOne(someId, someNotificationMessages));
assertEquals(someRelease, configServiceWithCache.findActiveOne(someId, someNotificationMessages));
assertEquals(someRelease, configServiceWithCache.findActiveOne(someId, someNotificationMessages));
verify(releaseService, times(1)).findActiveOne(someId);
}
@Test
public void testFindActiveOneWithMultipleIdMultipleTimes() throws Exception {
long someId = 1;
long anotherId = 2;
Release anotherRelease = mock(Release.class);
when(releaseService.findActiveOne(someId)).thenReturn(someRelease);
when(releaseService.findActiveOne(anotherId)).thenReturn(anotherRelease);
assertEquals(someRelease, configServiceWithCache.findActiveOne(someId, someNotificationMessages));
assertEquals(someRelease, configServiceWithCache.findActiveOne(someId, someNotificationMessages));
assertEquals(anotherRelease, configServiceWithCache.findActiveOne(anotherId, someNotificationMessages));
assertEquals(anotherRelease, configServiceWithCache.findActiveOne(anotherId, someNotificationMessages));
verify(releaseService, times(1)).findActiveOne(someId);
verify(releaseService, times(1)).findActiveOne(anotherId);
}
@Test
public void testFindActiveOneWithReleaseNotFoundMultipleTimes() throws Exception {
long someId = 1;
when(releaseService.findActiveOne(someId)).thenReturn(null);
assertNull(configServiceWithCache.findActiveOne(someId, someNotificationMessages));
assertNull(configServiceWithCache.findActiveOne(someId, someNotificationMessages));
assertNull(configServiceWithCache.findActiveOne(someId, someNotificationMessages));
verify(releaseService, times(1)).findActiveOne(someId);
}
@Test
public void testFindLatestActiveRelease() throws Exception {
when(releaseMessageService.findLatestReleaseMessageForMessages(Lists.newArrayList(someKey))).thenReturn
(someReleaseMessage);
when(releaseService.findLatestActiveRelease(someAppId, someClusterName, someNamespaceName)).thenReturn
(someRelease);
when(someReleaseMessage.getId()).thenReturn(someNotificationId);
Release release = configServiceWithCache.findLatestActiveRelease(someAppId, someClusterName, someNamespaceName,
someNotificationMessages);
Release anotherRelease = configServiceWithCache.findLatestActiveRelease(someAppId, someClusterName,
someNamespaceName, someNotificationMessages);
int retryTimes = 100;
for (int i = 0; i < retryTimes; i++) {
configServiceWithCache.findLatestActiveRelease(someAppId, someClusterName,
someNamespaceName, someNotificationMessages);
}
assertEquals(someRelease, release);
assertEquals(someRelease, anotherRelease);
verify(releaseMessageService, times(1)).findLatestReleaseMessageForMessages(Lists.newArrayList(someKey));
verify(releaseService, times(1)).findLatestActiveRelease(someAppId, someClusterName, someNamespaceName);
}
@Test
public void testFindLatestActiveReleaseWithReleaseNotFound() throws Exception {
when(releaseMessageService.findLatestReleaseMessageForMessages(Lists.newArrayList(someKey))).thenReturn(null);
when(releaseService.findLatestActiveRelease(someAppId, someClusterName, someNamespaceName)).thenReturn(null);
Release release = configServiceWithCache.findLatestActiveRelease(someAppId, someClusterName, someNamespaceName,
someNotificationMessages);
Release anotherRelease = configServiceWithCache.findLatestActiveRelease(someAppId, someClusterName,
someNamespaceName, someNotificationMessages);
int retryTimes = 100;
for (int i = 0; i < retryTimes; i++) {
configServiceWithCache.findLatestActiveRelease(someAppId, someClusterName,
someNamespaceName, someNotificationMessages);
}
assertNull(release);
assertNull(anotherRelease);
verify(releaseMessageService, times(1)).findLatestReleaseMessageForMessages(Lists.newArrayList(someKey));
verify(releaseService, times(1)).findLatestActiveRelease(someAppId, someClusterName, someNamespaceName);
}
@Test
public void testFindLatestActiveReleaseWithDirtyRelease() throws Exception {
long someNewNotificationId = someNotificationId + 1;
ReleaseMessage anotherReleaseMessage = mock(ReleaseMessage.class);
Release anotherRelease = mock(Release.class);
when(releaseMessageService.findLatestReleaseMessageForMessages(Lists.newArrayList(someKey))).thenReturn
(someReleaseMessage);
when(releaseService.findLatestActiveRelease(someAppId, someClusterName, someNamespaceName)).thenReturn
(someRelease);
when(someReleaseMessage.getId()).thenReturn(someNotificationId);
Release release = configServiceWithCache.findLatestActiveRelease(someAppId, someClusterName, someNamespaceName,
someNotificationMessages);
when(releaseMessageService.findLatestReleaseMessageForMessages(Lists.newArrayList(someKey))).thenReturn
(anotherReleaseMessage);
when(releaseService.findLatestActiveRelease(someAppId, someClusterName, someNamespaceName)).thenReturn
(anotherRelease);
when(anotherReleaseMessage.getId()).thenReturn(someNewNotificationId);
Release stillOldRelease = configServiceWithCache.findLatestActiveRelease(someAppId, someClusterName,
someNamespaceName, someNotificationMessages);
someNotificationMessages.put(someKey, someNewNotificationId);
Release shouldBeNewRelease = configServiceWithCache.findLatestActiveRelease(someAppId, someClusterName,
someNamespaceName, someNotificationMessages);
assertEquals(someRelease, release);
assertEquals(someRelease, stillOldRelease);
assertEquals(anotherRelease, shouldBeNewRelease);
verify(releaseMessageService, times(2)).findLatestReleaseMessageForMessages(Lists.newArrayList(someKey));
verify(releaseService, times(2)).findLatestActiveRelease(someAppId, someClusterName, someNamespaceName);
}
@Test
public void testFindLatestActiveReleaseWithReleaseMessageNotification() throws Exception {
long someNewNotificationId = someNotificationId + 1;
ReleaseMessage anotherReleaseMessage = mock(ReleaseMessage.class);
Release anotherRelease = mock(Release.class);
when(releaseMessageService.findLatestReleaseMessageForMessages(Lists.newArrayList(someKey))).thenReturn
(someReleaseMessage);
when(releaseService.findLatestActiveRelease(someAppId, someClusterName, someNamespaceName)).thenReturn
(someRelease);
when(someReleaseMessage.getId()).thenReturn(someNotificationId);
Release release = configServiceWithCache.findLatestActiveRelease(someAppId, someClusterName, someNamespaceName,
someNotificationMessages);
when(releaseMessageService.findLatestReleaseMessageForMessages(Lists.newArrayList(someKey))).thenReturn
(anotherReleaseMessage);
when(releaseService.findLatestActiveRelease(someAppId, someClusterName, someNamespaceName)).thenReturn
(anotherRelease);
when(anotherReleaseMessage.getMessage()).thenReturn(someKey);
when(anotherReleaseMessage.getId()).thenReturn(someNewNotificationId);
Release stillOldRelease = configServiceWithCache.findLatestActiveRelease(someAppId, someClusterName,
someNamespaceName, someNotificationMessages);
configServiceWithCache.handleMessage(anotherReleaseMessage, Topics.APOLLO_RELEASE_TOPIC);
Release shouldBeNewRelease = configServiceWithCache.findLatestActiveRelease(someAppId, someClusterName,
someNamespaceName, someNotificationMessages);
assertEquals(someRelease, release);
assertEquals(someRelease, stillOldRelease);
assertEquals(anotherRelease, shouldBeNewRelease);
verify(releaseMessageService, times(2)).findLatestReleaseMessageForMessages(Lists.newArrayList(someKey));
verify(releaseService, times(2)).findLatestActiveRelease(someAppId, someClusterName, someNamespaceName);
}
@Test
public void testFindLatestActiveReleaseWithIrrelevantMessages() throws Exception {
long someNewNotificationId = someNotificationId + 1;
ReleaseMessage anotherReleaseMessage = mock(ReleaseMessage.class);
Release anotherRelease = mock(Release.class);
String someIrrelevantKey = "someIrrelevantKey";
when(releaseMessageService.findLatestReleaseMessageForMessages(Lists.newArrayList(someKey))).thenReturn
(someReleaseMessage);
when(releaseService.findLatestActiveRelease(someAppId, someClusterName, someNamespaceName)).thenReturn
(someRelease);
when(someReleaseMessage.getId()).thenReturn(someNotificationId);
Release release = configServiceWithCache.findLatestActiveRelease(someAppId, someClusterName, someNamespaceName,
someNotificationMessages);
when(releaseMessageService.findLatestReleaseMessageForMessages(Lists.newArrayList(someKey))).thenReturn
(anotherReleaseMessage);
when(releaseService.findLatestActiveRelease(someAppId, someClusterName, someNamespaceName)).thenReturn
(anotherRelease);
when(anotherReleaseMessage.getId()).thenReturn(someNewNotificationId);
Release stillOldRelease = configServiceWithCache.findLatestActiveRelease(someAppId, someClusterName,
someNamespaceName, someNotificationMessages);
someNotificationMessages.put(someIrrelevantKey, someNewNotificationId);
Release shouldStillBeOldRelease = configServiceWithCache.findLatestActiveRelease(someAppId, someClusterName,
someNamespaceName, someNotificationMessages);
assertEquals(someRelease, release);
assertEquals(someRelease, stillOldRelease);
assertEquals(someRelease, shouldStillBeOldRelease);
verify(releaseMessageService, times(1)).findLatestReleaseMessageForMessages(Lists.newArrayList(someKey));
verify(releaseService, times(1)).findLatestActiveRelease(someAppId, someClusterName, someNamespaceName);
}
}
package com.ctrip.framework.apollo.configservice.service.config;
import static org.junit.Assert.*;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.grayReleaseRule.GrayReleaseRulesHolder;
import com.ctrip.framework.apollo.biz.service.ReleaseService;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.dto.ApolloNotificationMessages;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@RunWith(MockitoJUnitRunner.class)
public class DefaultConfigServiceTest {
private DefaultConfigService configService;
private String someClientAppId;
private String someConfigAppId;
private String someClusterName;
private String defaultClusterName;
private String defaultNamespaceName;
private String someDataCenter;
private String someClientIp;
@Mock
private ApolloNotificationMessages someNotificationMessages;
@Mock
private ReleaseService releaseService;
@Mock
private GrayReleaseRulesHolder grayReleaseRulesHolder;
@Mock
private Release someRelease;
@Before
public void setUp() throws Exception {
configService = new DefaultConfigService();
ReflectionTestUtils.setField(configService, "releaseService", releaseService);
ReflectionTestUtils.setField(configService, "grayReleaseRulesHolder", grayReleaseRulesHolder);
someClientAppId = "1234";
someConfigAppId = "1";
someClusterName = "someClusterName";
defaultClusterName = ConfigConsts.CLUSTER_NAME_DEFAULT;
defaultNamespaceName = ConfigConsts.NAMESPACE_APPLICATION;
someDataCenter = "someDC";
someClientIp = "someClientIp";
when(grayReleaseRulesHolder.findReleaseIdFromGrayReleaseRule(anyString(), anyString(),
anyString(), anyString(), anyString())).thenReturn(null);
}
@Test
public void testLoadConfig() throws Exception {
when(releaseService.findLatestActiveRelease(someConfigAppId, someClusterName, defaultNamespaceName))
.thenReturn(someRelease);
Release release = configService
.loadConfig(someClientAppId, someClientIp, someConfigAppId, someClusterName, defaultNamespaceName, someDataCenter,
someNotificationMessages);
verify(releaseService, times(1)).findLatestActiveRelease(someConfigAppId, someClusterName, defaultNamespaceName);
assertEquals(someRelease, release);
}
@Test
public void testLoadConfigWithGrayRelease() throws Exception {
Release grayRelease = mock(Release.class);
long grayReleaseId = 999;
when(grayReleaseRulesHolder.findReleaseIdFromGrayReleaseRule(someClientAppId, someClientIp,
someConfigAppId, someClusterName, defaultNamespaceName)).thenReturn(grayReleaseId);
when(releaseService.findActiveOne(grayReleaseId)).thenReturn(grayRelease);
when(releaseService.findLatestActiveRelease(someConfigAppId, someClusterName, defaultNamespaceName))
.thenReturn(someRelease);
Release release = configService
.loadConfig(someClientAppId, someClientIp, someConfigAppId, someClusterName, defaultNamespaceName, someDataCenter,
someNotificationMessages);
verify(releaseService, times(1)).findActiveOne(grayReleaseId);
verify(releaseService, never()).findLatestActiveRelease(someConfigAppId, someClusterName, defaultNamespaceName);
assertEquals(grayRelease, release);
}
@Test
public void testLoadConfigWithReleaseNotFound() throws Exception {
when(releaseService.findLatestActiveRelease(someConfigAppId, someClusterName, defaultNamespaceName))
.thenReturn(null);
Release release = configService
.loadConfig(someClientAppId, someClientIp, someConfigAppId, someClusterName, defaultNamespaceName, someDataCenter,
someNotificationMessages);
assertNull(release);
}
@Test
public void testLoadConfigWithDefaultClusterWithDataCenterRelease() throws Exception {
when(releaseService.findLatestActiveRelease(someConfigAppId, someDataCenter, defaultNamespaceName))
.thenReturn(someRelease);
Release release = configService
.loadConfig(someClientAppId, someClientIp, someConfigAppId, defaultClusterName, defaultNamespaceName, someDataCenter,
someNotificationMessages);
verify(releaseService, times(1)).findLatestActiveRelease(someConfigAppId, someDataCenter, defaultNamespaceName);
assertEquals(someRelease, release);
}
@Test
public void testLoadConfigWithDefaultClusterWithNoDataCenterRelease() throws Exception {
when(releaseService.findLatestActiveRelease(someConfigAppId, someDataCenter, defaultNamespaceName))
.thenReturn(null);
when(releaseService.findLatestActiveRelease(someConfigAppId, defaultClusterName, defaultNamespaceName))
.thenReturn(someRelease);
Release release = configService
.loadConfig(someClientAppId, someClientIp, someConfigAppId, defaultClusterName, defaultNamespaceName, someDataCenter,
someNotificationMessages);
verify(releaseService, times(1)).findLatestActiveRelease(someConfigAppId, someDataCenter, defaultNamespaceName);
verify(releaseService, times(1))
.findLatestActiveRelease(someConfigAppId, defaultClusterName, defaultNamespaceName);
assertEquals(someRelease, release);
}
}
package com.ctrip.framework.apollo.configservice.util;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.configservice.service.AppNamespaceServiceWithCache;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@RunWith(MockitoJUnitRunner.class)
public class NamespaceUtilTest {
private NamespaceUtil namespaceUtil;
@Mock
private AppNamespaceServiceWithCache appNamespaceServiceWithCache;
@Before
public void setUp() throws Exception {
namespaceUtil = new NamespaceUtil();
ReflectionTestUtils.setField(namespaceUtil, "appNamespaceServiceWithCache", appNamespaceServiceWithCache);
}
@Test
......@@ -50,4 +67,52 @@ public class NamespaceUtilTest {
assertEquals(someName, namespaceUtil.filterNamespaceName(someName));
}
@Test
public void testNormalizeNamespaceWithPrivateNamespace() throws Exception {
String someAppId = "someAppId";
String someNamespaceName = "someNamespaceName";
String someNormalizedNamespaceName = "someNormalizedNamespaceName";
AppNamespace someAppNamespace = mock(AppNamespace.class);
when(someAppNamespace.getName()).thenReturn(someNormalizedNamespaceName);
when(appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppId, someNamespaceName)).thenReturn
(someAppNamespace);
assertEquals(someNormalizedNamespaceName, namespaceUtil.normalizeNamespace(someAppId, someNamespaceName));
verify(appNamespaceServiceWithCache, times(1)).findByAppIdAndNamespace(someAppId, someNamespaceName);
verify(appNamespaceServiceWithCache, never()).findPublicNamespaceByName(someNamespaceName);
}
@Test
public void testNormalizeNamespaceWithPublicNamespace() throws Exception {
String someAppId = "someAppId";
String someNamespaceName = "someNamespaceName";
String someNormalizedNamespaceName = "someNormalizedNamespaceName";
AppNamespace someAppNamespace = mock(AppNamespace.class);
when(someAppNamespace.getName()).thenReturn(someNormalizedNamespaceName);
when(appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppId, someNamespaceName)).thenReturn(null);
when(appNamespaceServiceWithCache.findPublicNamespaceByName(someNamespaceName)).thenReturn(someAppNamespace);
assertEquals(someNormalizedNamespaceName, namespaceUtil.normalizeNamespace(someAppId, someNamespaceName));
verify(appNamespaceServiceWithCache, times(1)).findByAppIdAndNamespace(someAppId, someNamespaceName);
verify(appNamespaceServiceWithCache, times(1)).findPublicNamespaceByName(someNamespaceName);
}
@Test
public void testNormalizeNamespaceFailed() throws Exception {
String someAppId = "someAppId";
String someNamespaceName = "someNamespaceName";
when(appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppId, someNamespaceName)).thenReturn(null);
when(appNamespaceServiceWithCache.findPublicNamespaceByName(someNamespaceName)).thenReturn(null);
assertEquals(someNamespaceName, namespaceUtil.normalizeNamespace(someAppId, someNamespaceName));
verify(appNamespaceServiceWithCache, times(1)).findByAppIdAndNamespace(someAppId, someNamespaceName);
verify(appNamespaceServiceWithCache, times(1)).findPublicNamespaceByName(someNamespaceName);
}
}
package com.ctrip.framework.apollo.configservice.wrapper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.Map;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
@author Jason Song(song_s@ctrip.com)
*/
@RunWith(MockitoJUnitRunner.class)
public class CaseInsensitiveMapWrapperTest {
private CaseInsensitiveMapWrapper<Object> caseInsensitiveMapWrapper;
@Mock
private Map<String, Object> someMap;
@Before
public void setUp() throws Exception {
caseInsensitiveMapWrapper = new CaseInsensitiveMapWrapper<>(someMap);
}
@Test
public void testGet() throws Exception {
String someKey = "someKey";
Object someValue = mock(Object.class);
when(someMap.get(someKey.toLowerCase())).thenReturn(someValue);
assertEquals(someValue, caseInsensitiveMapWrapper.get(someKey));
verify(someMap, times(1)).get(someKey.toLowerCase());
}
@Test
public void testPut() throws Exception {
String someKey = "someKey";
Object someValue = mock(Object.class);
Object anotherValue = mock(Object.class);
when(someMap.put(someKey.toLowerCase(), someValue)).thenReturn(anotherValue);
assertEquals(anotherValue, caseInsensitiveMapWrapper.put(someKey, someValue));
verify(someMap, times(1)).put(someKey.toLowerCase(), someValue);
}
@Test
public void testRemove() throws Exception {
String someKey = "someKey";
Object someValue = mock(Object.class);
when(someMap.remove(someKey.toLowerCase())).thenReturn(someValue);
assertEquals(someValue, caseInsensitiveMapWrapper.remove(someKey));
verify(someMap, times(1)).remove(someKey.toLowerCase());
}
}
\ No newline at end of file
......@@ -7,4 +7,5 @@ public interface ConfigConsts {
String APOLLO_CLUSTER_KEY = "apollo.cluster";
String CONFIG_FILE_CONTENT_KEY = "content";
String NO_APPID_PLACEHOLDER = "ApolloNoAppIdPlaceHolder";
long NOTIFICATION_ID_PLACEHOLDER = -1;
}
......@@ -6,6 +6,7 @@ package com.ctrip.framework.apollo.core.dto;
public class ApolloConfigNotification {
private String namespaceName;
private long notificationId;
private volatile ApolloNotificationMessages messages;
//for json converter
public ApolloConfigNotification() {
......@@ -28,8 +29,23 @@ public class ApolloConfigNotification {
this.namespaceName = namespaceName;
}
public void setNotificationId(long notificationId) {
this.notificationId = notificationId;
public ApolloNotificationMessages getMessages() {
return messages;
}
public void setMessages(ApolloNotificationMessages messages) {
this.messages = messages;
}
public void addMessage(String key, long notificationId) {
if (this.messages == null) {
synchronized (this) {
if (this.messages == null) {
this.messages = new ApolloNotificationMessages();
}
}
}
this.messages.put(key, notificationId);
}
@Override
......
package com.ctrip.framework.apollo.core.dto;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import java.util.Map;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class ApolloNotificationMessages {
private Map<String, Long> details;
public ApolloNotificationMessages() {
this(Maps.<String, Long>newHashMap());
}
private ApolloNotificationMessages(Map<String, Long> details) {
this.details = details;
}
public void put(String key, long notificationId) {
details.put(key, notificationId);
}
public Long get(String key) {
return this.details.get(key);
}
public boolean has(String key) {
return this.details.containsKey(key);
}
public boolean isEmpty() {
return this.details.isEmpty();
}
public Map<String, Long> getDetails() {
return details;
}
public void setDetails(Map<String, Long> details) {
this.details = details;
}
public void mergeFrom(ApolloNotificationMessages source) {
if (source == null) {
return;
}
for (Map.Entry<String, Long> entry : source.getDetails().entrySet()) {
//to make sure the notification id always grows bigger
if (this.has(entry.getKey()) &&
this.get(entry.getKey()) >= entry.getValue()) {
continue;
}
this.put(entry.getKey(), entry.getValue());
}
}
public ApolloNotificationMessages clone() {
return new ApolloNotificationMessages(ImmutableMap.copyOf(this.details));
}
}
......@@ -367,7 +367,7 @@ VALUES
('eureka.service.url', 'default', 'http://apollo-configservice:8080/eureka/', 'Eureka服务Url,多个service以英文逗号分隔'),
('namespace.lock.switch', 'default', 'false', '一次发布只能有一个人修改开关'),
('item.value.length.limit', 'default', '20000', 'item value最大长度限制'),
('appnamespace.private.enable', 'default', 'false', '是否开启private namespace'),
('config-service.cache.enabled', 'default', 'false', 'ConfigService是否开启缓存,开启后能提高性能,但是会增大内存消耗!'),
('item.key.length.limit', 'default', '128', 'item key 最大长度限制');
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
......
......@@ -367,7 +367,7 @@ VALUES
('eureka.service.url', 'default', 'http://localhost:8080/eureka/', 'Eureka服务Url,多个service以英文逗号分隔'),
('namespace.lock.switch', 'default', 'false', '一次发布只能有一个人修改开关'),
('item.value.length.limit', 'default', '20000', 'item value最大长度限制'),
('appnamespace.private.enable', 'default', 'false', '是否开启private namespace'),
('config-service.cache.enabled', 'default', 'false', 'ConfigService是否开启缓存,开启后能提高性能,但是会增大内存消耗!'),
('item.key.length.limit', 'default', '128', 'item key 最大长度限制');
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册