Commit a76372f0 by Jason Song

add public config support

parent 12a89acc
*.class
.DS_Store
# Mobile Tools for Java (J2ME)
.mtj.tmp/
......
package com.ctrip.apollo.biz.message;
import com.dianping.cat.Cat;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class RedisMessageSender implements MessageSender {
private static final Logger logger = LoggerFactory.getLogger(RedisMessageSender.class);
private RedisTemplate<String, String> redisTemplate;
public RedisMessageSender(
......@@ -15,12 +22,16 @@ public class RedisMessageSender implements MessageSender {
@Override
public void sendMessage(String message, String channel) {
logger.info("Sending message {} to channel {}", message, channel);
Transaction transaction = Cat.newTransaction("Apollo.AdminService", "RedisMessageSender");
try {
redisTemplate.convertAndSend(channel, message);
transaction.setStatus(Message.SUCCESS);
} catch (Throwable ex) {
logger.error("Sending message to redis failed", ex);
transaction.setStatus(ex);
} finally {
transaction.complete();
}
}
}
......@@ -8,4 +8,6 @@ public interface AppNamespaceRepository extends PagingAndSortingRepository<AppNa
AppNamespace findByAppIdAndName(String appId, String namespaceName);
AppNamespace findByName(String namespaceName);
}
package com.ctrip.apollo.biz.service;
import com.google.common.base.Preconditions;
import java.util.Objects;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -27,6 +29,11 @@ public class AppNamespaceService {
return Objects.isNull(appNamespaceRepository.findByAppIdAndName(appId, namespaceName));
}
public AppNamespace findByNamespaceName(String namespaceName) {
Preconditions.checkArgument(namespaceName != null, "Namespace must not be null");
return appNamespaceRepository.findByName(namespaceName);
}
@Transactional
public void createDefaultAppNamespace(String appId, String createBy) {
if (!isAppNamespaceNameUnique(appId, appId)) {
......
......@@ -134,6 +134,7 @@ public class RemoteConfigRepository extends AbstractConfigRepository {
private ApolloConfig loadApolloConfig() {
String appId = m_configUtil.getAppId();
String cluster = m_configUtil.getCluster();
String dataCenter = m_configUtil.getDataCenter();
Cat.logEvent("Apollo.Client.ConfigInfo",
String.format("%s-%s-%s", appId, cluster, m_namespace));
int maxRetries = 2;
......@@ -147,7 +148,7 @@ public class RemoteConfigRepository extends AbstractConfigRepository {
for (ServiceDTO configService : randomConfigServices) {
String url =
assembleQueryConfigUrl(configService.getHomepageUrl(), appId, cluster, m_namespace,
m_configCache.get());
dataCenter, m_configCache.get());
logger.debug("Loading config from {}", url);
HttpRequest request = new HttpRequest(url);
......@@ -191,21 +192,30 @@ public class RemoteConfigRepository extends AbstractConfigRepository {
}
private String assembleQueryConfigUrl(String uri, String appId, String cluster, String namespace,
ApolloConfig previousConfig) {
String dataCenter, ApolloConfig previousConfig) {
Escaper escaper = UrlEscapers.urlPathSegmentEscaper();
String path = "configs/%s/%s";
List<String> params = Lists.newArrayList(escaper.escape(appId), escaper.escape(cluster));
List<String> pathParams = Lists.newArrayList(escaper.escape(appId), escaper.escape(cluster));
Map<String, String> queryParams = Maps.newHashMap();
if (!Strings.isNullOrEmpty(namespace)) {
path = path + "/%s";
params.add(escaper.escape(namespace));
pathParams.add(escaper.escape(namespace));
}
if (previousConfig != null) {
path = path + "?releaseId=%s";
params.add(escaper.escape(String.valueOf(previousConfig.getReleaseId())));
queryParams.put("releaseId", escaper.escape(String.valueOf(previousConfig.getReleaseId())));
}
if (!Strings.isNullOrEmpty(dataCenter)) {
queryParams.put("dataCenter", escaper.escape(dataCenter));
}
String pathExpanded = String.format(path, params.toArray());
String pathExpanded = String.format(path, pathParams.toArray());
if (!queryParams.isEmpty()) {
pathExpanded += "?" + Joiner.on("&").withKeyValueSeparator("=").join(queryParams);
}
if (!uri.endsWith("/")) {
uri += "/";
}
......@@ -215,18 +225,19 @@ public class RemoteConfigRepository extends AbstractConfigRepository {
private void scheduleLongPollingRefresh() {
final String appId = m_configUtil.getAppId();
final String cluster = m_configUtil.getCluster();
final String dataCenter = m_configUtil.getDataCenter();
final ExecutorService longPollingService =
Executors.newFixedThreadPool(2,
ApolloThreadFactory.create("RemoteConfigRepository-LongPolling", true));
longPollingService.submit(new Runnable() {
@Override
public void run() {
doLongPollingRefresh(appId, cluster, longPollingService);
doLongPollingRefresh(appId, cluster, dataCenter, longPollingService);
}
});
}
private void doLongPollingRefresh(String appId, String cluster,
private void doLongPollingRefresh(String appId, String cluster, String dataCenter,
ExecutorService longPollingService) {
final Random random = new Random();
ServiceDTO lastServiceDto = null;
......@@ -240,7 +251,7 @@ public class RemoteConfigRepository extends AbstractConfigRepository {
String url =
assembleLongPollRefreshUrl(lastServiceDto.getHomepageUrl(), appId, cluster,
m_namespace, m_configCache.get());
m_namespace, dataCenter, m_configCache.get());
logger.debug("Long polling from {}", url);
HttpRequest request = new HttpRequest(url);
......@@ -286,7 +297,7 @@ public class RemoteConfigRepository extends AbstractConfigRepository {
}
private String assembleLongPollRefreshUrl(String uri, String appId, String cluster,
String namespace,
String namespace, String dataCenter,
ApolloConfig previousConfig) {
Escaper escaper = UrlEscapers.urlPathSegmentEscaper();
Map<String, String> queryParams = Maps.newHashMap();
......@@ -296,6 +307,9 @@ public class RemoteConfigRepository extends AbstractConfigRepository {
if (!Strings.isNullOrEmpty(namespace)) {
queryParams.put("namespace", escaper.escape(namespace));
}
if (!Strings.isNullOrEmpty(dataCenter)) {
queryParams.put("dataCenter", escaper.escape(dataCenter));
}
if (previousConfig != null) {
queryParams.put("releaseId", escaper.escape(previousConfig.getReleaseId()));
}
......
......@@ -18,8 +18,8 @@ public class ConfigChangeEvent {
*/
public ConfigChangeEvent(String namespace,
Map<String, ConfigChange> changes) {
this.m_namespace = namespace;
this.m_changes = changes;
m_namespace = namespace;
m_changes = changes;
}
/**
......
......@@ -35,6 +35,14 @@ public class ConfigUtil {
}
/**
* Get the data center info for the current application.
* @return the current data center, null if there is no such info.
*/
public String getDataCenter() {
return Foundation.server().getDataCenter();
}
/**
* Get the cluster name for the current application.
* @return the cluster name, or "default" if not specified
*/
......
......@@ -42,6 +42,7 @@ public abstract class BaseIntegrationTest extends ComponentTestCase {
private static final String configServiceURL = "http://localhost:" + PORT;
protected static String someAppId;
protected static String someClusterName;
protected static String someDataCenter;
protected static int refreshInterval;
protected static TimeUnit refreshTimeUnit;
private Server server;
......@@ -59,6 +60,7 @@ public abstract class BaseIntegrationTest extends ComponentTestCase {
super.setUp();
someAppId = "1003171";
someClusterName = "someClusterName";
someDataCenter = "someDC";
refreshInterval = 5;
refreshTimeUnit = TimeUnit.MINUTES;
......@@ -160,6 +162,11 @@ public abstract class BaseIntegrationTest extends ComponentTestCase {
public Env getApolloEnv() {
return Env.LOCAL;
}
@Override
public String getDataCenter() {
return someDataCenter;
}
}
/**
......
......@@ -241,8 +241,7 @@ public class ConfigIntegrationTest extends BaseIntegrationTest {
ContextHandler configHandler = mockConfigServerHandler(HttpServletResponse.SC_OK, apolloConfig);
ContextHandler pollHandler =
mockPollNotificationHandler(pollTimeoutInMS, HttpServletResponse.SC_OK,
new ApolloConfigNotification(apolloConfig.getAppId(), apolloConfig.getCluster(),
apolloConfig.getNamespace()), false);
new ApolloConfigNotification(apolloConfig.getNamespace()), false);
startServerWithHandlers(configHandler, pollHandler);
......
......@@ -186,6 +186,11 @@ public class RemoteConfigRepositoryTest extends ComponentTestCase {
public String getCluster() {
return "someCluster";
}
@Override
public String getDataCenter() {
return null;
}
}
public static class MockConfigServiceLocator extends ConfigServiceLocator {
......
......@@ -8,7 +8,9 @@ import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.ctrip.apollo.biz.entity.AppNamespace;
import com.ctrip.apollo.biz.entity.Release;
import com.ctrip.apollo.biz.service.AppNamespaceService;
import com.ctrip.apollo.biz.service.ConfigService;
import com.ctrip.apollo.core.ConfigConsts;
import com.ctrip.apollo.core.dto.ApolloConfig;
......@@ -37,6 +39,8 @@ import javax.servlet.http.HttpServletResponse;
public class ConfigController {
@Autowired
private ConfigService configService;
@Autowired
private AppNamespaceService appNamespaceService;
private Gson gson = new Gson();
private Type configurationTypeReference =
......@@ -45,17 +49,17 @@ public class ConfigController {
@RequestMapping(value = "/{appId}/{clusterName}", method = RequestMethod.GET)
public ApolloConfig queryConfig(@PathVariable String appId, @PathVariable String clusterName,
@RequestParam(value = "datacenter", required = false) String datacenter,
@RequestParam(value = "dataCenter", required = false) String dataCenter,
@RequestParam(value = "releaseId", defaultValue = "-1") String clientSideReleaseId,
HttpServletResponse response) throws IOException {
return this.queryConfig(appId, clusterName, ConfigConsts.NAMESPACE_DEFAULT, datacenter,
return this.queryConfig(appId, clusterName, ConfigConsts.NAMESPACE_DEFAULT, dataCenter,
clientSideReleaseId, response);
}
@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 = "dataCenter", required = false) String dataCenter,
@RequestParam(value = "releaseId", defaultValue = "-1") String clientSideReleaseId,
HttpServletResponse response) throws IOException {
List<Release> releases = Lists.newLinkedList();
......@@ -66,13 +70,12 @@ public class ConfigController {
releases.add(currentAppRelease);
}
//if namespace is not appId itself, should check if it has its own configurations
//if namespace is not 'application', should check if it's a public configuration
if (!Objects.equals(ConfigConsts.NAMESPACE_DEFAULT, namespace)) {
//TODO find id for this particular namespace, if not equal to current app id, then do more
if (!Objects.isNull(datacenter)) {
//TODO load newAppId+datacenter+namespace configurations
Release publicRelease = this.findPublicConfig(appId, namespace, dataCenter);
if (!Objects.isNull(publicRelease)) {
releases.add(publicRelease);
}
//TODO if load from DC failed, then load newAppId+defaultCluster+namespace configurations
}
if (releases.isEmpty()) {
......@@ -81,7 +84,7 @@ public class ConfigController {
"Could not load configurations with appId: %s, clusterName: %s, namespace: %s",
appId, clusterName, namespace));
Cat.logEvent("Apollo.Config.NotFound",
assembleKey(appId, clusterName, namespace, datacenter));
assembleKey(appId, clusterName, namespace, dataCenter));
return null;
}
......@@ -92,18 +95,47 @@ public class ConfigController {
// Client side configuration is the same with server side, return 304
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
Cat.logEvent("Apollo.Config.NotModified",
assembleKey(appId, clusterName, namespace, datacenter));
assembleKey(appId, clusterName, namespace, dataCenter));
return null;
}
ApolloConfig apolloConfig = new ApolloConfig(appId, clusterName, namespace, mergedReleaseId);
apolloConfig.setConfigurations(mergeReleaseConfigurations(releases));
Cat.logEvent("Apollo.Config.Found", assembleKey(appId, clusterName, namespace, datacenter));
Cat.logEvent("Apollo.Config.Found", assembleKey(appId, clusterName, namespace, dataCenter));
return apolloConfig;
}
/**
* @param applicationId the application which uses public config
* @param namespace the namespace
* @param dataCenter the datacenter
*/
private Release findPublicConfig(String applicationId, String namespace, String dataCenter) {
AppNamespace appNamespace = appNamespaceService.findByNamespaceName(namespace);
//check whether the namespace's appId equals to current one
if (Objects.isNull(appNamespace) || Objects.equals(applicationId, appNamespace.getAppId())) {
return null;
}
String publicConfigAppId = appNamespace.getAppId();
//try to load via data center
if (!Objects.isNull(dataCenter)) {
Release dataCenterRelease =
configService.findRelease(publicConfigAppId, dataCenter, namespace);
if (!Objects.isNull(dataCenterRelease)) {
return dataCenterRelease;
}
}
//fallback to default release
return configService
.findRelease(publicConfigAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, namespace);
}
/**
* Merge configurations of releases.
* Release in lower index override those in higher index
*/
......
......@@ -6,14 +6,17 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.ctrip.apollo.biz.entity.AppNamespace;
import com.ctrip.apollo.biz.message.MessageListener;
import com.ctrip.apollo.biz.message.Topics;
import com.ctrip.apollo.biz.service.AppNamespaceService;
import com.ctrip.apollo.core.ConfigConsts;
import com.ctrip.apollo.core.dto.ApolloConfigNotification;
import com.dianping.cat.Cat;
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.web.bind.annotation.RequestMapping;
......@@ -40,23 +43,22 @@ public class NotificationController implements MessageListener {
deferredResults =
Multimaps.synchronizedSetMultimap(HashMultimap.create());
@Autowired
private AppNamespaceService appNamespaceService;
@RequestMapping(method = RequestMethod.GET)
public DeferredResult<ResponseEntity<ApolloConfigNotification>> pollNotification(
@RequestParam(value = "appId") String appId,
@RequestParam(value = "cluster") String cluster,
@RequestParam(value = "namespace", defaultValue = ConfigConsts.NAMESPACE_DEFAULT) String namespace,
@RequestParam(value = "datacenter", required = false) String datacenter,
@RequestParam(value = "dataCenter", required = false) String dataCenter,
@RequestParam(value = "releaseId", defaultValue = "-1") String clientSideReleaseId,
HttpServletResponse response) {
List<String> watchedKeys = Lists.newArrayList(assembleKey(appId, cluster, namespace));
//Listen more namespaces, since it's not the default namespace
//Listen on more namespaces, since it's not the default namespace
if (!Objects.equals(ConfigConsts.NAMESPACE_DEFAULT, namespace)) {
//TODO find id for this particular namespace, if not equal to current app id, then do more
if (!Objects.isNull(datacenter)) {
//TODO add newAppId+datacenter+namespace to listened keys
}
//TODO add newAppId+defaultCluster+namespace to listened keys
watchedKeys.addAll(this.findPublicConfigWatchKey(appId, namespace, dataCenter));
}
ResponseEntity<ApolloConfigNotification> body = new ResponseEntity<>(
......@@ -77,7 +79,7 @@ public class NotificationController implements MessageListener {
});
logger.info("Listening {} from appId: {}, cluster: {}, namespace: {}, datacenter: {}",
watchedKeys, appId, cluster, namespace, datacenter);
watchedKeys, appId, cluster, namespace, dataCenter);
return deferredResult;
}
......@@ -85,6 +87,30 @@ public class NotificationController implements MessageListener {
return String.format("%s-%s-%s", appId, cluster, namespace);
}
private List<String> findPublicConfigWatchKey(String applicationId, String namespace,
String dataCenter) {
List<String> publicWatchedKeys = Lists.newArrayList();
AppNamespace appNamespace = appNamespaceService.findByNamespaceName(namespace);
//check whether the namespace's appId equals to current one
if (Objects.isNull(appNamespace) || Objects.equals(applicationId, appNamespace.getAppId())) {
return publicWatchedKeys;
}
String publicConfigAppId = appNamespace.getAppId();
//watch data center config change
if (!Objects.isNull(dataCenter)) {
publicWatchedKeys.add(assembleKey(publicConfigAppId, dataCenter, namespace));
}
//watch default cluster config change
publicWatchedKeys
.add(assembleKey(publicConfigAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, namespace));
return publicWatchedKeys;
}
@Override
public void handleMessage(String message, String channel) {
logger.info("message received - channel: {}, message: {}", channel, message);
......@@ -101,8 +127,7 @@ public class NotificationController implements MessageListener {
ResponseEntity<ApolloConfigNotification> notification =
new ResponseEntity<>(
new ApolloConfigNotification(keys[0], keys[1], keys[2]),
HttpStatus.OK);
new ApolloConfigNotification(keys[2]), HttpStatus.OK);
Collection<DeferredResult<ResponseEntity<ApolloConfigNotification>>>
results = deferredResults.get(message);
......
package com.ctrip.apollo.configservice.controller;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.ctrip.apollo.biz.entity.AppNamespace;
import com.ctrip.apollo.biz.message.Topics;
import com.ctrip.apollo.biz.service.AppNamespaceService;
import com.ctrip.apollo.core.ConfigConsts;
import com.ctrip.apollo.core.dto.ApolloConfigNotification;
import org.junit.Before;
......@@ -15,10 +19,13 @@ import org.springframework.http.ResponseEntity;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.context.request.async.DeferredResult;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
/**
* @author Jason Song(song_s@ctrip.com)
......@@ -28,19 +35,26 @@ public class NotificationControllerTest {
private NotificationController controller;
private String someAppId;
private String someCluster;
private String someNamespace;
private String defaultNamespace;
private String somePublicNamespace;
private String someDataCenter;
private String someReleaseId;
@Mock
private HttpServletResponse response;
private Multimap<String, DeferredResult<ResponseEntity<ApolloConfigNotification>>> deferredResults;
@Mock
private AppNamespaceService appNamespaceService;
private Multimap<String, DeferredResult<ResponseEntity<ApolloConfigNotification>>>
deferredResults;
@Before
public void setUp() throws Exception {
controller = new NotificationController();
ReflectionTestUtils.setField(controller, "appNamespaceService", appNamespaceService);
someAppId = "someAppId";
someCluster = "someCluster";
someNamespace = "someNamespace";
defaultNamespace = ConfigConsts.NAMESPACE_DEFAULT;
somePublicNamespace = "somePublicNamespace";
someDataCenter = "someDC";
someReleaseId = "someRelease";
......@@ -51,29 +65,78 @@ public class NotificationControllerTest {
@Test
public void testPollNotificationWithDefaultNamespace() throws Exception {
someNamespace = someAppId; //default namespace
DeferredResult<ResponseEntity<ApolloConfigNotification>>
deferredResult = controller
.pollNotification(someAppId, someCluster, someNamespace, someDataCenter, someReleaseId,
.pollNotification(someAppId, someCluster, defaultNamespace, someDataCenter, someReleaseId,
response);
String key = String.format("%s-%s-%s", someAppId, someCluster, someNamespace);
String key = String.format("%s-%s-%s", someAppId, someCluster, defaultNamespace);
assertEquals(1, deferredResults.size());
assertTrue(deferredResults.get(key).contains(deferredResult));
}
@Test
public void testPollNotificationWithPublicNamespace() throws Exception {
String somePublicAppId = "somePublicAppId";
AppNamespace somePublicAppNamespace =
assmbleAppNamespace(somePublicAppId, somePublicNamespace);
when(appNamespaceService.findByNamespaceName(somePublicNamespace))
.thenReturn(somePublicAppNamespace);
DeferredResult<ResponseEntity<ApolloConfigNotification>>
deferredResult = controller
.pollNotification(someAppId, someCluster, somePublicNamespace, someDataCenter,
someReleaseId,
response);
List<String> publicClusters =
Lists.newArrayList(someDataCenter, ConfigConsts.CLUSTER_NAME_DEFAULT);
assertEquals(3, deferredResults.size());
String key = String.format("%s-%s-%s", someAppId, someCluster, somePublicNamespace);
assertTrue(deferredResults.get(key).contains(deferredResult));
for (String cluster : publicClusters) {
String publicKey = String.format("%s-%s-%s", somePublicAppId, cluster, somePublicNamespace);
assertTrue(deferredResults.get(publicKey).contains(deferredResult));
}
}
@Test
public void testPollNotificationWithDefaultNamespaceAndHandleMessage() throws Exception {
someNamespace = someAppId; //default namespace
DeferredResult<ResponseEntity<ApolloConfigNotification>>
deferredResult = controller
.pollNotification(someAppId, someCluster, defaultNamespace, someDataCenter, someReleaseId,
response);
String key = String.format("%s-%s-%s", someAppId, someCluster, defaultNamespace);
controller.handleMessage(key, Topics.APOLLO_RELEASE_TOPIC);
ResponseEntity<ApolloConfigNotification> response =
(ResponseEntity<ApolloConfigNotification>) deferredResult.getResult();
ApolloConfigNotification notification = response.getBody();
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(defaultNamespace, notification.getNamespace());
}
@Test
public void testPollNotificationWithPublicNamespaceAndHandleMessage() throws Exception {
String somePublicAppId = "somePublicAppId";
AppNamespace somePublicAppNamespace =
assmbleAppNamespace(somePublicAppId, somePublicNamespace);
when(appNamespaceService.findByNamespaceName(somePublicNamespace))
.thenReturn(somePublicAppNamespace);
DeferredResult<ResponseEntity<ApolloConfigNotification>>
deferredResult = controller
.pollNotification(someAppId, someCluster, someNamespace, someDataCenter, someReleaseId,
.pollNotification(someAppId, someCluster, somePublicNamespace, someDataCenter, someReleaseId,
response);
String key = String.format("%s-%s-%s", someAppId, someCluster, someNamespace);
String key = String.format("%s-%s-%s", somePublicAppId, someDataCenter, somePublicNamespace);
controller.handleMessage(key, Topics.APOLLO_RELEASE_TOPIC);
......@@ -82,8 +145,14 @@ public class NotificationControllerTest {
ApolloConfigNotification notification = response.getBody();
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(someAppId, notification.getAppId());
assertEquals(someCluster, notification.getCluster());
assertEquals(someNamespace, notification.getNamespace());
assertEquals(somePublicNamespace, notification.getNamespace());
}
private AppNamespace assmbleAppNamespace(String appId, String namespace) {
AppNamespace appNamespace = new AppNamespace();
appNamespace.setAppId(appId);
appNamespace.setName(namespace);
return appNamespace;
}
}
......@@ -19,12 +19,18 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest
private String someAppId;
private String someCluster;
private String someNamespace;
private String somePublicNamespace;
private String someDC;
private String someDefaultCluster;
@Before
public void setUp() throws Exception {
someAppId = "someAppId";
someCluster = "someCluster";
someNamespace = "someNamespace";
somePublicNamespace = "somePublicNamespace";
someDC = "someDC";
someDefaultCluster = ConfigConsts.CLUSTER_NAME_DEFAULT;
}
@Test
......@@ -83,4 +89,74 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest
assertEquals(HttpStatus.NOT_MODIFIED, response.getStatusCode());
}
@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 testQueryPublicConfigWithDataCenterFoundAndNoOverride() throws Exception {
ResponseEntity<ApolloConfig> response = restTemplate
.getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}?dataCenter={dateCenter}", ApolloConfig.class,
getHostUrl(), someAppId, someCluster, somePublicNamespace, someDC);
ApolloConfig result = response.getBody();
assertEquals("993", result.getReleaseId());
assertEquals(someAppId, result.getAppId());
assertEquals(someCluster, result.getCluster());
assertEquals(somePublicNamespace, result.getNamespace());
assertEquals("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)
public void testQueryPublicConfigWithDataCenterFoundAndOverride() throws Exception {
ResponseEntity<ApolloConfig> response = restTemplate
.getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}?dataCenter={dateCenter}", ApolloConfig.class,
getHostUrl(), someAppId, someDefaultCluster, somePublicNamespace, someDC);
ApolloConfig result = response.getBody();
assertEquals("994|993", result.getReleaseId());
assertEquals(someAppId, result.getAppId());
assertEquals(someDefaultCluster, result.getCluster());
assertEquals(somePublicNamespace, result.getNamespace());
assertEquals("override-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)
public void testQueryPublicConfigWithDataCenterNotFoundAndNoOverride() throws Exception {
String someDCNotFound = "someDCNotFound";
ResponseEntity<ApolloConfig> response = restTemplate
.getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}?dataCenter={dateCenter}", ApolloConfig.class,
getHostUrl(), someAppId, someCluster, somePublicNamespace, someDCNotFound);
ApolloConfig result = response.getBody();
assertEquals("992", result.getReleaseId());
assertEquals(someAppId, result.getAppId());
assertEquals(someCluster, result.getCluster());
assertEquals(somePublicNamespace, result.getNamespace());
assertEquals("default-v1", result.getConfigurations().get("k1"));
assertEquals("default-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)
public void testQueryPublicConfigWithDataCenterNotFoundAndOverride() throws Exception {
String someDCNotFound = "someDCNotFound";
ResponseEntity<ApolloConfig> response = restTemplate
.getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}?dataCenter={dateCenter}", ApolloConfig.class,
getHostUrl(), someAppId, someDefaultCluster, somePublicNamespace, someDCNotFound);
ApolloConfig result = response.getBody();
assertEquals("994|992", result.getReleaseId());
assertEquals(someAppId, result.getAppId());
assertEquals(someDefaultCluster, result.getCluster());
assertEquals(somePublicNamespace, result.getNamespace());
assertEquals("override-v1", result.getConfigurations().get("k1"));
assertEquals("default-v2", result.getConfigurations().get("k2"));
}
}
......@@ -10,11 +10,13 @@ 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 java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.Assert.assertEquals;
......@@ -26,40 +28,110 @@ public class NotificationControllerIntegrationTest extends AbstractBaseIntegrati
private NotificationController notificationController;
private String someAppId;
private String someCluster;
private String someNamespace;
private String defaultNamespace;
private String somePublicNamespace;
private ExecutorService executorService;
@Before
public void setUp() throws Exception {
someAppId = "someAppId";
someCluster = ConfigConsts.CLUSTER_NAME_DEFAULT;
someNamespace = "someNamespace";
defaultNamespace = ConfigConsts.NAMESPACE_DEFAULT;
somePublicNamespace = "somePublicNamespace";
executorService = Executors.newSingleThreadExecutor();
}
@Test
public void testPollNotification() throws Exception {
public void testPollNotificationWithDefaultNamespace() throws Exception {
Future<ResponseEntity<ApolloConfigNotification>> future =
executorService.submit(() -> restTemplate
.getForEntity(
"{baseurl}/notifications?appId={appId}&cluster={clusterName}&namespace={namespace}",
ApolloConfigNotification.class,
getHostUrl(), someAppId, someCluster, someNamespace));
getHostUrl(), someAppId, someCluster, defaultNamespace));
//wait for the request connected to server
TimeUnit.MILLISECONDS.sleep(500);
notificationController.handleMessage(assembleKey(someAppId, someCluster, someNamespace),
notificationController.handleMessage(assembleKey(someAppId, someCluster, defaultNamespace),
Topics.APOLLO_RELEASE_TOPIC);
ResponseEntity<ApolloConfigNotification> result = future.get(500, TimeUnit.MILLISECONDS);
ApolloConfigNotification notification = result.getBody();
assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals(someAppId, notification.getAppId());
assertEquals(someCluster, notification.getCluster());
assertEquals(someNamespace, notification.getNamespace());
assertEquals(defaultNamespace, notification.getNamespace());
}
@Test(timeout = 5000L)
@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 testPollNotificationWthPublicNamespaceAndNoDataCenter() throws Exception {
String publicAppId = "somePublicAppId";
AtomicBoolean stop = new AtomicBoolean();
executorService.submit((Runnable) () -> {
//wait for the request connected to server
while (!stop.get() && !Thread.currentThread().isInterrupted()) {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
}
notificationController.handleMessage(
assembleKey(publicAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, somePublicNamespace),
Topics.APOLLO_RELEASE_TOPIC);
}
});
ResponseEntity<ApolloConfigNotification> result = restTemplate
.getForEntity(
"{baseurl}/notifications?appId={appId}&cluster={clusterName}&namespace={namespace}",
ApolloConfigNotification.class,
getHostUrl(), someAppId, someCluster, somePublicNamespace);
stop.set(true);
ApolloConfigNotification notification = result.getBody();
assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals(somePublicNamespace, notification.getNamespace());
}
@Test(timeout = 5000L)
@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 testPollNotificationWthPublicNamespaceAndDataCenter() throws Exception {
String publicAppId = "somePublicAppId";
String someDC = "someDC";
AtomicBoolean stop = new AtomicBoolean();
executorService.submit((Runnable) () -> {
//wait for the request connected to server
while (!stop.get() && !Thread.currentThread().isInterrupted()) {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
}
notificationController.handleMessage(
assembleKey(publicAppId, someDC, somePublicNamespace),
Topics.APOLLO_RELEASE_TOPIC);
}
});
ResponseEntity<ApolloConfigNotification> result = restTemplate
.getForEntity(
"{baseurl}/notifications?appId={appId}&cluster={clusterName}&namespace={namespace}&dataCenter={dataCenter}",
ApolloConfigNotification.class,
getHostUrl(), someAppId, someCluster, somePublicNamespace, someDC);
stop.set(true);
ApolloConfigNotification notification = result.getBody();
assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals(somePublicNamespace, notification.getNamespace());
}
private String assembleKey(String appId, String cluster, String namespace) {
return String.format("%s-%s-%s", appId, cluster, namespace);
}
......
INSERT INTO App (AppId, Name, OwnerName, OwnerEmail) VALUES ('someAppId','someAppName','someOwnerName','someOwnerName@ctrip.com');
INSERT INTO App (AppId, Name, OwnerName, OwnerEmail) VALUES ('somePublicAppId','somePublicAppName','someOwnerName','someOwnerName@ctrip.com');
INSERT INTO Cluster (AppId, Name) VALUES ('someAppId', 'default');
INSERT INTO Cluster (AppId, Name) VALUES ('someAppId', 'someCluster');
INSERT INTO Cluster (AppId, Name) VALUES ('somePublicAppId', 'default');
INSERT INTO Cluster (AppId, Name) VALUES ('somePublicAppId', 'someDC');
INSERT INTO AppNamespace (AppId, Name) VALUES ('someAppId', 'someAppId');
INSERT INTO AppNamespace (AppId, Name) VALUES ('someAppId', 'application');
INSERT INTO AppNamespace (AppId, Name) VALUES ('someAppId', 'someNamespace');
INSERT INTO AppNamespace (AppId, Name) VALUES ('somePublicAppId', 'application');
INSERT INTO AppNamespace (AppId, Name) VALUES ('somePublicAppId', 'somePublicNamespace');
INSERT INTO Namespace (AppId, ClusterName, NamespaceName) VALUES ('someAppId', 'default', 'someAppId');
INSERT INTO Namespace (AppId, ClusterName, NamespaceName) VALUES ('someAppId', 'default', 'application');
INSERT INTO Namespace (AppId, ClusterName, NamespaceName) VALUES ('someAppId', 'someCluster', 'someNamespace');
INSERT INTO Namespace (AppId, ClusterName, NamespaceName) VALUES ('somePublicAppId', 'default', 'application');
INSERT INTO Namespace (AppId, ClusterName, NamespaceName) VALUES ('somePublicAppId', 'someDC', 'somePublicNamespace');
INSERT INTO Namespace (AppId, ClusterName, NamespaceName) VALUES ('someAppId', 'default', 'somePublicNamespace');
INSERT INTO RELEASE (id, Name, Comment, AppId, ClusterName, NamespaceName, Configurations) VALUES (990, 'INTEGRATION-TEST-DEFAULT','First Release','someAppId', 'default', 'application', '{"k1":"v1"}');
INSERT INTO RELEASE (id, Name, Comment, AppId, ClusterName, NamespaceName, Configurations) VALUES (991, 'INTEGRATION-TEST-NAMESPACE','First Release','someAppId', 'someCluster', 'someNamespace', '{"k2":"v2"}');
INSERT INTO RELEASE (id, Name, Comment, AppId, ClusterName, NamespaceName, Configurations)
VALUES (990, 'INTEGRATION-TEST-DEFAULT','First Release','someAppId', 'default', 'application', '{"k1":"v1"}');
INSERT INTO RELEASE (id, Name, Comment, AppId, ClusterName, NamespaceName, Configurations)
VALUES (991, 'INTEGRATION-TEST-NAMESPACE','First Release','someAppId', 'someCluster', 'someNamespace', '{"k2":"v2"}');
INSERT INTO RELEASE (id, Name, Comment, AppId, ClusterName, NamespaceName, Configurations)
VALUES (992, 'INTEGRATION-TEST-PUBLIC-DEFAULT','First Release','somePublicAppId', 'default', 'somePublicNamespace', '{"k1":"default-v1", "k2":"default-v2"}');
INSERT INTO RELEASE (id, Name, Comment, AppId, ClusterName, NamespaceName, Configurations)
VALUES (993, 'INTEGRATION-TEST-PUBLIC-NAMESPACE','First Release','somePublicAppId', 'someDC', 'somePublicNamespace', '{"k1":"someDC-v1", "k2":"someDC-v2"}');
INSERT INTO RELEASE (id, Name, Comment, AppId, ClusterName, NamespaceName, Configurations)
VALUES (994, 'INTEGRATION-TEST-DEFAULT-OVERRIDE-PUBLIC','First Release','someAppId', 'default', 'somePublicNamespace', '{"k1":"override-v1"}');
......@@ -4,40 +4,20 @@ package com.ctrip.apollo.core.dto;
* @author Jason Song(song_s@ctrip.com)
*/
public class ApolloConfigNotification {
private String appId;
private String cluster;
private String namespace;
//for json converter
public ApolloConfigNotification() {
}
public ApolloConfigNotification(String appId, String cluster, String namespace) {
this.appId = appId;
this.cluster = cluster;
public ApolloConfigNotification(String namespace) {
this.namespace = namespace;
}
public String getAppId() {
return appId;
}
public String getCluster() {
return cluster;
}
public String getNamespace() {
return namespace;
}
public void setAppId(String appId) {
this.appId = appId;
}
public void setCluster(String cluster) {
this.cluster = cluster;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
}
......
#Demo
## 1. 本地没有缓存文件,从服务端正常读取
## 2. 本地有缓存文件,从服务端正常读取
## 3. 本地没有缓存文件,从服务端读取失败
## 4. 本地有缓存文件,从服务端读取失败
## 5. 本地启动后,配置有新的发布
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment