Commit a76372f0 by Jason Song

add public config support

parent 12a89acc
*.class *.class
.DS_Store
# Mobile Tools for Java (J2ME) # Mobile Tools for Java (J2ME)
.mtj.tmp/ .mtj.tmp/
......
package com.ctrip.apollo.biz.message; 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; import org.springframework.data.redis.core.RedisTemplate;
/** /**
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
public class RedisMessageSender implements MessageSender { public class RedisMessageSender implements MessageSender {
private static final Logger logger = LoggerFactory.getLogger(RedisMessageSender.class);
private RedisTemplate<String, String> redisTemplate; private RedisTemplate<String, String> redisTemplate;
public RedisMessageSender( public RedisMessageSender(
...@@ -15,12 +22,16 @@ public class RedisMessageSender implements MessageSender { ...@@ -15,12 +22,16 @@ public class RedisMessageSender implements MessageSender {
@Override @Override
public void sendMessage(String message, String channel) { public void sendMessage(String message, String channel) {
logger.info("Sending message {} to channel {}", message, channel);
Transaction transaction = Cat.newTransaction("Apollo.AdminService", "RedisMessageSender");
try { try {
redisTemplate.convertAndSend(channel, message); redisTemplate.convertAndSend(channel, message);
transaction.setStatus(Message.SUCCESS);
} catch (Throwable ex) { } catch (Throwable ex) {
logger.error("Sending message to redis failed", ex);
transaction.setStatus(ex);
} finally { } finally {
transaction.complete();
} }
} }
} }
...@@ -8,4 +8,6 @@ public interface AppNamespaceRepository extends PagingAndSortingRepository<AppNa ...@@ -8,4 +8,6 @@ public interface AppNamespaceRepository extends PagingAndSortingRepository<AppNa
AppNamespace findByAppIdAndName(String appId, String namespaceName); AppNamespace findByAppIdAndName(String appId, String namespaceName);
AppNamespace findByName(String namespaceName);
} }
package com.ctrip.apollo.biz.service; package com.ctrip.apollo.biz.service;
import com.google.common.base.Preconditions;
import java.util.Objects; import java.util.Objects;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
...@@ -27,6 +29,11 @@ public class AppNamespaceService { ...@@ -27,6 +29,11 @@ public class AppNamespaceService {
return Objects.isNull(appNamespaceRepository.findByAppIdAndName(appId, namespaceName)); 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 @Transactional
public void createDefaultAppNamespace(String appId, String createBy) { public void createDefaultAppNamespace(String appId, String createBy) {
if (!isAppNamespaceNameUnique(appId, appId)) { if (!isAppNamespaceNameUnique(appId, appId)) {
......
...@@ -134,6 +134,7 @@ public class RemoteConfigRepository extends AbstractConfigRepository { ...@@ -134,6 +134,7 @@ public class RemoteConfigRepository extends AbstractConfigRepository {
private ApolloConfig loadApolloConfig() { private ApolloConfig loadApolloConfig() {
String appId = m_configUtil.getAppId(); String appId = m_configUtil.getAppId();
String cluster = m_configUtil.getCluster(); String cluster = m_configUtil.getCluster();
String dataCenter = m_configUtil.getDataCenter();
Cat.logEvent("Apollo.Client.ConfigInfo", Cat.logEvent("Apollo.Client.ConfigInfo",
String.format("%s-%s-%s", appId, cluster, m_namespace)); String.format("%s-%s-%s", appId, cluster, m_namespace));
int maxRetries = 2; int maxRetries = 2;
...@@ -147,7 +148,7 @@ public class RemoteConfigRepository extends AbstractConfigRepository { ...@@ -147,7 +148,7 @@ public class RemoteConfigRepository extends AbstractConfigRepository {
for (ServiceDTO configService : randomConfigServices) { for (ServiceDTO configService : randomConfigServices) {
String url = String url =
assembleQueryConfigUrl(configService.getHomepageUrl(), appId, cluster, m_namespace, assembleQueryConfigUrl(configService.getHomepageUrl(), appId, cluster, m_namespace,
m_configCache.get()); dataCenter, m_configCache.get());
logger.debug("Loading config from {}", url); logger.debug("Loading config from {}", url);
HttpRequest request = new HttpRequest(url); HttpRequest request = new HttpRequest(url);
...@@ -191,21 +192,30 @@ public class RemoteConfigRepository extends AbstractConfigRepository { ...@@ -191,21 +192,30 @@ public class RemoteConfigRepository extends AbstractConfigRepository {
} }
private String assembleQueryConfigUrl(String uri, String appId, String cluster, String namespace, private String assembleQueryConfigUrl(String uri, String appId, String cluster, String namespace,
ApolloConfig previousConfig) { String dataCenter, ApolloConfig previousConfig) {
Escaper escaper = UrlEscapers.urlPathSegmentEscaper(); Escaper escaper = UrlEscapers.urlPathSegmentEscaper();
String path = "configs/%s/%s"; 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)) { if (!Strings.isNullOrEmpty(namespace)) {
path = path + "/%s"; path = path + "/%s";
params.add(escaper.escape(namespace)); pathParams.add(escaper.escape(namespace));
} }
if (previousConfig != null) { if (previousConfig != null) {
path = path + "?releaseId=%s"; queryParams.put("releaseId", escaper.escape(String.valueOf(previousConfig.getReleaseId())));
params.add(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("/")) { if (!uri.endsWith("/")) {
uri += "/"; uri += "/";
} }
...@@ -215,18 +225,19 @@ public class RemoteConfigRepository extends AbstractConfigRepository { ...@@ -215,18 +225,19 @@ public class RemoteConfigRepository extends AbstractConfigRepository {
private void scheduleLongPollingRefresh() { private void scheduleLongPollingRefresh() {
final String appId = m_configUtil.getAppId(); final String appId = m_configUtil.getAppId();
final String cluster = m_configUtil.getCluster(); final String cluster = m_configUtil.getCluster();
final String dataCenter = m_configUtil.getDataCenter();
final ExecutorService longPollingService = final ExecutorService longPollingService =
Executors.newFixedThreadPool(2, Executors.newFixedThreadPool(2,
ApolloThreadFactory.create("RemoteConfigRepository-LongPolling", true)); ApolloThreadFactory.create("RemoteConfigRepository-LongPolling", true));
longPollingService.submit(new Runnable() { longPollingService.submit(new Runnable() {
@Override @Override
public void run() { 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) { ExecutorService longPollingService) {
final Random random = new Random(); final Random random = new Random();
ServiceDTO lastServiceDto = null; ServiceDTO lastServiceDto = null;
...@@ -240,7 +251,7 @@ public class RemoteConfigRepository extends AbstractConfigRepository { ...@@ -240,7 +251,7 @@ public class RemoteConfigRepository extends AbstractConfigRepository {
String url = String url =
assembleLongPollRefreshUrl(lastServiceDto.getHomepageUrl(), appId, cluster, assembleLongPollRefreshUrl(lastServiceDto.getHomepageUrl(), appId, cluster,
m_namespace, m_configCache.get()); m_namespace, dataCenter, m_configCache.get());
logger.debug("Long polling from {}", url); logger.debug("Long polling from {}", url);
HttpRequest request = new HttpRequest(url); HttpRequest request = new HttpRequest(url);
...@@ -286,7 +297,7 @@ public class RemoteConfigRepository extends AbstractConfigRepository { ...@@ -286,7 +297,7 @@ public class RemoteConfigRepository extends AbstractConfigRepository {
} }
private String assembleLongPollRefreshUrl(String uri, String appId, String cluster, private String assembleLongPollRefreshUrl(String uri, String appId, String cluster,
String namespace, String namespace, String dataCenter,
ApolloConfig previousConfig) { ApolloConfig previousConfig) {
Escaper escaper = UrlEscapers.urlPathSegmentEscaper(); Escaper escaper = UrlEscapers.urlPathSegmentEscaper();
Map<String, String> queryParams = Maps.newHashMap(); Map<String, String> queryParams = Maps.newHashMap();
...@@ -296,6 +307,9 @@ public class RemoteConfigRepository extends AbstractConfigRepository { ...@@ -296,6 +307,9 @@ public class RemoteConfigRepository extends AbstractConfigRepository {
if (!Strings.isNullOrEmpty(namespace)) { if (!Strings.isNullOrEmpty(namespace)) {
queryParams.put("namespace", escaper.escape(namespace)); queryParams.put("namespace", escaper.escape(namespace));
} }
if (!Strings.isNullOrEmpty(dataCenter)) {
queryParams.put("dataCenter", escaper.escape(dataCenter));
}
if (previousConfig != null) { if (previousConfig != null) {
queryParams.put("releaseId", escaper.escape(previousConfig.getReleaseId())); queryParams.put("releaseId", escaper.escape(previousConfig.getReleaseId()));
} }
......
...@@ -18,8 +18,8 @@ public class ConfigChangeEvent { ...@@ -18,8 +18,8 @@ public class ConfigChangeEvent {
*/ */
public ConfigChangeEvent(String namespace, public ConfigChangeEvent(String namespace,
Map<String, ConfigChange> changes) { Map<String, ConfigChange> changes) {
this.m_namespace = namespace; m_namespace = namespace;
this.m_changes = changes; m_changes = changes;
} }
/** /**
......
...@@ -35,6 +35,14 @@ public class ConfigUtil { ...@@ -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. * Get the cluster name for the current application.
* @return the cluster name, or "default" if not specified * @return the cluster name, or "default" if not specified
*/ */
......
...@@ -42,6 +42,7 @@ public abstract class BaseIntegrationTest extends ComponentTestCase { ...@@ -42,6 +42,7 @@ public abstract class BaseIntegrationTest extends ComponentTestCase {
private static final String configServiceURL = "http://localhost:" + PORT; private static final String configServiceURL = "http://localhost:" + PORT;
protected static String someAppId; protected static String someAppId;
protected static String someClusterName; protected static String someClusterName;
protected static String someDataCenter;
protected static int refreshInterval; protected static int refreshInterval;
protected static TimeUnit refreshTimeUnit; protected static TimeUnit refreshTimeUnit;
private Server server; private Server server;
...@@ -59,6 +60,7 @@ public abstract class BaseIntegrationTest extends ComponentTestCase { ...@@ -59,6 +60,7 @@ public abstract class BaseIntegrationTest extends ComponentTestCase {
super.setUp(); super.setUp();
someAppId = "1003171"; someAppId = "1003171";
someClusterName = "someClusterName"; someClusterName = "someClusterName";
someDataCenter = "someDC";
refreshInterval = 5; refreshInterval = 5;
refreshTimeUnit = TimeUnit.MINUTES; refreshTimeUnit = TimeUnit.MINUTES;
...@@ -160,6 +162,11 @@ public abstract class BaseIntegrationTest extends ComponentTestCase { ...@@ -160,6 +162,11 @@ public abstract class BaseIntegrationTest extends ComponentTestCase {
public Env getApolloEnv() { public Env getApolloEnv() {
return Env.LOCAL; return Env.LOCAL;
} }
@Override
public String getDataCenter() {
return someDataCenter;
}
} }
/** /**
......
...@@ -241,8 +241,7 @@ public class ConfigIntegrationTest extends BaseIntegrationTest { ...@@ -241,8 +241,7 @@ public class ConfigIntegrationTest extends BaseIntegrationTest {
ContextHandler configHandler = mockConfigServerHandler(HttpServletResponse.SC_OK, apolloConfig); ContextHandler configHandler = mockConfigServerHandler(HttpServletResponse.SC_OK, apolloConfig);
ContextHandler pollHandler = ContextHandler pollHandler =
mockPollNotificationHandler(pollTimeoutInMS, HttpServletResponse.SC_OK, mockPollNotificationHandler(pollTimeoutInMS, HttpServletResponse.SC_OK,
new ApolloConfigNotification(apolloConfig.getAppId(), apolloConfig.getCluster(), new ApolloConfigNotification(apolloConfig.getNamespace()), false);
apolloConfig.getNamespace()), false);
startServerWithHandlers(configHandler, pollHandler); startServerWithHandlers(configHandler, pollHandler);
......
...@@ -186,6 +186,11 @@ public class RemoteConfigRepositoryTest extends ComponentTestCase { ...@@ -186,6 +186,11 @@ public class RemoteConfigRepositoryTest extends ComponentTestCase {
public String getCluster() { public String getCluster() {
return "someCluster"; return "someCluster";
} }
@Override
public String getDataCenter() {
return null;
}
} }
public static class MockConfigServiceLocator extends ConfigServiceLocator { public static class MockConfigServiceLocator extends ConfigServiceLocator {
......
...@@ -8,7 +8,9 @@ import com.google.common.collect.Maps; ...@@ -8,7 +8,9 @@ import com.google.common.collect.Maps;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken; 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.entity.Release;
import com.ctrip.apollo.biz.service.AppNamespaceService;
import com.ctrip.apollo.biz.service.ConfigService; import com.ctrip.apollo.biz.service.ConfigService;
import com.ctrip.apollo.core.ConfigConsts; import com.ctrip.apollo.core.ConfigConsts;
import com.ctrip.apollo.core.dto.ApolloConfig; import com.ctrip.apollo.core.dto.ApolloConfig;
...@@ -37,6 +39,8 @@ import javax.servlet.http.HttpServletResponse; ...@@ -37,6 +39,8 @@ import javax.servlet.http.HttpServletResponse;
public class ConfigController { public class ConfigController {
@Autowired @Autowired
private ConfigService configService; private ConfigService configService;
@Autowired
private AppNamespaceService appNamespaceService;
private Gson gson = new Gson(); private Gson gson = new Gson();
private Type configurationTypeReference = private Type configurationTypeReference =
...@@ -45,17 +49,17 @@ public class ConfigController { ...@@ -45,17 +49,17 @@ public class ConfigController {
@RequestMapping(value = "/{appId}/{clusterName}", method = RequestMethod.GET) @RequestMapping(value = "/{appId}/{clusterName}", method = RequestMethod.GET)
public ApolloConfig queryConfig(@PathVariable String appId, @PathVariable String clusterName, 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, @RequestParam(value = "releaseId", defaultValue = "-1") String clientSideReleaseId,
HttpServletResponse response) throws IOException { HttpServletResponse response) throws IOException {
return this.queryConfig(appId, clusterName, ConfigConsts.NAMESPACE_DEFAULT, datacenter, return this.queryConfig(appId, clusterName, ConfigConsts.NAMESPACE_DEFAULT, dataCenter,
clientSideReleaseId, response); clientSideReleaseId, response);
} }
@RequestMapping(value = "/{appId}/{clusterName}/{namespace}", method = RequestMethod.GET) @RequestMapping(value = "/{appId}/{clusterName}/{namespace}", method = RequestMethod.GET)
public ApolloConfig queryConfig(@PathVariable String appId, @PathVariable String clusterName, public ApolloConfig queryConfig(@PathVariable String appId, @PathVariable String clusterName,
@PathVariable String namespace, @PathVariable String namespace,
@RequestParam(value = "datacenter", required = false) String datacenter, @RequestParam(value = "dataCenter", required = false) String dataCenter,
@RequestParam(value = "releaseId", defaultValue = "-1") String clientSideReleaseId, @RequestParam(value = "releaseId", defaultValue = "-1") String clientSideReleaseId,
HttpServletResponse response) throws IOException { HttpServletResponse response) throws IOException {
List<Release> releases = Lists.newLinkedList(); List<Release> releases = Lists.newLinkedList();
...@@ -66,13 +70,12 @@ public class ConfigController { ...@@ -66,13 +70,12 @@ public class ConfigController {
releases.add(currentAppRelease); 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)) { if (!Objects.equals(ConfigConsts.NAMESPACE_DEFAULT, namespace)) {
//TODO find id for this particular namespace, if not equal to current app id, then do more Release publicRelease = this.findPublicConfig(appId, namespace, dataCenter);
if (!Objects.isNull(datacenter)) { if (!Objects.isNull(publicRelease)) {
//TODO load newAppId+datacenter+namespace configurations releases.add(publicRelease);
} }
//TODO if load from DC failed, then load newAppId+defaultCluster+namespace configurations
} }
if (releases.isEmpty()) { if (releases.isEmpty()) {
...@@ -81,7 +84,7 @@ public class ConfigController { ...@@ -81,7 +84,7 @@ public class ConfigController {
"Could not load configurations with appId: %s, clusterName: %s, namespace: %s", "Could not load configurations with appId: %s, clusterName: %s, namespace: %s",
appId, clusterName, namespace)); appId, clusterName, namespace));
Cat.logEvent("Apollo.Config.NotFound", Cat.logEvent("Apollo.Config.NotFound",
assembleKey(appId, clusterName, namespace, datacenter)); assembleKey(appId, clusterName, namespace, dataCenter));
return null; return null;
} }
...@@ -92,18 +95,47 @@ public class ConfigController { ...@@ -92,18 +95,47 @@ public class ConfigController {
// Client side configuration is the same with server side, return 304 // Client side configuration is the same with server side, return 304
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
Cat.logEvent("Apollo.Config.NotModified", Cat.logEvent("Apollo.Config.NotModified",
assembleKey(appId, clusterName, namespace, datacenter)); assembleKey(appId, clusterName, namespace, dataCenter));
return null; return null;
} }
ApolloConfig apolloConfig = new ApolloConfig(appId, clusterName, namespace, mergedReleaseId); ApolloConfig apolloConfig = new ApolloConfig(appId, clusterName, namespace, mergedReleaseId);
apolloConfig.setConfigurations(mergeReleaseConfigurations(releases)); 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; 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. * Merge configurations of releases.
* Release in lower index override those in higher index * Release in lower index override those in higher index
*/ */
......
...@@ -6,14 +6,17 @@ import com.google.common.collect.Lists; ...@@ -6,14 +6,17 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps; 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.MessageListener;
import com.ctrip.apollo.biz.message.Topics; 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.ConfigConsts;
import com.ctrip.apollo.core.dto.ApolloConfigNotification; import com.ctrip.apollo.core.dto.ApolloConfigNotification;
import com.dianping.cat.Cat; import com.dianping.cat.Cat;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
...@@ -40,23 +43,22 @@ public class NotificationController implements MessageListener { ...@@ -40,23 +43,22 @@ public class NotificationController implements MessageListener {
deferredResults = deferredResults =
Multimaps.synchronizedSetMultimap(HashMultimap.create()); Multimaps.synchronizedSetMultimap(HashMultimap.create());
@Autowired
private AppNamespaceService appNamespaceService;
@RequestMapping(method = RequestMethod.GET) @RequestMapping(method = RequestMethod.GET)
public DeferredResult<ResponseEntity<ApolloConfigNotification>> pollNotification( public DeferredResult<ResponseEntity<ApolloConfigNotification>> pollNotification(
@RequestParam(value = "appId") String appId, @RequestParam(value = "appId") String appId,
@RequestParam(value = "cluster") String cluster, @RequestParam(value = "cluster") String cluster,
@RequestParam(value = "namespace", defaultValue = ConfigConsts.NAMESPACE_DEFAULT) String namespace, @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, @RequestParam(value = "releaseId", defaultValue = "-1") String clientSideReleaseId,
HttpServletResponse response) { HttpServletResponse response) {
List<String> watchedKeys = Lists.newArrayList(assembleKey(appId, cluster, namespace)); 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)) { if (!Objects.equals(ConfigConsts.NAMESPACE_DEFAULT, namespace)) {
//TODO find id for this particular namespace, if not equal to current app id, then do more watchedKeys.addAll(this.findPublicConfigWatchKey(appId, namespace, dataCenter));
if (!Objects.isNull(datacenter)) {
//TODO add newAppId+datacenter+namespace to listened keys
}
//TODO add newAppId+defaultCluster+namespace to listened keys
} }
ResponseEntity<ApolloConfigNotification> body = new ResponseEntity<>( ResponseEntity<ApolloConfigNotification> body = new ResponseEntity<>(
...@@ -77,7 +79,7 @@ public class NotificationController implements MessageListener { ...@@ -77,7 +79,7 @@ public class NotificationController implements MessageListener {
}); });
logger.info("Listening {} from appId: {}, cluster: {}, namespace: {}, datacenter: {}", logger.info("Listening {} from appId: {}, cluster: {}, namespace: {}, datacenter: {}",
watchedKeys, appId, cluster, namespace, datacenter); watchedKeys, appId, cluster, namespace, dataCenter);
return deferredResult; return deferredResult;
} }
...@@ -85,6 +87,30 @@ public class NotificationController implements MessageListener { ...@@ -85,6 +87,30 @@ public class NotificationController implements MessageListener {
return String.format("%s-%s-%s", appId, cluster, namespace); 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 @Override
public void handleMessage(String message, String channel) { public void handleMessage(String message, String channel) {
logger.info("message received - channel: {}, message: {}", channel, message); logger.info("message received - channel: {}, message: {}", channel, message);
...@@ -101,8 +127,7 @@ public class NotificationController implements MessageListener { ...@@ -101,8 +127,7 @@ public class NotificationController implements MessageListener {
ResponseEntity<ApolloConfigNotification> notification = ResponseEntity<ApolloConfigNotification> notification =
new ResponseEntity<>( new ResponseEntity<>(
new ApolloConfigNotification(keys[0], keys[1], keys[2]), new ApolloConfigNotification(keys[2]), HttpStatus.OK);
HttpStatus.OK);
Collection<DeferredResult<ResponseEntity<ApolloConfigNotification>>> Collection<DeferredResult<ResponseEntity<ApolloConfigNotification>>>
results = deferredResults.get(message); results = deferredResults.get(message);
......
package com.ctrip.apollo.configservice.controller; package com.ctrip.apollo.configservice.controller;
import static org.junit.Assert.assertEquals; import com.google.common.collect.ImmutableMap;
import static org.junit.Assert.assertNull; import com.google.common.collect.Lists;
import static org.mockito.Matchers.anyString; import com.google.gson.Gson;
import static org.mockito.Matchers.eq; import com.google.gson.JsonSyntaxException;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Map;
import javax.servlet.http.HttpServletResponse; 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;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
...@@ -20,13 +19,18 @@ import org.mockito.Mock; ...@@ -20,13 +19,18 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import com.ctrip.apollo.biz.entity.Release; import java.util.Map;
import com.ctrip.apollo.biz.service.ConfigService;
import com.ctrip.apollo.core.dto.ApolloConfig; import javax.servlet.http.HttpServletResponse;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists; import static org.junit.Assert.assertEquals;
import com.google.gson.Gson; import static org.junit.Assert.assertNull;
import com.google.gson.JsonSyntaxException; import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
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) * @author Jason Song(song_s@ctrip.com)
...@@ -36,26 +40,34 @@ public class ConfigControllerTest { ...@@ -36,26 +40,34 @@ public class ConfigControllerTest {
private ConfigController configController; private ConfigController configController;
@Mock @Mock
private ConfigService configService; private ConfigService configService;
@Mock
private AppNamespaceService appNamespaceService;
private String someAppId; private String someAppId;
private String someClusterName; private String someClusterName;
private String someNamespaceName; private String defaultNamespaceName;
private String somePublicNamespaceName;
private String someDataCenter; private String someDataCenter;
private String someValidConfiguration;
@Mock @Mock
private Release someRelease; private Release someRelease;
@Mock
private Release somePublicRelease;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
configController = new ConfigController(); configController = new ConfigController();
ReflectionTestUtils.setField(configController, "configService", configService); ReflectionTestUtils.setField(configController, "configService", configService);
ReflectionTestUtils.setField(configController, "appNamespaceService", appNamespaceService);
someAppId = "1"; someAppId = "1";
someClusterName = "someClusterName"; someClusterName = "someClusterName";
someNamespaceName = "someNamespaceName"; defaultNamespaceName = ConfigConsts.NAMESPACE_DEFAULT;
somePublicNamespaceName = "somePublicNamespace";
someDataCenter = "someDC"; someDataCenter = "someDC";
someValidConfiguration = "{\"apollo.bar\": \"foo\"}"; String someValidConfiguration = "{\"apollo.bar\": \"foo\"}";
String somePublicConfiguration = "{\"apollo.public.bar\": \"foo\"}";
when(someRelease.getConfigurations()).thenReturn(someValidConfiguration); when(someRelease.getConfigurations()).thenReturn(someValidConfiguration);
when(somePublicRelease.getConfigurations()).thenReturn(somePublicConfiguration);
} }
@Test @Test
...@@ -64,17 +76,18 @@ public class ConfigControllerTest { ...@@ -64,17 +76,18 @@ public class ConfigControllerTest {
long someServerSideNewReleaseId = 2; long someServerSideNewReleaseId = 2;
HttpServletResponse someResponse = mock(HttpServletResponse.class); HttpServletResponse someResponse = mock(HttpServletResponse.class);
when(configService.findRelease(someAppId, someClusterName, someNamespaceName)) when(configService.findRelease(someAppId, someClusterName, defaultNamespaceName))
.thenReturn(someRelease); .thenReturn(someRelease);
when(someRelease.getId()).thenReturn(someServerSideNewReleaseId); when(someRelease.getId()).thenReturn(someServerSideNewReleaseId);
ApolloConfig result = configController.queryConfig(someAppId, someClusterName, ApolloConfig result = configController.queryConfig(someAppId, someClusterName,
someNamespaceName, someDataCenter, String.valueOf(someClientSideReleaseId), someResponse); defaultNamespaceName, someDataCenter, String.valueOf(someClientSideReleaseId),
someResponse);
verify(configService, times(1)).findRelease(someAppId, someClusterName, someNamespaceName); verify(configService, times(1)).findRelease(someAppId, someClusterName, defaultNamespaceName);
assertEquals(someAppId, result.getAppId()); assertEquals(someAppId, result.getAppId());
assertEquals(someClusterName, result.getCluster()); assertEquals(someClusterName, result.getCluster());
assertEquals(someNamespaceName, result.getNamespace()); assertEquals(defaultNamespaceName, result.getNamespace());
assertEquals(String.valueOf(someServerSideNewReleaseId), result.getReleaseId()); assertEquals(String.valueOf(someServerSideNewReleaseId), result.getReleaseId());
} }
...@@ -84,10 +97,12 @@ public class ConfigControllerTest { ...@@ -84,10 +97,12 @@ public class ConfigControllerTest {
long someClientSideReleaseId = 1; long someClientSideReleaseId = 1;
HttpServletResponse someResponse = mock(HttpServletResponse.class); HttpServletResponse someResponse = mock(HttpServletResponse.class);
when(configService.findRelease(someAppId, someClusterName, someNamespaceName)).thenReturn(null); when(configService.findRelease(someAppId, someClusterName, defaultNamespaceName))
.thenReturn(null);
ApolloConfig result = configController.queryConfig(someAppId, someClusterName, ApolloConfig result = configController.queryConfig(someAppId, someClusterName,
someNamespaceName, someDataCenter, String.valueOf(someClientSideReleaseId), someResponse); defaultNamespaceName, someDataCenter, String.valueOf(someClientSideReleaseId),
someResponse);
assertNull(result); assertNull(result);
verify(someResponse, times(1)).sendError(eq(HttpServletResponse.SC_NOT_FOUND), anyString()); verify(someResponse, times(1)).sendError(eq(HttpServletResponse.SC_NOT_FOUND), anyString());
...@@ -99,13 +114,13 @@ public class ConfigControllerTest { ...@@ -99,13 +114,13 @@ public class ConfigControllerTest {
long someServerSideReleaseId = someClientSideReleaseId; long someServerSideReleaseId = someClientSideReleaseId;
HttpServletResponse someResponse = mock(HttpServletResponse.class); HttpServletResponse someResponse = mock(HttpServletResponse.class);
when(configService.findRelease(someAppId, someClusterName, someNamespaceName)) when(configService.findRelease(someAppId, someClusterName, defaultNamespaceName))
.thenReturn(someRelease); .thenReturn(someRelease);
when(someRelease.getId()).thenReturn(someServerSideReleaseId); when(someRelease.getId()).thenReturn(someServerSideReleaseId);
ApolloConfig ApolloConfig
result = result =
configController.queryConfig(someAppId, someClusterName, someNamespaceName, configController.queryConfig(someAppId, someClusterName, defaultNamespaceName,
someDataCenter, String.valueOf(someClientSideReleaseId), someResponse); someDataCenter, String.valueOf(someClientSideReleaseId), someResponse);
assertNull(result); assertNull(result);
...@@ -113,6 +128,131 @@ public class ConfigControllerTest { ...@@ -113,6 +128,131 @@ public class ConfigControllerTest {
} }
@Test @Test
public void testQueryConfigWithAppOwnNamespace() throws Exception {
String someClientSideReleaseId = "1";
String someServerSideReleaseId = "2";
String someAppOwnNamespaceName = "someAppOwn";
HttpServletResponse someResponse = mock(HttpServletResponse.class);
AppNamespace someAppOwnNamespace =
assmbleAppNamespace(someAppId, someAppOwnNamespaceName);
when(configService.findRelease(someAppId, someClusterName, someAppOwnNamespaceName))
.thenReturn(someRelease);
when(appNamespaceService.findByNamespaceName(someAppOwnNamespaceName))
.thenReturn(someAppOwnNamespace);
when(someRelease.getId()).thenReturn(Long.valueOf(someServerSideReleaseId));
ApolloConfig result =
configController
.queryConfig(someAppId, someClusterName, someAppOwnNamespaceName, someDataCenter,
someClientSideReleaseId, someResponse);
assertEquals(someServerSideReleaseId, result.getReleaseId());
assertEquals(someAppId, result.getAppId());
assertEquals(someClusterName, result.getCluster());
assertEquals(someAppOwnNamespaceName, result.getNamespace());
assertEquals("foo", result.getConfigurations().get("apollo.bar"));
}
@Test
public void testQueryConfigWithPubicNamespaceAndNoAppOverride() throws Exception {
String someClientSideReleaseId = "1";
String someServerSideReleaseId = "2";
HttpServletResponse someResponse = mock(HttpServletResponse.class);
String somePublicAppId = "somePublicAppId";
AppNamespace somePublicAppNamespace =
assmbleAppNamespace(somePublicAppId, somePublicNamespaceName);
when(configService.findRelease(someAppId, someClusterName, somePublicNamespaceName))
.thenReturn(null);
when(appNamespaceService.findByNamespaceName(somePublicNamespaceName))
.thenReturn(somePublicAppNamespace);
when(configService.findRelease(somePublicAppId, someDataCenter, somePublicNamespaceName))
.thenReturn(somePublicRelease);
when(somePublicRelease.getId()).thenReturn(Long.valueOf(someServerSideReleaseId));
ApolloConfig result =
configController
.queryConfig(someAppId, someClusterName, somePublicNamespaceName, someDataCenter,
someClientSideReleaseId, someResponse);
assertEquals(someServerSideReleaseId, result.getReleaseId());
assertEquals(someAppId, result.getAppId());
assertEquals(someClusterName, result.getCluster());
assertEquals(somePublicNamespaceName, result.getNamespace());
assertEquals("foo", result.getConfigurations().get("apollo.public.bar"));
}
@Test
public void testQueryConfigWithPublicNamespaceAndNoAppOverrideAndNoDataCenter() throws Exception {
String someClientSideReleaseId = "1";
String someServerSideReleaseId = "2";
HttpServletResponse someResponse = mock(HttpServletResponse.class);
String somePublicAppId = "somePublicAppId";
AppNamespace somePublicAppNamespace =
assmbleAppNamespace(somePublicAppId, somePublicNamespaceName);
when(configService.findRelease(someAppId, someClusterName, somePublicNamespaceName))
.thenReturn(null);
when(appNamespaceService.findByNamespaceName(somePublicNamespaceName))
.thenReturn(somePublicAppNamespace);
when(configService.findRelease(somePublicAppId, someDataCenter, somePublicNamespaceName))
.thenReturn(null);
when(configService
.findRelease(somePublicAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, somePublicNamespaceName))
.thenReturn(somePublicRelease);
when(somePublicRelease.getId()).thenReturn(Long.valueOf(someServerSideReleaseId));
ApolloConfig result =
configController
.queryConfig(someAppId, someClusterName, somePublicNamespaceName, someDataCenter,
someClientSideReleaseId, someResponse);
assertEquals(someServerSideReleaseId, result.getReleaseId());
assertEquals(someAppId, result.getAppId());
assertEquals(someClusterName, result.getCluster());
assertEquals(somePublicNamespaceName, result.getNamespace());
assertEquals("foo", result.getConfigurations().get("apollo.public.bar"));
}
@Test
public void testQueryConfigWithPublicNamespaceAndAppOverride() throws Exception {
String someAppSideReleaseId = "1";
String somePublicAppSideReleaseId = "2";
HttpServletResponse someResponse = mock(HttpServletResponse.class);
String somePublicAppId = "somePublicAppId";
AppNamespace somePublicAppNamespace =
assmbleAppNamespace(somePublicAppId, somePublicNamespaceName);
when(someRelease.getConfigurations()).thenReturn("{\"apollo.public.foo\": \"foo-override\"}");
when(somePublicRelease.getConfigurations())
.thenReturn("{\"apollo.public.foo\": \"foo\", \"apollo.public.bar\": \"bar\"}");
when(configService.findRelease(someAppId, someClusterName, somePublicNamespaceName))
.thenReturn(someRelease);
when(someRelease.getId()).thenReturn(Long.valueOf(someAppSideReleaseId));
when(appNamespaceService.findByNamespaceName(somePublicNamespaceName))
.thenReturn(somePublicAppNamespace);
when(configService.findRelease(somePublicAppId, someDataCenter, somePublicNamespaceName))
.thenReturn(somePublicRelease);
when(somePublicRelease.getId()).thenReturn(Long.valueOf(somePublicAppSideReleaseId));
ApolloConfig result =
configController
.queryConfig(someAppId, someClusterName, somePublicNamespaceName, someDataCenter,
someAppSideReleaseId, someResponse);
assertEquals(String.format("%s|%s", someAppSideReleaseId, somePublicAppSideReleaseId),
result.getReleaseId());
assertEquals(someAppId, result.getAppId());
assertEquals(someClusterName, result.getCluster());
assertEquals(somePublicNamespaceName, result.getNamespace());
assertEquals("foo-override", result.getConfigurations().get("apollo.public.foo"));
assertEquals("bar", result.getConfigurations().get("apollo.public.bar"));
}
@Test
public void testMergeConfigurations() throws Exception { public void testMergeConfigurations() throws Exception {
Gson gson = new Gson(); Gson gson = new Gson();
String key1 = "key1"; String key1 = "key1";
...@@ -148,4 +288,11 @@ public class ConfigControllerTest { ...@@ -148,4 +288,11 @@ public class ConfigControllerTest {
configController.mergeReleaseConfigurations(Lists.newArrayList(someRelease)); configController.mergeReleaseConfigurations(Lists.newArrayList(someRelease));
} }
private AppNamespace assmbleAppNamespace(String appId, String namespace) {
AppNamespace appNamespace = new AppNamespace();
appNamespace.setAppId(appId);
appNamespace.setName(namespace);
return appNamespace;
}
} }
package com.ctrip.apollo.configservice.controller; package com.ctrip.apollo.configservice.controller;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap; 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.message.Topics;
import com.ctrip.apollo.biz.service.AppNamespaceService;
import com.ctrip.apollo.core.ConfigConsts;
import com.ctrip.apollo.core.dto.ApolloConfigNotification; import com.ctrip.apollo.core.dto.ApolloConfigNotification;
import org.junit.Before; import org.junit.Before;
...@@ -15,10 +19,13 @@ import org.springframework.http.ResponseEntity; ...@@ -15,10 +19,13 @@ import org.springframework.http.ResponseEntity;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.DeferredResult;
import java.util.List;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
/** /**
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
...@@ -28,19 +35,26 @@ public class NotificationControllerTest { ...@@ -28,19 +35,26 @@ public class NotificationControllerTest {
private NotificationController controller; private NotificationController controller;
private String someAppId; private String someAppId;
private String someCluster; private String someCluster;
private String someNamespace; private String defaultNamespace;
private String somePublicNamespace;
private String someDataCenter; private String someDataCenter;
private String someReleaseId; private String someReleaseId;
@Mock @Mock
private HttpServletResponse response; private HttpServletResponse response;
private Multimap<String, DeferredResult<ResponseEntity<ApolloConfigNotification>>> deferredResults; @Mock
private AppNamespaceService appNamespaceService;
private Multimap<String, DeferredResult<ResponseEntity<ApolloConfigNotification>>>
deferredResults;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
controller = new NotificationController(); controller = new NotificationController();
ReflectionTestUtils.setField(controller, "appNamespaceService", appNamespaceService);
someAppId = "someAppId"; someAppId = "someAppId";
someCluster = "someCluster"; someCluster = "someCluster";
someNamespace = "someNamespace"; defaultNamespace = ConfigConsts.NAMESPACE_DEFAULT;
somePublicNamespace = "somePublicNamespace";
someDataCenter = "someDC"; someDataCenter = "someDC";
someReleaseId = "someRelease"; someReleaseId = "someRelease";
...@@ -51,29 +65,78 @@ public class NotificationControllerTest { ...@@ -51,29 +65,78 @@ public class NotificationControllerTest {
@Test @Test
public void testPollNotificationWithDefaultNamespace() throws Exception { public void testPollNotificationWithDefaultNamespace() throws Exception {
someNamespace = someAppId; //default namespace
DeferredResult<ResponseEntity<ApolloConfigNotification>> DeferredResult<ResponseEntity<ApolloConfigNotification>>
deferredResult = controller deferredResult = controller
.pollNotification(someAppId, someCluster, someNamespace, someDataCenter, someReleaseId, .pollNotification(someAppId, someCluster, defaultNamespace, someDataCenter, someReleaseId,
response); 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()); assertEquals(1, deferredResults.size());
assertTrue(deferredResults.get(key).contains(deferredResult)); 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 @Test
public void testPollNotificationWithDefaultNamespaceAndHandleMessage() throws Exception { 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<ResponseEntity<ApolloConfigNotification>>
deferredResult = controller deferredResult = controller
.pollNotification(someAppId, someCluster, someNamespace, someDataCenter, someReleaseId, .pollNotification(someAppId, someCluster, somePublicNamespace, someDataCenter, someReleaseId,
response); 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); controller.handleMessage(key, Topics.APOLLO_RELEASE_TOPIC);
...@@ -82,8 +145,14 @@ public class NotificationControllerTest { ...@@ -82,8 +145,14 @@ public class NotificationControllerTest {
ApolloConfigNotification notification = response.getBody(); ApolloConfigNotification notification = response.getBody();
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(someAppId, notification.getAppId()); assertEquals(somePublicNamespace, notification.getNamespace());
assertEquals(someCluster, notification.getCluster());
assertEquals(someNamespace, 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 ...@@ -19,12 +19,18 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest
private String someAppId; private String someAppId;
private String someCluster; private String someCluster;
private String someNamespace; private String someNamespace;
private String somePublicNamespace;
private String someDC;
private String someDefaultCluster;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
someAppId = "someAppId"; someAppId = "someAppId";
someCluster = "someCluster"; someCluster = "someCluster";
someNamespace = "someNamespace"; someNamespace = "someNamespace";
somePublicNamespace = "somePublicNamespace";
someDC = "someDC";
someDefaultCluster = ConfigConsts.CLUSTER_NAME_DEFAULT;
} }
@Test @Test
...@@ -83,4 +89,74 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest ...@@ -83,4 +89,74 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest
assertEquals(HttpStatus.NOT_MODIFIED, response.getStatusCode()); 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; ...@@ -10,11 +10,13 @@ import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.test.context.jdbc.Sql;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
...@@ -26,40 +28,110 @@ public class NotificationControllerIntegrationTest extends AbstractBaseIntegrati ...@@ -26,40 +28,110 @@ public class NotificationControllerIntegrationTest extends AbstractBaseIntegrati
private NotificationController notificationController; private NotificationController notificationController;
private String someAppId; private String someAppId;
private String someCluster; private String someCluster;
private String someNamespace; private String defaultNamespace;
private String somePublicNamespace;
private ExecutorService executorService; private ExecutorService executorService;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
someAppId = "someAppId"; someAppId = "someAppId";
someCluster = ConfigConsts.CLUSTER_NAME_DEFAULT; someCluster = ConfigConsts.CLUSTER_NAME_DEFAULT;
someNamespace = "someNamespace"; defaultNamespace = ConfigConsts.NAMESPACE_DEFAULT;
somePublicNamespace = "somePublicNamespace";
executorService = Executors.newSingleThreadExecutor(); executorService = Executors.newSingleThreadExecutor();
} }
@Test @Test
public void testPollNotification() throws Exception { public void testPollNotificationWithDefaultNamespace() throws Exception {
Future<ResponseEntity<ApolloConfigNotification>> future = Future<ResponseEntity<ApolloConfigNotification>> future =
executorService.submit(() -> restTemplate executorService.submit(() -> restTemplate
.getForEntity( .getForEntity(
"{baseurl}/notifications?appId={appId}&cluster={clusterName}&namespace={namespace}", "{baseurl}/notifications?appId={appId}&cluster={clusterName}&namespace={namespace}",
ApolloConfigNotification.class, ApolloConfigNotification.class,
getHostUrl(), someAppId, someCluster, someNamespace)); getHostUrl(), someAppId, someCluster, defaultNamespace));
//wait for the request connected to server //wait for the request connected to server
TimeUnit.MILLISECONDS.sleep(500); TimeUnit.MILLISECONDS.sleep(500);
notificationController.handleMessage(assembleKey(someAppId, someCluster, someNamespace), notificationController.handleMessage(assembleKey(someAppId, someCluster, defaultNamespace),
Topics.APOLLO_RELEASE_TOPIC); Topics.APOLLO_RELEASE_TOPIC);
ResponseEntity<ApolloConfigNotification> result = future.get(500, TimeUnit.MILLISECONDS); ResponseEntity<ApolloConfigNotification> result = future.get(500, TimeUnit.MILLISECONDS);
ApolloConfigNotification notification = result.getBody(); ApolloConfigNotification notification = result.getBody();
assertEquals(HttpStatus.OK, result.getStatusCode()); assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals(someAppId, notification.getAppId()); assertEquals(defaultNamespace, notification.getNamespace());
assertEquals(someCluster, notification.getCluster());
assertEquals(someNamespace, 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) { private String assembleKey(String appId, String cluster, String namespace) {
return String.format("%s-%s-%s", appId, cluster, 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 ('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', 'default');
INSERT INTO Cluster (AppId, Name) VALUES ('someAppId', 'someCluster'); 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 ('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 ('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)
INSERT INTO RELEASE (id, Name, Comment, AppId, ClusterName, NamespaceName, Configurations) VALUES (991, 'INTEGRATION-TEST-NAMESPACE','First Release','someAppId', 'someCluster', 'someNamespace', '{"k2":"v2"}'); 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; ...@@ -4,40 +4,20 @@ package com.ctrip.apollo.core.dto;
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
public class ApolloConfigNotification { public class ApolloConfigNotification {
private String appId;
private String cluster;
private String namespace; private String namespace;
//for json converter //for json converter
public ApolloConfigNotification() { public ApolloConfigNotification() {
} }
public ApolloConfigNotification(String appId, String cluster, String namespace) { public ApolloConfigNotification(String namespace) {
this.appId = appId;
this.cluster = cluster;
this.namespace = namespace; this.namespace = namespace;
} }
public String getAppId() {
return appId;
}
public String getCluster() {
return cluster;
}
public String getNamespace() { public String getNamespace() {
return namespace; return namespace;
} }
public void setAppId(String appId) {
this.appId = appId;
}
public void setCluster(String cluster) {
this.cluster = cluster;
}
public void setNamespace(String namespace) { public void setNamespace(String namespace) {
this.namespace = 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