Commit 91065c0d by 张乐 Committed by GitHub

Merge pull request #386 from nobodyiam/runtime-config-audit-server

Add runtime config info audit
parents 75d2be0b 9a621234
package com.ctrip.framework.apollo.biz.entity;
import com.google.common.base.MoreObjects;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.PrePersist;
import javax.persistence.Table;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@Entity
@Table(name = "Instance")
public class Instance {
@Id
@GeneratedValue
@Column(name = "Id")
private long id;
@Column(name = "AppId", nullable = false)
private String appId;
@Column(name = "ClusterName", nullable = false)
private String clusterName;
@Column(name = "DataCenter", nullable = false)
private String dataCenter;
@Column(name = "Ip", nullable = false)
private String ip;
@Column(name = "DataChange_CreatedTime", nullable = false)
private Date dataChangeCreatedTime;
@Column(name = "DataChange_LastTime")
private Date dataChangeLastModifiedTime;
@PrePersist
protected void prePersist() {
if (this.dataChangeCreatedTime == null) {
dataChangeCreatedTime = new Date();
}
if (this.dataChangeLastModifiedTime == null) {
dataChangeLastModifiedTime = dataChangeCreatedTime;
}
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getClusterName() {
return clusterName;
}
public void setClusterName(String clusterName) {
this.clusterName = clusterName;
}
public String getDataCenter() {
return dataCenter;
}
public void setDataCenter(String dataCenter) {
this.dataCenter = dataCenter;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public Date getDataChangeCreatedTime() {
return dataChangeCreatedTime;
}
public void setDataChangeCreatedTime(Date dataChangeCreatedTime) {
this.dataChangeCreatedTime = dataChangeCreatedTime;
}
public Date getDataChangeLastModifiedTime() {
return dataChangeLastModifiedTime;
}
public void setDataChangeLastModifiedTime(Date dataChangeLastModifiedTime) {
this.dataChangeLastModifiedTime = dataChangeLastModifiedTime;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.omitNullValues()
.add("id", id)
.add("appId", appId)
.add("clusterName", clusterName)
.add("dataCenter", dataCenter)
.add("ip", ip)
.add("dataChangeCreatedTime", dataChangeCreatedTime)
.add("dataChangeLastModifiedTime", dataChangeLastModifiedTime)
.toString();
}
}
package com.ctrip.framework.apollo.biz.entity;
import com.google.common.base.MoreObjects;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Table;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@Entity
@Table(name = "InstanceConfig")
public class InstanceConfig {
@Id
@GeneratedValue
@Column(name = "Id")
private long id;
@Column(name = "InstanceId")
private long instanceId;
@Column(name = "ConfigAppId", nullable = false)
private String configAppId;
@Column(name = "ConfigNamespaceName", nullable = false)
private String configNamespaceName;
@Column(name = "ReleaseKey", nullable = false)
private String releaseKey;
@Column(name = "DataChange_CreatedTime", nullable = false)
private Date dataChangeCreatedTime;
@Column(name = "DataChange_LastTime")
private Date dataChangeLastModifiedTime;
@PrePersist
protected void prePersist() {
if (this.dataChangeCreatedTime == null) {
dataChangeCreatedTime = new Date();
}
if (this.dataChangeLastModifiedTime == null) {
dataChangeLastModifiedTime = dataChangeCreatedTime;
}
}
@PreUpdate
protected void preUpdate() {
this.dataChangeLastModifiedTime = new Date();
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public long getInstanceId() {
return instanceId;
}
public void setInstanceId(long instanceId) {
this.instanceId = instanceId;
}
public String getConfigAppId() {
return configAppId;
}
public void setConfigAppId(String configAppId) {
this.configAppId = configAppId;
}
public String getConfigNamespaceName() {
return configNamespaceName;
}
public void setConfigNamespaceName(String configNamespaceName) {
this.configNamespaceName = configNamespaceName;
}
public String getReleaseKey() {
return releaseKey;
}
public void setReleaseKey(String releaseKey) {
this.releaseKey = releaseKey;
}
public Date getDataChangeCreatedTime() {
return dataChangeCreatedTime;
}
public void setDataChangeCreatedTime(Date dataChangeCreatedTime) {
this.dataChangeCreatedTime = dataChangeCreatedTime;
}
public Date getDataChangeLastModifiedTime() {
return dataChangeLastModifiedTime;
}
public void setDataChangeLastModifiedTime(Date dataChangeLastModifiedTime) {
this.dataChangeLastModifiedTime = dataChangeLastModifiedTime;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.omitNullValues()
.add("id", id)
.add("configAppId", configAppId)
.add("configNamespaceName", configNamespaceName)
.add("releaseKey", releaseKey)
.add("dataChangeCreatedTime", dataChangeCreatedTime)
.add("dataChangeLastModifiedTime", dataChangeLastModifiedTime)
.toString();
}
}
package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.biz.entity.InstanceConfig;
import org.springframework.data.repository.PagingAndSortingRepository;
public interface InstanceConfigRepository extends PagingAndSortingRepository<InstanceConfig, Long> {
InstanceConfig findByInstanceIdAndConfigAppIdAndConfigNamespaceName(long instanceId, String configAppId, String configNamespaceName);
}
package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.biz.entity.Instance;
import org.springframework.data.repository.PagingAndSortingRepository;
public interface InstanceRepository extends PagingAndSortingRepository<Instance, Long> {
Instance findByAppIdAndClusterNameAndDataCenterAndIp(String appId, String clusterName, String dataCenter, String ip);
}
package com.ctrip.framework.apollo.biz.service;
import com.google.common.base.Preconditions;
import com.ctrip.framework.apollo.biz.entity.Instance;
import com.ctrip.framework.apollo.biz.entity.InstanceConfig;
import com.ctrip.framework.apollo.biz.repository.InstanceConfigRepository;
import com.ctrip.framework.apollo.biz.repository.InstanceRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@Service
public class InstanceService {
@Autowired
private InstanceRepository instanceRepository;
@Autowired
private InstanceConfigRepository instanceConfigRepository;
public Instance findInstance(String appId, String clusterName, String dataCenter, String ip) {
return instanceRepository.findByAppIdAndClusterNameAndDataCenterAndIp(appId, clusterName,
dataCenter, ip);
}
@Transactional
public Instance createInstance(Instance instance) {
instance.setId(0); //protection
return instanceRepository.save(instance);
}
public InstanceConfig findInstanceConfig(long instanceId, String configAppId,
String configNamespaceName) {
return instanceConfigRepository.findByInstanceIdAndConfigAppIdAndConfigNamespaceName(
instanceId, configAppId, configNamespaceName);
}
@Transactional
public InstanceConfig createInstanceConfig(InstanceConfig instanceConfig) {
instanceConfig.setId(0); //protection
return instanceConfigRepository.save(instanceConfig);
}
@Transactional
public InstanceConfig updateInstanceConfig(InstanceConfig instanceConfig) {
InstanceConfig existedInstanceConfig = instanceConfigRepository.findOne(instanceConfig.getId());
Preconditions.checkArgument(existedInstanceConfig != null, String.format(
"Instance config %d doesn't exist", instanceConfig.getId()));
existedInstanceConfig.setReleaseKey(instanceConfig.getReleaseKey());
existedInstanceConfig.setDataChangeLastModifiedTime(instanceConfig
.getDataChangeLastModifiedTime());
return instanceConfigRepository.save(existedInstanceConfig);
}
}
...@@ -8,7 +8,9 @@ import com.ctrip.framework.apollo.biz.repository.AppRepositoryTest; ...@@ -8,7 +8,9 @@ import com.ctrip.framework.apollo.biz.repository.AppRepositoryTest;
import com.ctrip.framework.apollo.biz.service.AdminServiceTest; import com.ctrip.framework.apollo.biz.service.AdminServiceTest;
import com.ctrip.framework.apollo.biz.service.AdminServiceTransactionTest; import com.ctrip.framework.apollo.biz.service.AdminServiceTransactionTest;
import com.ctrip.framework.apollo.biz.service.ClusterServiceTest; import com.ctrip.framework.apollo.biz.service.ClusterServiceTest;
import com.ctrip.framework.apollo.biz.service.InstanceServiceTest;
import com.ctrip.framework.apollo.biz.service.PrivilegeServiceTest; import com.ctrip.framework.apollo.biz.service.PrivilegeServiceTest;
import com.ctrip.framework.apollo.biz.service.ReleaseServiceTest;
import com.ctrip.framework.apollo.biz.service.ServerConfigServiceTest; import com.ctrip.framework.apollo.biz.service.ServerConfigServiceTest;
import com.ctrip.framework.apollo.biz.utils.ReleaseKeyGeneratorTest; import com.ctrip.framework.apollo.biz.utils.ReleaseKeyGeneratorTest;
...@@ -26,9 +28,11 @@ import org.junit.runners.Suite.SuiteClasses; ...@@ -26,9 +28,11 @@ import org.junit.runners.Suite.SuiteClasses;
DatabaseMessageSenderTest.class, DatabaseMessageSenderTest.class,
ServerConfigServiceTest.class, ServerConfigServiceTest.class,
ApolloEurekaClientConfigTest.class, ApolloEurekaClientConfigTest.class,
ReleaseServiceTest.class,
ReleaseMessageScannerTest.class, ReleaseMessageScannerTest.class,
ClusterServiceTest.class, ClusterServiceTest.class,
ReleaseKeyGeneratorTest.class ReleaseKeyGeneratorTest.class,
InstanceServiceTest.class
}) })
public class AllTests { public class AllTests {
......
package com.ctrip.framework.apollo.biz.service;
import com.ctrip.framework.apollo.biz.AbstractIntegrationTest;
import com.ctrip.framework.apollo.biz.entity.Instance;
import com.ctrip.framework.apollo.biz.entity.InstanceConfig;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import static org.junit.Assert.*;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class InstanceServiceTest extends AbstractIntegrationTest {
@Autowired
private InstanceService instanceService;
@Test
@Rollback
public void testInstance() throws Exception {
String someAppId = "someAppId";
String someClusterName = "someClusterName";
String someDataCenter = "someDataCenter";
String someIp = "someIp";
Instance instance = instanceService.findInstance(someAppId, someClusterName, someDataCenter,
someIp);
assertNull(instance);
instanceService.createInstance(assembleInstance(someAppId, someClusterName, someDataCenter,
someIp));
instance = instanceService.findInstance(someAppId, someClusterName, someDataCenter,
someIp);
assertNotEquals(0, instance.getId());
}
@Test
@Rollback
public void testInstanceConfig() throws Exception {
long someInstanceId = 1;
String someConfigAppId = "someConfigAppId";
String someConfigNamespaceName = "someConfigNamespaceName";
String someReleaseKey = "someReleaseKey";
String anotherReleaseKey = "anotherReleaseKey";
InstanceConfig instanceConfig = instanceService.findInstanceConfig(someInstanceId,
someConfigAppId, someConfigNamespaceName);
assertNull(instanceConfig);
instanceService.createInstanceConfig(assembleInstanceConfig(someInstanceId, someConfigAppId,
someConfigNamespaceName, someReleaseKey));
instanceConfig = instanceService.findInstanceConfig(someInstanceId, someConfigAppId,
someConfigNamespaceName);
assertNotEquals(0, instanceConfig.getId());
assertEquals(someReleaseKey, instanceConfig.getReleaseKey());
instanceConfig.setReleaseKey(anotherReleaseKey);
instanceService.updateInstanceConfig(instanceConfig);
InstanceConfig updated = instanceService.findInstanceConfig(someInstanceId, someConfigAppId,
someConfigNamespaceName);
assertEquals(instanceConfig.getId(), updated.getId());
assertEquals(anotherReleaseKey, updated.getReleaseKey());
}
private Instance assembleInstance(String appId, String clusterName, String dataCenter, String ip) {
Instance instance = new Instance();
instance.setAppId(appId);
instance.setIp(ip);
instance.setClusterName(clusterName);
instance.setDataCenter(dataCenter);
return instance;
}
private InstanceConfig assembleInstanceConfig(long instanceId, String configAppId, String
configNamespaceName, String releaseKey) {
InstanceConfig instanceConfig = new InstanceConfig();
instanceConfig.setInstanceId(instanceId);
instanceConfig.setConfigAppId(configAppId);
instanceConfig.setConfigNamespaceName(configNamespaceName);
instanceConfig.setReleaseKey(releaseKey);
return instanceConfig;
}
}
\ No newline at end of file
...@@ -8,10 +8,11 @@ import com.google.common.collect.Maps; ...@@ -8,10 +8,11 @@ import com.google.common.collect.Maps;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import com.ctrip.framework.apollo.biz.service.ReleaseService;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.biz.entity.Release; import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.service.AppNamespaceService; import com.ctrip.framework.apollo.biz.service.AppNamespaceService;
import com.ctrip.framework.apollo.biz.service.ReleaseService;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.configservice.util.InstanceConfigAuditUtil;
import com.ctrip.framework.apollo.configservice.util.NamespaceUtil; import com.ctrip.framework.apollo.configservice.util.NamespaceUtil;
import com.ctrip.framework.apollo.core.ConfigConsts; import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.dto.ApolloConfig; import com.ctrip.framework.apollo.core.dto.ApolloConfig;
...@@ -30,6 +31,7 @@ import java.util.List; ...@@ -30,6 +31,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
/** /**
...@@ -44,6 +46,8 @@ public class ConfigController { ...@@ -44,6 +46,8 @@ public class ConfigController {
private AppNamespaceService appNamespaceService; private AppNamespaceService appNamespaceService;
@Autowired @Autowired
private NamespaceUtil namespaceUtil; private NamespaceUtil namespaceUtil;
@Autowired
private InstanceConfigAuditUtil instanceConfigAuditUtil;
private static final Gson gson = new Gson(); private static final Gson gson = new Gson();
private static final Type configurationTypeReference = private static final Type configurationTypeReference =
...@@ -54,14 +58,21 @@ public class ConfigController { ...@@ -54,14 +58,21 @@ public class ConfigController {
@RequestMapping(value = "/{appId}/{clusterName}/{namespace:.+}", method = RequestMethod.GET) @RequestMapping(value = "/{appId}/{clusterName}/{namespace:.+}", method = RequestMethod.GET)
public ApolloConfig queryConfig(@PathVariable String appId, @PathVariable String clusterName, public ApolloConfig queryConfig(@PathVariable String appId, @PathVariable String clusterName,
@PathVariable String namespace, @PathVariable String namespace,
@RequestParam(value = "dataCenter", required = false) String dataCenter, @RequestParam(value = "dataCenter", required = false) String
@RequestParam(value = "releaseKey", defaultValue = "-1") String clientSideReleaseKey, dataCenter,
@RequestParam(value = "releaseKey", defaultValue = "-1") String
clientSideReleaseKey,
@RequestParam(value = "ip", required = false) String clientIp, @RequestParam(value = "ip", required = false) String clientIp,
HttpServletRequest request,
HttpServletResponse response) throws IOException { HttpServletResponse response) throws IOException {
String originalNamespace = namespace; String originalNamespace = namespace;
//strip out .properties suffix //strip out .properties suffix
namespace = namespaceUtil.filterNamespaceName(namespace); namespace = namespaceUtil.filterNamespaceName(namespace);
if (Strings.isNullOrEmpty(clientIp)) {
clientIp = tryToGetClientIp(request);
}
List<Release> releases = Lists.newLinkedList(); List<Release> releases = Lists.newLinkedList();
String appClusterNameLoaded = clusterName; String appClusterNameLoaded = clusterName;
...@@ -93,8 +104,10 @@ public class ConfigController { ...@@ -93,8 +104,10 @@ public class ConfigController {
return null; return null;
} }
auditReleases(appId, clusterName, dataCenter, clientIp, releases);
String mergedReleaseKey = FluentIterable.from(releases).transform( String mergedReleaseKey = FluentIterable.from(releases).transform(
input -> String.valueOf(input.getReleaseKey())).join(STRING_JOINER); input -> input.getReleaseKey()).join(STRING_JOINER);
if (mergedReleaseKey.equals(clientSideReleaseKey)) { if (mergedReleaseKey.equals(clientSideReleaseKey)) {
// Client side configuration is the same with server side, return 304 // Client side configuration is the same with server side, return 304
...@@ -104,10 +117,12 @@ public class ConfigController { ...@@ -104,10 +117,12 @@ public class ConfigController {
return null; return null;
} }
ApolloConfig apolloConfig = new ApolloConfig(appId, appClusterNameLoaded, originalNamespace, mergedReleaseKey); ApolloConfig apolloConfig = new ApolloConfig(appId, appClusterNameLoaded, originalNamespace,
mergedReleaseKey);
apolloConfig.setConfigurations(mergeReleaseConfigurations(releases)); apolloConfig.setConfigurations(mergeReleaseConfigurations(releases));
Cat.logEvent("Apollo.Config.Found", assembleKey(appId, appClusterNameLoaded, originalNamespace, dataCenter)); Cat.logEvent("Apollo.Config.Found", assembleKey(appId, appClusterNameLoaded,
originalNamespace, dataCenter));
return apolloConfig; return apolloConfig;
} }
...@@ -132,7 +147,8 @@ public class ConfigController { ...@@ -132,7 +147,8 @@ public class ConfigController {
* @param namespace the namespace * @param namespace the namespace
* @param dataCenter the datacenter * @param dataCenter the datacenter
*/ */
private Release findPublicConfig(String applicationId, String clusterName, String namespace, String dataCenter) { private Release findPublicConfig(String applicationId, String clusterName, String namespace,
String dataCenter) {
AppNamespace appNamespace = appNamespaceService.findPublicNamespaceByName(namespace); AppNamespace appNamespace = appNamespaceService.findPublicNamespaceByName(namespace);
//check whether the namespace's appId equals to current one //check whether the namespace's appId equals to current one
...@@ -145,7 +161,8 @@ public class ConfigController { ...@@ -145,7 +161,8 @@ public class ConfigController {
return loadConfig(publicConfigAppId, clusterName, namespace, dataCenter); return loadConfig(publicConfigAppId, clusterName, namespace, dataCenter);
} }
private Release loadConfig(String appId, String clusterName, String namespace, String dataCenter) { private Release loadConfig(String appId, String clusterName, String namespace, String
dataCenter) {
//load from specified cluster fist //load from specified cluster fist
if (!Objects.equals(ConfigConsts.CLUSTER_NAME_DEFAULT, clusterName)) { if (!Objects.equals(ConfigConsts.CLUSTER_NAME_DEFAULT, clusterName)) {
Release clusterRelease = Release clusterRelease =
...@@ -190,4 +207,24 @@ public class ConfigController { ...@@ -190,4 +207,24 @@ public class ConfigController {
return STRING_JOINER.join(keyParts); return STRING_JOINER.join(keyParts);
} }
private void auditReleases(String appId, String cluster, String datacenter, String clientIp,
List<Release> releases) {
if (Strings.isNullOrEmpty(clientIp)) {
//no need to audit instance config when there is no ip
return;
}
for (Release release : releases) {
instanceConfigAuditUtil.audit(appId, cluster, datacenter, clientIp, release.getAppId(),
release.getNamespaceName(), release.getReleaseKey());
}
}
private String tryToGetClientIp(HttpServletRequest request) {
String ipAddress = request.getHeader("X-FORWARDED-FOR");
if (ipAddress == null) {
ipAddress = request.getRemoteAddr();
}
return ipAddress;
}
} }
...@@ -42,6 +42,7 @@ import java.util.Properties; ...@@ -42,6 +42,7 @@ import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
/** /**
...@@ -114,12 +115,13 @@ public class ConfigFileController implements ReleaseMessageListener { ...@@ -114,12 +115,13 @@ public class ConfigFileController implements ReleaseMessageListener {
@PathVariable String namespace, @PathVariable String namespace,
@RequestParam(value = "dataCenter", required = false) String dataCenter, @RequestParam(value = "dataCenter", required = false) String dataCenter,
@RequestParam(value = "ip", required = false) String clientIp, @RequestParam(value = "ip", required = false) String clientIp,
HttpServletRequest request,
HttpServletResponse response) HttpServletResponse response)
throws IOException { throws IOException {
String result = String result =
queryConfig(ConfigFileOutputFormat.PROPERTIES, appId, clusterName, namespace, dataCenter, queryConfig(ConfigFileOutputFormat.PROPERTIES, appId, clusterName, namespace, dataCenter,
clientIp, response); clientIp, request, response);
if (result == null) { if (result == null) {
return NOT_FOUND_RESPONSE; return NOT_FOUND_RESPONSE;
...@@ -134,11 +136,12 @@ public class ConfigFileController implements ReleaseMessageListener { ...@@ -134,11 +136,12 @@ public class ConfigFileController implements ReleaseMessageListener {
@PathVariable String namespace, @PathVariable String namespace,
@RequestParam(value = "dataCenter", required = false) String dataCenter, @RequestParam(value = "dataCenter", required = false) String dataCenter,
@RequestParam(value = "ip", required = false) String clientIp, @RequestParam(value = "ip", required = false) String clientIp,
HttpServletRequest request,
HttpServletResponse response) throws IOException { HttpServletResponse response) throws IOException {
String result = String result =
queryConfig(ConfigFileOutputFormat.JSON, appId, clusterName, namespace, dataCenter, queryConfig(ConfigFileOutputFormat.JSON, appId, clusterName, namespace, dataCenter,
clientIp, response); clientIp, request, response);
if (result == null) { if (result == null) {
return NOT_FOUND_RESPONSE; return NOT_FOUND_RESPONSE;
...@@ -149,6 +152,7 @@ public class ConfigFileController implements ReleaseMessageListener { ...@@ -149,6 +152,7 @@ public class ConfigFileController implements ReleaseMessageListener {
String queryConfig(ConfigFileOutputFormat outputFormat, String appId, String clusterName, String queryConfig(ConfigFileOutputFormat outputFormat, String appId, String clusterName,
String namespace, String dataCenter, String clientIp, String namespace, String dataCenter, String clientIp,
HttpServletRequest request,
HttpServletResponse response) throws IOException { HttpServletResponse response) throws IOException {
//strip out .properties suffix //strip out .properties suffix
namespace = namespaceUtil.filterNamespaceName(namespace); namespace = namespaceUtil.filterNamespaceName(namespace);
...@@ -162,7 +166,7 @@ public class ConfigFileController implements ReleaseMessageListener { ...@@ -162,7 +166,7 @@ public class ConfigFileController implements ReleaseMessageListener {
Cat.logEvent("ConfigFile.Cache.Miss", cacheKey); Cat.logEvent("ConfigFile.Cache.Miss", cacheKey);
ApolloConfig apolloConfig = ApolloConfig apolloConfig =
configController configController
.queryConfig(appId, clusterName, namespace, dataCenter, "-1", clientIp, .queryConfig(appId, clusterName, namespace, dataCenter, "-1", clientIp, request,
response); response);
if (apolloConfig == null || apolloConfig.getConfigurations() == null) { if (apolloConfig == null || apolloConfig.getConfigurations() == null) {
......
package com.ctrip.framework.apollo.configservice.util;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Lists;
import com.google.common.collect.Queues;
import com.ctrip.framework.apollo.biz.entity.Instance;
import com.ctrip.framework.apollo.biz.entity.InstanceConfig;
import com.ctrip.framework.apollo.biz.service.InstanceService;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory;
import com.dianping.cat.Cat;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@Service
public class InstanceConfigAuditUtil implements InitializingBean {
private static final int INSTANCE_CONFIG_AUDIT_MAX_SIZE = 2000;
private static final int INSTANCE_CACHE_MAX_SIZE = 10000;
private static final int INSTANCE_CONFIG_CACHE_MAX_SIZE = 10000;
private static final Joiner STRING_JOINER = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR);
private final ExecutorService auditExecutorService;
private final AtomicBoolean auditStopped;
private BlockingQueue<InstanceConfigAuditModel> audits = Queues.newLinkedBlockingQueue
(INSTANCE_CONFIG_AUDIT_MAX_SIZE);
private Cache<String, Long> instanceCache;
private Cache<String, String> instanceConfigReleaseKeyCache;
@Autowired
private InstanceService instanceService;
public InstanceConfigAuditUtil() {
auditExecutorService = Executors.newSingleThreadExecutor(
ApolloThreadFactory.create("InstanceConfigAuditUtil", true));
auditStopped = new AtomicBoolean(false);
instanceCache = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.HOURS)
.maximumSize(INSTANCE_CACHE_MAX_SIZE).build();
instanceConfigReleaseKeyCache = CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.DAYS)
.maximumSize(INSTANCE_CONFIG_CACHE_MAX_SIZE).build();
}
public boolean audit(String appId, String clusterName, String dataCenter, String
ip, String configAppId, String configNamespace, String releaseKey) {
return this.audits.offer(new InstanceConfigAuditModel(appId, clusterName, dataCenter, ip,
configAppId, configNamespace, releaseKey));
}
void doAudit(InstanceConfigAuditModel auditModel) {
String instanceCacheKey = assembleInstanceKey(auditModel.getAppId(), auditModel
.getClusterName(),
auditModel.getIp(), auditModel.getDataCenter());
Long instanceId = instanceCache.getIfPresent(instanceCacheKey);
if (instanceId == null) {
instanceId = prepareInstanceId(auditModel);
instanceCache.put(instanceCacheKey, instanceId);
}
//load instance config release key from cache, and check if release key is the same
String instanceConfigCacheKey = assembleInstanceConfigKey(instanceId, auditModel
.getConfigAppId(), auditModel.getConfigNamespace());
String cacheReleaseKey = instanceConfigReleaseKeyCache.getIfPresent(instanceConfigCacheKey);
//if release key is the same, then skip audit
if (cacheReleaseKey != null && Objects.equals(cacheReleaseKey, auditModel.getReleaseKey())) {
return;
}
instanceConfigReleaseKeyCache.put(instanceConfigCacheKey, auditModel.getReleaseKey());
//if release key is not the same or cannot find in cache, then do audit
InstanceConfig instanceConfig = instanceService.findInstanceConfig(instanceId, auditModel
.getConfigAppId(), auditModel.getConfigNamespace());
//we need to update no matter the release key is the same or not, to ensure the
//last modified time is updated each day
if (instanceConfig != null) {
instanceConfig.setReleaseKey(auditModel.getReleaseKey());
instanceConfig.setDataChangeLastModifiedTime(new Date());
instanceService.updateInstanceConfig(instanceConfig);
return;
}
instanceConfig = new InstanceConfig();
instanceConfig.setInstanceId(instanceId);
instanceConfig.setConfigAppId(auditModel.getConfigAppId());
instanceConfig.setConfigNamespaceName(auditModel.getConfigNamespace());
instanceConfig.setReleaseKey(auditModel.getReleaseKey());
try {
instanceService.createInstanceConfig(instanceConfig);
} catch (DataIntegrityViolationException ex) {
//concurrent insertion, safe to ignore
}
}
private long prepareInstanceId(InstanceConfigAuditModel auditModel) {
Instance instance = instanceService.findInstance(auditModel.getAppId(), auditModel
.getClusterName(), auditModel.getDataCenter(), auditModel.getIp());
if (instance != null) {
return instance.getId();
}
instance = new Instance();
instance.setAppId(auditModel.getAppId());
instance.setClusterName(auditModel.getClusterName());
instance.setDataCenter(auditModel.getDataCenter());
instance.setIp(auditModel.getIp());
try {
return instanceService.createInstance(instance).getId();
} catch (DataIntegrityViolationException ex) {
//return the one exists
return instanceService.findInstance(instance.getAppId(), instance.getClusterName(),
instance.getDataCenter(), instance.getIp()).getId();
}
}
@Override
public void afterPropertiesSet() throws Exception {
auditExecutorService.submit(() -> {
while (!auditStopped.get() && !Thread.currentThread().isInterrupted()) {
try {
InstanceConfigAuditModel model = audits.poll();
if (model == null) {
TimeUnit.SECONDS.sleep(1);
continue;
}
doAudit(model);
} catch (Throwable ex) {
Cat.logError(ex);
}
}
});
}
private String assembleInstanceKey(String appId, String cluster, String ip, String datacenter) {
List<String> keyParts = Lists.newArrayList(appId, cluster, ip);
if (!Strings.isNullOrEmpty(datacenter)) {
keyParts.add(datacenter);
}
return STRING_JOINER.join(keyParts);
}
private String assembleInstanceConfigKey(long instanceId, String configAppId, String
configNamespace) {
return STRING_JOINER.join(instanceId, configAppId, configNamespace);
}
public static class InstanceConfigAuditModel {
private String appId;
private String clusterName;
private String dataCenter;
private String ip;
private String configAppId;
private String configNamespace;
private String releaseKey;
public InstanceConfigAuditModel(String appId, String clusterName, String dataCenter, String
clientIp, String configAppId, String configNamespace, String releaseKey) {
this.appId = appId;
this.clusterName = clusterName;
this.dataCenter = Strings.isNullOrEmpty(dataCenter) ? "" : dataCenter;
this.ip = clientIp;
this.configAppId = configAppId;
this.configNamespace = configNamespace;
this.releaseKey = releaseKey;
}
public String getAppId() {
return appId;
}
public String getClusterName() {
return clusterName;
}
public String getDataCenter() {
return dataCenter;
}
public String getIp() {
return ip;
}
public String getConfigAppId() {
return configAppId;
}
public String getConfigNamespace() {
return configNamespace;
}
public String getReleaseKey() {
return releaseKey;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
InstanceConfigAuditModel model = (InstanceConfigAuditModel) o;
return Objects.equals(appId, model.appId) &&
Objects.equals(clusterName, model.clusterName) &&
Objects.equals(dataCenter, model.dataCenter) &&
Objects.equals(ip, model.ip) &&
Objects.equals(configAppId, model.configAppId) &&
Objects.equals(configNamespace, model.configNamespace) &&
Objects.equals(releaseKey, model.releaseKey);
}
@Override
public int hashCode() {
return Objects.hash(appId, clusterName, dataCenter, ip, configAppId, configNamespace,
releaseKey);
}
}
}
...@@ -8,6 +8,7 @@ import com.ctrip.framework.apollo.configservice.integration.ConfigControllerInte ...@@ -8,6 +8,7 @@ import com.ctrip.framework.apollo.configservice.integration.ConfigControllerInte
import com.ctrip.framework.apollo.configservice.integration.ConfigFileControllerIntegrationTest; import com.ctrip.framework.apollo.configservice.integration.ConfigFileControllerIntegrationTest;
import com.ctrip.framework.apollo.configservice.integration.NotificationControllerIntegrationTest; import com.ctrip.framework.apollo.configservice.integration.NotificationControllerIntegrationTest;
import com.ctrip.framework.apollo.configservice.integration.NotificationControllerV2IntegrationTest; import com.ctrip.framework.apollo.configservice.integration.NotificationControllerV2IntegrationTest;
import com.ctrip.framework.apollo.configservice.util.InstanceConfigAuditUtilTest;
import com.ctrip.framework.apollo.configservice.util.NamespaceUtilTest; import com.ctrip.framework.apollo.configservice.util.NamespaceUtilTest;
import com.ctrip.framework.apollo.configservice.util.WatchKeysUtilTest; import com.ctrip.framework.apollo.configservice.util.WatchKeysUtilTest;
...@@ -20,7 +21,8 @@ import org.junit.runners.Suite.SuiteClasses; ...@@ -20,7 +21,8 @@ import org.junit.runners.Suite.SuiteClasses;
ConfigControllerIntegrationTest.class, NotificationControllerIntegrationTest.class, ConfigControllerIntegrationTest.class, NotificationControllerIntegrationTest.class,
NamespaceUtilTest.class, ConfigFileControllerTest.class, NamespaceUtilTest.class, ConfigFileControllerTest.class,
ConfigFileControllerIntegrationTest.class, WatchKeysUtilTest.class, ConfigFileControllerIntegrationTest.class, WatchKeysUtilTest.class,
NotificationControllerV2Test.class, NotificationControllerV2IntegrationTest.class NotificationControllerV2Test.class, NotificationControllerV2IntegrationTest.class,
InstanceConfigAuditUtilTest.class
}) })
public class AllTests { public class AllTests {
......
...@@ -27,6 +27,7 @@ import java.lang.reflect.Type; ...@@ -27,6 +27,7 @@ import java.lang.reflect.Type;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
...@@ -56,6 +57,8 @@ public class ConfigFileControllerTest { ...@@ -56,6 +57,8 @@ public class ConfigFileControllerTest {
private String someClientIp; private String someClientIp;
@Mock @Mock
private HttpServletResponse someResponse; private HttpServletResponse someResponse;
@Mock
private HttpServletRequest someRequest;
Multimap<String, String> watchedKeys2CacheKey; Multimap<String, String> watchedKeys2CacheKey;
Multimap<String, String> cacheKey2WatchedKeys; Multimap<String, String> cacheKey2WatchedKeys;
...@@ -103,7 +106,7 @@ public class ConfigFileControllerTest { ...@@ -103,7 +106,7 @@ public class ConfigFileControllerTest {
when(someApolloConfig.getConfigurations()).thenReturn(configurations); when(someApolloConfig.getConfigurations()).thenReturn(configurations);
when(configController when(configController
.queryConfig(someAppId, someClusterName, someNamespace, someDataCenter, "-1", someClientIp, .queryConfig(someAppId, someClusterName, someNamespace, someDataCenter, "-1", someClientIp,
someResponse)).thenReturn(someApolloConfig); someRequest, someResponse)).thenReturn(someApolloConfig);
when(watchKeysUtil when(watchKeysUtil
.assembleAllWatchKeys(someAppId, someClusterName, someNamespace, someDataCenter)) .assembleAllWatchKeys(someAppId, someClusterName, someNamespace, someDataCenter))
.thenReturn(watchKeys); .thenReturn(watchKeys);
...@@ -111,7 +114,7 @@ public class ConfigFileControllerTest { ...@@ -111,7 +114,7 @@ public class ConfigFileControllerTest {
ResponseEntity<String> response = ResponseEntity<String> response =
configFileController configFileController
.queryConfigAsProperties(someAppId, someClusterName, someNamespace, someDataCenter, .queryConfigAsProperties(someAppId, someClusterName, someNamespace, someDataCenter,
someClientIp, someResponse); someClientIp, someRequest, someResponse);
assertEquals(2, watchedKeys2CacheKey.size()); assertEquals(2, watchedKeys2CacheKey.size());
assertEquals(2, cacheKey2WatchedKeys.size()); assertEquals(2, cacheKey2WatchedKeys.size());
...@@ -127,13 +130,13 @@ public class ConfigFileControllerTest { ...@@ -127,13 +130,13 @@ public class ConfigFileControllerTest {
ResponseEntity<String> anotherResponse = ResponseEntity<String> anotherResponse =
configFileController configFileController
.queryConfigAsProperties(someAppId, someClusterName, someNamespace, someDataCenter, .queryConfigAsProperties(someAppId, someClusterName, someNamespace, someDataCenter,
someClientIp, someResponse); someClientIp, someRequest, someResponse);
assertEquals(response, anotherResponse); assertEquals(response, anotherResponse);
verify(configController, times(1)) verify(configController, times(1))
.queryConfig(someAppId, someClusterName, someNamespace, someDataCenter, "-1", someClientIp, .queryConfig(someAppId, someClusterName, someNamespace, someDataCenter, "-1", someClientIp,
someResponse); someRequest, someResponse);
} }
@Test @Test
...@@ -151,7 +154,7 @@ public class ConfigFileControllerTest { ...@@ -151,7 +154,7 @@ public class ConfigFileControllerTest {
ApolloConfig someApolloConfig = mock(ApolloConfig.class); ApolloConfig someApolloConfig = mock(ApolloConfig.class);
when(configController when(configController
.queryConfig(someAppId, someClusterName, someNamespace, someDataCenter, "-1", someClientIp, .queryConfig(someAppId, someClusterName, someNamespace, someDataCenter, "-1", someClientIp,
someResponse)).thenReturn(someApolloConfig); someRequest, someResponse)).thenReturn(someApolloConfig);
when(someApolloConfig.getConfigurations()).thenReturn(configurations); when(someApolloConfig.getConfigurations()).thenReturn(configurations);
when(watchKeysUtil when(watchKeysUtil
.assembleAllWatchKeys(someAppId, someClusterName, someNamespace, someDataCenter)) .assembleAllWatchKeys(someAppId, someClusterName, someNamespace, someDataCenter))
...@@ -160,7 +163,7 @@ public class ConfigFileControllerTest { ...@@ -160,7 +163,7 @@ public class ConfigFileControllerTest {
ResponseEntity<String> response = ResponseEntity<String> response =
configFileController configFileController
.queryConfigAsJson(someAppId, someClusterName, someNamespace, someDataCenter, .queryConfigAsJson(someAppId, someClusterName, someNamespace, someDataCenter,
someClientIp, someResponse); someClientIp, someRequest, someResponse);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(configurations, gson.fromJson(response.getBody(), responseType)); assertEquals(configurations, gson.fromJson(response.getBody(), responseType));
......
package com.ctrip.framework.apollo.configservice.util;
import com.ctrip.framework.apollo.biz.entity.Instance;
import com.ctrip.framework.apollo.biz.entity.InstanceConfig;
import com.ctrip.framework.apollo.biz.service.InstanceService;
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.test.util.ReflectionTestUtils;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.function.ObjDoubleConsumer;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@RunWith(MockitoJUnitRunner.class)
public class InstanceConfigAuditUtilTest {
private InstanceConfigAuditUtil instanceConfigAuditUtil;
@Mock
private InstanceService instanceService;
private BlockingQueue<InstanceConfigAuditUtil.InstanceConfigAuditModel> audits;
private String someAppId;
private String someClusterName;
private String someDataCenter;
private String someIp;
private String someConfigAppId;
private String someConfigNamespace;
private String someReleaseKey;
private InstanceConfigAuditUtil.InstanceConfigAuditModel someAuditModel;
@Before
public void setUp() throws Exception {
instanceConfigAuditUtil = new InstanceConfigAuditUtil();
ReflectionTestUtils.setField(instanceConfigAuditUtil, "instanceService", instanceService);
audits = (BlockingQueue<InstanceConfigAuditUtil.InstanceConfigAuditModel>)
ReflectionTestUtils.getField(instanceConfigAuditUtil, "audits");
someAppId = "someAppId";
someClusterName = "someClusterName";
someDataCenter = "someDataCenter";
someIp = "someIp";
someConfigAppId = "someConfigAppId";
someConfigNamespace = "someConfigNamespace";
someReleaseKey = "someReleaseKey";
someAuditModel = new InstanceConfigAuditUtil.InstanceConfigAuditModel(someAppId,
someClusterName, someDataCenter, someIp, someConfigAppId, someConfigNamespace,
someReleaseKey);
}
@Test
public void testAudit() throws Exception {
boolean result = instanceConfigAuditUtil.audit(someAppId, someClusterName, someDataCenter,
someIp, someConfigAppId, someConfigNamespace, someReleaseKey);
InstanceConfigAuditUtil.InstanceConfigAuditModel audit = audits.poll();
assertTrue(result);
assertTrue(Objects.equals(someAuditModel, audit));
}
@Test
public void testDoAudit() throws Exception {
long someInstanceId = 1;
Instance someInstance = mock(Instance.class);
when(someInstance.getId()).thenReturn(someInstanceId);
when(instanceService.createInstance(any(Instance.class))).thenReturn(someInstance);
instanceConfigAuditUtil.doAudit(someAuditModel);
verify(instanceService, times(1)).findInstance(someAppId, someClusterName, someDataCenter,
someIp);
verify(instanceService, times(1)).createInstance(any(Instance.class));
verify(instanceService, times(1)).findInstanceConfig(someInstanceId, someConfigAppId,
someConfigNamespace);
verify(instanceService, times(1)).createInstanceConfig(any(InstanceConfig.class));
}
}
\ 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