package com.ctrip.framework.apollo.integration;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.test.util.ReflectionTestUtils;
import com.ctrip.framework.apollo.BaseIntegrationTest;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.ConfigService;
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.ApolloConfigNotification;
import com.ctrip.framework.apollo.core.utils.ClassLoaderUtil;
import com.ctrip.framework.apollo.internals.RemoteConfigLongPollService;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.SettableFuture;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class ConfigIntegrationTest extends BaseIntegrationTest {
private String someReleaseKey;
private File configDir;
private String defaultNamespace;
private String someOtherNamespace;
private RemoteConfigLongPollService remoteConfigLongPollService;
@Before
public void setUp() throws Exception {
super.setUp();
defaultNamespace = ConfigConsts.NAMESPACE_APPLICATION;
someOtherNamespace = "someOtherNamespace";
someReleaseKey = "1";
configDir = new File(ClassLoaderUtil.getClassPath() + "config-cache");
if (configDir.exists()) {
configDir.delete();
}
configDir.mkdirs();
remoteConfigLongPollService = ApolloInjector.getInstance(RemoteConfigLongPollService.class);
}
@Override
@After
public void tearDown() throws Exception {
ReflectionTestUtils.invokeMethod(remoteConfigLongPollService, "stopLongPollingRefresh");
recursiveDelete(configDir);
super.tearDown();
}
private void recursiveDelete(File file) {
if (!file.exists()) {
return;
}
if (file.isDirectory()) {
for (File f : file.listFiles()) {
recursiveDelete(f);
}
}
try {
Files.deleteIfExists(file.toPath());
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void testGetConfigWithNoLocalFileButWithRemoteConfig() throws Exception {
String someKey = "someKey";
String someValue = "someValue";
String someNonExistedKey = "someNonExistedKey";
String someDefaultValue = "someDefaultValue";
ApolloConfig apolloConfig = assembleApolloConfig(ImmutableMap.of(someKey, someValue));
ContextHandler handler = mockConfigServerHandler(HttpServletResponse.SC_OK, apolloConfig);
startServerWithHandlers(handler);
Config config = ConfigService.getAppConfig();
assertEquals(someValue, config.getProperty(someKey, null));
assertEquals(someDefaultValue, config.getProperty(someNonExistedKey, someDefaultValue));
}
@Test
public void testGetConfigWithLocalFileAndWithRemoteConfig() throws Exception {
String someKey = "someKey";
String someValue = "someValue";
String anotherValue = "anotherValue";
Properties properties = new Properties();
properties.put(someKey, someValue);
createLocalCachePropertyFile(properties);
ApolloConfig apolloConfig = assembleApolloConfig(ImmutableMap.of(someKey, anotherValue));
ContextHandler handler = mockConfigServerHandler(HttpServletResponse.SC_OK, apolloConfig);
startServerWithHandlers(handler);
Config config = ConfigService.getAppConfig();
assertEquals(anotherValue, config.getProperty(someKey, null));
}
@Test
public void testGetConfigWithNoLocalFileAndRemoteConfigError() throws Exception {
ContextHandler handler =
mockConfigServerHandler(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null);
startServerWithHandlers(handler);
Config config = ConfigService.getAppConfig();
String someKey = "someKey";
String someDefaultValue = "defaultValue" + Math.random();
assertEquals(someDefaultValue, config.getProperty(someKey, someDefaultValue));
}
@Test
public void testGetConfigWithLocalFileAndRemoteConfigError() throws Exception {
String someKey = "someKey";
String someValue = "someValue";
Properties properties = new Properties();
properties.put(someKey, someValue);
createLocalCachePropertyFile(properties);
ContextHandler handler =
mockConfigServerHandler(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null);
startServerWithHandlers(handler);
Config config = ConfigService.getAppConfig();
assertEquals(someValue, config.getProperty(someKey, null));
}
@Test
public void testGetConfigWithNoLocalFileAndRemoteMetaServiceRetry() throws Exception {
String someKey = "someKey";
String someValue = "someValue";
ApolloConfig apolloConfig = assembleApolloConfig(ImmutableMap.of(someKey, someValue));
ContextHandler configHandler = mockConfigServerHandler(HttpServletResponse.SC_OK, apolloConfig);
boolean failAtFirstTime = true;
ContextHandler metaServerHandler = mockMetaServerHandler(failAtFirstTime);
startServerWithHandlers(metaServerHandler, configHandler);
Config config = ConfigService.getAppConfig();
assertEquals(someValue, config.getProperty(someKey, null));
}
@Test
public void testGetConfigWithNoLocalFileAndRemoteConfigServiceRetry() throws Exception {
String someKey = "someKey";
String someValue = "someValue";
ApolloConfig apolloConfig = assembleApolloConfig(ImmutableMap.of(someKey, someValue));
boolean failedAtFirstTime = true;
ContextHandler handler =
mockConfigServerHandler(HttpServletResponse.SC_OK, apolloConfig, failedAtFirstTime);
startServerWithHandlers(handler);
Config config = ConfigService.getAppConfig();
assertEquals(someValue, config.getProperty(someKey, null));
}
@Test
public void testRefreshConfig() throws Exception {
final String someKey = "someKey";
final String someValue = "someValue";
final String anotherValue = "anotherValue";
int someRefreshInterval = 500;
TimeUnit someRefreshTimeUnit = TimeUnit.MILLISECONDS;
setRefreshInterval(someRefreshInterval);
setRefreshTimeUnit(someRefreshTimeUnit);
Map<String, String> configurations = Maps.newHashMap();
configurations.put(someKey, someValue);
ApolloConfig apolloConfig = assembleApolloConfig(configurations);
ContextHandler handler = mockConfigServerHandler(HttpServletResponse.SC_OK, apolloConfig);
startServerWithHandlers(handler);
Config config = ConfigService.getAppConfig();
final List<ConfigChangeEvent> changeEvents = Lists.newArrayList();
final SettableFuture<Boolean> refreshFinished = SettableFuture.create();
config.addChangeListener(new ConfigChangeListener() {
AtomicInteger counter = new AtomicInteger(0);
@Override
public void onChange(ConfigChangeEvent changeEvent) {
//only need to assert once
if (counter.incrementAndGet() > 1) {
return;
}
assertEquals(1, changeEvent.changedKeys().size());
assertTrue(changeEvent.isChanged(someKey));
assertEquals(someValue, changeEvent.getChange(someKey).getOldValue());
assertEquals(anotherValue, changeEvent.getChange(someKey).getNewValue());
// if there is any assertion failed above, this line won't be executed
changeEvents.add(changeEvent);
refreshFinished.set(true);
}
});
apolloConfig.getConfigurations().put(someKey, anotherValue);
refreshFinished.get(someRefreshInterval * 5, someRefreshTimeUnit);
assertThat(
"Change event's size should equal to one or there must be some assertion failed in change listener",
1, equalTo(changeEvents.size()));
assertEquals(anotherValue, config.getProperty(someKey, null));
}
@Test
public void testLongPollRefresh() throws Exception {
final String someKey = "someKey";
final String someValue = "someValue";
final String anotherValue = "anotherValue";
long someNotificationId = 1;
long pollTimeoutInMS = 50;
Map<String, String> configurations = Maps.newHashMap();
configurations.put(someKey, someValue);
ApolloConfig apolloConfig = assembleApolloConfig(configurations);
ContextHandler configHandler = mockConfigServerHandler(HttpServletResponse.SC_OK, apolloConfig);
ContextHandler pollHandler =
mockPollNotificationHandler(pollTimeoutInMS, HttpServletResponse.SC_OK,
Lists.newArrayList(
new ApolloConfigNotification(apolloConfig.getNamespaceName(), someNotificationId)),
false);
startServerWithHandlers(configHandler, pollHandler);
Config config = ConfigService.getAppConfig();
assertEquals(someValue, config.getProperty(someKey, null));
final SettableFuture<Boolean> longPollFinished = SettableFuture.create();
config.addChangeListener(new ConfigChangeListener() {
@Override
public void onChange(ConfigChangeEvent changeEvent) {
longPollFinished.set(true);
}
});
apolloConfig.getConfigurations().put(someKey, anotherValue);
longPollFinished.get(pollTimeoutInMS * 20, TimeUnit.MILLISECONDS);
assertEquals(anotherValue, config.getProperty(someKey, null));
}
@Test
public void testLongPollRefreshWithMultipleNamespacesAndOnlyOneNamespaceNotified() throws Exception {
final String someKey = "someKey";
final String someValue = "someValue";
final String anotherValue = "anotherValue";
long someNotificationId = 1;
long pollTimeoutInMS = 50;
Map<String, String> configurations = Maps.newHashMap();
configurations.put(someKey, someValue);
ApolloConfig apolloConfig = assembleApolloConfig(configurations);
ContextHandler configHandler = mockConfigServerHandler(HttpServletResponse.SC_OK, apolloConfig);
ContextHandler pollHandler =
mockPollNotificationHandler(pollTimeoutInMS, HttpServletResponse.SC_OK,
Lists.newArrayList(
new ApolloConfigNotification(apolloConfig.getNamespaceName(), someNotificationId)),
false);
startServerWithHandlers(configHandler, pollHandler);
Config someOtherConfig = ConfigService.getConfig(someOtherNamespace);
Config config = ConfigService.getAppConfig();
assertEquals(someValue, config.getProperty(someKey, null));
assertEquals(someValue, someOtherConfig.getProperty(someKey, null));
final SettableFuture<Boolean> longPollFinished = SettableFuture.create();
config.addChangeListener(new ConfigChangeListener() {
@Override
public void onChange(ConfigChangeEvent changeEvent) {
longPollFinished.set(true);
}
});
apolloConfig.getConfigurations().put(someKey, anotherValue);
longPollFinished.get(5000, TimeUnit.MILLISECONDS);
assertEquals(anotherValue, config.getProperty(someKey, null));
TimeUnit.MILLISECONDS.sleep(pollTimeoutInMS * 10);
assertEquals(someValue, someOtherConfig.getProperty(someKey, null));
}
@Test
public void testLongPollRefreshWithMultipleNamespacesAndMultipleNamespaceNotified() throws Exception {
final String someKey = "someKey";
final String someValue = "someValue";
final String anotherValue = "anotherValue";
long someNotificationId = 1;
long pollTimeoutInMS = 50;
Map<String, String> configurations = Maps.newHashMap();
configurations.put(someKey, someValue);
ApolloConfig apolloConfig = assembleApolloConfig(configurations);
ContextHandler configHandler = mockConfigServerHandler(HttpServletResponse.SC_OK, apolloConfig);
ContextHandler pollHandler =
mockPollNotificationHandler(pollTimeoutInMS, HttpServletResponse.SC_OK,
Lists.newArrayList(
new ApolloConfigNotification(apolloConfig.getNamespaceName(), someNotificationId),
new ApolloConfigNotification(someOtherNamespace, someNotificationId)),
false);
startServerWithHandlers(configHandler, pollHandler);
Config config = ConfigService.getAppConfig();
Config someOtherConfig = ConfigService.getConfig(someOtherNamespace);
assertEquals(someValue, config.getProperty(someKey, null));
assertEquals(someValue, someOtherConfig.getProperty(someKey, null));
final SettableFuture<Boolean> longPollFinished = SettableFuture.create();
final SettableFuture<Boolean> someOtherNamespacelongPollFinished = SettableFuture.create();
config.addChangeListener(new ConfigChangeListener() {
@Override
public void onChange(ConfigChangeEvent changeEvent) {
longPollFinished.set(true);
}
});
someOtherConfig.addChangeListener(new ConfigChangeListener() {
@Override
public void onChange(ConfigChangeEvent changeEvent) {
someOtherNamespacelongPollFinished.set(true);
}
});
apolloConfig.getConfigurations().put(someKey, anotherValue);
longPollFinished.get(5000, TimeUnit.MILLISECONDS);
someOtherNamespacelongPollFinished.get(5000, TimeUnit.MILLISECONDS);
assertEquals(anotherValue, config.getProperty(someKey, null));
assertEquals(anotherValue, someOtherConfig.getProperty(someKey, null));
}
private ContextHandler mockPollNotificationHandler(final long pollResultTimeOutInMS,
final int statusCode,
final List<ApolloConfigNotification> result,
final boolean failedAtFirstTime) {
ContextHandler context = new ContextHandler("/notifications/v2");
context.setHandler(new AbstractHandler() {
AtomicInteger counter = new AtomicInteger(0);
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
if (failedAtFirstTime && counter.incrementAndGet() == 1) {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
baseRequest.setHandled(true);
return;
}
try {
TimeUnit.MILLISECONDS.sleep(pollResultTimeOutInMS);
} catch (InterruptedException e) {
}
response.setContentType("application/json;charset=UTF-8");
response.setStatus(statusCode);
response.getWriter().println(gson.toJson(result));
baseRequest.setHandled(true);
}
});
return context;
}
private ContextHandler mockConfigServerHandler(final int statusCode, final ApolloConfig result,
final boolean failedAtFirstTime) {
ContextHandler context = new ContextHandler("/configs/*");
context.setHandler(new AbstractHandler() {
AtomicInteger counter = new AtomicInteger(0);
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
if (failedAtFirstTime && counter.incrementAndGet() == 1) {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
baseRequest.setHandled(true);
return;
}
response.setContentType("application/json;charset=UTF-8");
response.setStatus(statusCode);
response.getWriter().println(gson.toJson(result));
baseRequest.setHandled(true);
}
});
return context;
}
private ContextHandler mockConfigServerHandler(int statusCode, ApolloConfig result) {
return mockConfigServerHandler(statusCode, result, false);
}
private ApolloConfig assembleApolloConfig(Map<String, String> configurations) {
ApolloConfig apolloConfig =
new ApolloConfig(someAppId, someClusterName, defaultNamespace, someReleaseKey);
apolloConfig.setConfigurations(configurations);
return apolloConfig;
}
private File createLocalCachePropertyFile(Properties properties) throws IOException {
File file = new File(configDir, assembleLocalCacheFileName());
FileOutputStream in = null;
try {
in = new FileOutputStream(file);
properties.store(in, "Persisted by ConfigIntegrationTest");
} finally {
if (in != null) {
in.close();
}
}
return file;
}
private String assembleLocalCacheFileName() {
return String.format("%s.properties", Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR)
.join(someAppId, someClusterName, defaultNamespace));
}
}