提交 49c1b308 编写于 作者: J Jason Song

Add refresh config support and refactor

上级 51978dd6
...@@ -6,3 +6,4 @@ INSERT INTO Version (AppId, IsDeleted, Name, ReleaseId) VALUES (102, 0, '1.0', 2 ...@@ -6,3 +6,4 @@ INSERT INTO Version (AppId, IsDeleted, Name, ReleaseId) VALUES (102, 0, '1.0', 2
INSERT INTO RELEASESNAPSHOT (ClusterName, IsDeleted, ReleaseId, Configurations) VALUES ('default', 0, 1, '{"apollo.foo":"bar", "apollo.bar":"foo"}'); INSERT INTO RELEASESNAPSHOT (ClusterName, IsDeleted, ReleaseId, Configurations) VALUES ('default', 0, 1, '{"apollo.foo":"bar", "apollo.bar":"foo"}');
INSERT INTO RELEASESNAPSHOT (ClusterName, IsDeleted, ReleaseId, Configurations) VALUES ('default', 0, 2, '{"demo.foo":"demo1", "demo.bar":"demo2"}'); INSERT INTO RELEASESNAPSHOT (ClusterName, IsDeleted, ReleaseId, Configurations) VALUES ('default', 0, 2, '{"demo.foo":"demo1", "demo.bar":"demo2"}');
INSERT INTO RELEASESNAPSHOT (ClusterName, IsDeleted, ReleaseId, Configurations) VALUES ('default', 0, 3, '{"apollo.foo":"another bar", "apollo.bar_new":"foo"}');
package com.ctrip.apollo.client; package com.ctrip.apollo.client;
import com.ctrip.apollo.client.loader.ConfigLoader;
import com.ctrip.apollo.client.loader.ConfigLoaderFactory; import com.ctrip.apollo.client.loader.ConfigLoaderFactory;
import com.ctrip.apollo.client.loader.ConfigLoaderManager;
import com.ctrip.apollo.client.model.PropertyChange;
import com.ctrip.apollo.client.model.PropertySourceReloadResult;
import com.ctrip.apollo.client.util.ConfigUtil; import com.ctrip.apollo.client.util.ConfigUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
...@@ -19,6 +23,7 @@ import org.springframework.core.PriorityOrdered; ...@@ -19,6 +23,7 @@ import org.springframework.core.PriorityOrdered;
import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.MutablePropertySources;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
/** /**
...@@ -27,19 +32,18 @@ import java.util.concurrent.atomic.AtomicReference; ...@@ -27,19 +32,18 @@ import java.util.concurrent.atomic.AtomicReference;
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
public class ApolloConfigManager implements BeanDefinitionRegistryPostProcessor, PriorityOrdered, ApplicationContextAware { public class ApolloConfigManager implements BeanDefinitionRegistryPostProcessor, PriorityOrdered, ApplicationContextAware {
public static final String APOLLO_PROPERTY_SOURCE_NAME = "ApolloConfigProperties"; private static final Logger logger = LoggerFactory.getLogger(ApolloConfigManager.class);
private static AtomicReference<ApolloConfigManager> singletonProtector = new AtomicReference<ApolloConfigManager>(); private static AtomicReference<ApolloConfigManager> singletonProtector = new AtomicReference<ApolloConfigManager>();
private ConfigLoader configLoader; private ConfigLoaderManager configLoaderManager;
private ConfigurableApplicationContext applicationContext; private ConfigurableApplicationContext applicationContext;
private RefreshScope scope;
private CompositePropertySource currentPropertySource;
public ApolloConfigManager() { public ApolloConfigManager() {
if(!singletonProtector.compareAndSet(null, this)) { if(!singletonProtector.compareAndSet(null, this)) {
throw new IllegalStateException("There should be only one ApolloConfigManager instance!"); throw new IllegalStateException("There should be only one ApolloConfigManager instance!");
} }
this.configLoader = ConfigLoaderFactory.getInstance().getRemoteConfigLoader(); this.configLoaderManager = ConfigLoaderFactory.getInstance().getConfigLoaderManager();
} }
@Override @Override
...@@ -97,23 +101,42 @@ public class ApolloConfigManager implements BeanDefinitionRegistryPostProcessor, ...@@ -97,23 +101,42 @@ public class ApolloConfigManager implements BeanDefinitionRegistryPostProcessor,
} }
/** /**
* Prepare property sources * Initialize property sources
* First try to load from remote
* If loading from remote failed, then fall back to local cached properties
*/ */
void initializePropertySource() { void initializePropertySource() {
currentPropertySource = loadPropertySource(); //TODO stop application from starting when config cannot be loaded?
CompositePropertySource result = this.configLoaderManager.loadPropertySource();
updateEnvironmentPropertySource(result);
}
private void updateEnvironmentPropertySource(CompositePropertySource currentPropertySource) {
MutablePropertySources currentPropertySources = applicationContext.getEnvironment().getPropertySources(); MutablePropertySources currentPropertySources = applicationContext.getEnvironment().getPropertySources();
if (currentPropertySources.contains(currentPropertySource.getName())) { if (currentPropertySources.contains(currentPropertySource.getName())) {
currentPropertySources.remove(currentPropertySource.getName()); currentPropertySources.replace(currentPropertySource.getName(), currentPropertySource);
return;
} }
currentPropertySources.addFirst(currentPropertySource); currentPropertySources.addFirst(currentPropertySource);
} }
CompositePropertySource loadPropertySource() { public List<PropertyChange> updatePropertySource() {
CompositePropertySource compositePropertySource = new CompositePropertySource(APOLLO_PROPERTY_SOURCE_NAME); PropertySourceReloadResult result = this.configLoaderManager.reloadPropertySource();
compositePropertySource.addPropertySource(configLoader.loadPropertySource()); if (result.hasChanges()) {
return compositePropertySource; updateEnvironmentPropertySource(result.getPropertySource());
refreshBeans();
}
return result.getChanges();
}
private void refreshBeans() {
if (this.scope == null) {
this.scope = applicationContext.getBean("refreshScope", RefreshScope.class);
}
if (this.scope == null) {
logger.error("Could not get refresh scope object, skip refresh beans");
}
this.scope.refreshAll();
} }
} }
package com.ctrip.apollo.client.enums;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public enum PropertyChangeType {
NEW("New"),
MODIFIED("Modified"),
DELETED("Deleted");
private String type;
PropertyChangeType(String type) {
this.type = type;
}
public String getType() {
return type;
}
}
package com.ctrip.apollo.client.loader; package com.ctrip.apollo.client.loader;
import org.springframework.core.env.CompositePropertySource; import com.ctrip.apollo.client.model.ApolloRegistry;
import com.ctrip.apollo.core.dto.ApolloConfig;
/** /**
* @author Jason Song(songs_ctrip.com) * @author Jason Song(songs_ctrip.com)
*/ */
public interface ConfigLoader { public interface ConfigLoader {
/** ApolloConfig loadApolloConfig(ApolloRegistry apolloRegistry, ApolloConfig previous);
* Load property source for client use
* @return property source void setFallBackLoader(ConfigLoader configLoader);
*/
CompositePropertySource loadPropertySource();
} }
package com.ctrip.apollo.client.loader; package com.ctrip.apollo.client.loader;
import com.ctrip.apollo.client.loader.impl.InMemoryConfigLoader;
import com.ctrip.apollo.client.loader.impl.LocalFileConfigLoader;
import com.ctrip.apollo.client.loader.impl.RemoteConfigLoader; import com.ctrip.apollo.client.loader.impl.RemoteConfigLoader;
import com.ctrip.apollo.client.util.ConfigUtil;
import org.springframework.web.client.RestTemplate;
/** /**
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
...@@ -15,7 +19,24 @@ public class ConfigLoaderFactory { ...@@ -15,7 +19,24 @@ public class ConfigLoaderFactory {
return configLoaderFactory; return configLoaderFactory;
} }
public ConfigLoader getLocalFileConfigLoader() {
ConfigLoader configLoader = new LocalFileConfigLoader();
return configLoader;
}
public ConfigLoader getInMemoryConfigLoader() {
ConfigLoader inMemoryConfigLoader = new InMemoryConfigLoader();
inMemoryConfigLoader.setFallBackLoader(getLocalFileConfigLoader());
return inMemoryConfigLoader;
}
public ConfigLoader getRemoteConfigLoader() { public ConfigLoader getRemoteConfigLoader() {
return new RemoteConfigLoader(); ConfigLoader remoteConfigLoader = new RemoteConfigLoader(new RestTemplate(), ConfigUtil.getInstance());
// remoteConfigLoader.setFallBackLoader(getInMemoryConfigLoader());
return remoteConfigLoader;
}
public ConfigLoaderManager getConfigLoaderManager() {
return new ConfigLoaderManager(getRemoteConfigLoader(), ConfigUtil.getInstance());
} }
} }
package com.ctrip.apollo.client.loader;
import com.ctrip.apollo.client.enums.PropertyChangeType;
import com.ctrip.apollo.client.model.ApolloRegistry;
import com.ctrip.apollo.client.model.PropertyChange;
import com.ctrip.apollo.client.model.PropertySourceReloadResult;
import com.ctrip.apollo.client.util.ConfigUtil;
import com.ctrip.apollo.core.dto.ApolloConfig;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.MapPropertySource;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class ConfigLoaderManager {
public static final String APOLLO_PROPERTY_SOURCE_NAME = "ApolloConfigProperties";
private static final Logger logger = LoggerFactory.getLogger(ConfigLoaderManager.class);
private ConfigLoader configLoader;
private ConfigUtil configUtil;
private final ExecutorService executorService;
private final AtomicLong counter;
private Map<ApolloRegistry, ApolloConfig> currentApolloRegistryConfigCache;
private Map<ApolloRegistry, ApolloConfig> previousApolloRegistryConfigCache;
private List<ApolloRegistry> apolloRegistries;
public ConfigLoaderManager(ConfigLoader configLoader, ConfigUtil configUtil) {
this.configLoader = configLoader;
this.configUtil = configUtil;
this.counter = new AtomicLong();
this.executorService = Executors.newFixedThreadPool(5, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, "ConfigLoaderManager-" + counter.incrementAndGet());
return thread;
}
});
this.currentApolloRegistryConfigCache = Maps.newConcurrentMap();
}
public CompositePropertySource loadPropertySource() {
try {
apolloRegistries = configUtil.loadApolloRegistries();
} catch (IOException e) {
throw new RuntimeException("Load apollo config registry failed", e);
}
return loadPropertySourceWithApolloRegistries(apolloRegistries);
}
public PropertySourceReloadResult reloadPropertySource() {
CompositePropertySource composite = loadPropertySourceWithApolloRegistries(apolloRegistries);
List<ApolloConfig> previous = Lists.newArrayList(this.previousApolloRegistryConfigCache.values());
List<ApolloConfig> current = Lists.newArrayList(this.currentApolloRegistryConfigCache.values());
return new PropertySourceReloadResult(composite, calcPropertyChanges(previous, current));
}
/**
* Load property source with apollo registries provided
* Should not be invoked in parallel since there are some operations like create/destroy cache,
* writing to files etc.
* @param apolloRegistries
* @return
*/
private synchronized CompositePropertySource loadPropertySourceWithApolloRegistries(List<ApolloRegistry> apolloRegistries) {
resetApolloRegistryConfigCache();
CompositePropertySource composite = new CompositePropertySource(APOLLO_PROPERTY_SOURCE_NAME);
if (apolloRegistries == null || apolloRegistries.isEmpty()) {
logger.warn("No Apollo Registry found!");
return composite;
}
try {
List<ApolloConfig> apolloConfigList = loadApolloConfigs(apolloRegistries);
Collections.sort(apolloConfigList);
for (ApolloConfig apolloConfig : apolloConfigList) {
composite.addPropertySource(new MapPropertySource(assemblePropertySourceName(apolloConfig), apolloConfig.getConfigurations()));
}
return composite;
} catch (Throwable throwable) {
throw new RuntimeException("Load apollo configs failed", throwable);
}
}
List<PropertyChange> calcPropertyChanges(List<ApolloConfig> previous, List<ApolloConfig> current) {
Map<String, Object> previousMap = collectConfigurations(previous);
Map<String, Object> currentMap = collectConfigurations(current);
Set<String> previousKeys = previousMap.keySet();
Set<String> currentKeys = currentMap.keySet();
Set<String> commonKeys = Sets.intersection(previousKeys, currentKeys);
Set<String> newKeys = Sets.difference(currentKeys, commonKeys);
Set<String> removedKeys = Sets.difference(previousKeys, commonKeys);
List<PropertyChange> changes = Lists.newArrayList();
for (String newKey : newKeys) {
changes.add(new PropertyChange(newKey, null, currentMap.get(newKey), PropertyChangeType.NEW));
}
for (String removedKey : removedKeys) {
changes.add(new PropertyChange(removedKey, previousMap.get(removedKey), null, PropertyChangeType.DELETED));
}
for (String commonKey : commonKeys) {
if (previousMap.get(commonKey).equals(currentMap.get(commonKey))) {
continue;
}
changes.add(new PropertyChange(commonKey, previousMap.get(commonKey), currentMap.get(commonKey), PropertyChangeType.MODIFIED));
}
return changes;
}
Map<String, Object> collectConfigurations(List<ApolloConfig> apolloConfigs) {
Collections.sort(apolloConfigs);
Map<String, Object> configMap = Maps.newHashMap();
for (int i = apolloConfigs.size() - 1; i > -1; i--) {
configMap.putAll(apolloConfigs.get(i).getConfigurations());
}
return configMap;
}
List<ApolloConfig> loadApolloConfigs(List<ApolloRegistry> apolloRegistries) throws Throwable {
List<Future<ApolloConfig>> futures = Lists.newArrayList();
for (final ApolloRegistry apolloRegistry : apolloRegistries) {
futures.add(executorService.submit(new Callable<ApolloConfig>() {
@Override
public ApolloConfig call() throws Exception {
return loadSingleApolloConfig(apolloRegistry);
}
}));
}
List<ApolloConfig> apolloConfigList = Lists.newArrayList();
for (Future<ApolloConfig> future : futures) {
try {
ApolloConfig result = future.get();
if (result == null) {
continue;
}
apolloConfigList.add(result);
} catch (ExecutionException e) {
throw e.getCause();
}
}
return apolloConfigList;
}
ApolloConfig loadSingleApolloConfig(ApolloRegistry apolloRegistry) {
ApolloConfig result = configLoader.loadApolloConfig(apolloRegistry, getPreviousApolloConfig(apolloRegistry));
if (result == null) {
logger.error("Loaded config null...");
return null;
}
logger.info("Loaded config: {}", result);
updateCurrentApolloConfigCache(apolloRegistry, result);
return result;
}
void resetApolloRegistryConfigCache() {
this.previousApolloRegistryConfigCache = currentApolloRegistryConfigCache;
this.currentApolloRegistryConfigCache = Maps.newConcurrentMap();
}
ApolloConfig getPreviousApolloConfig(ApolloRegistry apolloRegistry) {
return previousApolloRegistryConfigCache.get(apolloRegistry);
}
void updateCurrentApolloConfigCache(ApolloRegistry apolloRegistry, ApolloConfig apolloConfig) {
currentApolloRegistryConfigCache.put(apolloRegistry, apolloConfig);
}
private String assemblePropertySourceName(ApolloConfig apolloConfig) {
return String.format("%d-%s-%s-%d", apolloConfig.getAppId(), apolloConfig.getCluster(), apolloConfig.getVersion(), apolloConfig.getReleaseId());
}
}
package com.ctrip.apollo.client.loader.impl;
import com.ctrip.apollo.client.loader.ConfigLoader;
import com.ctrip.apollo.client.model.ApolloRegistry;
import com.ctrip.apollo.core.dto.ApolloConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public abstract class AbstractConfigLoader implements ConfigLoader {
private static final Logger logger = LoggerFactory.getLogger(AbstractConfigLoader.class);
private ConfigLoader fallback;
@Override
public ApolloConfig loadApolloConfig(ApolloRegistry apolloRegistry, ApolloConfig previous) {
try {
return doLoadApolloConfig(apolloRegistry, previous);
} catch (Throwable e) {
if (this.fallback == null) {
throw new RuntimeException(String.format("Load Apollo Config failed - %s", apolloRegistry.toString()), e);
}
logger.error("Load Config via {} failed, try to use its fallback {} to load", getClass().getSimpleName(), fallback.getClass().getSimpleName(), e);
return this.fallback.loadApolloConfig(apolloRegistry, previous);
}
}
protected abstract ApolloConfig doLoadApolloConfig(ApolloRegistry apolloRegistry, ApolloConfig previous);
@Override
public void setFallBackLoader(ConfigLoader configLoader) {
this.fallback = configLoader;
}
}
package com.ctrip.apollo.client.loader.impl;
import com.ctrip.apollo.client.model.ApolloRegistry;
import com.ctrip.apollo.core.dto.ApolloConfig;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class InMemoryConfigLoader extends AbstractConfigLoader{
@Override
protected ApolloConfig doLoadApolloConfig(ApolloRegistry apolloRegistry, ApolloConfig previous) {
return null;
}
}
package com.ctrip.apollo.client.loader.impl; package com.ctrip.apollo.client.loader.impl;
import com.ctrip.apollo.client.loader.ConfigLoader; import com.ctrip.apollo.client.model.ApolloRegistry;
import org.springframework.core.env.CompositePropertySource; import com.ctrip.apollo.core.dto.ApolloConfig;
/** /**
* Load config from local backup file * Load config from local backup file
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
public class LocalConfigLoader implements ConfigLoader { public class LocalFileConfigLoader extends AbstractConfigLoader{
private static final String PROPERTY_SOURCE_NAME = "ApolloLocalConfigProperties";
@Override @Override
public CompositePropertySource loadPropertySource() { public ApolloConfig doLoadApolloConfig(ApolloRegistry apolloRegistry, ApolloConfig previous) {
return null; return null;
} }
} }
package com.ctrip.apollo.client.loader.impl; package com.ctrip.apollo.client.loader.impl;
import com.ctrip.apollo.client.loader.ConfigLoader;
import com.ctrip.apollo.client.model.ApolloRegistry; import com.ctrip.apollo.client.model.ApolloRegistry;
import com.ctrip.apollo.client.util.ConfigUtil; import com.ctrip.apollo.client.util.ConfigUtil;
import com.ctrip.apollo.core.dto.ApolloConfig; import com.ctrip.apollo.core.dto.ApolloConfig;
import com.google.common.collect.Lists; import com.google.common.collect.Maps;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.MapPropertySource;
import org.springframework.http.HttpEntity; import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
...@@ -16,126 +13,74 @@ import org.springframework.http.ResponseEntity; ...@@ -16,126 +13,74 @@ import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import java.io.IOException; import java.util.Map;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
/** /**
* Load config from remote config server * Load config from remote config server
* *
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
public class RemoteConfigLoader implements ConfigLoader { public class RemoteConfigLoader extends AbstractConfigLoader {
private static final String PROPERTY_SOURCE_NAME = "ApolloRemoteConfigProperties";
private static final Logger logger = LoggerFactory.getLogger(RemoteConfigLoader.class); private static final Logger logger = LoggerFactory.getLogger(RemoteConfigLoader.class);
private final RestTemplate restTemplate; private final RestTemplate restTemplate;
private final ConfigUtil configUtil; private final ConfigUtil configUtil;
private final ExecutorService executorService;
private final AtomicLong counter;
public RemoteConfigLoader() {
this(new RestTemplate(), ConfigUtil.getInstance());
}
public RemoteConfigLoader(RestTemplate restTemplate, ConfigUtil configUtil) { public RemoteConfigLoader(RestTemplate restTemplate, ConfigUtil configUtil) {
this.restTemplate = restTemplate; this.restTemplate = restTemplate;
this.configUtil = configUtil; this.configUtil = configUtil;
this.counter = new AtomicLong();
this.executorService = Executors.newFixedThreadPool(5, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, "RemoteConfigLoader-" + counter.incrementAndGet());
return thread;
}
});
}
@Override
public CompositePropertySource loadPropertySource() {
CompositePropertySource composite = new CompositePropertySource(PROPERTY_SOURCE_NAME);
List<ApolloRegistry> apolloRegistries;
try {
apolloRegistries = configUtil.loadApolloRegistries();
} catch (IOException e) {
throw new RuntimeException("Load apollo config registry failed", e);
}
if (apolloRegistries == null || apolloRegistries.isEmpty()) {
logger.warn("No Apollo Registry found!");
return composite;
}
try {
doLoadRemoteApolloConfig(apolloRegistries, composite);
} catch (Throwable throwable) {
throw new RuntimeException("Load remote property source failed", throwable);
}
return composite;
}
void doLoadRemoteApolloConfig(List<ApolloRegistry> apolloRegistries, CompositePropertySource compositePropertySource) throws Throwable {
List<Future<MapPropertySource>> futures = Lists.newArrayList();
for (final ApolloRegistry apolloRegistry : apolloRegistries) {
futures.add(executorService.submit(new Callable<MapPropertySource>() {
@Override
public MapPropertySource call() throws Exception {
return loadSingleApolloConfig(apolloRegistry.getAppId(), apolloRegistry.getVersion());
}
}));
}
for (Future<MapPropertySource> future : futures) {
try {
MapPropertySource result = future.get();
if (result == null) {
continue;
}
compositePropertySource.addPropertySource(result);
} catch (ExecutionException e) {
throw e.getCause();
}
}
} }
MapPropertySource loadSingleApolloConfig(long appId, String version) { ApolloConfig getRemoteConfig(RestTemplate restTemplate, String uri, String cluster, ApolloRegistry apolloRegistry, ApolloConfig previousConfig) {
ApolloConfig result = long appId = apolloRegistry.getAppId();
this.getRemoteConfig(restTemplate, configUtil.getConfigServerUrl(), appId, configUtil.getCluster(), version); String version = apolloRegistry.getVersion();
if (result == null) {
logger.error("Loaded config null...");
return null;
}
logger.info("Loaded config: {}", result);
return new MapPropertySource(assemblePropertySourceName(result), result.getConfigurations());
}
private String assemblePropertySourceName(ApolloConfig apolloConfig) {
return String.format("%d-%s-%s-%d", apolloConfig.getAppId(), apolloConfig.getCluster(), apolloConfig.getVersion(), apolloConfig.getReleaseId());
}
ApolloConfig getRemoteConfig(RestTemplate restTemplate, String uri, long appId, String cluster, String version) {
logger.info("Loading config from {}, appId={}, cluster={}, version={}", uri, appId, cluster, version); logger.info("Loading config from {}, appId={}, cluster={}, version={}", uri, appId, cluster, version);
String path = "config/{appId}/{cluster}"; String path = "/config/{appId}/{cluster}";
Object[] args = new String[] {String.valueOf(appId), cluster}; Map<String, Object> paramMap = Maps.newHashMap();
paramMap.put("appId", appId);
paramMap.put("cluster", cluster);
if (StringUtils.hasText(version)) { if (StringUtils.hasText(version)) {
args = new String[] {String.valueOf(appId), cluster, version};
path = path + "/{version}"; path = path + "/{version}";
paramMap.put("version", version);
}
if (previousConfig != null) {
path = path + "?releaseId={releaseId}";
paramMap.put("releaseId", previousConfig.getReleaseId());
} }
ResponseEntity<ApolloConfig> response = null;
ResponseEntity<ApolloConfig> response;
try { try {
// TODO retry // TODO retry
response = restTemplate.exchange(uri response = restTemplate.exchange(uri
+ path, HttpMethod.GET, new HttpEntity<Void>((Void) null), ApolloConfig.class, args); + path, HttpMethod.GET, new HttpEntity<Void>((Void) null), ApolloConfig.class, paramMap);
} catch (Exception e) { } catch (Throwable e) {
throw e; throw e;
} }
if (response == null || response.getStatusCode() != HttpStatus.OK) { if (response == null) {
throw new RuntimeException("Load apollo config failed, response is null");
}
if (response.getStatusCode() == HttpStatus.NOT_MODIFIED) {
return null; return null;
} }
if (response.getStatusCode() != HttpStatus.OK) {
throw new RuntimeException(String.format("Load apollo config failed, response status %s", response.getStatusCode()));
}
ApolloConfig result = response.getBody(); ApolloConfig result = response.getBody();
return result; return result;
} }
@Override
protected ApolloConfig doLoadApolloConfig(ApolloRegistry apolloRegistry, ApolloConfig previous) {
ApolloConfig result = this.getRemoteConfig(restTemplate,
configUtil.getConfigServerUrl(), configUtil.getCluster(),
apolloRegistry, previous);
//When remote server return 304, we need to return the previous result
return result == null ? previous : result;
}
} }
package com.ctrip.apollo.client.model; package com.ctrip.apollo.client.model;
import com.google.common.base.MoreObjects;
/** /**
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
...@@ -22,4 +24,13 @@ public class ApolloRegistry { ...@@ -22,4 +24,13 @@ public class ApolloRegistry {
public void setVersion(String version) { public void setVersion(String version) {
this.version = version; this.version = version;
} }
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.omitNullValues()
.add("appId", appId)
.add("version", version)
.toString();
}
} }
package com.ctrip.apollo.client.model;
import com.ctrip.apollo.client.enums.PropertyChangeType;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class PropertyChange {
private String propertyName;
private Object oldValue;
private Object newValue;
private PropertyChangeType changeType;
public PropertyChange(String propertyName, Object oldValue, Object newValue, PropertyChangeType changeType) {
this.propertyName = propertyName;
this.oldValue = oldValue;
this.newValue = newValue;
this.changeType = changeType;
}
public String getPropertyName() {
return propertyName;
}
public void setPropertyName(String propertyName) {
this.propertyName = propertyName;
}
public Object getOldValue() {
return oldValue;
}
public void setOldValue(Object oldValue) {
this.oldValue = oldValue;
}
public Object getNewValue() {
return newValue;
}
public void setNewValue(Object newValue) {
this.newValue = newValue;
}
public PropertyChangeType getChangeType() {
return changeType;
}
public void setChangeType(PropertyChangeType changeType) {
this.changeType = changeType;
}
}
package com.ctrip.apollo.client.model;
import com.google.common.collect.Lists;
import org.springframework.core.env.CompositePropertySource;
import java.util.List;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class PropertySourceReloadResult {
private CompositePropertySource propertySource;
private List<PropertyChange> changes;
public PropertySourceReloadResult(CompositePropertySource propertySource) {
this.propertySource = propertySource;
changes = Lists.newArrayList();
}
public PropertySourceReloadResult(CompositePropertySource propertySource, List<PropertyChange> changes) {
this.propertySource = propertySource;
this.changes = changes;
}
public CompositePropertySource getPropertySource() {
return propertySource;
}
public void setPropertySource(CompositePropertySource propertySource) {
this.propertySource = propertySource;
}
public List<PropertyChange> getChanges() {
return changes;
}
public void setChanges(List<PropertyChange> changes) {
this.changes = changes;
}
public boolean hasChanges() {
return !changes.isEmpty();
}
}
...@@ -33,7 +33,7 @@ public class ConfigUtil { ...@@ -33,7 +33,7 @@ public class ConfigUtil {
public String getConfigServerUrl() { public String getConfigServerUrl() {
// TODO return the meta server url based on different environments // TODO return the meta server url based on different environments
return "http://localhost:8888"; return "http://localhost";
} }
public String getCluster() { public String getCluster() {
......
package com.ctrip.apollo.client; package com.ctrip.apollo.client;
import com.ctrip.apollo.client.loader.ConfigLoaderManagerTest;
import com.ctrip.apollo.client.loader.impl.RemoteConfigLoaderTest; import com.ctrip.apollo.client.loader.impl.RemoteConfigLoaderTest;
import com.ctrip.apollo.client.util.ConfigUtilTest; import com.ctrip.apollo.client.util.ConfigUtilTest;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
...@@ -8,7 +9,8 @@ import org.junit.runners.Suite.SuiteClasses; ...@@ -8,7 +9,8 @@ import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class) @RunWith(Suite.class)
@SuiteClasses({ @SuiteClasses({
ApolloConfigManagerTest.class, RemoteConfigLoaderTest.class, ConfigUtilTest.class ApolloConfigManagerTest.class, ConfigLoaderManagerTest.class, RemoteConfigLoaderTest.class,
ConfigUtilTest.class
}) })
public class AllTests { public class AllTests {
......
package com.ctrip.apollo.client; package com.ctrip.apollo.client;
import com.ctrip.apollo.client.loader.ConfigLoader; import com.ctrip.apollo.client.loader.ConfigLoaderManager;
import com.ctrip.apollo.client.model.PropertyChange;
import com.ctrip.apollo.client.model.PropertySourceReloadResult;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
...@@ -11,6 +12,7 @@ import org.mockito.Mock; ...@@ -11,6 +12,7 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.CompositePropertySource;
...@@ -18,10 +20,11 @@ import org.springframework.core.env.ConfigurableEnvironment; ...@@ -18,10 +20,11 @@ import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.MutablePropertySources;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
/** /**
...@@ -31,7 +34,7 @@ import static org.mockito.Mockito.*; ...@@ -31,7 +34,7 @@ import static org.mockito.Mockito.*;
public class ApolloConfigManagerTest { public class ApolloConfigManagerTest {
private ApolloConfigManager apolloConfigManager; private ApolloConfigManager apolloConfigManager;
@Mock @Mock
private ConfigLoader configLoader; private ConfigLoaderManager configLoaderManager;
@Mock @Mock
private ConfigurableApplicationContext applicationContext; private ConfigurableApplicationContext applicationContext;
@Mock @Mock
...@@ -40,6 +43,8 @@ public class ApolloConfigManagerTest { ...@@ -40,6 +43,8 @@ public class ApolloConfigManagerTest {
private MutablePropertySources mutablePropertySources; private MutablePropertySources mutablePropertySources;
@Mock @Mock
private BeanDefinitionRegistry beanDefinitionRegistry; private BeanDefinitionRegistry beanDefinitionRegistry;
@Mock
private RefreshScope scope;
@Before @Before
public void setUp() { public void setUp() {
...@@ -49,7 +54,8 @@ public class ApolloConfigManagerTest { ...@@ -49,7 +54,8 @@ public class ApolloConfigManagerTest {
when(env.getPropertySources()).thenReturn(mutablePropertySources); when(env.getPropertySources()).thenReturn(mutablePropertySources);
apolloConfigManager.setApplicationContext(applicationContext); apolloConfigManager.setApplicationContext(applicationContext);
ReflectionTestUtils.setField(apolloConfigManager, "configLoader", configLoader); ReflectionTestUtils.setField(apolloConfigManager, "configLoaderManager", configLoaderManager);
ReflectionTestUtils.setField(apolloConfigManager, "scope", scope);
} }
@After @After
...@@ -66,21 +72,20 @@ public class ApolloConfigManagerTest { ...@@ -66,21 +72,20 @@ public class ApolloConfigManagerTest {
} }
@Test @Test
public void testPreparePropertySourceSuccessfully() { public void testInitializePropertySourceSuccessfully() {
CompositePropertySource somePropertySource = mock(CompositePropertySource.class); CompositePropertySource somePropertySource = mock(CompositePropertySource.class);
final ArgumentCaptor<CompositePropertySource> captor = ArgumentCaptor.forClass(CompositePropertySource.class); final ArgumentCaptor<CompositePropertySource> captor = ArgumentCaptor.forClass(CompositePropertySource.class);
when(configLoader.loadPropertySource()).thenReturn(somePropertySource); when(configLoaderManager.loadPropertySource()).thenReturn(somePropertySource);
apolloConfigManager.initializePropertySource(); apolloConfigManager.initializePropertySource();
verify(configLoader, times(1)).loadPropertySource(); verify(configLoaderManager, times(1)).loadPropertySource();
verify(mutablePropertySources, times(1)).addFirst(captor.capture()); verify(mutablePropertySources, times(1)).addFirst(captor.capture());
final CompositePropertySource insertedPropertySource = captor.getValue(); final CompositePropertySource insertedPropertySource = captor.getValue();
assertEquals(ApolloConfigManager.APOLLO_PROPERTY_SOURCE_NAME, insertedPropertySource.getName()); assertEquals(insertedPropertySource, somePropertySource);
assertTrue(insertedPropertySource.getPropertySources().contains(somePropertySource));
} }
@Test @Test
...@@ -92,4 +97,38 @@ public class ApolloConfigManagerTest { ...@@ -92,4 +97,38 @@ public class ApolloConfigManagerTest {
verify(beanDefinitionRegistry, times(2)).registerBeanDefinition(anyString(), any(BeanDefinition.class)); verify(beanDefinitionRegistry, times(2)).registerBeanDefinition(anyString(), any(BeanDefinition.class));
} }
@Test
public void testUpdatePropertySourceWithChanges() throws Exception {
PropertySourceReloadResult somePropertySourceReloadResult = mock(PropertySourceReloadResult.class);
CompositePropertySource somePropertySource = mock(CompositePropertySource.class);
List<PropertyChange> someChanges = mock(List.class);
when(somePropertySourceReloadResult.hasChanges()).thenReturn(true);
when(somePropertySourceReloadResult.getPropertySource()).thenReturn(somePropertySource);
when(somePropertySourceReloadResult.getChanges()).thenReturn(someChanges);
when(configLoaderManager.reloadPropertySource()).thenReturn(somePropertySourceReloadResult);
List<PropertyChange> result = apolloConfigManager.updatePropertySource();
assertEquals(someChanges, result);
verify(scope, times(1)).refreshAll();
}
@Test
public void testUpdatePropertySourceWithNoChange() throws Exception {
PropertySourceReloadResult somePropertySourceReloadResult = mock(PropertySourceReloadResult.class);
CompositePropertySource somePropertySource = mock(CompositePropertySource.class);
List<PropertyChange> emptyChanges = Collections.emptyList();
when(somePropertySourceReloadResult.hasChanges()).thenReturn(false);
when(somePropertySourceReloadResult.getPropertySource()).thenReturn(somePropertySource);
when(somePropertySourceReloadResult.getChanges()).thenReturn(emptyChanges);
when(configLoaderManager.reloadPropertySource()).thenReturn(somePropertySourceReloadResult);
List<PropertyChange> result = apolloConfigManager.updatePropertySource();
assertEquals(emptyChanges, result);
verify(scope, never()).refreshAll();
}
} }
package com.ctrip.apollo.client.loader;
import com.ctrip.apollo.client.enums.PropertyChangeType;
import com.ctrip.apollo.client.model.ApolloRegistry;
import com.ctrip.apollo.client.model.PropertyChange;
import com.ctrip.apollo.client.model.PropertySourceReloadResult;
import com.ctrip.apollo.client.util.ConfigUtil;
import com.ctrip.apollo.core.dto.ApolloConfig;
import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.ObjectArrays;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@RunWith(MockitoJUnitRunner.class)
public class ConfigLoaderManagerTest {
private ConfigLoaderManager configLoaderManager;
@Mock
private ConfigLoader configLoader;
@Mock
private ConfigUtil configUtil;
@Before
public void setUp() {
configLoaderManager = spy(new ConfigLoaderManager(configLoader, configUtil));
}
@Test
public void testLoadPropertySource() throws Exception {
long someAppId = 100;
long anotherAppId = 101;
ApolloRegistry someApolloRegistry = assembleSomeApolloRegistry(someAppId, "someVersion");
ApolloRegistry anotherApolloRegistry = assembleSomeApolloRegistry(anotherAppId, "anotherVersion");
ApolloConfig someApolloConfig = mock(ApolloConfig.class);
ApolloConfig anotherApolloConfig = mock(ApolloConfig.class);
Map<String, Object> someMap = mock(Map.class);
Map<String, Object> anotherMap = mock(Map.class);
when(someApolloConfig.getAppId()).thenReturn(someAppId);
when(someApolloConfig.getAppId()).thenReturn(anotherAppId);
when(configUtil.loadApolloRegistries()).thenReturn(Lists.newArrayList(someApolloRegistry, anotherApolloRegistry));
doReturn(someApolloConfig).when(configLoaderManager).loadSingleApolloConfig(someApolloRegistry);
doReturn(anotherApolloConfig).when(configLoaderManager).loadSingleApolloConfig(anotherApolloRegistry);
when(someApolloConfig.getConfigurations()).thenReturn(someMap);
when(anotherApolloConfig.getConfigurations()).thenReturn(anotherMap);
CompositePropertySource result = configLoaderManager.loadPropertySource();
assertEquals(2, result.getPropertySources().size());
List<Map<String, Object>> resultMaps = FluentIterable.from(result.getPropertySources()).transform(new Function<PropertySource<?>, Map<String, Object>>() {
@Override
public Map<String, Object> apply(PropertySource<?> input) {
return (Map<String, Object>)input.getSource();
}
}).toList();
assertTrue(resultMaps.containsAll(Lists.newArrayList(someMap, anotherMap)));
}
@Test(expected = RuntimeException.class)
public void testLoadPropertySourceWithError() throws Exception {
Exception someException = mock(Exception.class);
long someAppId = 100;
ApolloRegistry someApolloRegistry = assembleSomeApolloRegistry(someAppId, "someVersion");
when(configUtil.loadApolloRegistries()).thenReturn(Lists.newArrayList(someApolloRegistry));
doThrow(someException).when(configLoaderManager).loadSingleApolloConfig(someApolloRegistry);
configLoaderManager.loadPropertySource();
}
@Test
public void testLoadApolloConfigsWithNoApolloRegistry() throws Exception {
when(configUtil.loadApolloRegistries()).thenReturn(null);
CompositePropertySource result = configLoaderManager.loadPropertySource();
assertTrue(result.getPropertySources().isEmpty());
}
@Test
public void testLoadSingleApolloConfig() throws Exception {
ApolloConfig someApolloConfig = mock(ApolloConfig.class);
Map<String, Object> someMap = Maps.newHashMap();
long someAppId = 100;
ApolloRegistry someApolloRegistry = assembleSomeApolloRegistry(someAppId, "someVersion");
ApolloConfig previousConfig = null;
doReturn(null).when(configLoaderManager).getPreviousApolloConfig(someApolloRegistry);
when(someApolloConfig.getConfigurations()).thenReturn(someMap);
when(configLoader.loadApolloConfig(someApolloRegistry, previousConfig)).thenReturn(someApolloConfig);
ApolloConfig result = configLoaderManager.loadSingleApolloConfig(someApolloRegistry);
assertEquals(someMap, result.getConfigurations());
}
@Test
public void testReloadPropertySource() throws Exception {
long someAppId = 100;
ApolloRegistry someApolloRegistry = assembleSomeApolloRegistry(someAppId, "someVersion");
ApolloConfig someApolloConfig = mock(ApolloConfig.class);
Map<String, Object> someMap = mock(Map.class);
List<PropertyChange> someChanges = mock(List.class);
ReflectionTestUtils.setField(configLoaderManager, "apolloRegistries", Lists.newArrayList(someApolloRegistry));
doReturn(someApolloConfig).when(configLoaderManager).loadSingleApolloConfig(someApolloRegistry);
when(someApolloConfig.getAppId()).thenReturn(someAppId);
when(someApolloConfig.getConfigurations()).thenReturn(someMap);
doReturn(someChanges).when(configLoaderManager).calcPropertyChanges(anyList(), anyList());
PropertySourceReloadResult result = configLoaderManager.reloadPropertySource();
assertEquals(1, result.getPropertySource().getPropertySources().size());
assertEquals(someChanges, result.getChanges());
List<Map<String, Object>> resultMaps = FluentIterable.from(result.getPropertySource().getPropertySources()).transform(new Function<PropertySource<?>, Map<String, Object>>() {
@Override
public Map<String, Object> apply(PropertySource<?> input) {
return (Map<String, Object>)input.getSource();
}
}).toList();
assertTrue(resultMaps.containsAll(Lists.newArrayList(someMap)));
}
@Test
public void testCalcPropertyChanges() throws Exception {
long someAppId = 1;
Map<String, Object> someConfig = Maps.newHashMap();
someConfig.put("key1", "val1");
someConfig.put("key2", "val2");
Map<String, Object> anotherConfig = Maps.newHashMap();
anotherConfig.put("key1", "val11");
anotherConfig.put("key3", "val3");
List<ApolloConfig> previous = Lists.newArrayList(assembleApolloConfig(someAppId, someConfig));
List<ApolloConfig> current = Lists.newArrayList(assembleApolloConfig(someAppId, anotherConfig));
List<PropertyChange> changes = configLoaderManager.calcPropertyChanges(previous, current);
assertEquals(3, changes.size());
List<String> changeResult = FluentIterable.from(changes).transform(new Function<PropertyChange, String>() {
@Override
public String apply(PropertyChange input) {
return String.format("%s-%s", input.getPropertyName(), input.getChangeType());
}
}).toList();
assertTrue(changeResult.containsAll(
Lists.newArrayList(
"key1-" + PropertyChangeType.MODIFIED,
"key2-" + PropertyChangeType.DELETED,
"key3-" + PropertyChangeType.NEW
)));
}
ApolloConfig assembleApolloConfig(long appId, Map<String, Object> configurations) {
String someCluster = "someCluster";
String someVersion = "someVersion";
long someReleaseId = 1;
ApolloConfig config = new ApolloConfig(appId, someCluster, someVersion, someReleaseId);
config.setConfigurations(configurations);
return config;
}
private ApolloRegistry assembleSomeApolloRegistry(long someAppId, String someVersion) {
ApolloRegistry someApolloRegistry = new ApolloRegistry();
someApolloRegistry.setAppId(someAppId);
someApolloRegistry.setVersion(someVersion);
return someApolloRegistry;
}
}
...@@ -12,11 +12,16 @@ import org.mockito.Mock; ...@@ -12,11 +12,16 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MapPropertySource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import java.util.Map; import java.util.Map;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
...@@ -29,6 +34,8 @@ public class RemoteConfigLoaderTest { ...@@ -29,6 +34,8 @@ public class RemoteConfigLoaderTest {
@Mock @Mock
private RestTemplate restTemplate; private RestTemplate restTemplate;
private ConfigUtil configUtil; private ConfigUtil configUtil;
@Mock
private ResponseEntity<ApolloConfig> someResponse;
@Before @Before
public void setUp() { public void setUp() {
...@@ -37,57 +44,78 @@ public class RemoteConfigLoaderTest { ...@@ -37,57 +44,78 @@ public class RemoteConfigLoaderTest {
} }
@Test @Test
public void testLoadPropertySource() throws Exception { public void testLoadApolloConfig() throws Exception {
long someAppId = 100; String someServerUrl = "http://someUrl";
long anotherAppId = 101; String someCluster = "some cluster";
ApolloRegistry someApolloRegistry = assembleSomeApolloRegistry(someAppId, "someVersion"); ApolloConfig apolloConfig = mock(ApolloConfig.class);
ApolloRegistry anotherApolloRegistry = assembleSomeApolloRegistry(anotherAppId, "anotherVersion"); long someAppId = 1;
MapPropertySource somePropertySource = mock(MapPropertySource.class); ApolloRegistry apolloRegistry = assembleSomeApolloRegistry(someAppId, "someVersion");
MapPropertySource anotherPropertySource = mock(MapPropertySource.class); ApolloConfig previousConfig = null;
doReturn(Lists.newArrayList(someApolloRegistry, anotherApolloRegistry)).when(configUtil).loadApolloRegistries(); when(configUtil.getConfigServerUrl()).thenReturn(someServerUrl);
doReturn(somePropertySource).when(remoteConfigLoader).loadSingleApolloConfig(someApolloRegistry.getAppId(), someApolloRegistry.getVersion()); when(configUtil.getCluster()).thenReturn(someCluster);
doReturn(anotherPropertySource).when(remoteConfigLoader).loadSingleApolloConfig(anotherApolloRegistry.getAppId(), anotherApolloRegistry.getVersion()); doReturn(apolloConfig).when(remoteConfigLoader)
.getRemoteConfig(restTemplate, someServerUrl, someCluster, apolloRegistry, previousConfig);
CompositePropertySource result = remoteConfigLoader.loadPropertySource();
ApolloConfig result = remoteConfigLoader.loadApolloConfig(apolloRegistry, previousConfig);
assertEquals(2, result.getPropertySources().size());
assertTrue(result.getPropertySources().containsAll(Lists.newArrayList(somePropertySource, anotherPropertySource))); assertEquals(apolloConfig, result);
} }
@Test @Test
public void testLoadPropertySourceWithNoApolloRegistry() throws Exception { public void testGetRemoteConfig() throws Exception {
doReturn(null).when(configUtil).loadApolloRegistries(); long someAppId = 1;
String someServerUrl = "http://someServer";
String someClusterName = "someCluster";
String someVersionName = "someVersion";
ApolloConfig someApolloConfig = mock(ApolloConfig.class);
ApolloRegistry apolloRegistry = assembleSomeApolloRegistry(someAppId, someVersionName);
ApolloConfig previousConfig = null;
CompositePropertySource result = remoteConfigLoader.loadPropertySource(); when(someResponse.getStatusCode()).thenReturn(HttpStatus.OK);
when(someResponse.getBody()).thenReturn(someApolloConfig);
when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class),
eq(ApolloConfig.class), anyMap())).thenReturn(someResponse);
assertTrue(result.getPropertySources().isEmpty()); ApolloConfig result = remoteConfigLoader.getRemoteConfig(restTemplate, someServerUrl, someClusterName, apolloRegistry, previousConfig);
assertEquals(someApolloConfig, result);
} }
@Test(expected = RuntimeException.class) @Test(expected = RuntimeException.class)
public void testLoadPropertySourceWithError() throws Exception { public void testGetRemoteConfigWithServerError() throws Exception {
Exception someException = mock(Exception.class); long someAppId = 1;
long someAppId = 100; String someServerUrl = "http://someServer";
ApolloRegistry someApolloRegistry = assembleSomeApolloRegistry(someAppId, "someVersion"); String someClusterName = "someCluster";
doReturn(Lists.newArrayList(someApolloRegistry)).when(configUtil).loadApolloRegistries(); String someVersionName = "someVersion";
ApolloRegistry apolloRegistry = assembleSomeApolloRegistry(someAppId, someVersionName);
doThrow(someException).when(remoteConfigLoader).loadSingleApolloConfig(someApolloRegistry.getAppId(), someApolloRegistry.getVersion()); ApolloConfig previousConfig = null;
HttpStatus someErrorCode = HttpStatus.INTERNAL_SERVER_ERROR;
remoteConfigLoader.loadPropertySource();
when(someResponse.getStatusCode()).thenReturn(someErrorCode);
when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class),
eq(ApolloConfig.class), anyMap())).thenReturn(someResponse);
remoteConfigLoader.getRemoteConfig(restTemplate, someServerUrl, someClusterName, apolloRegistry, previousConfig);
} }
@Test @Test
public void testLoadSingleApolloConfig() throws Exception { public void testGetRemoteConfigWith304Response() throws Exception {
ApolloConfig someApolloConfig = mock(ApolloConfig.class); long someAppId = 1;
Map<String, Object> someMap = Maps.newHashMap(); String someServerUrl = "http://someServer";
String someClusterName = "someCluster";
String someVersionName = "someVersion";
ApolloRegistry apolloRegistry = assembleSomeApolloRegistry(someAppId, someVersionName);
ApolloConfig previousConfig = null;
when(someResponse.getStatusCode()).thenReturn(HttpStatus.NOT_MODIFIED);
when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class),
eq(ApolloConfig.class), anyMap())).thenReturn(someResponse);
when(someApolloConfig.getConfigurations()).thenReturn(someMap); ApolloConfig result = remoteConfigLoader.getRemoteConfig(restTemplate, someServerUrl, someClusterName, apolloRegistry, previousConfig);
doReturn(someApolloConfig).when(remoteConfigLoader).getRemoteConfig(any(RestTemplate.class), anyString(), anyLong(), anyString(), anyString());
long someAppId = 100; assertNull(result);
MapPropertySource result = remoteConfigLoader.loadSingleApolloConfig(someAppId, "someVersion");
assertEquals(someMap, result.getSource());
} }
private ApolloRegistry assembleSomeApolloRegistry(long someAppId, String someVersion) { private ApolloRegistry assembleSomeApolloRegistry(long someAppId, String someVersion) {
......
...@@ -3,10 +3,7 @@ package com.ctrip.apollo.configservice.controller; ...@@ -3,10 +3,7 @@ package com.ctrip.apollo.configservice.controller;
import com.ctrip.apollo.biz.entity.Version; import com.ctrip.apollo.biz.entity.Version;
import com.ctrip.apollo.biz.service.ConfigService; import com.ctrip.apollo.biz.service.ConfigService;
import com.ctrip.apollo.core.dto.ApolloConfig; import com.ctrip.apollo.core.dto.ApolloConfig;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
...@@ -21,7 +18,7 @@ public class ConfigController { ...@@ -21,7 +18,7 @@ public class ConfigController {
@Resource(name = "configService") @Resource(name = "configService")
private ConfigService configService; private ConfigService configService;
@RequestMapping(value = "/{appId}/{clusterName}/{versionName:.*}") @RequestMapping(value = "/{appId}/{clusterName}/{versionName:.*}", method = RequestMethod.GET)
public ApolloConfig queryConfig(@PathVariable long appId, public ApolloConfig queryConfig(@PathVariable long appId,
@PathVariable String clusterName, @PathVariable String clusterName,
@PathVariable String versionName, @PathVariable String versionName,
......
...@@ -4,12 +4,13 @@ import com.fasterxml.jackson.annotation.JsonCreator; ...@@ -4,12 +4,13 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects;
import java.util.Comparator;
import java.util.Map; import java.util.Map;
/** /**
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
public class ApolloConfig { public class ApolloConfig implements Comparable<ApolloConfig> {
private long appId; private long appId;
...@@ -21,6 +22,8 @@ public class ApolloConfig { ...@@ -21,6 +22,8 @@ public class ApolloConfig {
private long releaseId; private long releaseId;
private int order;
@JsonCreator @JsonCreator
public ApolloConfig(@JsonProperty("appId") long appId, public ApolloConfig(@JsonProperty("appId") long appId,
@JsonProperty("cluster") String cluster, @JsonProperty("cluster") String cluster,
...@@ -57,6 +60,14 @@ public class ApolloConfig { ...@@ -57,6 +60,14 @@ public class ApolloConfig {
return releaseId; return releaseId;
} }
public int getOrder() {
return order;
}
public void setOrder(int order) {
this.order = order;
}
@Override @Override
public String toString() { public String toString() {
return MoreObjects.toStringHelper(this) return MoreObjects.toStringHelper(this)
...@@ -68,4 +79,15 @@ public class ApolloConfig { ...@@ -68,4 +79,15 @@ public class ApolloConfig {
.add("configurations", configurations) .add("configurations", configurations)
.toString(); .toString();
} }
@Override
public int compareTo(ApolloConfig o) {
if (o == null || this.getOrder() > o.getOrder()) {
return 1;
}
if (o.getOrder() > this.getOrder()) {
return -1;
}
return 0;
}
} }
package com.ctrip.apollo.demo.controller; package com.ctrip.apollo.demo.controller;
import com.ctrip.apollo.client.ApolloConfigManager;
import com.ctrip.apollo.client.model.ApolloRegistry; import com.ctrip.apollo.client.model.ApolloRegistry;
import com.ctrip.apollo.client.model.PropertyChange;
import com.ctrip.apollo.client.util.ConfigUtil; import com.ctrip.apollo.client.util.ConfigUtil;
import com.ctrip.apollo.demo.model.Config; import com.ctrip.apollo.demo.model.Config;
import com.ctrip.apollo.demo.service.DemoService; import com.ctrip.apollo.demo.service.DemoService;
...@@ -27,11 +29,12 @@ public class DemoController { ...@@ -27,11 +29,12 @@ public class DemoController {
private Environment env; private Environment env;
@Autowired @Autowired
private DemoService demoService; private DemoService demoService;
//Apollo config client internal impl, not intended to be used by application, only for this test page //Apollo config client internal impl, not intended to be used by application, only for this test page!
private ConfigUtil configUtil = ConfigUtil.getInstance(); private ConfigUtil configUtil = ConfigUtil.getInstance();
//ApolloConfigManager, not intended to be used by application, only for this test page!
@Autowired @Autowired
private RefreshScope scope; private ApolloConfigManager apolloConfigManager;
@RequestMapping(value = "/config/{configName:.*}", method = RequestMethod.GET) @RequestMapping(value = "/config/{configName:.*}", method = RequestMethod.GET)
public Config queryConfig(@PathVariable String configName) { public Config queryConfig(@PathVariable String configName) {
...@@ -49,8 +52,8 @@ public class DemoController { ...@@ -49,8 +52,8 @@ public class DemoController {
} }
@RequestMapping(value = "/refresh", method = RequestMethod.POST) @RequestMapping(value = "/refresh", method = RequestMethod.POST)
public String refreshBeans() { public List<PropertyChange> refreshBeans() {
this.scope.refreshAll(); List<PropertyChange> changes = this.apolloConfigManager.updatePropertySource();
return "ok"; return changes;
} }
} }
...@@ -7,11 +7,11 @@ ...@@ -7,11 +7,11 @@
]); ]);
app.controller('DemoController', function ($scope, $http, $modal, toastr) { app.controller('DemoController', function ($scope, $http, $modal, toastr) {
//var NONE = "none"; var NONE = "none";
this.registries = {}; this.registries = {};
this.configQuery = {}; this.configQuery = {};
//this.refreshResult = NONE; this.refreshResult = NONE;
this.injectedConfigValue = ''; this.injectedConfigValue = '';
var self = this; var self = this;
...@@ -46,24 +46,26 @@ ...@@ -46,24 +46,26 @@
}); });
}; };
//this.refreshConfig = function() { this.refreshConfig = function() {
// $http.post("refresh") $http.post("demo/refresh")
// .success(function(data) { .success(function(data) {
// self.assembleRefreshResult(data); self.assembleRefreshResult(data);
// }) })
// .error(function(data, status) { .error(function(data, status) {
// toastr.error((data && data.msg) || 'Refresh config failed'); toastr.error((data && data.msg) || 'Refresh config failed');
// }); });
//
//};
//this.assembleRefreshResult = function(changedPropertyArray) { };
// if(!changedPropertyArray || !changedPropertyArray.length) {
// this.refreshResult = NONE; this.assembleRefreshResult = function(changedPropertyArray) {
// return; if(!changedPropertyArray || !changedPropertyArray.length) {
// } this.refreshResult = NONE;
// this.refreshResult = changedPropertyArray.join(','); return;
//}; }
this.refreshResult = _.map(changedPropertyArray, function(propertyChange) {
return propertyChange.propertyName + '(' + propertyChange.changeType + ')';
});
};
this.loadRegistries(); this.loadRegistries();
......
...@@ -70,12 +70,14 @@ ...@@ -70,12 +70,14 @@
</form> </form>
</div> </div>
<!--<div id="refresh-config-wrapper">--> <div id="refresh-config-wrapper">
<!--<h3>Refresh Config:</h3>--> <h3>Refresh Config:</h3>
<!--<button type="button" class="btn btn-primary" ng-click="demoCtrl.refreshConfig()">Refresh Config</button>--> <button type="button" class="btn btn-primary" ng-click="demoCtrl.refreshConfig()">Refresh
<!--<div id="refresh-result">--> Config
<!--<strong>Changed Properties:</strong> {{demoCtrl.refreshResult}}--> </button>
<!--</div>--> <div id="refresh-result">
<!--</div>--> <strong>Changed Properties:</strong> {{demoCtrl.refreshResult}}
</div>
</div>
</div> </div>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册