Commit 6153339b authored by Jason Song's avatar Jason Song Committed by GitHub

Merge pull request #393 from lepdou/instances

Instances
parents 9e27f19d a8af467f
package com.ctrip.framework.apollo.adminservice.controller;
import com.google.common.base.Splitter;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.ctrip.framework.apollo.biz.entity.Instance;
import com.ctrip.framework.apollo.biz.entity.InstanceConfig;
import com.ctrip.framework.apollo.biz.entity.Release;
......@@ -7,87 +13,146 @@ import com.ctrip.framework.apollo.biz.service.InstanceService;
import com.ctrip.framework.apollo.biz.service.ReleaseService;
import com.ctrip.framework.apollo.common.dto.InstanceConfigDTO;
import com.ctrip.framework.apollo.common.dto.InstanceDTO;
import com.ctrip.framework.apollo.common.dto.PageDTO;
import com.ctrip.framework.apollo.common.dto.ReleaseDTO;
import com.ctrip.framework.apollo.common.exception.NotFoundException;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@RestController
@RequestMapping(path = "/instances")
@RequestMapping("/instances")
public class InstanceConfigController {
private static final Splitter RELEASES_SPLITTER = Splitter.on(",").omitEmptyStrings()
.trimResults();
@Autowired
private ReleaseService releaseService;
@Autowired
private InstanceService instanceService;
@RequestMapping(value = "/by-release", method = RequestMethod.GET)
public List<InstanceDTO> getByRelease(@RequestParam("releaseId") long releaseId,
@RequestParam(value = "withReleaseDetail", defaultValue =
"false") boolean withReleaseDetail,
Pageable pageable) {
public PageDTO<InstanceDTO> getByRelease(@RequestParam("releaseId") long releaseId,
Pageable pageable) {
Release release = releaseService.findOne(releaseId);
if (release == null) {
throw new NotFoundException(String.format("release not found for %s", releaseId));
}
List<InstanceConfig> instanceConfigs = instanceService.findActiveInstanceConfigsByReleaseKey
Page<InstanceConfig> instanceConfigsPage = instanceService.findActiveInstanceConfigsByReleaseKey
(release.getReleaseKey(), pageable);
if (instanceConfigs.isEmpty()) {
return Collections.emptyList();
List<InstanceDTO> instanceDTOs = Collections.emptyList();
if (instanceConfigsPage.hasContent()) {
Set<Long> instanceIds = instanceConfigsPage.getContent().stream().map
(InstanceConfig::getInstanceId).collect(Collectors.toSet());
List<Instance> instances = instanceService.findInstancesByIds(instanceIds);
if (!CollectionUtils.isEmpty(instances)) {
instanceDTOs = BeanUtils.batchTransform(InstanceDTO.class, instances);
}
}
return new PageDTO<>(instanceDTOs, pageable, instanceConfigsPage.getTotalElements());
}
@RequestMapping(value = "/by-namespace-and-releases-not-in", method = RequestMethod.GET)
public List<InstanceDTO> getByReleasesNotIn(@RequestParam("appId") String appId,
@RequestParam("clusterName") String clusterName,
@RequestParam("namespaceName") String namespaceName,
@RequestParam("releaseIds") String releaseIds) {
Set<Long> releaseIdSet = RELEASES_SPLITTER.splitToList(releaseIds).stream().map(Long::parseLong)
.collect(Collectors.toSet());
List<Release> releases = releaseService.findByReleaseIds(releaseIdSet);
if (CollectionUtils.isEmpty(releases)) {
throw new NotFoundException(String.format("releases not found for %s", releaseIds));
}
Map<Long, List<InstanceConfig>> instanceConfigMap = instanceConfigs.stream().collect(Collectors
.groupingBy(InstanceConfig::getInstanceId));
Set<String> releaseKeys = releases.stream().map(Release::getReleaseKey).collect(Collectors
.toSet());
List<InstanceConfig> instanceConfigs = instanceService
.findInstanceConfigsByNamespaceWithReleaseKeysNotIn(appId, clusterName, namespaceName,
releaseKeys);
Multimap<Long, InstanceConfig> instanceConfigMap = HashMultimap.create();
Set<String> otherReleaseKeys = Sets.newHashSet();
for (InstanceConfig instanceConfig : instanceConfigs) {
instanceConfigMap.put(instanceConfig.getInstanceId(), instanceConfig);
otherReleaseKeys.add(instanceConfig.getReleaseKey());
}
List<Instance> instances = instanceService.findInstancesByIds(instanceConfigMap.keySet());
if (instances.isEmpty()) {
if (CollectionUtils.isEmpty(instances)) {
return Collections.emptyList();
}
return instances.stream().map(transformToInstanceConfigDto).peek(instanceDTO -> {
List<InstanceConfig> instanceConfigList = instanceConfigMap.get(instanceDTO.getId());
ReleaseDTO releaseDTO = withReleaseDetail ? BeanUtils.transfrom(ReleaseDTO.class, release)
: null;
instanceDTO.setConfigs(instanceConfigList.stream()
.map(instanceConfig -> transformToInstanceConfigDto(instanceConfig, releaseDTO))
.collect(Collectors.toList()));
}).collect(Collectors.toList());
List<InstanceDTO> instanceDTOs = BeanUtils.batchTransform(InstanceDTO.class, instances);
List<Release> otherReleases = releaseService.findByReleaseKeys(otherReleaseKeys);
Map<String, ReleaseDTO> releaseMap = Maps.newHashMap();
for (Release release : otherReleases) {
//unset configurations to save space
release.setConfigurations(null);
ReleaseDTO releaseDTO = BeanUtils.transfrom(ReleaseDTO.class, release);
releaseMap.put(release.getReleaseKey(), releaseDTO);
}
for (InstanceDTO instanceDTO : instanceDTOs) {
Collection<InstanceConfig> configs = instanceConfigMap.get(instanceDTO.getId());
List<InstanceConfigDTO> configDTOs = configs.stream().map(instanceConfig -> {
InstanceConfigDTO instanceConfigDTO = new InstanceConfigDTO();
instanceConfigDTO.setRelease(releaseMap.get(instanceConfig.getReleaseKey()));
instanceConfigDTO.setDataChangeLastModifiedTime(instanceConfig
.getDataChangeLastModifiedTime());
return instanceConfigDTO;
}).collect(Collectors.toList());
instanceDTO.setConfigs(configDTOs);
}
return instanceDTOs;
}
private InstanceConfigDTO transformToInstanceConfigDto(InstanceConfig instanceConfig,
ReleaseDTO releaseDTO) {
InstanceConfigDTO instanceConfigDTO = new InstanceConfigDTO();
instanceConfigDTO.setDataChangeLastModifiedTime(instanceConfig
.getDataChangeLastModifiedTime());
instanceConfigDTO.setRelease(releaseDTO);
return instanceConfigDTO;
@RequestMapping(value = "/by-namespace", method = RequestMethod.GET)
public PageDTO<InstanceDTO> getInstancesByNamespace(@RequestParam("appId") String appId,
@RequestParam("clusterName") String clusterName,
@RequestParam("namespaceName") String
namespaceName, Pageable pageable) {
Page<Instance> instances = instanceService.findInstancesByNamespace(appId, clusterName,
namespaceName, pageable);
List<InstanceDTO> instanceDTOs = BeanUtils.batchTransform(InstanceDTO.class, instances.getContent());
return new PageDTO<>(instanceDTOs, pageable, instances.getTotalElements());
}
private static Function<Instance, InstanceDTO> transformToInstanceConfigDto = instance -> {
InstanceDTO instanceDTO = new InstanceDTO();
instanceDTO.setId(instance.getId());
instanceDTO.setAppId(instance.getAppId());
instanceDTO.setClusterName(instance.getClusterName());
instanceDTO.setDataCenter(instance.getDataCenter());
instanceDTO.setIp(instance.getIp());
instanceDTO.setDataChangeCreatedTime(instance.getDataChangeCreatedTime());
return instanceDTO;
};
@RequestMapping(value = "/by-namespace/count", method = RequestMethod.GET)
public long getInstancesCountByNamespace(@RequestParam("appId") String appId,
@RequestParam("clusterName") String clusterName,
@RequestParam("namespaceName") String namespaceName) {
Page<Instance> instances = instanceService.findInstancesByNamespace(appId, clusterName,
namespaceName, new PageRequest(0, 1));
return instances.getTotalElements();
}
}
......@@ -29,6 +29,9 @@ public class InstanceConfig {
@Column(name = "ConfigAppId", nullable = false)
private String configAppId;
@Column(name = "ConfigClusterName", nullable = false)
private String configClusterName;
@Column(name = "ConfigNamespaceName", nullable = false)
private String configNamespaceName;
......@@ -112,12 +115,21 @@ public class InstanceConfig {
this.dataChangeLastModifiedTime = dataChangeLastModifiedTime;
}
public String getConfigClusterName() {
return configClusterName;
}
public void setConfigClusterName(String configClusterName) {
this.configClusterName = configClusterName;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.omitNullValues()
.add("id", id)
.add("configAppId", configAppId)
.add("configClusterName", configClusterName)
.add("configNamespaceName", configNamespaceName)
.add("releaseKey", releaseKey)
.add("dataChangeCreatedTime", dataChangeCreatedTime)
......
......@@ -2,16 +2,26 @@ package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.biz.entity.InstanceConfig;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.PagingAndSortingRepository;
import java.util.Date;
import java.util.List;
import java.util.Set;
public interface InstanceConfigRepository extends PagingAndSortingRepository<InstanceConfig, Long> {
InstanceConfig findByInstanceIdAndConfigAppIdAndConfigNamespaceName(long instanceId, String
configAppId, String configNamespaceName);
List<InstanceConfig> findByReleaseKeyAndDataChangeLastModifiedTimeAfter(String releaseKey, Date
InstanceConfig findByInstanceIdAndConfigAppIdAndConfigClusterNameAndConfigNamespaceName(long instanceId, String
configAppId, String configClusterName, String configNamespaceName);
Page<InstanceConfig> findByReleaseKeyAndDataChangeLastModifiedTimeAfter(String releaseKey, Date
validDate, Pageable pageable);
Page<InstanceConfig> findByConfigAppIdAndConfigClusterNameAndConfigNamespaceNameAndDataChangeLastModifiedTimeAfter(
String appId, String clusterName, String namespaceName, Date validDate, Pageable pageable);
List<InstanceConfig> findByConfigAppIdAndConfigClusterNameAndConfigNamespaceNameAndDataChangeLastModifiedTimeAfterAndReleaseKeyNotIn(
String appId, String clusterName, String namespaceName, Date validDate, Set<String> releaseKey);
}
package com.ctrip.framework.apollo.biz.repository;
import java.util.List;
import java.util.Set;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Modifying;
......@@ -22,6 +23,8 @@ public interface ReleaseRepository extends PagingAndSortingRepository<Release, L
List<Release> findByAppIdAndClusterNameAndNamespaceNameAndIsAbandonedFalseOrderByIdDesc(String appId, String clusterName, String namespaceName, Pageable page);
List<Release> findByReleaseKeyIn(Set<String> releaseKey);
@Modifying
@Query("update Release set isdeleted=1,DataChange_LastModifiedBy = ?4 where appId=?1 and clusterName=?2 and namespaceName = ?3")
int batchDelete(String appId, String clusterName, String namespaceName, String operator);
......
......@@ -9,15 +9,19 @@ 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.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author Jason Song(song_s@ctrip.com)
......@@ -50,20 +54,51 @@ public class InstanceService {
return instanceRepository.save(instance);
}
public InstanceConfig findInstanceConfig(long instanceId, String configAppId,
String configNamespaceName) {
return instanceConfigRepository.findByInstanceIdAndConfigAppIdAndConfigNamespaceName(
instanceId, configAppId, configNamespaceName);
public InstanceConfig findInstanceConfig(long instanceId, String configAppId, String
configClusterName, String configNamespaceName) {
return instanceConfigRepository
.findByInstanceIdAndConfigAppIdAndConfigClusterNameAndConfigNamespaceName(
instanceId, configAppId, configClusterName, configNamespaceName);
}
public List<InstanceConfig> findActiveInstanceConfigsByReleaseKey(String releaseKey, Pageable
public Page<InstanceConfig> findActiveInstanceConfigsByReleaseKey(String releaseKey, Pageable
pageable) {
List<InstanceConfig> instanceConfigs = instanceConfigRepository
Page<InstanceConfig> instanceConfigs = instanceConfigRepository
.findByReleaseKeyAndDataChangeLastModifiedTimeAfter(releaseKey,
getValidInstanceConfigDate(), pageable);
if (instanceConfigs == null) {
return instanceConfigs;
}
public Page<Instance> findInstancesByNamespace(String appId, String clusterName, String
namespaceName, Pageable pageable) {
Page<InstanceConfig> instanceConfigs = instanceConfigRepository.
findByConfigAppIdAndConfigClusterNameAndConfigNamespaceNameAndDataChangeLastModifiedTimeAfter(appId, clusterName,
namespaceName, getValidInstanceConfigDate(), pageable);
List<Instance> instances = Collections.emptyList();
if (instanceConfigs.hasContent()) {
Set<Long> instanceIds = instanceConfigs.getContent().stream().map
(InstanceConfig::getInstanceId).collect(Collectors.toSet());
instances = findInstancesByIds(instanceIds);
}
return new PageImpl<>(instances, pageable, instanceConfigs.getTotalElements());
}
public List<InstanceConfig> findInstanceConfigsByNamespaceWithReleaseKeysNotIn(String appId,
String clusterName,
String
namespaceName,
Set<String>
releaseKeysNotIn) {
List<InstanceConfig> instanceConfigs = instanceConfigRepository.
findByConfigAppIdAndConfigClusterNameAndConfigNamespaceNameAndDataChangeLastModifiedTimeAfterAndReleaseKeyNotIn(appId, clusterName,
namespaceName, getValidInstanceConfigDate(), releaseKeysNotIn);
if (CollectionUtils.isEmpty(instanceConfigs)) {
return Collections.emptyList();
}
return instanceConfigs;
}
......
package com.ctrip.framework.apollo.biz.service;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import com.ctrip.framework.apollo.biz.entity.Audit;
import com.ctrip.framework.apollo.biz.entity.Instance;
import com.ctrip.framework.apollo.biz.entity.Item;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.entity.NamespaceLock;
......@@ -14,6 +16,8 @@ import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.exception.NotFoundException;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import org.apache.commons.collections.collection.UnmodifiableCollection;
import org.apache.commons.collections.list.UnmodifiableList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
......@@ -25,6 +29,7 @@ import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author Jason Song(song_s@ctrip.com)
......@@ -48,6 +53,18 @@ public class ReleaseService {
return release;
}
public List<Release> findByReleaseIds(Set<Long> releaseIds) {
Iterable<Release> releases = releaseRepository.findAll(releaseIds);
if (releases == null) {
return Collections.emptyList();
}
return Lists.newArrayList(releases);
}
public List<Release> findByReleaseKeys(Set<String> releaseKeys) {
return releaseRepository.findByReleaseKeyIn(releaseKeys);
}
public Release findLatestActiveRelease(String appId, String clusterName, String namespaceName) {
Release release = releaseRepository.findFirstByAppIdAndClusterNameAndNamespaceNameAndIsAbandonedFalseOrderByIdDesc(
appId, clusterName, namespaceName);
......
package com.ctrip.framework.apollo.biz.service;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.ctrip.framework.apollo.biz.AbstractIntegrationTest;
......@@ -8,11 +9,11 @@ import com.ctrip.framework.apollo.biz.entity.InstanceConfig;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.test.annotation.Rollback;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
......@@ -79,20 +80,21 @@ public class InstanceServiceTest extends AbstractIntegrationTest {
public void testCreateAndFindInstanceConfig() throws Exception {
long someInstanceId = 1;
String someConfigAppId = "someConfigAppId";
String someConfigClusterName = "someConfigClusterName";
String someConfigNamespaceName = "someConfigNamespaceName";
String someReleaseKey = "someReleaseKey";
String anotherReleaseKey = "anotherReleaseKey";
InstanceConfig instanceConfig = instanceService.findInstanceConfig(someInstanceId,
someConfigAppId, someConfigNamespaceName);
someConfigAppId, someConfigClusterName, someConfigNamespaceName);
assertNull(instanceConfig);
instanceService.createInstanceConfig(assembleInstanceConfig(someInstanceId, someConfigAppId,
someConfigNamespaceName, someReleaseKey));
someConfigClusterName, someConfigNamespaceName, someReleaseKey));
instanceConfig = instanceService.findInstanceConfig(someInstanceId, someConfigAppId,
someConfigNamespaceName);
someConfigClusterName, someConfigNamespaceName);
assertNotEquals(0, instanceConfig.getId());
assertEquals(someReleaseKey, instanceConfig.getReleaseKey());
......@@ -102,7 +104,7 @@ public class InstanceServiceTest extends AbstractIntegrationTest {
instanceService.updateInstanceConfig(instanceConfig);
InstanceConfig updated = instanceService.findInstanceConfig(someInstanceId, someConfigAppId,
someConfigNamespaceName);
someConfigClusterName, someConfigNamespaceName);
assertEquals(instanceConfig.getId(), updated.getId());
assertEquals(anotherReleaseKey, updated.getReleaseKey());
......@@ -114,30 +116,102 @@ public class InstanceServiceTest extends AbstractIntegrationTest {
long someInstanceId = 1;
long anotherInstanceId = 2;
String someConfigAppId = "someConfigAppId";
String someConfigClusterName = "someConfigClusterName";
String someConfigNamespaceName = "someConfigNamespaceName";
String someReleaseKey = "someReleaseKey";
Date someValidDate = new Date();
Pageable pageable = new PageRequest(0, 10);
String someReleaseKey = "someReleaseKey";
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, -2);
Date someInvalidDate = calendar.getTime();
InstanceConfig someValidConfig = assembleInstanceConfig(someInstanceId, someConfigAppId,
someConfigNamespaceName, someReleaseKey);
someValidConfig.setDataChangeCreatedTime(someValidDate);
InstanceConfig someInvalidConfig = assembleInstanceConfig(anotherInstanceId, someConfigAppId,
someConfigNamespaceName, someReleaseKey);
someInvalidConfig.setDataChangeCreatedTime(someInvalidDate);
instanceService.createInstanceConfig(someValidConfig);
instanceService.createInstanceConfig(someInvalidConfig);
prepareInstanceConfigForInstance(someInstanceId, someConfigAppId, someConfigClusterName,
someConfigNamespaceName, someReleaseKey, someValidDate);
prepareInstanceConfigForInstance(anotherInstanceId, someConfigAppId, someConfigClusterName,
someConfigNamespaceName, someReleaseKey, someInvalidDate);
List<InstanceConfig> validInstanceConfigs = instanceService
Page<InstanceConfig> validInstanceConfigs = instanceService
.findActiveInstanceConfigsByReleaseKey(someReleaseKey, pageable);
assertEquals(1, validInstanceConfigs.size());
assertEquals(someInstanceId, validInstanceConfigs.get(0).getInstanceId());
assertEquals(1, validInstanceConfigs.getContent().size());
assertEquals(someInstanceId, validInstanceConfigs.getContent().get(0).getInstanceId());
}
@Test
@Rollback
public void testFindInstancesByNamespace() throws Exception {
String someConfigAppId = "someConfigAppId";
String someConfigClusterName = "someConfigClusterName";
String someConfigNamespaceName = "someConfigNamespaceName";
String someReleaseKey = "someReleaseKey";
Date someValidDate = new Date();
String someAppId = "someAppId";
String someClusterName = "someClusterName";
String someDataCenter = "someDataCenter";
String someIp = "someIp";
String anotherIp = "anotherIp";
Instance someInstance = instanceService.createInstance(assembleInstance(someAppId,
someClusterName, someDataCenter, someIp));
Instance anotherInstance = instanceService.createInstance(assembleInstance(someAppId,
someClusterName, someDataCenter, anotherIp));
prepareInstanceConfigForInstance(someInstance.getId(), someConfigAppId, someConfigClusterName,
someConfigNamespaceName, someReleaseKey, someValidDate);
prepareInstanceConfigForInstance(anotherInstance.getId(), someConfigAppId,
someConfigClusterName,
someConfigNamespaceName, someReleaseKey, someValidDate);
Page<Instance> result = instanceService.findInstancesByNamespace(someConfigAppId,
someConfigClusterName, someConfigNamespaceName, new PageRequest(0, 10));
assertEquals(Lists.newArrayList(someInstance, anotherInstance), result.getContent());
}
@Test
@Rollback
public void testFindInstanceConfigsByNamespaceWithReleaseKeysNotIn() throws Exception {
long someInstanceId = 1;
long anotherInstanceId = 2;
long yetAnotherInstanceId = 3;
String someConfigAppId = "someConfigAppId";
String someConfigClusterName = "someConfigClusterName";
String someConfigNamespaceName = "someConfigNamespaceName";
Date someValidDate = new Date();
String someReleaseKey = "someReleaseKey";
String anotherReleaseKey = "anotherReleaseKey";
String yetAnotherReleaseKey = "yetAnotherReleaseKey";
InstanceConfig someInstanceConfig = prepareInstanceConfigForInstance(someInstanceId,
someConfigAppId, someConfigClusterName,
someConfigNamespaceName, someReleaseKey, someValidDate);
InstanceConfig anotherInstanceConfig = prepareInstanceConfigForInstance(anotherInstanceId,
someConfigAppId, someConfigClusterName,
someConfigNamespaceName, someReleaseKey, someValidDate);
prepareInstanceConfigForInstance(yetAnotherInstanceId, someConfigAppId, someConfigClusterName,
someConfigNamespaceName, anotherReleaseKey, someValidDate);
List<InstanceConfig> instanceConfigs = instanceService
.findInstanceConfigsByNamespaceWithReleaseKeysNotIn(someConfigAppId,
someConfigClusterName, someConfigNamespaceName, Sets.newHashSet(anotherReleaseKey,
yetAnotherReleaseKey));
assertEquals(Lists.newArrayList(someInstanceConfig, anotherInstanceConfig), instanceConfigs);
}
private InstanceConfig prepareInstanceConfigForInstance(long instanceId, String configAppId,
String configClusterName, String
configNamespace, String releaseKey,
Date lastModifiedTime) {
InstanceConfig someConfig = assembleInstanceConfig(instanceId, configAppId, configClusterName,
configNamespace, releaseKey);
someConfig.setDataChangeCreatedTime(lastModifiedTime);
someConfig.setDataChangeLastModifiedTime(lastModifiedTime);
return instanceService.createInstanceConfig(someConfig);
}
private Instance assembleInstance(String appId, String clusterName, String dataCenter, String
......@@ -152,12 +226,13 @@ public class InstanceServiceTest extends AbstractIntegrationTest {
}
private InstanceConfig assembleInstanceConfig(long instanceId, String configAppId, String
configNamespaceName, String releaseKey) {
configClusterName, String configNamespaceName, String releaseKey) {
InstanceConfig instanceConfig = new InstanceConfig();
instanceConfig.setInstanceId(instanceId);
instanceConfig.setConfigAppId(configAppId);
instanceConfig.setConfigClusterName(configClusterName);
instanceConfig.setConfigNamespaceName(configNamespaceName);
instanceConfig.setReleaseKey(releaseKey);
return instanceConfig;
}
}
\ No newline at end of file
}
package com.ctrip.framework.apollo.biz.service;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.ctrip.framework.apollo.biz.AbstractUnitTest;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.repository.ReleaseRepository;
......@@ -13,9 +16,12 @@ import org.mockito.Mock;
import org.springframework.data.domain.PageRequest;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
......@@ -147,6 +153,38 @@ public class ReleaseServiceTest extends AbstractUnitTest {
someAppId, someClusterName, someNamespaceName);
}
@Test
public void testFindByReleaseIds() throws Exception {
Release someRelease = mock(Release.class);
Release anotherRelease = mock(Release.class);
long someReleaseId = 1;
long anotherReleaseId = 2;
List<Release> someReleases = Lists.newArrayList(someRelease, anotherRelease);
Set<Long> someReleaseIds = Sets.newHashSet(someReleaseId, anotherReleaseId);
when(releaseRepository.findAll(someReleaseIds)).thenReturn(someReleases);
List<Release> result = releaseService.findByReleaseIds(someReleaseIds);
assertEquals(someReleases, result);
}
@Test
public void testFindByReleaseKeys() throws Exception {
Release someRelease = mock(Release.class);
Release anotherRelease = mock(Release.class);
String someReleaseKey = "key1";
String anotherReleaseKey = "key2";
List<Release> someReleases = Lists.newArrayList(someRelease, anotherRelease);
Set<String> someReleaseKeys = Sets.newHashSet(someReleaseKey, anotherReleaseKey);
when(releaseRepository.findByReleaseKeyIn(someReleaseKeys)).thenReturn(someReleases);
List<Release> result = releaseService.findByReleaseKeys(someReleaseKeys);
assertEquals(someReleases, result);
}
private Release assembleRelease(long releaseId, String releaseKey, String appId,
String clusterName,
String groupName, String configurations) {
......
package com.ctrip.framework.apollo.common.dto;
import org.springframework.data.domain.Pageable;
import java.util.Collections;
import java.util.List;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class PageDTO<T> {
private final long total;
private final List<T> content;
private final int page;
private final int size;
public PageDTO(List<T> content, Pageable pageable, long total) {
this.total = total;
this.content = content;
this.page = pageable.getPageNumber();
this.size = pageable.getPageSize();
}
public long getTotal() {
return total;
}
public List<T> getContent() {
return Collections.unmodifiableList(content);
}
public int getPage() {
return page;
}
public int getSize() {
return size;
}
}
......@@ -217,7 +217,7 @@ public class ConfigController {
return;
}
for (Release release : releases) {
instanceConfigAuditUtil.audit(appId, cluster, datacenter, clientIp, release.getAppId(),
instanceConfigAuditUtil.audit(appId, cluster, datacenter, clientIp, release.getAppId(), release.getClusterName(),
release.getNamespaceName(), release.getReleaseKey());
}
}
......
......@@ -58,15 +58,14 @@ public class InstanceConfigAuditUtil implements InitializingBean {
}
public boolean audit(String appId, String clusterName, String dataCenter, String
ip, String configAppId, String configNamespace, String releaseKey) {
ip, String configAppId, String configClusterName, String configNamespace, String releaseKey) {
return this.audits.offer(new InstanceConfigAuditModel(appId, clusterName, dataCenter, ip,
configAppId, configNamespace, releaseKey));
configAppId, configClusterName, configNamespace, releaseKey));
}
void doAudit(InstanceConfigAuditModel auditModel) {
String instanceCacheKey = assembleInstanceKey(auditModel.getAppId(), auditModel
.getClusterName(),
auditModel.getIp(), auditModel.getDataCenter());
.getClusterName(), auditModel.getIp(), auditModel.getDataCenter());
Long instanceId = instanceCache.getIfPresent(instanceCacheKey);
if (instanceId == null) {
instanceId = prepareInstanceId(auditModel);
......@@ -75,7 +74,7 @@ public class InstanceConfigAuditUtil implements InitializingBean {
//load instance config release key from cache, and check if release key is the same
String instanceConfigCacheKey = assembleInstanceConfigKey(instanceId, auditModel
.getConfigAppId(), auditModel.getConfigNamespace());
.getConfigAppId(), auditModel.getConfigClusterName(), auditModel.getConfigNamespace());
String cacheReleaseKey = instanceConfigReleaseKeyCache.getIfPresent(instanceConfigCacheKey);
//if release key is the same, then skip audit
......@@ -87,7 +86,7 @@ public class InstanceConfigAuditUtil implements InitializingBean {
//if release key is not the same or cannot find in cache, then do audit
InstanceConfig instanceConfig = instanceService.findInstanceConfig(instanceId, auditModel
.getConfigAppId(), auditModel.getConfigNamespace());
.getConfigAppId(), auditModel.getConfigClusterName(), 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
......@@ -101,6 +100,7 @@ public class InstanceConfigAuditUtil implements InitializingBean {
instanceConfig = new InstanceConfig();
instanceConfig.setInstanceId(instanceId);
instanceConfig.setConfigAppId(auditModel.getConfigAppId());
instanceConfig.setConfigClusterName(auditModel.getConfigClusterName());
instanceConfig.setConfigNamespaceName(auditModel.getConfigNamespace());
instanceConfig.setReleaseKey(auditModel.getReleaseKey());
......@@ -160,8 +160,9 @@ public class InstanceConfigAuditUtil implements InitializingBean {
}
private String assembleInstanceConfigKey(long instanceId, String configAppId, String
configNamespace) {
return STRING_JOINER.join(instanceId, configAppId, configNamespace);
configClusterName,
String configNamespace) {
return STRING_JOINER.join(instanceId, configAppId, configClusterName, configNamespace);
}
public static class InstanceConfigAuditModel {
......@@ -170,16 +171,19 @@ public class InstanceConfigAuditUtil implements InitializingBean {
private String dataCenter;
private String ip;
private String configAppId;
private String configClusterName;
private String configNamespace;
private String releaseKey;
public InstanceConfigAuditModel(String appId, String clusterName, String dataCenter, String
clientIp, String configAppId, String configNamespace, String releaseKey) {
clientIp, String configAppId, String configClusterName, String configNamespace, String
releaseKey) {
this.appId = appId;
this.clusterName = clusterName;
this.dataCenter = Strings.isNullOrEmpty(dataCenter) ? "" : dataCenter;
this.ip = clientIp;
this.configAppId = configAppId;
this.configClusterName = configClusterName;
this.configNamespace = configNamespace;
this.releaseKey = releaseKey;
}
......@@ -212,6 +216,10 @@ public class InstanceConfigAuditUtil implements InitializingBean {
return releaseKey;
}
public String getConfigClusterName() {
return configClusterName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
......@@ -222,13 +230,15 @@ public class InstanceConfigAuditUtil implements InitializingBean {
Objects.equals(dataCenter, model.dataCenter) &&
Objects.equals(ip, model.ip) &&
Objects.equals(configAppId, model.configAppId) &&
Objects.equals(configClusterName, model.configClusterName) &&
Objects.equals(configNamespace, model.configNamespace) &&
Objects.equals(releaseKey, model.releaseKey);
}
@Override
public int hashCode() {
return Objects.hash(appId, clusterName, dataCenter, ip, configAppId, configNamespace,
return Objects.hash(appId, clusterName, dataCenter, ip, configAppId, configClusterName,
configNamespace,
releaseKey);
}
}
......
......@@ -113,7 +113,7 @@ public class ConfigControllerTest {
assertEquals(defaultNamespaceName, result.getNamespaceName());
assertEquals(someServerSideNewReleaseKey, result.getReleaseKey());
verify(instanceConfigAuditUtil, times(1)).audit(someAppId, someClusterName, someDataCenter,
someClientIp, someAppId, defaultNamespaceName, someServerSideNewReleaseKey);
someClientIp, someAppId, someClusterName, defaultNamespaceName, someServerSideNewReleaseKey);
}
@Test
......@@ -283,6 +283,7 @@ public class ConfigControllerTest {
String someServerSideReleaseKey = "2";
HttpServletResponse someResponse = mock(HttpServletResponse.class);
String somePublicAppId = "somePublicAppId";
String somePublicClusterName = "somePublicClusterName";
AppNamespace somePublicAppNamespace =
assemblePublicAppNamespace(somePublicAppId, somePublicNamespaceName);
......@@ -294,6 +295,7 @@ public class ConfigControllerTest {
.thenReturn(somePublicRelease);
when(somePublicRelease.getReleaseKey()).thenReturn(someServerSideReleaseKey);
when(somePublicRelease.getAppId()).thenReturn(somePublicAppId);
when(somePublicRelease.getClusterName()).thenReturn(somePublicClusterName);
when(somePublicRelease.getNamespaceName()).thenReturn(somePublicNamespaceName);
ApolloConfig result = configController
......@@ -306,7 +308,7 @@ public class ConfigControllerTest {
assertEquals(somePublicNamespaceName, result.getNamespaceName());
assertEquals("foo", result.getConfigurations().get("apollo.public.bar"));
verify(instanceConfigAuditUtil, times(1)).audit(someAppId, someClusterName, someDataCenter,
someClientIp, somePublicAppId, somePublicNamespaceName, someServerSideReleaseKey);
someClientIp, somePublicAppId, somePublicClusterName, somePublicNamespaceName, someServerSideReleaseKey);
}
@Test
......@@ -396,6 +398,7 @@ public class ConfigControllerTest {
.thenReturn(somePublicRelease);
when(somePublicRelease.getReleaseKey()).thenReturn(somePublicAppSideReleaseKey);
when(somePublicRelease.getAppId()).thenReturn(somePublicAppId);
when(somePublicRelease.getClusterName()).thenReturn(someDataCenter);
when(somePublicRelease.getNamespaceName()).thenReturn(somePublicNamespaceName);
ApolloConfig result =
......@@ -412,9 +415,9 @@ public class ConfigControllerTest {
assertEquals("foo-override", result.getConfigurations().get("apollo.public.foo"));
assertEquals("bar", result.getConfigurations().get("apollo.public.bar"));
verify(instanceConfigAuditUtil, times(1)).audit(someAppId, someClusterName, someDataCenter,
someClientIp, someAppId, somePublicNamespaceName, someAppSideReleaseKey);
someClientIp, someAppId, someClusterName, somePublicNamespaceName, someAppSideReleaseKey);
verify(instanceConfigAuditUtil, times(1)).audit(someAppId, someClusterName, someDataCenter,
someClientIp, somePublicAppId, somePublicNamespaceName, somePublicAppSideReleaseKey);
someClientIp, somePublicAppId, someDataCenter, somePublicNamespaceName, somePublicAppSideReleaseKey);
}
@Test
......
......@@ -36,6 +36,7 @@ public class InstanceConfigAuditUtilTest {
private BlockingQueue<InstanceConfigAuditUtil.InstanceConfigAuditModel> audits;
private String someAppId;
private String someConfigClusterName;
private String someClusterName;
private String someDataCenter;
private String someIp;
......@@ -59,18 +60,19 @@ public class InstanceConfigAuditUtilTest {
someDataCenter = "someDataCenter";
someIp = "someIp";
someConfigAppId = "someConfigAppId";
someConfigClusterName= "someConfigClusterName";
someConfigNamespace = "someConfigNamespace";
someReleaseKey = "someReleaseKey";
someAuditModel = new InstanceConfigAuditUtil.InstanceConfigAuditModel(someAppId,
someClusterName, someDataCenter, someIp, someConfigAppId, someConfigNamespace,
someClusterName, someDataCenter, someIp, someConfigAppId, someConfigClusterName, someConfigNamespace,
someReleaseKey);
}
@Test
public void testAudit() throws Exception {
boolean result = instanceConfigAuditUtil.audit(someAppId, someClusterName, someDataCenter,
someIp, someConfigAppId, someConfigNamespace, someReleaseKey);
someIp, someConfigAppId, someConfigClusterName, someConfigNamespace, someReleaseKey);
InstanceConfigAuditUtil.InstanceConfigAuditModel audit = audits.poll();
......@@ -91,10 +93,10 @@ public class InstanceConfigAuditUtilTest {
verify(instanceService, times(1)).findInstance(someAppId, someClusterName, someDataCenter,
someIp);
verify(instanceService, times(1)).createInstance(any(Instance.class));
verify(instanceService, times(1)).findInstanceConfig(someInstanceId, someConfigAppId,
verify(instanceService, times(1)).findInstanceConfig(someInstanceId, someConfigAppId, someConfigClusterName,
someConfigNamespace);
verify(instanceService, times(1)).createInstanceConfig(any(InstanceConfig.class));
}
}
\ No newline at end of file
}
package com.ctrip.framework.apollo.portal.api;
import com.google.common.base.Joiner;
import com.ctrip.framework.apollo.common.dto.AppDTO;
import com.ctrip.framework.apollo.common.dto.AppNamespaceDTO;
import com.ctrip.framework.apollo.common.dto.ClusterDTO;
import com.ctrip.framework.apollo.common.dto.CommitDTO;
import com.ctrip.framework.apollo.common.dto.InstanceDTO;
import com.ctrip.framework.apollo.common.dto.ItemChangeSets;
import com.ctrip.framework.apollo.common.dto.ItemDTO;
import com.ctrip.framework.apollo.common.dto.NamespaceDTO;
import com.ctrip.framework.apollo.common.dto.NamespaceLockDTO;
import com.ctrip.framework.apollo.common.dto.PageDTO;
import com.ctrip.framework.apollo.common.dto.ReleaseDTO;
import com.ctrip.framework.apollo.core.enums.Env;
import org.springframework.boot.actuate.health.Health;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
@Service
......@@ -237,4 +245,39 @@ public class AdminServiceAPI {
}
}
@Service
public static class InstanceAPI extends API {
private Joiner joiner = Joiner.on(",");
private ParameterizedTypeReference<PageDTO<InstanceDTO>> pageInstanceDtoType = new ParameterizedTypeReference<PageDTO<InstanceDTO>>() {};
public PageDTO<InstanceDTO> getByRelease(Env env, long releaseId, int page, int size){
ResponseEntity<PageDTO<InstanceDTO>> entity = restTemplate.get(env, "/instances/by-release?releaseId={releaseId}&page={page}&size={size}", pageInstanceDtoType, releaseId, page, size);
return entity.getBody();
}
public List<InstanceDTO> getByReleasesNotIn(String appId, Env env, String clusterName, String namespaceName, Set<Long> releaseIds){
InstanceDTO[] instanceDTOs = restTemplate.get(env, "/instances/by-namespace-and-releases-not-in?appId={appId}&clusterName={clusterName}&namespaceName={namespaceName}&releaseIds={releaseIds}",
InstanceDTO[].class, appId, clusterName, namespaceName, joiner.join(releaseIds));
return Arrays.asList(instanceDTOs);
}
public PageDTO<InstanceDTO> getByNamespace(String appId, Env env, String clusterName, String namespaceName, int page, int size){
ResponseEntity<PageDTO<InstanceDTO>> entity = restTemplate.get(env, "/instances/by-namespace?appId={appId}&clusterName={clusterName}&namespaceName={namespaceName}&page={page}&size={size}",
pageInstanceDtoType, appId, clusterName, namespaceName, page, size);
return entity.getBody();
}
public int getInstanceCountByNamespace(String appId, Env env, String clusterName, String namespaceName){
Integer count = restTemplate.get(env, "/instances/by-namespace/count?appId={appId}&clusterName={clusterName}&namespaceName={namespaceName}",
Integer.class, appId, clusterName, namespaceName);
if (count == null){
return 0;
}
return count;
}
}
}
......@@ -31,7 +31,8 @@ import javax.annotation.PostConstruct;
public class AdminServiceAddressLocator {
private static final int DEFAULT_TIMEOUT_MS = 1000;
private static final long REFRESH_INTERVAL = 5 * 60 * 1000;
private static final long NORMAL_REFRESH_INTERVAL = 5 * 60 * 1000;
private static final long OFFLINE_REFRESH_INTERVAL = 10 * 1000;
private static final int RETRY_TIMES = 3;
private static final String ADMIN_SERVICE_URL_PATH = "/services/admin";
......@@ -65,9 +66,7 @@ public class AdminServiceAddressLocator {
refreshServiceAddressService = Executors.newScheduledThreadPool(1);
refreshServiceAddressService.scheduleWithFixedDelay(
new RefreshAdminServerAddressTask(), 0, REFRESH_INTERVAL,
TimeUnit.MILLISECONDS);
refreshServiceAddressService.schedule(new RefreshAdminServerAddressTask(), 1, TimeUnit.MILLISECONDS);
}
public List<ServiceDTO> getServiceList(Env env) {
......@@ -85,13 +84,22 @@ public class AdminServiceAddressLocator {
@Override
public void run() {
boolean refreshSuccess = true;
//refresh fail if get any env address fail
for (Env env : allEnvs) {
refreshServerAddressCache(env);
boolean currentEnvRefreshResult = refreshServerAddressCache(env);
refreshSuccess = refreshSuccess && currentEnvRefreshResult;
}
if (refreshSuccess){
refreshServiceAddressService.schedule(new RefreshAdminServerAddressTask(), NORMAL_REFRESH_INTERVAL, TimeUnit.MILLISECONDS);
} else {
refreshServiceAddressService.schedule(new RefreshAdminServerAddressTask(), OFFLINE_REFRESH_INTERVAL, TimeUnit.MILLISECONDS);
}
}
}
private void refreshServerAddressCache(Env env) {
private boolean refreshServerAddressCache(Env env) {
for (int i = 0; i < RETRY_TIMES; i++) {
......@@ -101,12 +109,13 @@ public class AdminServiceAddressLocator {
continue;
}
cache.put(env, Arrays.asList(services));
break;
return true;
} catch (Throwable e) {//meta server error
Cat.logError("get admin server address fail", e);
continue;
}
}
return false;
}
private ServiceDTO[] getAdminServerAddress(Env env) {
......
......@@ -11,7 +11,9 @@ import com.dianping.cat.message.Transaction;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.HttpHostConnectException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestClientException;
......@@ -50,6 +52,13 @@ public class RetryableRestTemplate {
return execute(HttpMethod.GET, env, path, null, responseType, urlVariables);
}
public <T> ResponseEntity<T> get(Env env, String path, ParameterizedTypeReference<T> reference,
Object... uriVariables)
throws RestClientException {
return execute(env, path, reference, uriVariables);
}
public <T> T post(Env env, String path, Object request, Class<T> responseType, Object... uriVariables)
throws RestClientException {
return execute(HttpMethod.POST, env, path, request, responseType, uriVariables);
......@@ -73,14 +82,7 @@ public class RetryableRestTemplate {
String uri = uriTemplateHandler.expand(path, uriVariables).getPath();
Transaction ct = Cat.newTransaction("AdminAPI", uri);
List<ServiceDTO> services = adminServiceAddressLocator.getServiceList(env);
if (CollectionUtils.isEmpty(services)) {
ServiceException e = new ServiceException("No available admin service");
ct.setStatus(e);
ct.complete();
throw e;
}
List<ServiceDTO> services = getAdminServices(env, ct);
for (ServiceDTO serviceDTO : services) {
try {
......@@ -110,6 +112,55 @@ public class RetryableRestTemplate {
throw e;
}
private <T> ResponseEntity<T> execute(Env env, String path, ParameterizedTypeReference<T> reference,
Object... uriVariables){
if (path.startsWith("/")) {
path = path.substring(1, path.length());
}
String uri = uriTemplateHandler.expand(path, uriVariables).getPath();
Transaction ct = Cat.newTransaction("AdminAPI", uri);
List<ServiceDTO> services = getAdminServices(env, ct);
for (ServiceDTO serviceDTO : services) {
try {
ResponseEntity<T> result =
restTemplate.exchange(parseHost(serviceDTO) + path, HttpMethod.GET, null, reference, uriVariables);
ct.setStatus(Message.SUCCESS);
ct.complete();
return result;
} catch (Throwable t) {
Cat.logError(t);
Cat.logEvent(CatEventType.API_RETRY, uri);
continue;
}
}
//all admin server down
ServiceException e = new ServiceException("No available admin service");
ct.setStatus(e);
ct.complete();
throw e;
}
private List<ServiceDTO> getAdminServices( Env env, Transaction ct){
List<ServiceDTO> services = adminServiceAddressLocator.getServiceList(env);
if (CollectionUtils.isEmpty(services)) {
ServiceException e = new ServiceException("No available admin service");
ct.setStatus(e);
ct.complete();
throw e;
}
return services;
}
private <T> T doExecute(HttpMethod method, ServiceDTO service, String path, Object request,
Class<T> responseType,
Object... uriVariables) {
......@@ -143,11 +194,11 @@ public class RetryableRestTemplate {
Throwable nestedException = e.getCause();
if (method == HttpMethod.GET) {
return nestedException instanceof SocketTimeoutException
|| nestedException instanceof HttpHostConnectException
|| nestedException instanceof ConnectTimeoutException;
|| nestedException instanceof HttpHostConnectException
|| nestedException instanceof ConnectTimeoutException;
} else {
return nestedException instanceof HttpHostConnectException
|| nestedException instanceof ConnectTimeoutException;
|| nestedException instanceof ConnectTimeoutException;
}
}
......
package com.ctrip.framework.apollo.portal.controller;
import com.google.common.base.Splitter;
import com.ctrip.framework.apollo.common.dto.InstanceDTO;
import com.ctrip.framework.apollo.common.dto.PageDTO;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.entity.vo.Number;
import com.ctrip.framework.apollo.portal.service.InstanceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@RestController
public class InstanceController {
private static final Splitter RELEASES_SPLITTER = Splitter.on(",").omitEmptyStrings()
.trimResults();
@Autowired
private InstanceService instanceService;
@RequestMapping("/envs/{env}/instances/by-release")
public PageDTO<InstanceDTO> getByRelease(@PathVariable String env, @RequestParam long releaseId,
@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) {
return instanceService.getByRelease(Env.valueOf(env), releaseId, page, size);
}
@RequestMapping("/envs/{env}/instances/by-namespace")
public PageDTO<InstanceDTO> getByNamespace(@PathVariable String env, @RequestParam String appId,
@RequestParam String clusterName, @RequestParam String namespaceName,
@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) {
return instanceService.getByNamespace(Env.valueOf(env), appId, clusterName, namespaceName, page, size);
}
@RequestMapping("/envs/{env}/instances/by-namespace/count")
public ResponseEntity<Number> getInstanceCountByNamespace(@PathVariable String env, @RequestParam String appId,
@RequestParam String clusterName, @RequestParam String namespaceName) {
int count = instanceService.getInstanceCountByNamepsace(appId, Env.valueOf(env), clusterName, namespaceName);
return ResponseEntity.ok(new Number(count));
}
@RequestMapping("/envs/{env}/instances/by-namespace-and-releases-not-in")
public List<InstanceDTO> getByReleasesNotIn(@PathVariable String env, @RequestParam String appId,
@RequestParam String clusterName, @RequestParam String namespaceName,
@RequestParam String releaseIds) {
Set<Long> releaseIdSet = RELEASES_SPLITTER.splitToList(releaseIds).stream().map(Long::parseLong)
.collect(Collectors.toSet());
if (CollectionUtils.isEmpty(releaseIdSet)){
throw new BadRequestException("release ids can not be empty");
}
return instanceService.getByReleasesNotIn(Env.valueOf(env), appId, clusterName, namespaceName, releaseIdSet);
}
}
package com.ctrip.framework.apollo.portal.entity.vo;
public class Number {
private int num;
public Number(int num){
this.num = num;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
package com.ctrip.framework.apollo.portal.service;
import com.ctrip.framework.apollo.common.dto.InstanceDTO;
import com.ctrip.framework.apollo.common.dto.PageDTO;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Set;
@Service
public class InstanceService {
@Autowired
private AdminServiceAPI.InstanceAPI instanceAPI;
public PageDTO<InstanceDTO> getByRelease(Env env, long releaseId, int page, int size){
return instanceAPI.getByRelease(env, releaseId, page, size);
}
public PageDTO<InstanceDTO> getByNamespace(Env env, String appId, String clusterName, String namespaceName, int page, int size){
return instanceAPI.getByNamespace(appId, env, clusterName, namespaceName, page, size);
}
public int getInstanceCountByNamepsace(String appId, Env env, String clusterName, String namespaceName){
return instanceAPI.getInstanceCountByNamespace(appId, env, clusterName, namespaceName);
}
public List<InstanceDTO> getByReleasesNotIn(Env env, String appId, String clusterName, String namespaceName, Set<Long> releaseIds){
return instanceAPI.getByReleasesNotIn(appId, env, clusterName, namespaceName, releaseIds);
}
}
......@@ -254,8 +254,7 @@
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header panel-primary">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"
ng-click="cancelEdit()"><span
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
<h4 class="modal-title">
<span ng-show="tableViewOperType == 'create'"> 添加配置项</span>
......@@ -310,7 +309,7 @@
</div>
<div class="modal-footer">
</button>
<button type="button" class="btn btn-default" data-dismiss="modal" ng-click="cancelEdit()">
<button type="button" class="btn btn-default" data-dismiss="modal" >
关闭
</button>
<button type="submit" class="btn btn-primary"
......@@ -470,6 +469,7 @@
<script type="application/javascript" src="scripts/services/PermissionService.js"></script>
<script type="application/javascript" src="scripts/services/CommitService.js"></script>
<script type="application/javascript" src="scripts/services/NamespaceLockService.js"></script>
<script type="application/javascript" src="scripts/services/InstanceService.js"></script>
<script type="application/javascript" src="scripts/AppUtils.js"></script>
......
......@@ -37,7 +37,7 @@
<span class="label label-primary no-radius"
ng-if="release.active">当前生效</span>
</div>
<div class="row">
<div class="row" id="{{release.baseInfo.name}}">
<div class="col-md-2 user">
<img src="../img/user.png" class="i-20">
<span class="info" ng-bind="release.baseInfo.dataChangeCreatedBy"></span>
......@@ -50,7 +50,8 @@
<div class="col-md-3 time">
<img src="../img/title.png" class="i-20">
<span class="info"
ng-bind="release.baseInfo.name"></span>
ng-bind="release.baseInfo.name" ng-class="{'highlight':pageContext.scrollTo == release.baseInfo.name}"></span>
</div>
<div class="col-md-4 comment" ng-show="release.baseInfo.comment">
<img src="../img/comment.png" class="i-20">
......
......@@ -9,7 +9,7 @@ application_module.controller("ConfigNamespaceController",
var namespace_view_type = {
TEXT: 'text',
TABLE: 'table',
LOG: 'log'
HISTORY: 'history'
};
var TABLE_VIEW_OPER_TYPE = {
......@@ -39,8 +39,6 @@ application_module.controller("ConfigNamespaceController",
$scope.editItem = editItem;
$scope.cancelEdit = cancelEdit;
$scope.createItem = createItem;
$scope.doItem = doItem;
......@@ -169,10 +167,10 @@ application_module.controller("ConfigNamespaceController",
function preRollback(namespace) {
$scope.toRollbackNamespace = namespace;
//load latest two active releases
ReleaseService.findActiveRelease($rootScope.pageContext.appId,
$rootScope.pageContext.env,
$rootScope.pageContext.clusterName,
$scope.toRollbackNamespace.baseInfo.namespaceName, 0, 2)
ReleaseService.findActiveReleases($rootScope.pageContext.appId,
$rootScope.pageContext.env,
$rootScope.pageContext.clusterName,
$scope.toRollbackNamespace.baseInfo.namespaceName, 0, 2)
.then(function (result) {
if (result.length <= 1) {
toastr.error("没有可以回滚的发布历史");
......@@ -242,28 +240,18 @@ application_module.controller("ConfigNamespaceController",
});
}
var backupItem = {};
//修改配置
function editItem(namespace, item) {
if (!lockCheck(namespace)) {
return;
}
switchTableViewOperType(TABLE_VIEW_OPER_TYPE.UPDATE);
$scope.item = item;
backupItem.value = item.value;
backupItem.comment = item.comment;
$scope.item = _.clone(item);
toOperationNamespace = namespace;
$("#itemModal").modal("show");
}
function cancelEdit() {
if($scope.tableViewOperType = TABLE_VIEW_OPER_TYPE.UPDATE){
$scope.item.value = backupItem.value;
$scope.item.comment = backupItem.comment;
}
}
//新增配置
function createItem(namespace) {
if (!lockCheck(namespace)) {
......
release_history_module.controller("ReleaseHistoryController",
['$scope', '$location', '$window', 'toastr', 'AppService', 'AppUtil',
['$scope', '$location', '$anchorScroll', '$window', 'toastr', 'AppService', 'AppUtil',
'ReleaseService',
function ($scope, $location, $window, toastr, AppService, AppUtil, ReleaseService) {
function ($scope, $location, $anchorScroll, $window, toastr, AppService, AppUtil, ReleaseService) {
var params = AppUtil.parseParams($location.$$url);
$scope.pageContext = {
appId: params.appid,
env: params.env,
clusterName: params.clusterName,
namespaceName: params.namespaceName
namespaceName: params.namespaceName,
scrollTo: params.scrollTo
};
$scope.page = 0;
......@@ -49,6 +50,12 @@ release_history_module.controller("ReleaseHistoryController",
}
$scope.releases.push(release);
})
if ($scope.pageContext.scrollTo){
$location.hash($scope.pageContext.scrollTo);
$anchorScroll();
}
}, function (result) {
toastr.error(AppUtil.errorMsg(result));
});
......
appService.service('InstanceService', ['$resource', '$q', function ($resource, $q) {
var resource = $resource('', {}, {
find_instances_by_release: {
method: 'GET',
url: '/envs/:env/instances/by-release'
},
find_instances_by_namespace: {
method: 'GET',
isArray: false,
url: '/envs/:env/instances/by-namespace'
},
find_by_releases_not_in: {
method: 'GET',
isArray: true,
url: '/envs/:env/instances/by-namespace-and-releases-not-in'
},
get_instance_count_by_namespace: {
method: 'GET',
isArray: false,
url: "envs/:env/instances/by-namespace/count"
}
});
return {
findInstancesByRelease: function (env, releaseId, page) {
var d = $q.defer();
resource.find_instances_by_release({
env: env,
releaseId: releaseId,
page: page
},
function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
});
return d.promise;
},
findInstancesByNamespace: function (appId, env, clusterName, namespaceName, page) {
var d = $q.defer();
resource.find_instances_by_namespace({
env: env,
appId: appId,
clusterName: clusterName,
namespaceName: namespaceName,
page: page
},
function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
});
return d.promise;
},
findByReleasesNotIn: function (appId, env, clusterName, namespaceName, releaseIds) {
var d = $q.defer();
resource.find_by_releases_not_in({
env: env,
appId: appId,
clusterName: clusterName,
namespaceName: namespaceName,
releaseIds: releaseIds
},
function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
});
return d.promise;
},
getInstanceCountByNamespace: function (appId, env, clusterName, namespaceName) {
var d = $q.defer();
resource.get_instance_count_by_namespace({
env: env,
appId: appId,
clusterName: clusterName,
namespaceName: namespaceName
},
function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
});
return d.promise;
}
}
}]);
......@@ -107,7 +107,7 @@ appService.service('ReleaseService', ['$resource', '$q', function ($resource, $q
return {
release: createRelease,
findAllRelease: findAllReleases,
findActiveRelease: findActiveReleases,
findActiveReleases: findActiveReleases,
compare: compare,
rollback: rollback
}
......
......@@ -33,6 +33,10 @@ table {
border-radius: 0px;
}
.highlight {
background: #ffa;
}
.hide-border-top {
border-top: 0px;
}
......@@ -58,6 +62,24 @@ table {
width: 15px;
}
.badge{
padding: 1px 4px;
}
.badge-grey {
background: #777;
color: #fff;
}
.badge-white {
background: #ffffff;
color: #0f0f0f;
}
.panel-default > .panel-heading .badge{
}
.apollo-container {
min-height: 90%;
}
......@@ -219,12 +241,6 @@ table th {
font-size: 18px;
}
.config-item-container .panel-heading .badge {
padding: 1px 4px;
background: #ffffff;
color: #0f0f0f;
}
.config-item-container .form-control[disabled] {
background: #ffffff;
border: 0px;
......@@ -313,6 +329,16 @@ table th {
}
.instance-view .btn-primary .badge{
color: #337ab7;
background-color: #fff;
}
.instance-view .btn-default .badge{
background: #777;
color: #fff;
}
.line {
width: 20px;
border: 1px solid #ddd;
......@@ -365,7 +391,6 @@ table th {
padding: 5px 0 5px 50px;
}
/*搜索框*/
::-webkit-scrollbar {
width: 0;
......
......@@ -26,7 +26,7 @@
</li>
</ul>
<form class="navbar-form navbar-right form-inline" role="search">
<div class="navbar-form navbar-right form-inline" role="search">
<div class="form-group">
<input type="text" class="form-control search-input" placeholder="应用ID/应用名" style="width: 350px"
ng-model="searchKey" ng-change="changeSearchKey()" ng-focus="changeSearchKey()">
......@@ -37,8 +37,8 @@
</div>
</div>
</div>
<button type="submit" class="btn btn-default" ng-click="jumpToConfigPage()">Go</button>
</form>
<button type="button" class="btn btn-default" ng-click="jumpToConfigPage()">Go</button>
</div>
</div>
</div>
......
......@@ -16,7 +16,7 @@
title="{{namespace.comment}}"></b>
<span class="label label-info no-radius" ng-bind="namespace.format"></span>
<span class="label label-primary no-radius" ng-show="namespace.itemModifiedCnt > 0">有修改
<span class="badge label" ng-bind="namespace.itemModifiedCnt"></span></span>
<span class="badge label badge-white" ng-bind="namespace.itemModifiedCnt"></span></span>
<span class="label label-warning no-radius"
ng-show="namespace.lockOwner">当前修改者:{{namespace.lockOwner}}</span>
</div>
......@@ -60,12 +60,10 @@
</div>
</div>
</header>
<!--f1f2f7-->
<header class="panel-heading second-panel-heading">
<div class="row">
<div class="col-md-5 pull-left">
<div class="col-md-8 pull-left">
<ul class="nav nav-tabs">
<li role="presentation" ng-click="switchView(namespace, 'table')"
ng-show="namespace.isPropertiesFormat">
......@@ -86,9 +84,16 @@
更改历史
</a>
</li>
<li role="presentation" ng-click="switchView(namespace, 'instance')">
<a ng-class="{node_active:namespace.viewType == 'instance'}">
<img src="img/machine.png">
实例列表
<span class="badge badge-grey" ng-bind="namespace.instancesCount"></span>
</a>
</li>
</ul>
</div>
<div class="col-md-7 text-right">
<div class="col-md-4 text-right">
<a data-tooltip="tooltip" data-placement="bottom" title="取消修改"
ng-show="namespace.isTextEditing && namespace.viewType == 'text'"
ng-click="toggleTextEditStatus(namespace)">
......@@ -195,9 +200,8 @@
</table>
</div>
<!--历史修改视图-->
<!--history view-->
<div class="J_historyview history-view" ng-show="namespace.viewType == 'history'">
<div class="media" ng-repeat="commits in namespace.commits">
<div class="media-body">
<div class="row">
......@@ -322,4 +326,142 @@
<span class="glyphicon glyphicon-menu-down"></span></button>
</div>
</div>
<!--instance view-->
<div class="panel panel-default instance-view" ng-show="namespace.viewType == 'instance'">
<div class="panel-heading">
<div class="row text-right" style="padding-right: 15px;">
<div class="btn-group btn-group-sm" role="group">
<button type="button" class="btn btn-default"
ng-class="{'btn-primary':namespace.instanceViewType == 'latest_release'}"
ng-click="switchInstanceViewType(namespace, 'latest_release')"> 使用最新配置的实例
<span class="badge" ng-bind="namespace.latestReleaseInstances.total"></span>
</button>
<button type="button" class="btn btn-default"
ng-class="{'btn-primary':namespace.instanceViewType == 'not_latest_release'}"
ng-click="switchInstanceViewType(namespace, 'not_latest_release')">使用非最新配置的实例
<span class="badge"
ng-bind="namespace.instancesCount - namespace.latestReleaseInstances.total"></span>
</button>
<button type="button" class="btn btn-default"
ng-class="{'btn-primary':namespace.instanceViewType == 'all'}"
ng-click="switchInstanceViewType(namespace, 'all')">所有实例
<span class="badge" ng-bind="namespace.instancesCount"></span>
</button>
</div>
<button class="btn btn-default btn-sm"
data-tooltip="tooltip" data-placement="bottom" title="刷新列表"
ng-click="refreshInstancesInfo(namespace)">
<img src="../../img/refresh.png"/>
</button>
</div>
</div>
<!--latest release instances-->
<div class="panel-body" ng-show="namespace.instanceViewType == 'latest_release'">
<div class="panel-default" ng-if="namespace.latestReleaseInstances.total > 0">
<div class="panel-heading">
<a target="_blank" data-tooltip="tooltip" data-placement="bottom" title="查看配置"
href="/config/history.html?#/appid={{appId}}&env={{env}}&clusterName={{cluster}}&namespaceName={{namespace.baseInfo.namespaceName}}&scrollTo={{namespace.latestRelease.name}}">
{{namespace.latestRelease.name}}
</a>
</div>
<table class="table table-bordered table-striped">
<thead>
<tr>
<td>App ID</td>
<td>Cluster Name</td>
<td>Data Center</td>
<td>IP</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="instance in namespace.latestReleaseInstances.content">
<td width="25%" ng-bind="instance.appId"></td>
<td width="25%" ng-bind="instance.clusterName"></td>
<td width="25%" ng-bind="instance.dataCenter"></td>
<td width="25%" ng-bind="instance.ip"></td>
</tr>
</tbody>
</table>
<div class="row text-center" ng-show="namespace.latestReleaseInstances.content.length < namespace.latestReleaseInstances.total">
<button class="btn btn-default" ng-click="loadInstanceInfo(namespace)">加载更多</button>
</div>
</div>
<div class="text-center" ng-if="namespace.latestReleaseInstances.total == 0">
无实例信息
</div>
</div>
<!--not latest release instances-->
<div class="panel-body" ng-show="namespace.instanceViewType == 'not_latest_release'">
<div class="panel-default" ng-if="namespace.instancesCount - namespace.latestReleaseInstances.total > 0" ng-repeat="releaseName in namespace.notLatestReleaseNames">
<div class="panel-heading">
<a target="_blank" data-tooltip="tooltip" data-placement="bottom" title="查看配置"
href="/config/history.html?#/appid={{appId}}&env={{env}}&clusterName={{cluster}}&namespaceName={{namespace.baseInfo.namespaceName}}&scrollTo={{releaseName}}">
{{releaseName}}
</a>
</div>
<table class="table table-bordered table-striped">
<thead>
<tr>
<td>App ID</td>
<td>Cluster Name</td>
<td>Data Center</td>
<td>IP</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="instance in namespace.notLatestReleaseInstances[releaseName]">
<td width="25%" ng-bind="instance.appId"></td>
<td width="25%" ng-bind="instance.clusterName"></td>
<td width="25%" ng-bind="instance.dataCenter"></td>
<td width="25%" ng-bind="instance.ip"></td>
</tr>
</tbody>
</table>
</div>
<div class="text-center" ng-if="namespace.instancesCount - namespace.latestReleaseInstances.total == 0">
无实例信息
</div>
</div>
<!--all instances-->
<div class="panel-body" ng-show="namespace.instanceViewType == 'all'">
<div class="panel-default" ng-if="namespace.instancesCount > 0">
<table class="table table-bordered table-striped" ng-if="namespace.allInstances">
<thead>
<tr>
<td>App ID</td>
<td>Cluster Name</td>
<td>Data Center</td>
<td>IP</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="instance in namespace.allInstances">
<td width="25%" ng-bind="instance.appId"></td>
<td width="25%" ng-bind="instance.clusterName"></td>
<td width="25%" ng-bind="instance.dataCenter"></td>
<td width="25%" ng-bind="instance.ip"></td>
</tr>
</tbody>
</table>
<div class="row text-center" ng-show="namespace.allInstances.length < namespace.instancesCount">
<button class="btn btn-default" ng-click="loadInstanceInfo(namespace)">加载更多</button>
</div>
</div>
<div class="text-center" ng-if="namespace.instancesCount == 0">
无实例信息
</div>
</div>
</div>
</div>
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