Commit a81d7287 authored by Anilople's avatar Anilople Committed by GitHub

feat(apollo-portal): read dynamic environment from key...

feat(apollo-portal): read dynamic environment from key "apollo.portal.meta.servers" in PortalDB.ServerConfig (#2953)
parent 0769c80f
package com.ctrip.framework.apollo.portal.component; package com.ctrip.framework.apollo.portal.component;
import com.ctrip.framework.apollo.portal.environment.PortalMetaDomainConsts; import com.ctrip.framework.apollo.portal.environment.PortalMetaDomainService;
import com.ctrip.framework.apollo.core.dto.ServiceDTO; import com.ctrip.framework.apollo.core.dto.ServiceDTO;
import com.ctrip.framework.apollo.portal.environment.Env; import com.ctrip.framework.apollo.portal.environment.Env;
import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory; import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory;
...@@ -39,13 +39,17 @@ public class AdminServiceAddressLocator { ...@@ -39,13 +39,17 @@ public class AdminServiceAddressLocator {
private final PortalSettings portalSettings; private final PortalSettings portalSettings;
private final RestTemplateFactory restTemplateFactory; private final RestTemplateFactory restTemplateFactory;
private final PortalMetaDomainService portalMetaDomainService;
public AdminServiceAddressLocator( public AdminServiceAddressLocator(
final HttpMessageConverters httpMessageConverters, final HttpMessageConverters httpMessageConverters,
final PortalSettings portalSettings, final PortalSettings portalSettings,
final RestTemplateFactory restTemplateFactory) { final RestTemplateFactory restTemplateFactory,
final PortalMetaDomainService portalMetaDomainService
) {
this.portalSettings = portalSettings; this.portalSettings = portalSettings;
this.restTemplateFactory = restTemplateFactory; this.restTemplateFactory = restTemplateFactory;
this.portalMetaDomainService = portalMetaDomainService;
} }
@PostConstruct @PostConstruct
...@@ -106,17 +110,17 @@ public class AdminServiceAddressLocator { ...@@ -106,17 +110,17 @@ public class AdminServiceAddressLocator {
return true; return true;
} catch (Throwable e) { } catch (Throwable e) {
logger.error(String.format("Get admin server address from meta server failed. env: %s, meta server address:%s", logger.error(String.format("Get admin server address from meta server failed. env: %s, meta server address:%s",
env, PortalMetaDomainConsts.getDomain(env)), e); env, portalMetaDomainService.getDomain(env)), e);
Tracer Tracer
.logError(String.format("Get admin server address from meta server failed. env: %s, meta server address:%s", .logError(String.format("Get admin server address from meta server failed. env: %s, meta server address:%s",
env, PortalMetaDomainConsts.getDomain(env)), e); env, portalMetaDomainService.getDomain(env)), e);
} }
} }
return false; return false;
} }
private ServiceDTO[] getAdminServerAddress(Env env) { private ServiceDTO[] getAdminServerAddress(Env env) {
String domainName = PortalMetaDomainConsts.getDomain(env); String domainName = portalMetaDomainService.getDomain(env);
String url = domainName + ADMIN_SERVICE_URL_PATH; String url = domainName + ADMIN_SERVICE_URL_PATH;
return restTemplate.getForObject(url, ServiceDTO[].class); return restTemplate.getForObject(url, ServiceDTO[].class);
} }
......
package com.ctrip.framework.apollo.portal.component; package com.ctrip.framework.apollo.portal.component;
import com.ctrip.framework.apollo.portal.environment.PortalMetaDomainConsts; import com.ctrip.framework.apollo.portal.environment.PortalMetaDomainService;
import com.ctrip.framework.apollo.portal.environment.Env; import com.ctrip.framework.apollo.portal.environment.Env;
import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory; import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI; import com.ctrip.framework.apollo.portal.api.AdminServiceAPI;
...@@ -31,15 +31,21 @@ public class PortalSettings { ...@@ -31,15 +31,21 @@ public class PortalSettings {
private final ApplicationContext applicationContext; private final ApplicationContext applicationContext;
private final PortalConfig portalConfig; private final PortalConfig portalConfig;
private final PortalMetaDomainService portalMetaDomainService;
private List<Env> allEnvs = new ArrayList<>(); private List<Env> allEnvs = new ArrayList<>();
//mark env up or down //mark env up or down
private Map<Env, Boolean> envStatusMark = new ConcurrentHashMap<>(); private Map<Env, Boolean> envStatusMark = new ConcurrentHashMap<>();
public PortalSettings(final ApplicationContext applicationContext, final PortalConfig portalConfig) { public PortalSettings(
final ApplicationContext applicationContext,
final PortalConfig portalConfig,
final PortalMetaDomainService portalMetaDomainService
) {
this.applicationContext = applicationContext; this.applicationContext = applicationContext;
this.portalConfig = portalConfig; this.portalConfig = portalConfig;
this.portalMetaDomainService = portalMetaDomainService;
} }
@PostConstruct @PostConstruct
...@@ -108,14 +114,14 @@ public class PortalSettings { ...@@ -108,14 +114,14 @@ public class PortalSettings {
} }
} else { } else {
logger.error("Env health check failed, maybe because of admin server down. env: {}, meta server address: {}", env, logger.error("Env health check failed, maybe because of admin server down. env: {}, meta server address: {}", env,
PortalMetaDomainConsts.getDomain(env)); portalMetaDomainService.getDomain(env));
handleEnvDown(env); handleEnvDown(env);
} }
} catch (Exception e) { } catch (Exception e) {
logger.error("Env health check failed, maybe because of meta server down " logger.error("Env health check failed, maybe because of meta server down "
+ "or configure wrong meta server address. env: {}, meta server address: {}", env, + "or configure wrong meta server address. env: {}, meta server address: {}", env,
PortalMetaDomainConsts.getDomain(env), e); portalMetaDomainService.getDomain(env), e);
handleEnvDown(env); handleEnvDown(env);
} }
} }
...@@ -133,17 +139,17 @@ public class PortalSettings { ...@@ -133,17 +139,17 @@ public class PortalSettings {
if (!envStatusMark.get(env)) { if (!envStatusMark.get(env)) {
logger.error("Env is down. env: {}, failed times: {}, meta server address: {}", env, failedTimes, logger.error("Env is down. env: {}, failed times: {}, meta server address: {}", env, failedTimes,
PortalMetaDomainConsts.getDomain(env)); portalMetaDomainService.getDomain(env));
} else { } else {
if (failedTimes >= ENV_DOWN_THRESHOLD) { if (failedTimes >= ENV_DOWN_THRESHOLD) {
envStatusMark.put(env, false); envStatusMark.put(env, false);
logger.error("Env is down because health check failed for {} times, " logger.error("Env is down because health check failed for {} times, "
+ "which equals to down threshold. env: {}, meta server address: {}", ENV_DOWN_THRESHOLD, env, + "which equals to down threshold. env: {}, meta server address: {}", ENV_DOWN_THRESHOLD, env,
PortalMetaDomainConsts.getDomain(env)); portalMetaDomainService.getDomain(env));
} else { } else {
logger.error( logger.error(
"Env health check failed for {} times which less than down threshold. down threshold:{}, env: {}, meta server address: {}", "Env health check failed for {} times which less than down threshold. down threshold:{}, env: {}, meta server address: {}",
failedTimes, ENV_DOWN_THRESHOLD, env, PortalMetaDomainConsts.getDomain(env)); failedTimes, ENV_DOWN_THRESHOLD, env, portalMetaDomainService.getDomain(env));
} }
} }
......
package com.ctrip.framework.apollo.portal.component; package com.ctrip.framework.apollo.portal.component;
import com.ctrip.framework.apollo.common.exception.ServiceException; import com.ctrip.framework.apollo.common.exception.ServiceException;
import com.ctrip.framework.apollo.portal.environment.PortalMetaDomainConsts; import com.ctrip.framework.apollo.portal.environment.PortalMetaDomainService;
import com.ctrip.framework.apollo.core.dto.ServiceDTO; import com.ctrip.framework.apollo.core.dto.ServiceDTO;
import com.ctrip.framework.apollo.portal.environment.Env; import com.ctrip.framework.apollo.portal.environment.Env;
import com.ctrip.framework.apollo.portal.constant.TracerEventType; import com.ctrip.framework.apollo.portal.constant.TracerEventType;
...@@ -40,12 +40,16 @@ public class RetryableRestTemplate { ...@@ -40,12 +40,16 @@ public class RetryableRestTemplate {
private final RestTemplateFactory restTemplateFactory; private final RestTemplateFactory restTemplateFactory;
private final AdminServiceAddressLocator adminServiceAddressLocator; private final AdminServiceAddressLocator adminServiceAddressLocator;
private final PortalMetaDomainService portalMetaDomainService;
public RetryableRestTemplate( public RetryableRestTemplate(
final @Lazy RestTemplateFactory restTemplateFactory, final @Lazy RestTemplateFactory restTemplateFactory,
final @Lazy AdminServiceAddressLocator adminServiceAddressLocator) { final @Lazy AdminServiceAddressLocator adminServiceAddressLocator,
final PortalMetaDomainService portalMetaDomainService
) {
this.restTemplateFactory = restTemplateFactory; this.restTemplateFactory = restTemplateFactory;
this.adminServiceAddressLocator = adminServiceAddressLocator; this.adminServiceAddressLocator = adminServiceAddressLocator;
this.portalMetaDomainService = portalMetaDomainService;
} }
...@@ -116,7 +120,7 @@ public class RetryableRestTemplate { ...@@ -116,7 +120,7 @@ public class RetryableRestTemplate {
//all admin server down //all admin server down
ServiceException e = ServiceException e =
new ServiceException(String.format("Admin servers are unresponsive. meta server address: %s, admin servers: %s", new ServiceException(String.format("Admin servers are unresponsive. meta server address: %s, admin servers: %s",
PortalMetaDomainConsts.getDomain(env), services)); portalMetaDomainService.getDomain(env), services));
ct.setStatus(e); ct.setStatus(e);
ct.complete(); ct.complete();
throw e; throw e;
...@@ -160,7 +164,7 @@ public class RetryableRestTemplate { ...@@ -160,7 +164,7 @@ public class RetryableRestTemplate {
//all admin server down //all admin server down
ServiceException e = ServiceException e =
new ServiceException(String.format("Admin servers are unresponsive. meta server address: %s, admin servers: %s", new ServiceException(String.format("Admin servers are unresponsive. meta server address: %s, admin servers: %s",
PortalMetaDomainConsts.getDomain(env), services)); portalMetaDomainService.getDomain(env), services));
ct.setStatus(e); ct.setStatus(e);
ct.complete(); ct.complete();
throw e; throw e;
...@@ -175,7 +179,7 @@ public class RetryableRestTemplate { ...@@ -175,7 +179,7 @@ public class RetryableRestTemplate {
ServiceException e = new ServiceException(String.format("No available admin server." ServiceException e = new ServiceException(String.format("No available admin server."
+ " Maybe because of meta server down or all admin server down. " + " Maybe because of meta server down or all admin server down. "
+ "Meta server address: %s", + "Meta server address: %s",
PortalMetaDomainConsts.getDomain(env))); portalMetaDomainService.getDomain(env)));
ct.setStatus(e); ct.setStatus(e);
ct.complete(); ct.complete();
throw e; throw e;
......
...@@ -3,8 +3,8 @@ package com.ctrip.framework.apollo.portal.component.config; ...@@ -3,8 +3,8 @@ package com.ctrip.framework.apollo.portal.component.config;
import com.ctrip.framework.apollo.common.config.RefreshableConfig; import com.ctrip.framework.apollo.common.config.RefreshableConfig;
import com.ctrip.framework.apollo.common.config.RefreshablePropertySource; import com.ctrip.framework.apollo.common.config.RefreshablePropertySource;
import com.ctrip.framework.apollo.portal.environment.Env;
import com.ctrip.framework.apollo.portal.entity.vo.Organization; import com.ctrip.framework.apollo.portal.entity.vo.Organization;
import com.ctrip.framework.apollo.portal.environment.Env;
import com.ctrip.framework.apollo.portal.service.PortalDBPropertySource; import com.ctrip.framework.apollo.portal.service.PortalDBPropertySource;
import com.ctrip.framework.apollo.portal.service.SystemRoleManagerService; import com.ctrip.framework.apollo.portal.service.SystemRoleManagerService;
import com.google.common.base.Strings; import com.google.common.base.Strings;
...@@ -12,21 +12,27 @@ import com.google.common.collect.Lists; ...@@ -12,21 +12,27 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.Collections; import java.util.*;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@Component @Component
public class PortalConfig extends RefreshableConfig { public class PortalConfig extends RefreshableConfig {
private static final Logger logger = LoggerFactory.getLogger(PortalConfig.class);
private Gson gson = new Gson(); private Gson gson = new Gson();
private static final Type ORGANIZATION = new TypeToken<List<Organization>>() { private static final Type ORGANIZATION = new TypeToken<List<Organization>>() {
}.getType(); }.getType();
/**
* meta servers config in "PortalDB.ServerConfig"
*/
private static final Type META_SERVERS = new TypeToken<Map<String, String>>(){}.getType();
private final PortalDBPropertySource portalDBPropertySource; private final PortalDBPropertySource portalDBPropertySource;
public PortalConfig(final PortalDBPropertySource portalDBPropertySource) { public PortalConfig(final PortalDBPropertySource portalDBPropertySource) {
...@@ -52,6 +58,28 @@ public class PortalConfig extends RefreshableConfig { ...@@ -52,6 +58,28 @@ public class PortalConfig extends RefreshableConfig {
return envs; return envs;
} }
/**
* @return the relationship between environment and its meta server. empty if meet exception
*/
public Map<String, String> getMetaServers() {
final String key = "apollo.portal.meta.servers";
String jsonContent = getValue(key);
if(null == jsonContent) {
return Collections.emptyMap();
}
// watch out that the format of content may be wrong
// that will cause exception
Map<String, String> map = Collections.emptyMap();
try {
// try to parse
map = gson.fromJson(jsonContent, META_SERVERS);
} catch (Exception e) {
logger.error("wrong format with key: {}", key);
}
return map;
}
public List<String> superAdmins() { public List<String> superAdmins() {
String superAdminConfig = getValue("superAdmin", ""); String superAdminConfig = getValue("superAdmin", "");
if (Strings.isNullOrEmpty(superAdminConfig)) { if (Strings.isNullOrEmpty(superAdminConfig)) {
......
package com.ctrip.framework.apollo.portal.controller; package com.ctrip.framework.apollo.portal.controller;
import com.ctrip.framework.apollo.Apollo; import com.ctrip.framework.apollo.Apollo;
import com.ctrip.framework.apollo.portal.environment.PortalMetaDomainConsts; import com.ctrip.framework.apollo.portal.environment.PortalMetaDomainService;
import com.ctrip.framework.apollo.core.dto.ServiceDTO; import com.ctrip.framework.apollo.core.dto.ServiceDTO;
import com.ctrip.framework.apollo.portal.environment.Env; import com.ctrip.framework.apollo.portal.environment.Env;
import com.ctrip.framework.apollo.portal.component.PortalSettings; import com.ctrip.framework.apollo.portal.component.PortalSettings;
...@@ -32,12 +32,16 @@ public class SystemInfoController { ...@@ -32,12 +32,16 @@ public class SystemInfoController {
private RestTemplate restTemplate; private RestTemplate restTemplate;
private final PortalSettings portalSettings; private final PortalSettings portalSettings;
private final RestTemplateFactory restTemplateFactory; private final RestTemplateFactory restTemplateFactory;
private final PortalMetaDomainService portalMetaDomainService;
public SystemInfoController( public SystemInfoController(
final PortalSettings portalSettings, final PortalSettings portalSettings,
final RestTemplateFactory restTemplateFactory) { final RestTemplateFactory restTemplateFactory,
final PortalMetaDomainService portalMetaDomainService
) {
this.portalSettings = portalSettings; this.portalSettings = portalSettings;
this.restTemplateFactory = restTemplateFactory; this.restTemplateFactory = restTemplateFactory;
this.portalMetaDomainService = portalMetaDomainService;
} }
@PostConstruct @PostConstruct
...@@ -101,13 +105,13 @@ public class SystemInfoController { ...@@ -101,13 +105,13 @@ public class SystemInfoController {
private EnvironmentInfo adaptEnv2EnvironmentInfo(final Env env) { private EnvironmentInfo adaptEnv2EnvironmentInfo(final Env env) {
EnvironmentInfo environmentInfo = new EnvironmentInfo(); EnvironmentInfo environmentInfo = new EnvironmentInfo();
String metaServerAddresses = PortalMetaDomainConsts.getMetaServerAddress(env); String metaServerAddresses = portalMetaDomainService.getMetaServerAddress(env);
environmentInfo.setEnv(env); environmentInfo.setEnv(env);
environmentInfo.setActive(portalSettings.isEnvActive(env)); environmentInfo.setActive(portalSettings.isEnvActive(env));
environmentInfo.setMetaServerAddress(metaServerAddresses); environmentInfo.setMetaServerAddress(metaServerAddresses);
String selectedMetaServerAddress = PortalMetaDomainConsts.getDomain(env); String selectedMetaServerAddress = portalMetaDomainService.getDomain(env);
try { try {
environmentInfo.setConfigServices(getServerAddress(selectedMetaServerAddress, CONFIG_SERVICE_URL_PATH)); environmentInfo.setConfigServices(getServerAddress(selectedMetaServerAddress, CONFIG_SERVICE_URL_PATH));
......
package com.ctrip.framework.apollo.portal.environment;
import com.ctrip.framework.apollo.portal.component.config.PortalConfig;
import java.util.Map;
/**
* load meta server addressed from database.
* PortalDB.ServerConfig
*/
class DatabasePortalMetaServerProvider implements PortalMetaServerProvider {
/**
* read config from database
*/
private final PortalConfig portalConfig;
private volatile Map<Env, String> addresses;
public DatabasePortalMetaServerProvider(
final PortalConfig portalConfig
) {
this.portalConfig = portalConfig;
// will cause NullPointException if portalConfig is null
Map<String, String> map = portalConfig.getMetaServers();
addresses = Env.conversionKey(map);
}
@Override
public String getMetaServerAddress(Env targetEnv) {
return addresses.get(targetEnv);
}
@Override
public boolean exists(Env targetEnv) {
return addresses.containsKey(targetEnv);
}
@Override
public void reload() {
Map<String, String> map = portalConfig.getMetaServers();
addresses = Env.conversionKey(map);
}
}
package com.ctrip.framework.apollo.portal.environment;
import static com.ctrip.framework.apollo.portal.environment.Env.conversionKey;
import com.ctrip.framework.apollo.core.utils.ResourceUtils;
import com.ctrip.framework.apollo.portal.util.KeyValueUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
/**
* Only use in apollo-portal
* load all meta server address from
* - System Property [key ends with "_meta" (case insensitive)]
* - OS environment variable [key ends with "_meta" (case insensitive)]
* - user's configuration file [key ends with ".meta" (case insensitive)]
* when apollo-portal start up.
* @see com.ctrip.framework.apollo.core.internals.LegacyMetaServerProvider
* @author wxq
*/
class DefaultPortalMetaServerProvider implements PortalMetaServerProvider {
private static final Logger logger = LoggerFactory.getLogger(DefaultPortalMetaServerProvider.class);
/**
* environments and their meta server address
* properties file path
*/
private static final String APOLLO_ENV_PROPERTIES_FILE_PATH = "apollo-env.properties";
private static final DefaultPortalMetaServerProvider INSTANCE = new DefaultPortalMetaServerProvider();
public static DefaultPortalMetaServerProvider getInstance() {
return INSTANCE;
}
private volatile Map<Env, String> domains;
private DefaultPortalMetaServerProvider() {
domains = initializeDomains();
}
@Override
public String getMetaServerAddress(Env targetEnv) {
String metaServerAddress = domains.get(targetEnv);
return metaServerAddress == null ? null : metaServerAddress.trim();
}
@Override
public boolean exists(Env targetEnv) {
return domains.containsKey(targetEnv);
}
@Override
public void reload() {
domains = initializeDomains();
}
/**
* load all environment's meta address dynamically when this class loaded by JVM
*/
private static Map<Env, String> initializeDomains() {
// add to domain
Map<Env, String> map = new ConcurrentHashMap<>();
// lower priority add first
map.putAll(getDomainsFromPropertiesFile());
map.putAll(getDomainsFromOSEnvironment());
map.putAll(getDomainsFromSystemProperty());
// log all
logger.info("Loaded meta server addresses: {}", map);
return map;
}
static Map<Env, String> getDomainsFromSystemProperty() {
// find key-value from System Property which key ends with "_meta" (case insensitive)
Map<String, String> metaServerAddressesFromSystemProperty = KeyValueUtils.filterWithKeyIgnoreCaseEndsWith(System.getProperties(), "_meta");
// remove key's suffix "_meta" (case insensitive)
metaServerAddressesFromSystemProperty = KeyValueUtils.removeKeySuffix(metaServerAddressesFromSystemProperty, "_meta".length());
return conversionKey(metaServerAddressesFromSystemProperty);
}
static Map<Env, String> getDomainsFromOSEnvironment() {
// find key-value from OS environment variable which key ends with "_meta" (case insensitive)
Map<String, String> metaServerAddressesFromOSEnvironment = KeyValueUtils.filterWithKeyIgnoreCaseEndsWith(System.getenv(), "_meta");
// remove key's suffix "_meta" (case insensitive)
metaServerAddressesFromOSEnvironment = KeyValueUtils.removeKeySuffix(metaServerAddressesFromOSEnvironment, "_meta".length());
return conversionKey(metaServerAddressesFromOSEnvironment);
}
static Map<Env, String> getDomainsFromPropertiesFile() {
// find key-value from properties file which key ends with ".meta" (case insensitive)
Properties properties = new Properties();
properties = ResourceUtils.readConfigFile(APOLLO_ENV_PROPERTIES_FILE_PATH, properties);
Map<String, String> metaServerAddressesFromPropertiesFile = KeyValueUtils.filterWithKeyIgnoreCaseEndsWith(properties, ".meta");
// remove key's suffix ".meta" (case insensitive)
metaServerAddressesFromPropertiesFile = KeyValueUtils.removeKeySuffix(metaServerAddressesFromPropertiesFile, ".meta".length());
return conversionKey(metaServerAddressesFromPropertiesFile);
}
}
...@@ -192,5 +192,23 @@ public class Env { ...@@ -192,5 +192,23 @@ public class Env {
public String getName() { public String getName() {
return name; return name;
} }
/**
* conversion key from {@link String} to {@link Env}
* @param metaServerAddresses key is environment, value is environment's meta server address
* @return relationship between {@link Env} and meta server address
*/
public static Map<Env, String> conversionKey(Map<String, String> metaServerAddresses) {
// add to domain
Map<Env, String> map = new ConcurrentHashMap<>();
for(Map.Entry<String, String> entry : metaServerAddresses.entrySet()) {
// add new environment
Env env = Env.addEnvironment(entry.getKey());
// get meta server address value
String value = entry.getValue();
// put pair (Env, meta server address)
map.put(env, value);
}
return map;
}
} }
...@@ -2,14 +2,14 @@ package com.ctrip.framework.apollo.portal.environment; ...@@ -2,14 +2,14 @@ package com.ctrip.framework.apollo.portal.environment;
import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory; import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory;
import com.ctrip.framework.apollo.core.utils.NetUtil; import com.ctrip.framework.apollo.core.utils.NetUtil;
import com.ctrip.framework.apollo.portal.component.config.PortalConfig;
import com.ctrip.framework.apollo.tracer.Tracer; import com.ctrip.framework.apollo.tracer.Tracer;
import com.ctrip.framework.apollo.tracer.spi.Transaction; import com.ctrip.framework.apollo.tracer.spi.Transaction;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import org.slf4j.Logger; import java.util.ArrayList;
import org.slf4j.LoggerFactory; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
...@@ -17,6 +17,9 @@ import java.util.concurrent.Executors; ...@@ -17,6 +17,9 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
/** /**
* Only use in apollo-portal * Only use in apollo-portal
...@@ -26,26 +29,38 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -26,26 +29,38 @@ import java.util.concurrent.atomic.AtomicBoolean;
* @see com.ctrip.framework.apollo.core.MetaDomainConsts * @see com.ctrip.framework.apollo.core.MetaDomainConsts
* @author wxq * @author wxq
*/ */
public class PortalMetaDomainConsts { @Service
public class PortalMetaDomainService {
public static final String DEFAULT_META_URL = "http://apollo.meta"; public static final String DEFAULT_META_URL = "http://apollo.meta";
// env -> meta server address cache // env -> meta server address cache
private static final Map<Env, String> metaServerAddressCache = Maps.newConcurrentMap(); private static final Map<Env, String> metaServerAddressCache = Maps.newConcurrentMap();
// initialize meta server provider without cache /**
private static final PortalMetaServerProvider metaServerProvider = PortalMetaServerProvider.getInstance(); * initialize meta server provider without cache.
* Multiple {@link PortalMetaServerProvider}
*/
private final List<PortalMetaServerProvider> portalMetaServerProviders = new ArrayList<>();
private static final long REFRESH_INTERVAL_IN_SECOND = 60;// 1 min private static final long REFRESH_INTERVAL_IN_SECOND = 60;// 1 min
private static final Logger logger = LoggerFactory.getLogger(PortalMetaDomainConsts.class); private static final Logger logger = LoggerFactory.getLogger(PortalMetaDomainService.class);
// comma separated meta server address -> selected single meta server address cache // comma separated meta server address -> selected single meta server address cache
private static final Map<String, String> selectedMetaServerAddressCache = Maps.newConcurrentMap(); private static final Map<String, String> selectedMetaServerAddressCache = Maps.newConcurrentMap();
private static final AtomicBoolean periodicRefreshStarted = new AtomicBoolean(false); private static final AtomicBoolean periodicRefreshStarted = new AtomicBoolean(false);
public PortalMetaDomainService(final PortalConfig portalConfig) {
// high priority with data in database
portalMetaServerProviders.add(new DatabasePortalMetaServerProvider(portalConfig));
// System properties, OS environment, configuration file
portalMetaServerProviders.add(DefaultPortalMetaServerProvider.getInstance());
}
/** /**
* Return one meta server address. If multiple meta server addresses are configured, will select one. * Return one meta server address. If multiple meta server addresses are configured, will select one.
*/ */
public static String getDomain(Env env) { synchronized public String getDomain(Env env) {
String metaServerAddress = getMetaServerAddress(env); String metaServerAddress = getMetaServerAddress(env);
// if there is more than one address, need to select one // if there is more than one address, need to select one
if (metaServerAddress.contains(",")) { if (metaServerAddress.contains(",")) {
...@@ -57,13 +72,13 @@ public class PortalMetaDomainConsts { ...@@ -57,13 +72,13 @@ public class PortalMetaDomainConsts {
/** /**
* Return meta server address. If multiple meta server addresses are configured, will return the comma separated string. * Return meta server address. If multiple meta server addresses are configured, will return the comma separated string.
*/ */
public static String getMetaServerAddress(Env env) { synchronized public String getMetaServerAddress(Env env) {
// in cache? // in cache?
if (!metaServerAddressCache.containsKey(env)) { if (!metaServerAddressCache.containsKey(env)) {
// put it to cache // put it to cache
metaServerAddressCache.put( metaServerAddressCache.put(
env, env,
getMetaServerAddressCacheValue(metaServerProvider, env) getMetaServerAddressCacheValue(portalMetaServerProviders, env)
); );
} }
...@@ -75,14 +90,25 @@ public class PortalMetaDomainConsts { ...@@ -75,14 +90,25 @@ public class PortalMetaDomainConsts {
* Get the meta server from provider by given environment. * Get the meta server from provider by given environment.
* If there is no available meta server url for the given environment, * If there is no available meta server url for the given environment,
* the default meta server url will be used(http://apollo.meta). * the default meta server url will be used(http://apollo.meta).
* @param provider * @param providers provide environment's meta server address
* @param env * @param env environment
* @return * @return meta server address
*/ */
private static String getMetaServerAddressCacheValue(PortalMetaServerProvider provider, Env env) { private static String getMetaServerAddressCacheValue(
String metaAddress = provider.getMetaServerAddress(env); Collection<PortalMetaServerProvider> providers, Env env) {
logger.info("Located meta server address [{}] for env [{}]", metaAddress, env);
// null value
String metaAddress = null;
for(PortalMetaServerProvider portalMetaServerProvider : providers) {
if(portalMetaServerProvider.exists(env)) {
metaAddress = portalMetaServerProvider.getMetaServerAddress(env);
logger.info("Located meta server address [{}] for env [{}]", metaAddress, env);
break;
}
}
// check find it or not
if (Strings.isNullOrEmpty(metaAddress)) { if (Strings.isNullOrEmpty(metaAddress)) {
// Fallback to default meta address // Fallback to default meta address
metaAddress = DEFAULT_META_URL; metaAddress = DEFAULT_META_URL;
...@@ -93,6 +119,17 @@ public class PortalMetaDomainConsts { ...@@ -93,6 +119,17 @@ public class PortalMetaDomainConsts {
return metaAddress.trim(); return metaAddress.trim();
} }
/**
* reload all {@link PortalMetaServerProvider}.
* clear cache {@link this#metaServerAddressCache}
*/
public void reload() {
for(PortalMetaServerProvider portalMetaServerProvider : portalMetaServerProviders) {
portalMetaServerProvider.reload();
}
metaServerAddressCache.clear();
}
/** /**
* Select one available meta server from the comma separated meta server addresses, e.g. * Select one available meta server from the comma separated meta server addresses, e.g.
* http://1.2.3.4:8080,http://2.3.4.5:8080 * http://1.2.3.4:8080,http://2.3.4.5:8080
......
package com.ctrip.framework.apollo.portal.environment; package com.ctrip.framework.apollo.portal.environment;
import com.ctrip.framework.apollo.core.utils.ResourceUtils;
import com.ctrip.framework.apollo.portal.util.KeyValueUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
/** /**
* Only use in apollo-portal * For the supporting of multiple meta server address providers.
* load all meta server address from * From configuration file,
* - System Property [key ends with "_meta" (case insensitive)] * from OS environment,
* - OS environment variable [key ends with "_meta" (case insensitive)] * From database,
* - user's configuration file [key ends with ".meta" (case insensitive)] * ...
* when apollo-portal start up. * Just implement this interface
* @see com.ctrip.framework.apollo.core.internals.LegacyMetaServerProvider
* @author wxq * @author wxq
*/ */
public class PortalMetaServerProvider { public interface PortalMetaServerProvider {
private static final Logger logger = LoggerFactory.getLogger(PortalMetaServerProvider.class);
/**
* environments and their meta server address
* properties file path
*/
private static final String APOLLO_ENV_PROPERTIES_FILE_PATH = "apollo-env.properties";
private static final PortalMetaServerProvider instance = new PortalMetaServerProvider();
private Map<Env, String> domains;
private PortalMetaServerProvider() {
domains = initializeDomains();
}
public static PortalMetaServerProvider getInstance() {
return instance;
}
String getMetaServerAddress(Env targetEnv) {
String metaServerAddress = domains.get(targetEnv);
return metaServerAddress == null ? null : metaServerAddress.trim();
}
/**
* load all environment's meta address dynamically when this class loaded by JVM
*/
private Map<Env, String> initializeDomains() {
// find key-value from System Property which key ends with "_meta" (case insensitive)
Map<String, String> metaServerAddressesFromSystemProperty = KeyValueUtils.filterWithKeyIgnoreCaseEndsWith(System.getProperties(), "_meta");
// remove key's suffix "_meta" (case insensitive)
metaServerAddressesFromSystemProperty = KeyValueUtils.removeKeySuffix(metaServerAddressesFromSystemProperty, "_meta".length());
// find key-value from OS environment variable which key ends with "_meta" (case insensitive)
Map<String, String> metaServerAddressesFromOSEnvironment = KeyValueUtils.filterWithKeyIgnoreCaseEndsWith(System.getenv(), "_meta");
// remove key's suffix "_meta" (case insensitive)
metaServerAddressesFromOSEnvironment = KeyValueUtils.removeKeySuffix(metaServerAddressesFromOSEnvironment, "_meta".length());
// find key-value from properties file which key ends with ".meta" (case insensitive)
Properties properties = new Properties();
properties = ResourceUtils.readConfigFile(APOLLO_ENV_PROPERTIES_FILE_PATH, properties);
Map<String, String> metaServerAddressesFromPropertiesFile = KeyValueUtils.filterWithKeyIgnoreCaseEndsWith(properties, ".meta");
// remove key's suffix ".meta" (case insensitive)
metaServerAddressesFromPropertiesFile = KeyValueUtils.removeKeySuffix(metaServerAddressesFromPropertiesFile, ".meta".length());
// begin to add key-value, key is environment, value is meta server address matched
Map<String, String> metaServerAddresses = new HashMap<>();
// lower priority add first
metaServerAddresses.putAll(metaServerAddressesFromPropertiesFile);
metaServerAddresses.putAll(metaServerAddressesFromOSEnvironment);
metaServerAddresses.putAll(metaServerAddressesFromSystemProperty);
// add to domain /**
Map<Env, String> map = new ConcurrentHashMap<>(); * @param targetEnv environment
for(Map.Entry<String, String> entry : metaServerAddresses.entrySet()) { * @return meta server address matched environment
// add new environment */
Env env = Env.addEnvironment(entry.getKey()); String getMetaServerAddress(Env targetEnv);
// get meta server address value
String value = entry.getValue();
// put pair (Env, meta server address)
map.put(env, value);
}
// log all /**
logger.info("Loaded meta server addresses: {}", map); * @param targetEnv environment
return map; * @return environment's meta server address exists or not
} */
boolean exists(Env targetEnv);
/** /**
* add a environment's meta server address * reload the meta server address in runtime
* for the feature: add self-define environment in the web ui */
*/ void reload();
void mockMetaServerAddress(Env env, String metaServerAddress) {
domains.put(env, metaServerAddress);
}
/**
* only for test
* reload all environments and meta server addresses
*/
void reset() {
domains = initializeDomains();
}
} }
package com.ctrip.framework.apollo.portal; package com.ctrip.framework.apollo.portal;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.ctrip.framework.apollo.common.exception.ServiceException; import com.ctrip.framework.apollo.common.exception.ServiceException;
import com.ctrip.framework.apollo.core.dto.ServiceDTO; import com.ctrip.framework.apollo.core.dto.ServiceDTO;
import com.ctrip.framework.apollo.portal.environment.Env;
import com.ctrip.framework.apollo.portal.component.AdminServiceAddressLocator; import com.ctrip.framework.apollo.portal.component.AdminServiceAddressLocator;
import com.ctrip.framework.apollo.portal.component.RetryableRestTemplate; import com.ctrip.framework.apollo.portal.component.RetryableRestTemplate;
import com.ctrip.framework.apollo.portal.environment.Env;
import com.ctrip.framework.apollo.portal.environment.PortalMetaDomainService;
import java.net.SocketTimeoutException;
import java.util.Arrays;
import java.util.Collections;
import org.apache.http.HttpHost; import org.apache.http.HttpHost;
import org.apache.http.conn.ConnectTimeoutException; import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.HttpHostConnectException; import org.apache.http.conn.HttpHostConnectException;
...@@ -19,21 +27,14 @@ import org.springframework.http.ResponseEntity; ...@@ -19,21 +27,14 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.client.ResourceAccessException; import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import java.net.SocketTimeoutException;
import java.util.Arrays;
import java.util.Collections;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class RetryableRestTemplateTest extends AbstractUnitTest { public class RetryableRestTemplateTest extends AbstractUnitTest {
@Mock @Mock
private AdminServiceAddressLocator serviceAddressLocator; private AdminServiceAddressLocator serviceAddressLocator;
@Mock @Mock
private RestTemplate restTemplate; private RestTemplate restTemplate;
@Mock
private PortalMetaDomainService portalMetaDomainService;
@InjectMocks @InjectMocks
private RetryableRestTemplate retryableRestTemplate; private RetryableRestTemplate retryableRestTemplate;
......
package com.ctrip.framework.apollo.portal.environment;
import static org.junit.Assert.*;
import com.ctrip.framework.apollo.portal.component.config.PortalConfig;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class DatabasePortalMetaServerProviderTest {
private DatabasePortalMetaServerProvider databasePortalMetaServerProvider;
@Before
public void init() {
MockitoAnnotations.initMocks(this);
// mock it
PortalConfig portalConfig = Mockito.mock(PortalConfig.class);
final Map<String, String> map = new HashMap<>();
map.put("nothing", "http://unknown.com");
map.put("dev", "http://server.com:8080");
Mockito.when(portalConfig.getMetaServers()).thenReturn(map);
// use mocked object to construct
databasePortalMetaServerProvider = new DatabasePortalMetaServerProvider(portalConfig);
}
@Test
public void getMetaServerAddress() {
String address = databasePortalMetaServerProvider.getMetaServerAddress(Env.DEV);
assertEquals("http://server.com:8080", address);
}
}
\ No newline at end of file
package com.ctrip.framework.apollo.portal.environment; package com.ctrip.framework.apollo.portal.environment;
import static org.junit.Assert.assertEquals;
import com.ctrip.framework.apollo.portal.AbstractUnitTest;
import org.junit.After; import org.junit.After;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.assertEquals; public class DefaultPortalMetaServerProviderTest extends AbstractUnitTest {
public class PortalMetaServerProviderTest { private final DefaultPortalMetaServerProvider defaultPortalMetaServerProvider = DefaultPortalMetaServerProvider.getInstance();
private PortalMetaServerProvider portalMetaServerProvider;
@Before
public void setUp() throws Exception {
portalMetaServerProvider = PortalMetaServerProvider.getInstance();
}
@After @After
public void tearDown() throws Exception { public void tearDown() throws Exception {
System.clearProperty("dev_meta"); System.clearProperty("dev_meta");
System.clearProperty("fat_meta"); System.clearProperty("fat_meta");
PortalMetaServerProvider.getInstance().reset(); defaultPortalMetaServerProvider.reload();
} }
@Test @Test
public void testFromPropertyFile() { public void testFromPropertyFile() {
assertEquals("http://localhost:8080", portalMetaServerProvider.getMetaServerAddress(Env.LOCAL)); assertEquals("http://localhost:8080", defaultPortalMetaServerProvider.getMetaServerAddress(Env.LOCAL));
assertEquals("${dev_meta}", portalMetaServerProvider.getMetaServerAddress(Env.DEV)); assertEquals("${dev_meta}", defaultPortalMetaServerProvider.getMetaServerAddress(Env.DEV));
assertEquals("${pro_meta}", portalMetaServerProvider.getMetaServerAddress(Env.PRO)); assertEquals("${pro_meta}", defaultPortalMetaServerProvider.getMetaServerAddress(Env.PRO));
} }
/** /**
...@@ -39,17 +35,17 @@ public class PortalMetaServerProviderTest { ...@@ -39,17 +35,17 @@ public class PortalMetaServerProviderTest {
System.setProperty("dev_meta", someDevMetaAddress); System.setProperty("dev_meta", someDevMetaAddress);
System.setProperty("fat_meta", someFatMetaAddress); System.setProperty("fat_meta", someFatMetaAddress);
// reload above added // reload above added
portalMetaServerProvider.reset(); defaultPortalMetaServerProvider.reload();
assertEquals(someDevMetaAddress, portalMetaServerProvider.getMetaServerAddress(Env.DEV)); assertEquals(someDevMetaAddress, defaultPortalMetaServerProvider.getMetaServerAddress(Env.DEV));
assertEquals(someFatMetaAddress, portalMetaServerProvider.getMetaServerAddress(Env.FAT)); assertEquals(someFatMetaAddress, defaultPortalMetaServerProvider.getMetaServerAddress(Env.FAT));
String randomAddress = "randomAddress"; String randomAddress = "randomAddress";
String randomEnvironment = "randomEnvironment"; String randomEnvironment = "randomEnvironment";
System.setProperty(randomEnvironment + "_meta", randomAddress); System.setProperty(randomEnvironment + "_meta", randomAddress);
// reload above added // reload above added
portalMetaServerProvider.reset(); defaultPortalMetaServerProvider.reload();
assertEquals(randomAddress, assertEquals(randomAddress,
portalMetaServerProvider.getMetaServerAddress(Env.valueOf(randomEnvironment))); defaultPortalMetaServerProvider.getMetaServerAddress(Env.valueOf(randomEnvironment)));
// clear the property // clear the property
System.clearProperty(randomEnvironment + "_meta"); System.clearProperty(randomEnvironment + "_meta");
......
package com.ctrip.framework.apollo.portal.environment; package com.ctrip.framework.apollo.portal.environment;
import org.junit.After; import static org.junit.Assert.assertEquals;
import org.junit.Test; import static org.junit.Assert.assertTrue;
import com.ctrip.framework.apollo.portal.component.config.PortalConfig;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import static org.junit.Assert.assertEquals; public class PortalMetaDomainServiceTest extends BaseIntegrationTest {
import static org.junit.Assert.assertTrue;
@Mock
private PortalMetaDomainService portalMetaDomainService;
public class PortalMetaDomainConstsTest extends BaseIntegrationTest { @Before
public void init() {
MockitoAnnotations.initMocks(this);
// mock it
PortalConfig portalConfig = Mockito.mock(PortalConfig.class);
final Map<String, String> map = new HashMap<>();
map.put("nothing", "http://unknown.com");
Mockito.when(portalConfig.getMetaServers()).thenReturn(map);
@Override portalMetaDomainService = new PortalMetaDomainService(portalConfig);
@After
public void tearDown() throws Exception {
super.tearDown();
clear();
} }
@Test @Test
...@@ -22,12 +35,12 @@ public class PortalMetaDomainConstsTest extends BaseIntegrationTest { ...@@ -22,12 +35,12 @@ public class PortalMetaDomainConstsTest extends BaseIntegrationTest {
// local // local
String localMetaServerAddress = "http://localhost:8080"; String localMetaServerAddress = "http://localhost:8080";
mockMetaServerAddress(Env.LOCAL, localMetaServerAddress); mockMetaServerAddress(Env.LOCAL, localMetaServerAddress);
assertEquals(localMetaServerAddress, PortalMetaDomainConsts.getDomain(Env.LOCAL)); assertEquals(localMetaServerAddress, portalMetaDomainService.getDomain(Env.LOCAL));
// add this environment without meta server address // add this environment without meta server address
String randomEnvironment = "randomEnvironment"; String randomEnvironment = "randomEnvironment";
Env.addEnvironment(randomEnvironment); Env.addEnvironment(randomEnvironment);
assertEquals(PortalMetaDomainConsts.DEFAULT_META_URL, PortalMetaDomainConsts.getDomain(Env.valueOf(randomEnvironment))); assertEquals(PortalMetaDomainService.DEFAULT_META_URL, portalMetaDomainService.getDomain(Env.valueOf(randomEnvironment)));
} }
@Test @Test
...@@ -40,9 +53,10 @@ public class PortalMetaDomainConstsTest extends BaseIntegrationTest { ...@@ -40,9 +53,10 @@ public class PortalMetaDomainConstsTest extends BaseIntegrationTest {
mockMetaServerAddress(Env.FAT, validServer + "," + invalidServer); mockMetaServerAddress(Env.FAT, validServer + "," + invalidServer);
mockMetaServerAddress(Env.UAT, invalidServer + "," + validServer); mockMetaServerAddress(Env.UAT, invalidServer + "," + validServer);
portalMetaDomainService.reload();
assertEquals(validServer.trim(), PortalMetaDomainConsts.getDomain(Env.FAT)); assertEquals(validServer.trim(), portalMetaDomainService.getDomain(Env.FAT));
assertEquals(validServer.trim(), PortalMetaDomainConsts.getDomain(Env.UAT)); assertEquals(validServer.trim(), portalMetaDomainService.getDomain(Env.UAT));
} }
@Test @Test
...@@ -52,16 +66,16 @@ public class PortalMetaDomainConstsTest extends BaseIntegrationTest { ...@@ -52,16 +66,16 @@ public class PortalMetaDomainConstsTest extends BaseIntegrationTest {
mockMetaServerAddress(Env.LPT, invalidServer + "," + anotherInvalidServer); mockMetaServerAddress(Env.LPT, invalidServer + "," + anotherInvalidServer);
String metaServer = PortalMetaDomainConsts.getDomain(Env.LPT); portalMetaDomainService.reload();
String metaServer = portalMetaDomainService.getDomain(Env.LPT);
assertTrue(metaServer.equals(invalidServer.trim()) || metaServer.equals(anotherInvalidServer.trim())); assertTrue(metaServer.equals(invalidServer.trim()) || metaServer.equals(anotherInvalidServer.trim()));
} }
private void mockMetaServerAddress(Env env, String metaServerAddress) { private void mockMetaServerAddress(Env env, String metaServerAddress) {
PortalMetaServerProvider.getInstance().mockMetaServerAddress(env, metaServerAddress); // add it to system's property
System.setProperty(env.getName() + "_meta", metaServerAddress);
} }
private void clear() {
PortalMetaServerProvider.getInstance().reset();
}
} }
\ 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