From 5c4d269ba38be8dd4e020c31296b2bfdde8d7222 Mon Sep 17 00:00:00 2001 From: Jason Song Date: Fri, 29 Apr 2016 12:01:59 +0800 Subject: [PATCH] Release key generator, eureka url config to db. 1. Add a release key generator to generate human-readable release keys 2. Add retry policy for client long polling 3. Move eureka url config to database --- .../src/test/resources/data.sql | 2 +- .../com/ctrip/apollo/biz/entity/Release.java | 10 +++ .../ctrip/apollo/biz/entity/ServerConfig.java | 54 +++++++++++++ .../biz/eureka/ApolloEurekaClientConfig.java | 49 ++++++++++++ .../biz/eureka/CtripEurekaClientConfig.java | 28 ------- .../biz/eureka/CtripEurekaSettings.java | 47 ----------- .../repository/ServerConfigRepository.java | 12 +++ .../apollo/biz/service/ReleaseService.java | 25 +++--- .../apollo/biz/utils/ReleaseKeyGenerator.java | 55 +++++++++++++ .../eureka/ApolloEurekaClientConfigTest.java | 80 +++++++++++++++++++ .../apollo/biz/service/ConfigServiceTest.java | 35 ++++---- .../biz/utils/ReleaseKeyGeneratorTest.java | 73 +++++++++++++++++ .../internals/RemoteConfigRepository.java | 27 ++++--- .../integration/ConfigIntegrationTest.java | 6 +- .../internals/RemoteConfigRepositoryTest.java | 4 +- .../controller/ConfigController.java | 14 ++-- .../controller/NotificationController.java | 5 +- .../controller/ConfigControllerTest.java | 66 +++++++-------- .../NotificationControllerTest.java | 14 +--- .../ConfigControllerIntegrationTest.java | 18 ++--- .../src/test/resources/data.sql | 2 +- .../integration-test/test-release.sql | 20 ++--- .../ctrip/apollo/core/dto/ApolloConfig.java | 16 ++-- .../com/ctrip/apollo/core/dto/ReleaseDTO.java | 10 +++ .../schedule/ExponentialSchedulePolicy.java | 35 ++++++++ .../apollo/core/schedule/SchedulePolicy.java | 11 +++ .../com/ctrip/apollo/core/utils/ByteUtil.java | 36 +++++++++ .../ctrip/apollo/core/utils/MachineUtil.java | 59 ++++++++++++++ 28 files changed, 614 insertions(+), 199 deletions(-) create mode 100644 apollo-biz/src/main/java/com/ctrip/apollo/biz/entity/ServerConfig.java create mode 100644 apollo-biz/src/main/java/com/ctrip/apollo/biz/eureka/ApolloEurekaClientConfig.java delete mode 100644 apollo-biz/src/main/java/com/ctrip/apollo/biz/eureka/CtripEurekaClientConfig.java delete mode 100644 apollo-biz/src/main/java/com/ctrip/apollo/biz/eureka/CtripEurekaSettings.java create mode 100644 apollo-biz/src/main/java/com/ctrip/apollo/biz/repository/ServerConfigRepository.java create mode 100644 apollo-biz/src/main/java/com/ctrip/apollo/biz/utils/ReleaseKeyGenerator.java create mode 100644 apollo-biz/src/test/java/com/ctrip/apollo/biz/eureka/ApolloEurekaClientConfigTest.java create mode 100644 apollo-biz/src/test/java/com/ctrip/apollo/biz/utils/ReleaseKeyGeneratorTest.java create mode 100644 apollo-core/src/main/java/com/ctrip/apollo/core/schedule/ExponentialSchedulePolicy.java create mode 100644 apollo-core/src/main/java/com/ctrip/apollo/core/schedule/SchedulePolicy.java create mode 100644 apollo-core/src/main/java/com/ctrip/apollo/core/utils/ByteUtil.java create mode 100644 apollo-core/src/main/java/com/ctrip/apollo/core/utils/MachineUtil.java diff --git a/apollo-adminservice/src/test/resources/data.sql b/apollo-adminservice/src/test/resources/data.sql index 18c8103b5..6794d87ae 100644 --- a/apollo-adminservice/src/test/resources/data.sql +++ b/apollo-adminservice/src/test/resources/data.sql @@ -29,5 +29,5 @@ INSERT INTO Item (NamespaceId, `Key`, Value, Comment) VALUES (1, 'k2', 'v2', 'co INSERT INTO Item (NamespaceId, `Key`, Value, Comment) VALUES (2, 'k3', 'v3', 'comment3'); INSERT INTO Item (NamespaceId, `Key`, Value, Comment) VALUES (5, 'k3', 'v4', 'comment4'); -INSERT INTO RELEASE (Name, Comment, AppId, ClusterName, NamespaceName, Configurations) VALUES ('REV1','First Release','100003171', 'default', 'application', '{"k1":"v1"}'); +INSERT INTO RELEASE (ReleaseKey, Name, Comment, AppId, ClusterName, NamespaceName, Configurations) VALUES ('TEST-RELEASE-KEY', 'REV1','First Release','100003171', 'default', 'application', '{"k1":"v1"}'); diff --git a/apollo-biz/src/main/java/com/ctrip/apollo/biz/entity/Release.java b/apollo-biz/src/main/java/com/ctrip/apollo/biz/entity/Release.java index e0ba78ef6..eae1513d9 100644 --- a/apollo-biz/src/main/java/com/ctrip/apollo/biz/entity/Release.java +++ b/apollo-biz/src/main/java/com/ctrip/apollo/biz/entity/Release.java @@ -16,6 +16,8 @@ import javax.persistence.Table; @SQLDelete(sql = "Update Release set isDeleted = 1 where id = ?") @Where(clause = "isDeleted = 0") public class Release extends BaseEntity { + @Column(name = "ReleaseKey", nullable = false) + private String releaseKey; @Column(name = "Name", nullable = false) private String name; @@ -36,6 +38,10 @@ public class Release extends BaseEntity { @Column(name = "Comment", nullable = false) private String comment; + public String getReleaseKey() { + return releaseKey; + } + public String getAppId() { return appId; } @@ -60,6 +66,10 @@ public class Release extends BaseEntity { return name; } + public void setReleaseKey(String releaseKey) { + this.releaseKey = releaseKey; + } + public void setAppId(String appId) { this.appId = appId; } diff --git a/apollo-biz/src/main/java/com/ctrip/apollo/biz/entity/ServerConfig.java b/apollo-biz/src/main/java/com/ctrip/apollo/biz/entity/ServerConfig.java new file mode 100644 index 000000000..fdee10dbf --- /dev/null +++ b/apollo-biz/src/main/java/com/ctrip/apollo/biz/entity/ServerConfig.java @@ -0,0 +1,54 @@ +package com.ctrip.apollo.biz.entity; + +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.Where; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * @author Jason Song(song_s@ctrip.com) + */ +@Entity +@Table(name = "ServerConfig") +@SQLDelete(sql = "Update ServerConfig set isDeleted = 1 where id = ?") +@Where(clause = "isDeleted = 0") +public class ServerConfig extends BaseEntity { + @Column(name = "Key", nullable = false) + private String key; + + @Column(name = "Value", nullable = false) + private String value; + + @Column(name = "Comment", nullable = false) + private String comment; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public String toString() { + return toStringHelper().add("key", key).add("value", value).add("comment", comment).toString(); + } +} diff --git a/apollo-biz/src/main/java/com/ctrip/apollo/biz/eureka/ApolloEurekaClientConfig.java b/apollo-biz/src/main/java/com/ctrip/apollo/biz/eureka/ApolloEurekaClientConfig.java new file mode 100644 index 000000000..d3d27ff77 --- /dev/null +++ b/apollo-biz/src/main/java/com/ctrip/apollo/biz/eureka/ApolloEurekaClientConfig.java @@ -0,0 +1,49 @@ +package com.ctrip.apollo.biz.eureka; + +import com.google.common.base.Splitter; +import com.google.common.base.Strings; + +import com.ctrip.apollo.biz.entity.ServerConfig; +import com.ctrip.apollo.biz.repository.ServerConfigRepository; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Objects; + +@Component +public class ApolloEurekaClientConfig extends EurekaClientConfigBean { + static final String EUREKA_URL_CONFIG = "eureka.service.url"; + private static final Splitter URL_SPLITTER = Splitter.on(",").omitEmptyStrings(); + + @Autowired + private ServerConfigRepository serverConfigRepository; + + @Autowired + private Environment environment; + + /** + * Assert only one zone: defaultZone, but multiple environments. + */ + public List getEurekaServerServiceUrls(String myZone) { + //First check if there is any system property override + if (!Strings.isNullOrEmpty(environment.getProperty(EUREKA_URL_CONFIG))) { + return URL_SPLITTER.splitToList(environment.getProperty(EUREKA_URL_CONFIG)); + } + + //Second check if it is configured in database + ServerConfig eurekaUrl = serverConfigRepository.findByKey(EUREKA_URL_CONFIG); + + if (!Objects.isNull(eurekaUrl) && !Strings.isNullOrEmpty(eurekaUrl.getValue())) { + return URL_SPLITTER.splitToList(eurekaUrl.getValue()); + + } + + //fallback to default + return super.getEurekaServerServiceUrls(myZone); + } + +} diff --git a/apollo-biz/src/main/java/com/ctrip/apollo/biz/eureka/CtripEurekaClientConfig.java b/apollo-biz/src/main/java/com/ctrip/apollo/biz/eureka/CtripEurekaClientConfig.java deleted file mode 100644 index 7decae67b..000000000 --- a/apollo-biz/src/main/java/com/ctrip/apollo/biz/eureka/CtripEurekaClientConfig.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.ctrip.apollo.biz.eureka; - -import java.util.Arrays; -import java.util.List; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean; -import org.springframework.stereotype.Component; - -@Component -public class CtripEurekaClientConfig extends EurekaClientConfigBean { - - @Autowired - private CtripEurekaSettings eurekaSettings; - - /** - * Assert only one zone: defaultZone, but multiple environments. - */ - public List getEurekaServerServiceUrls(String myZone) { - String serviceUrls = eurekaSettings.getDefaultEurekaUrl(myZone); - if (serviceUrls != null) { - return Arrays.asList(serviceUrls.split(",")); - }else{ - return super.getEurekaServerServiceUrls(myZone); - } - } - -} diff --git a/apollo-biz/src/main/java/com/ctrip/apollo/biz/eureka/CtripEurekaSettings.java b/apollo-biz/src/main/java/com/ctrip/apollo/biz/eureka/CtripEurekaSettings.java deleted file mode 100644 index 817c56a56..000000000 --- a/apollo-biz/src/main/java/com/ctrip/apollo/biz/eureka/CtripEurekaSettings.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.ctrip.apollo.biz.eureka; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import com.ctrip.apollo.core.enums.Env; -import com.ctrip.apollo.core.enums.EnvUtils; -import com.ctrip.framework.foundation.Foundation; - -@Component -public class CtripEurekaSettings { - - @Value("${ctrip.eureka.dev:http://localhost:8080/eureka}") - private String devEureka; - - @Value("${ctrip.eureka.fat:http://localhost:8080/eureka}") - private String fatEureka; - - @Value("${ctrip.eureka.uat:http://localhost:8080/eureka}") - private String uatEureka; - - @Value("${ctrip.eureka.pro:http://localhost:8080/eureka}") - private String proEureka; - - public String getDefaultEurekaUrl(String zone) { - Env env = EnvUtils.transformEnv(Foundation.server().getEnvType()); - if (env == null) { - return null; - } - switch (env) { - case LOCAL: - return null; - case DEV: - return devEureka; - case FAT: - case FWS: - return fatEureka; - case UAT: - return uatEureka; - case TOOLS: - case PRO: - return proEureka; - default: - return null; - } - } -} diff --git a/apollo-biz/src/main/java/com/ctrip/apollo/biz/repository/ServerConfigRepository.java b/apollo-biz/src/main/java/com/ctrip/apollo/biz/repository/ServerConfigRepository.java new file mode 100644 index 000000000..65e56bfc8 --- /dev/null +++ b/apollo-biz/src/main/java/com/ctrip/apollo/biz/repository/ServerConfigRepository.java @@ -0,0 +1,12 @@ +package com.ctrip.apollo.biz.repository; + +import com.ctrip.apollo.biz.entity.ServerConfig; + +import org.springframework.data.repository.PagingAndSortingRepository; + +/** + * @author Jason Song(song_s@ctrip.com) + */ +public interface ServerConfigRepository extends PagingAndSortingRepository { + ServerConfig findByKey(String key); +} diff --git a/apollo-biz/src/main/java/com/ctrip/apollo/biz/service/ReleaseService.java b/apollo-biz/src/main/java/com/ctrip/apollo/biz/service/ReleaseService.java index c86136590..a910eba35 100644 --- a/apollo-biz/src/main/java/com/ctrip/apollo/biz/service/ReleaseService.java +++ b/apollo-biz/src/main/java/com/ctrip/apollo/biz/service/ReleaseService.java @@ -1,14 +1,6 @@ package com.ctrip.apollo.biz.service; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; +import com.google.gson.Gson; import com.ctrip.apollo.biz.entity.Audit; import com.ctrip.apollo.biz.entity.Item; @@ -16,8 +8,18 @@ import com.ctrip.apollo.biz.entity.Namespace; import com.ctrip.apollo.biz.entity.Release; import com.ctrip.apollo.biz.repository.ItemRepository; import com.ctrip.apollo.biz.repository.ReleaseRepository; +import com.ctrip.apollo.biz.utils.ReleaseKeyGenerator; import com.ctrip.apollo.core.utils.StringUtils; -import com.google.gson.Gson; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * @author Jason Song(song_s@ctrip.com) @@ -49,7 +51,7 @@ public class ReleaseService { } return releases; } - + @Transactional public Release buildRelease(String name, String comment, Namespace namespace, String owner) { List items = itemRepository.findByNamespaceIdOrderByLineNumAsc(namespace.getId()); @@ -62,6 +64,7 @@ public class ReleaseService { } Release release = new Release(); + release.setReleaseKey(ReleaseKeyGenerator.generateReleaseKey(namespace)); release.setDataChangeCreatedTime(new Date()); release.setDataChangeCreatedBy(owner); release.setName(name); diff --git a/apollo-biz/src/main/java/com/ctrip/apollo/biz/utils/ReleaseKeyGenerator.java b/apollo-biz/src/main/java/com/ctrip/apollo/biz/utils/ReleaseKeyGenerator.java new file mode 100644 index 000000000..95f7e6a5b --- /dev/null +++ b/apollo-biz/src/main/java/com/ctrip/apollo/biz/utils/ReleaseKeyGenerator.java @@ -0,0 +1,55 @@ +package com.ctrip.apollo.biz.utils; + +import com.google.common.base.Joiner; + +import com.ctrip.apollo.biz.entity.Namespace; +import com.ctrip.apollo.core.utils.ByteUtil; +import com.ctrip.apollo.core.utils.MachineUtil; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author Jason Song(song_s@ctrip.com) + */ +public class ReleaseKeyGenerator { + private static final DateFormat TIMESTAMP_FORMAT = new SimpleDateFormat("yyyyMMddHHmmss"); + private static final AtomicInteger releaseCounter = new AtomicInteger(); + private static final Joiner KEY_JOINER = Joiner.on("-"); + + /** + * Generate the release key in the format: timestamp+appId+cluster+namespace+hash(ipAsInt+counter) + * + * @param namespace the namespace of the release + * @return the unique release key + */ + public static String generateReleaseKey(Namespace namespace) { + String hexIdString = + ByteUtil.toHexString( + toByteArray(Objects.hash(namespace.getAppId(), namespace.getClusterName(), + namespace.getNamespaceName()), MachineUtil.getMachineIdentifier(), + releaseCounter.incrementAndGet())); + + return KEY_JOINER.join(TIMESTAMP_FORMAT.format(new Date()), hexIdString); + } + + /** + * Concat machine id, counter and key to byte array + * Only retrieve lower 3 bytes of the id and counter and 2 bytes of the keyHashCode + */ + private static byte[] toByteArray(int keyHashCode, int machineIdentifier, int counter) { + byte[] bytes = new byte[8]; + bytes[0] = ByteUtil.int1(keyHashCode); + bytes[1] = ByteUtil.int0(keyHashCode); + bytes[2] = ByteUtil.int2(machineIdentifier); + bytes[3] = ByteUtil.int1(machineIdentifier); + bytes[4] = ByteUtil.int0(machineIdentifier); + bytes[5] = ByteUtil.int2(counter); + bytes[6] = ByteUtil.int1(counter); + bytes[7] = ByteUtil.int0(counter); + return bytes; + } +} diff --git a/apollo-biz/src/test/java/com/ctrip/apollo/biz/eureka/ApolloEurekaClientConfigTest.java b/apollo-biz/src/test/java/com/ctrip/apollo/biz/eureka/ApolloEurekaClientConfigTest.java new file mode 100644 index 000000000..dec81cdf9 --- /dev/null +++ b/apollo-biz/src/test/java/com/ctrip/apollo/biz/eureka/ApolloEurekaClientConfigTest.java @@ -0,0 +1,80 @@ +package com.ctrip.apollo.biz.eureka; + +import com.ctrip.apollo.biz.entity.ServerConfig; +import com.ctrip.apollo.biz.repository.ServerConfigRepository; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.core.env.Environment; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.List; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.when; + +/** + * @author Jason Song(song_s@ctrip.com) + */ +@RunWith(MockitoJUnitRunner.class) +public class ApolloEurekaClientConfigTest { + private ApolloEurekaClientConfig eurekaClientConfig; + @Mock + private ServerConfigRepository serverConfigRepository; + @Mock + private Environment environment; + + @Before + public void setUp() throws Exception { + eurekaClientConfig = new ApolloEurekaClientConfig(); + ReflectionTestUtils.setField(eurekaClientConfig, "serverConfigRepository", serverConfigRepository); + ReflectionTestUtils.setField(eurekaClientConfig, "environment", environment); + } + + @Test + public void testGetEurekaServerServiceUrlsFromDB() throws Exception { + String someEurekaUrl = "http://xxx,http://yyy"; + String myZone = "xx"; + + when(serverConfigRepository.findByKey(ApolloEurekaClientConfig.EUREKA_URL_CONFIG)) + .thenReturn(assembleServerConfig(ApolloEurekaClientConfig.EUREKA_URL_CONFIG, someEurekaUrl)); + + List eurekaUrls = eurekaClientConfig.getEurekaServerServiceUrls(myZone); + String[] expected = someEurekaUrl.split(","); + + assertEquals(expected.length, eurekaUrls.size()); + for (String url : expected) { + assertTrue(eurekaUrls.contains(url)); + } + } + + @Test + public void testGetEurekaServiceUrlsFromSystemProperty() throws Exception { + String someEurekaUrl = "http://xxx,http://yyy"; + String myZone = "xx"; + String someEurekaUrlFromSystemProperty = "http://zzz"; + + when(environment.getProperty(ApolloEurekaClientConfig.EUREKA_URL_CONFIG)) + .thenReturn(someEurekaUrlFromSystemProperty); + when(serverConfigRepository.findByKey(ApolloEurekaClientConfig.EUREKA_URL_CONFIG)) + .thenReturn(assembleServerConfig(ApolloEurekaClientConfig.EUREKA_URL_CONFIG, someEurekaUrl)); + + List eurekaUrls = eurekaClientConfig.getEurekaServerServiceUrls(myZone); + + String[] expected = someEurekaUrlFromSystemProperty.split(","); + assertEquals(expected.length, eurekaUrls.size()); + for (String url : expected) { + assertTrue(eurekaUrls.contains(url)); + } + } + + private ServerConfig assembleServerConfig(String key, String value) { + ServerConfig config = new ServerConfig(); + config.setKey(key); + config.setValue(value); + return config; + } +} diff --git a/apollo-biz/src/test/java/com/ctrip/apollo/biz/service/ConfigServiceTest.java b/apollo-biz/src/test/java/com/ctrip/apollo/biz/service/ConfigServiceTest.java index 9b6d2ac4e..0e6ec85fe 100644 --- a/apollo-biz/src/test/java/com/ctrip/apollo/biz/service/ConfigServiceTest.java +++ b/apollo-biz/src/test/java/com/ctrip/apollo/biz/service/ConfigServiceTest.java @@ -1,10 +1,7 @@ package com.ctrip.apollo.biz.service; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import com.ctrip.apollo.biz.entity.Release; +import com.ctrip.apollo.biz.repository.ReleaseRepository; import org.junit.Before; import org.junit.Test; @@ -13,8 +10,11 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.test.util.ReflectionTestUtils; -import com.ctrip.apollo.biz.entity.Release; -import com.ctrip.apollo.biz.repository.ReleaseRepository; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +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) @@ -37,11 +37,15 @@ public class ConfigServiceTest { String someAppId = "1"; String someClusterName = "someClusterName"; String someNamespaceName = "someNamespaceName"; - String someReleaseId = "1"; + long someReleaseId = 1; + String someReleaseKey = "someKey"; String someValidConfiguration = "{\"apollo.bar\": \"foo\"}"; - Release someRelease = assembleRelease(someReleaseId, someAppId, someClusterName, someNamespaceName, - someValidConfiguration); + Release + someRelease = + assembleRelease(someReleaseId, someReleaseKey, someAppId, someClusterName, + someNamespaceName, + someValidConfiguration); when(releaseRepository.findFirstByAppIdAndClusterNameAndNamespaceNameOrderByIdDesc(someAppId, someClusterName, someNamespaceName)).thenReturn(someRelease); @@ -53,7 +57,8 @@ public class ConfigServiceTest { someNamespaceName); assertEquals(someAppId, result.getAppId()); assertEquals(someClusterName, result.getClusterName()); - assertEquals(someReleaseId, String.valueOf(result.getId())); + assertEquals(someReleaseId, result.getId()); + assertEquals(someReleaseKey, result.getReleaseKey()); assertEquals(someValidConfiguration, result.getConfigurations()); } @@ -73,10 +78,12 @@ public class ConfigServiceTest { someAppId, someClusterName, someNamespaceName); } - private Release assembleRelease(String releaseId, String appId, String clusterName, - String groupName, String configurations) { + private Release assembleRelease(long releaseId, String releaseKey, String appId, + String clusterName, + String groupName, String configurations) { Release release = new Release(); - release.setId(Long.valueOf(releaseId)); + release.setId(releaseId); + release.setReleaseKey(releaseKey); release.setAppId(appId); release.setClusterName(clusterName); release.setNamespaceName(groupName); diff --git a/apollo-biz/src/test/java/com/ctrip/apollo/biz/utils/ReleaseKeyGeneratorTest.java b/apollo-biz/src/test/java/com/ctrip/apollo/biz/utils/ReleaseKeyGeneratorTest.java new file mode 100644 index 000000000..93a372659 --- /dev/null +++ b/apollo-biz/src/test/java/com/ctrip/apollo/biz/utils/ReleaseKeyGeneratorTest.java @@ -0,0 +1,73 @@ +package com.ctrip.apollo.biz.utils; + +import com.google.common.collect.Sets; + +import com.ctrip.apollo.biz.entity.Namespace; + +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; + +/** + * @author Jason Song(song_s@ctrip.com) + */ +public class ReleaseKeyGeneratorTest { + private static final Logger logger = LoggerFactory.getLogger(ReleaseKeyGeneratorTest.class); + @Test + public void testGenerateReleaseKey() throws Exception { + String someAppId = "someAppId"; + String someCluster = "someCluster"; + String someNamespace = "someNamespace"; + + String anotherAppId = "anotherAppId"; + + Namespace namespace = assembleNamespace(someAppId, someCluster, someNamespace); + Namespace anotherNamespace = assembleNamespace(anotherAppId, someCluster, someNamespace); + int generateTimes = 50000; + Set releaseKeys = Sets.newConcurrentHashSet(); + + ExecutorService executorService = Executors.newFixedThreadPool(2); + CountDownLatch latch = new CountDownLatch(1); + + executorService.submit(generateReleaseKeysTask(namespace, releaseKeys, generateTimes, latch)); + executorService.submit(generateReleaseKeysTask(anotherNamespace, releaseKeys, generateTimes, latch)); + + latch.countDown(); + + executorService.shutdown(); + executorService.awaitTermination(10, TimeUnit.SECONDS); + + //make sure keys are unique + assertEquals(generateTimes * 2, releaseKeys.size()); + } + + private Runnable generateReleaseKeysTask(Namespace namespace, Set releaseKeys, + int generateTimes, CountDownLatch latch) { + return () -> { + try { + latch.await(); + } catch (InterruptedException e) { + //ignore + } + for (int i = 0; i < generateTimes; i++) { + releaseKeys.add(ReleaseKeyGenerator.generateReleaseKey(namespace)); + } + }; + } + + private Namespace assembleNamespace(String appId, String cluster, String namespaceName) { + Namespace namespace = new Namespace(); + namespace.setAppId(appId); + namespace.setClusterName(cluster); + namespace.setNamespaceName(namespaceName); + return namespace; + } +} diff --git a/apollo-client/src/main/java/com/ctrip/apollo/internals/RemoteConfigRepository.java b/apollo-client/src/main/java/com/ctrip/apollo/internals/RemoteConfigRepository.java index 4fd923ff0..4f32e2934 100644 --- a/apollo-client/src/main/java/com/ctrip/apollo/internals/RemoteConfigRepository.java +++ b/apollo-client/src/main/java/com/ctrip/apollo/internals/RemoteConfigRepository.java @@ -11,6 +11,8 @@ import com.ctrip.apollo.core.ConfigConsts; import com.ctrip.apollo.core.dto.ApolloConfig; import com.ctrip.apollo.core.dto.ApolloConfigNotification; import com.ctrip.apollo.core.dto.ServiceDTO; +import com.ctrip.apollo.core.schedule.ExponentialSchedulePolicy; +import com.ctrip.apollo.core.schedule.SchedulePolicy; import com.ctrip.apollo.core.utils.ApolloThreadFactory; import com.ctrip.apollo.util.ConfigUtil; import com.ctrip.apollo.util.ExceptionUtil; @@ -54,6 +56,7 @@ public class RemoteConfigRepository extends AbstractConfigRepository { private final String m_namespace; private final ScheduledExecutorService m_executorService; private final AtomicBoolean m_longPollingStopped; + private SchedulePolicy m_longPollSchedulePolicy; /** * Constructor. @@ -72,8 +75,9 @@ public class RemoteConfigRepository extends AbstractConfigRepository { Cat.logError(ex); throw new IllegalStateException("Unable to load component!", ex); } - this.m_longPollingStopped = new AtomicBoolean(false); - this.m_executorService = Executors.newScheduledThreadPool(1, + m_longPollSchedulePolicy = new ExponentialSchedulePolicy(1, 120); + m_longPollingStopped = new AtomicBoolean(false); + m_executorService = Executors.newScheduledThreadPool(1, ApolloThreadFactory.create("RemoteConfigRepository", true)); this.trySync(); this.schedulePeriodicRefresh(); @@ -206,7 +210,7 @@ public class RemoteConfigRepository extends AbstractConfigRepository { } if (previousConfig != null) { - queryParams.put("releaseId", escaper.escape(String.valueOf(previousConfig.getReleaseId()))); + queryParams.put("releaseKey", escaper.escape(String.valueOf(previousConfig.getReleaseKey()))); } if (!Strings.isNullOrEmpty(dataCenter)) { @@ -253,7 +257,7 @@ public class RemoteConfigRepository extends AbstractConfigRepository { String url = assembleLongPollRefreshUrl(lastServiceDto.getHomepageUrl(), appId, cluster, - m_namespace, dataCenter, m_configCache.get()); + m_namespace, dataCenter); logger.debug("Long polling from {}", url); HttpRequest request = new HttpRequest(url); @@ -275,18 +279,21 @@ public class RemoteConfigRepository extends AbstractConfigRepository { } }); } + m_longPollSchedulePolicy.success(); transaction.addData("StatusCode", response.getStatusCode()); transaction.setStatus(Message.SUCCESS); } catch (Throwable ex) { - logger.warn("Long polling failed, will retry. appId: {}, cluster: {}, namespace: {}, reason: {}", - appId, cluster, m_namespace, ExceptionUtil.getDetailMessage(ex)); lastServiceDto = null; Cat.logError(ex); if (transaction != null) { transaction.setStatus(ex); } + long sleepTime = m_longPollSchedulePolicy.fail(); + logger.warn( + "Long polling failed, will retry in {} seconds. appId: {}, cluster: {}, namespace: {}, reason: {}", + sleepTime, appId, cluster, m_namespace, ExceptionUtil.getDetailMessage(ex)); try { - TimeUnit.SECONDS.sleep(5); + TimeUnit.SECONDS.sleep(sleepTime); } catch (InterruptedException ie) { //ignore } @@ -299,8 +306,7 @@ public class RemoteConfigRepository extends AbstractConfigRepository { } private String assembleLongPollRefreshUrl(String uri, String appId, String cluster, - String namespace, String dataCenter, - ApolloConfig previousConfig) { + String namespace, String dataCenter) { Escaper escaper = UrlEscapers.urlPathSegmentEscaper(); Map queryParams = Maps.newHashMap(); queryParams.put("appId", escaper.escape(appId)); @@ -312,9 +318,6 @@ public class RemoteConfigRepository extends AbstractConfigRepository { if (!Strings.isNullOrEmpty(dataCenter)) { queryParams.put("dataCenter", escaper.escape(dataCenter)); } - if (previousConfig != null) { - queryParams.put("releaseId", escaper.escape(previousConfig.getReleaseId())); - } String params = MAP_JOINER.join(queryParams); if (!uri.endsWith("/")) { diff --git a/apollo-client/src/test/java/com/ctrip/apollo/integration/ConfigIntegrationTest.java b/apollo-client/src/test/java/com/ctrip/apollo/integration/ConfigIntegrationTest.java index 2cc675791..cd2125472 100644 --- a/apollo-client/src/test/java/com/ctrip/apollo/integration/ConfigIntegrationTest.java +++ b/apollo-client/src/test/java/com/ctrip/apollo/integration/ConfigIntegrationTest.java @@ -46,7 +46,7 @@ import static org.junit.Assert.assertTrue; * @author Jason Song(song_s@ctrip.com) */ public class ConfigIntegrationTest extends BaseIntegrationTest { - private String someReleaseId; + private String someReleaseKey; private File configDir; private String defaultNamespace; @@ -55,7 +55,7 @@ public class ConfigIntegrationTest extends BaseIntegrationTest { super.setUp(); defaultNamespace = ConfigConsts.NAMESPACE_DEFAULT; - someReleaseId = "1"; + someReleaseKey = "1"; configDir = new File(ClassLoaderUtil.getClassPath() + "config-cache"); configDir.mkdirs(); } @@ -329,7 +329,7 @@ public class ConfigIntegrationTest extends BaseIntegrationTest { private ApolloConfig assembleApolloConfig(Map configurations) { ApolloConfig apolloConfig = - new ApolloConfig(someAppId, someClusterName, defaultNamespace, someReleaseId); + new ApolloConfig(someAppId, someClusterName, defaultNamespace, someReleaseKey); apolloConfig.setConfigurations(configurations); diff --git a/apollo-client/src/test/java/com/ctrip/apollo/internals/RemoteConfigRepositoryTest.java b/apollo-client/src/test/java/com/ctrip/apollo/internals/RemoteConfigRepositoryTest.java index 22626394c..7073e5afa 100644 --- a/apollo-client/src/test/java/com/ctrip/apollo/internals/RemoteConfigRepositoryTest.java +++ b/apollo-client/src/test/java/com/ctrip/apollo/internals/RemoteConfigRepositoryTest.java @@ -167,9 +167,9 @@ public class RemoteConfigRepositoryTest extends ComponentTestCase { private ApolloConfig assembleApolloConfig(Map configurations) { String someAppId = "appId"; String someClusterName = "cluster"; - String someReleaseId = "1"; + String someReleaseKey = "1"; ApolloConfig apolloConfig = - new ApolloConfig(someAppId, someClusterName, someNamespace, someReleaseId); + new ApolloConfig(someAppId, someClusterName, someNamespace, someReleaseKey); apolloConfig.setConfigurations(configurations); diff --git a/apollo-configservice/src/main/java/com/ctrip/apollo/configservice/controller/ConfigController.java b/apollo-configservice/src/main/java/com/ctrip/apollo/configservice/controller/ConfigController.java index ac1a78a2a..eb961a922 100644 --- a/apollo-configservice/src/main/java/com/ctrip/apollo/configservice/controller/ConfigController.java +++ b/apollo-configservice/src/main/java/com/ctrip/apollo/configservice/controller/ConfigController.java @@ -51,17 +51,17 @@ public class ConfigController { @RequestMapping(value = "/{appId}/{clusterName}", method = RequestMethod.GET) public ApolloConfig queryConfig(@PathVariable String appId, @PathVariable String clusterName, @RequestParam(value = "dataCenter", required = false) String dataCenter, - @RequestParam(value = "releaseId", defaultValue = "-1") String clientSideReleaseId, + @RequestParam(value = "releaseKey", defaultValue = "-1") String clientSideReleaseKey, HttpServletResponse response) throws IOException { return this.queryConfig(appId, clusterName, ConfigConsts.NAMESPACE_DEFAULT, dataCenter, - clientSideReleaseId, response); + clientSideReleaseKey, response); } @RequestMapping(value = "/{appId}/{clusterName}/{namespace}", method = RequestMethod.GET) public ApolloConfig queryConfig(@PathVariable String appId, @PathVariable String clusterName, @PathVariable String namespace, @RequestParam(value = "dataCenter", required = false) String dataCenter, - @RequestParam(value = "releaseId", defaultValue = "-1") String clientSideReleaseId, + @RequestParam(value = "releaseKey", defaultValue = "-1") String clientSideReleaseKey, HttpServletResponse response) throws IOException { List releases = Lists.newLinkedList(); @@ -89,10 +89,10 @@ public class ConfigController { return null; } - String mergedReleaseId = FluentIterable.from(releases).transform( - input -> String.valueOf(input.getId())).join(STRING_JOINER); + String mergedReleaseKey = FluentIterable.from(releases).transform( + input -> String.valueOf(input.getReleaseKey())).join(STRING_JOINER); - if (mergedReleaseId.equals(clientSideReleaseId)) { + if (mergedReleaseKey.equals(clientSideReleaseKey)) { // Client side configuration is the same with server side, return 304 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); Cat.logEvent("Apollo.Config.NotModified", @@ -100,7 +100,7 @@ public class ConfigController { return null; } - ApolloConfig apolloConfig = new ApolloConfig(appId, clusterName, namespace, mergedReleaseId); + ApolloConfig apolloConfig = new ApolloConfig(appId, clusterName, namespace, mergedReleaseKey); apolloConfig.setConfigurations(mergeReleaseConfigurations(releases)); Cat.logEvent("Apollo.Config.Found", assembleKey(appId, clusterName, namespace, dataCenter)); diff --git a/apollo-configservice/src/main/java/com/ctrip/apollo/configservice/controller/NotificationController.java b/apollo-configservice/src/main/java/com/ctrip/apollo/configservice/controller/NotificationController.java index f4448db30..de934f89a 100644 --- a/apollo-configservice/src/main/java/com/ctrip/apollo/configservice/controller/NotificationController.java +++ b/apollo-configservice/src/main/java/com/ctrip/apollo/configservice/controller/NotificationController.java @@ -45,7 +45,7 @@ public class NotificationController implements MessageListener { NOT_MODIFIED_RESPONSE = new ResponseEntity<>(HttpStatus.NOT_MODIFIED); private static final Joiner STRING_JOINER = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR); private static final Splitter STRING_SPLITTER = - Splitter.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR); + Splitter.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).omitEmptyStrings(); @Autowired private AppNamespaceService appNamespaceService; @@ -55,8 +55,7 @@ public class NotificationController implements MessageListener { @RequestParam(value = "appId") String appId, @RequestParam(value = "cluster") String cluster, @RequestParam(value = "namespace", defaultValue = ConfigConsts.NAMESPACE_DEFAULT) String namespace, - @RequestParam(value = "dataCenter", required = false) String dataCenter, - @RequestParam(value = "releaseId", defaultValue = "-1") String clientSideReleaseId) { + @RequestParam(value = "dataCenter", required = false) String dataCenter) { List watchedKeys = Lists.newArrayList(assembleKey(appId, cluster, namespace)); //Listen on more namespaces, since it's not the default namespace diff --git a/apollo-configservice/src/test/java/com/ctrip/apollo/configservice/controller/ConfigControllerTest.java b/apollo-configservice/src/test/java/com/ctrip/apollo/configservice/controller/ConfigControllerTest.java index 65e45e63f..e7d4a6276 100644 --- a/apollo-configservice/src/test/java/com/ctrip/apollo/configservice/controller/ConfigControllerTest.java +++ b/apollo-configservice/src/test/java/com/ctrip/apollo/configservice/controller/ConfigControllerTest.java @@ -73,36 +73,36 @@ public class ConfigControllerTest { @Test public void testQueryConfig() throws Exception { - long someClientSideReleaseId = 1; - long someServerSideNewReleaseId = 2; + String someClientSideReleaseKey = "1"; + String someServerSideNewReleaseKey = "2"; HttpServletResponse someResponse = mock(HttpServletResponse.class); when(configService.findRelease(someAppId, someClusterName, defaultNamespaceName)) .thenReturn(someRelease); - when(someRelease.getId()).thenReturn(someServerSideNewReleaseId); + when(someRelease.getReleaseKey()).thenReturn(someServerSideNewReleaseKey); ApolloConfig result = configController.queryConfig(someAppId, someClusterName, - defaultNamespaceName, someDataCenter, String.valueOf(someClientSideReleaseId), + defaultNamespaceName, someDataCenter, someClientSideReleaseKey, someResponse); verify(configService, times(1)).findRelease(someAppId, someClusterName, defaultNamespaceName); assertEquals(someAppId, result.getAppId()); assertEquals(someClusterName, result.getCluster()); assertEquals(defaultNamespaceName, result.getNamespace()); - assertEquals(String.valueOf(someServerSideNewReleaseId), result.getReleaseId()); + assertEquals(someServerSideNewReleaseKey, result.getReleaseKey()); } @Test public void testQueryConfigWithReleaseNotFound() throws Exception { - long someClientSideReleaseId = 1; + String someClientSideReleaseKey = "1"; HttpServletResponse someResponse = mock(HttpServletResponse.class); when(configService.findRelease(someAppId, someClusterName, defaultNamespaceName)) .thenReturn(null); ApolloConfig result = configController.queryConfig(someAppId, someClusterName, - defaultNamespaceName, someDataCenter, String.valueOf(someClientSideReleaseId), + defaultNamespaceName, someDataCenter, someClientSideReleaseKey, someResponse); assertNull(result); @@ -111,18 +111,18 @@ public class ConfigControllerTest { @Test public void testQueryConfigWithApolloConfigNotModified() throws Exception { - long someClientSideReleaseId = 1; - long someServerSideReleaseId = someClientSideReleaseId; + String someClientSideReleaseKey = "1"; + String someServerSideReleaseKey = someClientSideReleaseKey; HttpServletResponse someResponse = mock(HttpServletResponse.class); when(configService.findRelease(someAppId, someClusterName, defaultNamespaceName)) .thenReturn(someRelease); - when(someRelease.getId()).thenReturn(someServerSideReleaseId); + when(someRelease.getReleaseKey()).thenReturn(someServerSideReleaseKey); ApolloConfig result = configController.queryConfig(someAppId, someClusterName, defaultNamespaceName, - someDataCenter, String.valueOf(someClientSideReleaseId), someResponse); + someDataCenter, String.valueOf(someClientSideReleaseKey), someResponse); assertNull(result); verify(someResponse, times(1)).setStatus(HttpServletResponse.SC_NOT_MODIFIED); @@ -130,8 +130,8 @@ public class ConfigControllerTest { @Test public void testQueryConfigWithAppOwnNamespace() throws Exception { - String someClientSideReleaseId = "1"; - String someServerSideReleaseId = "2"; + String someClientSideReleaseKey = "1"; + String someServerSideReleaseKey = "2"; String someAppOwnNamespaceName = "someAppOwn"; HttpServletResponse someResponse = mock(HttpServletResponse.class); AppNamespace someAppOwnNamespace = @@ -141,14 +141,14 @@ public class ConfigControllerTest { .thenReturn(someRelease); when(appNamespaceService.findByNamespaceName(someAppOwnNamespaceName)) .thenReturn(someAppOwnNamespace); - when(someRelease.getId()).thenReturn(Long.valueOf(someServerSideReleaseId)); + when(someRelease.getReleaseKey()).thenReturn(someServerSideReleaseKey); ApolloConfig result = configController .queryConfig(someAppId, someClusterName, someAppOwnNamespaceName, someDataCenter, - someClientSideReleaseId, someResponse); + someClientSideReleaseKey, someResponse); - assertEquals(someServerSideReleaseId, result.getReleaseId()); + assertEquals(someServerSideReleaseKey, result.getReleaseKey()); assertEquals(someAppId, result.getAppId()); assertEquals(someClusterName, result.getCluster()); assertEquals(someAppOwnNamespaceName, result.getNamespace()); @@ -157,8 +157,8 @@ public class ConfigControllerTest { @Test public void testQueryConfigWithPubicNamespaceAndNoAppOverride() throws Exception { - String someClientSideReleaseId = "1"; - String someServerSideReleaseId = "2"; + String someClientSideReleaseKey = "1"; + String someServerSideReleaseKey = "2"; HttpServletResponse someResponse = mock(HttpServletResponse.class); String somePublicAppId = "somePublicAppId"; AppNamespace somePublicAppNamespace = @@ -170,14 +170,14 @@ public class ConfigControllerTest { .thenReturn(somePublicAppNamespace); when(configService.findRelease(somePublicAppId, someDataCenter, somePublicNamespaceName)) .thenReturn(somePublicRelease); - when(somePublicRelease.getId()).thenReturn(Long.valueOf(someServerSideReleaseId)); + when(somePublicRelease.getReleaseKey()).thenReturn(someServerSideReleaseKey); ApolloConfig result = configController .queryConfig(someAppId, someClusterName, somePublicNamespaceName, someDataCenter, - someClientSideReleaseId, someResponse); + someClientSideReleaseKey, someResponse); - assertEquals(someServerSideReleaseId, result.getReleaseId()); + assertEquals(someServerSideReleaseKey, result.getReleaseKey()); assertEquals(someAppId, result.getAppId()); assertEquals(someClusterName, result.getCluster()); assertEquals(somePublicNamespaceName, result.getNamespace()); @@ -186,8 +186,8 @@ public class ConfigControllerTest { @Test public void testQueryConfigWithPublicNamespaceAndNoAppOverrideAndNoDataCenter() throws Exception { - String someClientSideReleaseId = "1"; - String someServerSideReleaseId = "2"; + String someClientSideReleaseKey = "1"; + String someServerSideReleaseKey = "2"; HttpServletResponse someResponse = mock(HttpServletResponse.class); String somePublicAppId = "somePublicAppId"; AppNamespace somePublicAppNamespace = @@ -202,14 +202,14 @@ public class ConfigControllerTest { when(configService .findRelease(somePublicAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, somePublicNamespaceName)) .thenReturn(somePublicRelease); - when(somePublicRelease.getId()).thenReturn(Long.valueOf(someServerSideReleaseId)); + when(somePublicRelease.getReleaseKey()).thenReturn(someServerSideReleaseKey); ApolloConfig result = configController .queryConfig(someAppId, someClusterName, somePublicNamespaceName, someDataCenter, - someClientSideReleaseId, someResponse); + someClientSideReleaseKey, someResponse); - assertEquals(someServerSideReleaseId, result.getReleaseId()); + assertEquals(someServerSideReleaseKey, result.getReleaseKey()); assertEquals(someAppId, result.getAppId()); assertEquals(someClusterName, result.getCluster()); assertEquals(somePublicNamespaceName, result.getNamespace()); @@ -218,8 +218,8 @@ public class ConfigControllerTest { @Test public void testQueryConfigWithPublicNamespaceAndAppOverride() throws Exception { - String someAppSideReleaseId = "1"; - String somePublicAppSideReleaseId = "2"; + String someAppSideReleaseKey = "1"; + String somePublicAppSideReleaseKey = "2"; HttpServletResponse someResponse = mock(HttpServletResponse.class); String somePublicAppId = "somePublicAppId"; @@ -232,21 +232,21 @@ public class ConfigControllerTest { when(configService.findRelease(someAppId, someClusterName, somePublicNamespaceName)) .thenReturn(someRelease); - when(someRelease.getId()).thenReturn(Long.valueOf(someAppSideReleaseId)); + when(someRelease.getReleaseKey()).thenReturn(someAppSideReleaseKey); when(appNamespaceService.findByNamespaceName(somePublicNamespaceName)) .thenReturn(somePublicAppNamespace); when(configService.findRelease(somePublicAppId, someDataCenter, somePublicNamespaceName)) .thenReturn(somePublicRelease); - when(somePublicRelease.getId()).thenReturn(Long.valueOf(somePublicAppSideReleaseId)); + when(somePublicRelease.getReleaseKey()).thenReturn(somePublicAppSideReleaseKey); ApolloConfig result = configController .queryConfig(someAppId, someClusterName, somePublicNamespaceName, someDataCenter, - someAppSideReleaseId, someResponse); + someAppSideReleaseKey, someResponse); assertEquals(Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR) - .join(someAppSideReleaseId, somePublicAppSideReleaseId), - result.getReleaseId()); + .join(someAppSideReleaseKey, somePublicAppSideReleaseKey), + result.getReleaseKey()); assertEquals(someAppId, result.getAppId()); assertEquals(someClusterName, result.getCluster()); assertEquals(somePublicNamespaceName, result.getNamespace()); diff --git a/apollo-configservice/src/test/java/com/ctrip/apollo/configservice/controller/NotificationControllerTest.java b/apollo-configservice/src/test/java/com/ctrip/apollo/configservice/controller/NotificationControllerTest.java index 323110703..89b42e75d 100644 --- a/apollo-configservice/src/test/java/com/ctrip/apollo/configservice/controller/NotificationControllerTest.java +++ b/apollo-configservice/src/test/java/com/ctrip/apollo/configservice/controller/NotificationControllerTest.java @@ -22,8 +22,6 @@ import org.springframework.web.context.request.async.DeferredResult; import java.util.List; -import javax.servlet.http.HttpServletResponse; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.when; @@ -39,7 +37,6 @@ public class NotificationControllerTest { private String defaultNamespace; private String somePublicNamespace; private String someDataCenter; - private String someReleaseId; @Mock private AppNamespaceService appNamespaceService; private Multimap>> @@ -55,7 +52,6 @@ public class NotificationControllerTest { defaultNamespace = ConfigConsts.NAMESPACE_DEFAULT; somePublicNamespace = "somePublicNamespace"; someDataCenter = "someDC"; - someReleaseId = "someRelease"; deferredResults = (Multimap>>) ReflectionTestUtils @@ -66,7 +62,7 @@ public class NotificationControllerTest { public void testPollNotificationWithDefaultNamespace() throws Exception { DeferredResult> deferredResult = controller - .pollNotification(someAppId, someCluster, defaultNamespace, someDataCenter, someReleaseId); + .pollNotification(someAppId, someCluster, defaultNamespace, someDataCenter); String key = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR) @@ -86,8 +82,7 @@ public class NotificationControllerTest { DeferredResult> deferredResult = controller - .pollNotification(someAppId, someCluster, somePublicNamespace, someDataCenter, - someReleaseId); + .pollNotification(someAppId, someCluster, somePublicNamespace, someDataCenter); List publicClusters = Lists.newArrayList(someDataCenter, ConfigConsts.CLUSTER_NAME_DEFAULT); @@ -110,7 +105,7 @@ public class NotificationControllerTest { public void testPollNotificationWithDefaultNamespaceAndHandleMessage() throws Exception { DeferredResult> deferredResult = controller - .pollNotification(someAppId, someCluster, defaultNamespace, someDataCenter, someReleaseId); + .pollNotification(someAppId, someCluster, defaultNamespace, someDataCenter); String key = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR) @@ -137,8 +132,7 @@ public class NotificationControllerTest { DeferredResult> deferredResult = controller - .pollNotification(someAppId, someCluster, somePublicNamespace, someDataCenter, - someReleaseId); + .pollNotification(someAppId, someCluster, somePublicNamespace, someDataCenter); String key = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR) diff --git a/apollo-configservice/src/test/java/com/ctrip/apollo/configservice/integration/ConfigControllerIntegrationTest.java b/apollo-configservice/src/test/java/com/ctrip/apollo/configservice/integration/ConfigControllerIntegrationTest.java index 83993db7c..89aa10df6 100644 --- a/apollo-configservice/src/test/java/com/ctrip/apollo/configservice/integration/ConfigControllerIntegrationTest.java +++ b/apollo-configservice/src/test/java/com/ctrip/apollo/configservice/integration/ConfigControllerIntegrationTest.java @@ -43,7 +43,7 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest ApolloConfig result = response.getBody(); assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(String.valueOf(990), result.getReleaseId()); + assertEquals("TEST-RELEASE-KEY1", result.getReleaseKey()); assertEquals("v1", result.getConfigurations().get("k1")); } @@ -57,7 +57,7 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest ApolloConfig result = response.getBody(); assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(String.valueOf(991), result.getReleaseId()); + assertEquals("TEST-RELEASE-KEY2", result.getReleaseKey()); assertEquals("v2", result.getConfigurations().get("k2")); } @@ -81,11 +81,11 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest @Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) public void testQueryConfigNotModified() throws Exception { - String releaseId = String.valueOf(991); + String releaseKey = "TEST-RELEASE-KEY2"; ResponseEntity response = restTemplate - .getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}?releaseId={releaseId}", + .getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}?releaseKey={releaseKey}", ApolloConfig.class, - getHostUrl(), someAppId, someCluster, someNamespace, releaseId); + getHostUrl(), someAppId, someCluster, someNamespace, releaseKey); assertEquals(HttpStatus.NOT_MODIFIED, response.getStatusCode()); } @@ -100,7 +100,7 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest getHostUrl(), someAppId, someCluster, somePublicNamespace, someDC); ApolloConfig result = response.getBody(); - assertEquals("993", result.getReleaseId()); + assertEquals("TEST-RELEASE-KEY4", result.getReleaseKey()); assertEquals(someAppId, result.getAppId()); assertEquals(someCluster, result.getCluster()); assertEquals(somePublicNamespace, result.getNamespace()); @@ -118,7 +118,7 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest getHostUrl(), someAppId, someDefaultCluster, somePublicNamespace, someDC); ApolloConfig result = response.getBody(); - assertEquals("994" + ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR + "993", result.getReleaseId()); + assertEquals("TEST-RELEASE-KEY5" + ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR + "TEST-RELEASE-KEY4", result.getReleaseKey()); assertEquals(someAppId, result.getAppId()); assertEquals(someDefaultCluster, result.getCluster()); assertEquals(somePublicNamespace, result.getNamespace()); @@ -137,7 +137,7 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest getHostUrl(), someAppId, someCluster, somePublicNamespace, someDCNotFound); ApolloConfig result = response.getBody(); - assertEquals("992", result.getReleaseId()); + assertEquals("TEST-RELEASE-KEY3", result.getReleaseKey()); assertEquals(someAppId, result.getAppId()); assertEquals(someCluster, result.getCluster()); assertEquals(somePublicNamespace, result.getNamespace()); @@ -156,7 +156,7 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest getHostUrl(), someAppId, someDefaultCluster, somePublicNamespace, someDCNotFound); ApolloConfig result = response.getBody(); - assertEquals("994" + ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR + "992", result.getReleaseId()); + assertEquals("TEST-RELEASE-KEY5" + ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR + "TEST-RELEASE-KEY3", result.getReleaseKey()); assertEquals(someAppId, result.getAppId()); assertEquals(someDefaultCluster, result.getCluster()); assertEquals(somePublicNamespace, result.getNamespace()); diff --git a/apollo-configservice/src/test/resources/data.sql b/apollo-configservice/src/test/resources/data.sql index b786e3609..2fee78941 100644 --- a/apollo-configservice/src/test/resources/data.sql +++ b/apollo-configservice/src/test/resources/data.sql @@ -30,5 +30,5 @@ INSERT INTO Item (NamespaceId, `Key`, Value, Comment) VALUES (1, 'k2', 'v2', 'co INSERT INTO Item (NamespaceId, `Key`, Value, Comment) VALUES (2, 'k3', 'v3', 'comment3'); INSERT INTO Item (NamespaceId, `Key`, Value, Comment) VALUES (5, 'k3', 'v4', 'comment4'); -INSERT INTO RELEASE (Name, Comment, AppId, ClusterName, NamespaceName, Configurations) VALUES ('REV1','First Release','100003171', 'default', 'application', '{"k1":"v1"}'); +INSERT INTO RELEASE (ReleaseKey, Name, Comment, AppId, ClusterName, NamespaceName, Configurations) VALUES ('TEST-RELEASE-KEY', 'REV1','First Release','100003171', 'default', 'application', '{"k1":"v1"}'); diff --git a/apollo-configservice/src/test/resources/integration-test/test-release.sql b/apollo-configservice/src/test/resources/integration-test/test-release.sql index ec7b66002..48418106b 100644 --- a/apollo-configservice/src/test/resources/integration-test/test-release.sql +++ b/apollo-configservice/src/test/resources/integration-test/test-release.sql @@ -17,13 +17,13 @@ INSERT INTO Namespace (AppId, ClusterName, NamespaceName) VALUES ('somePublicApp INSERT INTO Namespace (AppId, ClusterName, NamespaceName) VALUES ('somePublicAppId', 'someDC', 'somePublicNamespace'); INSERT INTO Namespace (AppId, ClusterName, NamespaceName) VALUES ('someAppId', 'default', 'somePublicNamespace'); -INSERT INTO RELEASE (id, Name, Comment, AppId, ClusterName, NamespaceName, Configurations) - VALUES (990, 'INTEGRATION-TEST-DEFAULT','First Release','someAppId', 'default', 'application', '{"k1":"v1"}'); -INSERT INTO RELEASE (id, Name, Comment, AppId, ClusterName, NamespaceName, Configurations) - VALUES (991, 'INTEGRATION-TEST-NAMESPACE','First Release','someAppId', 'someCluster', 'someNamespace', '{"k2":"v2"}'); -INSERT INTO RELEASE (id, Name, Comment, AppId, ClusterName, NamespaceName, Configurations) - VALUES (992, 'INTEGRATION-TEST-PUBLIC-DEFAULT','First Release','somePublicAppId', 'default', 'somePublicNamespace', '{"k1":"default-v1", "k2":"default-v2"}'); -INSERT INTO RELEASE (id, Name, Comment, AppId, ClusterName, NamespaceName, Configurations) - VALUES (993, 'INTEGRATION-TEST-PUBLIC-NAMESPACE','First Release','somePublicAppId', 'someDC', 'somePublicNamespace', '{"k1":"someDC-v1", "k2":"someDC-v2"}'); -INSERT INTO RELEASE (id, Name, Comment, AppId, ClusterName, NamespaceName, Configurations) - VALUES (994, 'INTEGRATION-TEST-DEFAULT-OVERRIDE-PUBLIC','First Release','someAppId', 'default', 'somePublicNamespace', '{"k1":"override-v1"}'); +INSERT INTO RELEASE (id, ReleaseKey, Name, Comment, AppId, ClusterName, NamespaceName, Configurations) + VALUES (990, 'TEST-RELEASE-KEY1', 'INTEGRATION-TEST-DEFAULT','First Release','someAppId', 'default', 'application', '{"k1":"v1"}'); +INSERT INTO RELEASE (id, ReleaseKey, Name, Comment, AppId, ClusterName, NamespaceName, Configurations) + VALUES (991, 'TEST-RELEASE-KEY2', 'INTEGRATION-TEST-NAMESPACE','First Release','someAppId', 'someCluster', 'someNamespace', '{"k2":"v2"}'); +INSERT INTO RELEASE (id, ReleaseKey, Name, Comment, AppId, ClusterName, NamespaceName, Configurations) + VALUES (992, 'TEST-RELEASE-KEY3', 'INTEGRATION-TEST-PUBLIC-DEFAULT','First Release','somePublicAppId', 'default', 'somePublicNamespace', '{"k1":"default-v1", "k2":"default-v2"}'); +INSERT INTO RELEASE (id, ReleaseKey, Name, Comment, AppId, ClusterName, NamespaceName, Configurations) + VALUES (993, 'TEST-RELEASE-KEY4', 'INTEGRATION-TEST-PUBLIC-NAMESPACE','First Release','somePublicAppId', 'someDC', 'somePublicNamespace', '{"k1":"someDC-v1", "k2":"someDC-v2"}'); +INSERT INTO RELEASE (id, ReleaseKey, Name, Comment, AppId, ClusterName, NamespaceName, Configurations) + VALUES (994, 'TEST-RELEASE-KEY5', 'INTEGRATION-TEST-DEFAULT-OVERRIDE-PUBLIC','First Release','someAppId', 'default', 'somePublicNamespace', '{"k1":"override-v1"}'); diff --git a/apollo-core/src/main/java/com/ctrip/apollo/core/dto/ApolloConfig.java b/apollo-core/src/main/java/com/ctrip/apollo/core/dto/ApolloConfig.java index e114d4232..14a47067b 100644 --- a/apollo-core/src/main/java/com/ctrip/apollo/core/dto/ApolloConfig.java +++ b/apollo-core/src/main/java/com/ctrip/apollo/core/dto/ApolloConfig.java @@ -17,7 +17,7 @@ public class ApolloConfig { private Map configurations; - private String releaseId; + private String releaseKey; public ApolloConfig() { } @@ -25,11 +25,11 @@ public class ApolloConfig { public ApolloConfig(String appId, String cluster, String namespace, - String releaseId) { + String releaseKey) { this.appId = appId; this.cluster = cluster; this.namespace = namespace; - this.releaseId = releaseId; + this.releaseKey = releaseKey; } public String getAppId() { @@ -44,8 +44,8 @@ public class ApolloConfig { return namespace; } - public String getReleaseId() { - return releaseId; + public String getReleaseKey() { + return releaseKey; } public Map getConfigurations() { @@ -64,8 +64,8 @@ public class ApolloConfig { this.namespace = namespace; } - public void setReleaseId(String releaseId) { - this.releaseId = releaseId; + public void setReleaseKey(String releaseKey) { + this.releaseKey = releaseKey; } public void setConfigurations(Map configurations) { @@ -79,7 +79,7 @@ public class ApolloConfig { .add("appId", appId) .add("cluster", cluster) .add("namespace", namespace) - .add("releaseId", releaseId) + .add("releaseKey", releaseKey) .add("configurations", configurations) .toString(); } diff --git a/apollo-core/src/main/java/com/ctrip/apollo/core/dto/ReleaseDTO.java b/apollo-core/src/main/java/com/ctrip/apollo/core/dto/ReleaseDTO.java index 48c3ed03b..849e03608 100644 --- a/apollo-core/src/main/java/com/ctrip/apollo/core/dto/ReleaseDTO.java +++ b/apollo-core/src/main/java/com/ctrip/apollo/core/dto/ReleaseDTO.java @@ -3,6 +3,8 @@ package com.ctrip.apollo.core.dto; public class ReleaseDTO{ private long id; + private String releaseKey; + private String name; private String appId; @@ -23,6 +25,14 @@ public class ReleaseDTO{ this.id = id; } + public String getReleaseKey() { + return releaseKey; + } + + public void setReleaseKey(String releaseKey) { + this.releaseKey = releaseKey; + } + public String getAppId() { return appId; } diff --git a/apollo-core/src/main/java/com/ctrip/apollo/core/schedule/ExponentialSchedulePolicy.java b/apollo-core/src/main/java/com/ctrip/apollo/core/schedule/ExponentialSchedulePolicy.java new file mode 100644 index 000000000..07d51f1f4 --- /dev/null +++ b/apollo-core/src/main/java/com/ctrip/apollo/core/schedule/ExponentialSchedulePolicy.java @@ -0,0 +1,35 @@ +package com.ctrip.apollo.core.schedule; + +/** + * @author Jason Song(song_s@ctrip.com) + */ +public class ExponentialSchedulePolicy implements SchedulePolicy { + private final long delayTimeLowerBound; + private final long delayTimeUpperBound; + private long lastDelayTime; + + public ExponentialSchedulePolicy(long delayTimeLowerBound, long delayTimeUpperBound) { + this.delayTimeLowerBound = delayTimeLowerBound; + this.delayTimeUpperBound = delayTimeUpperBound; + } + + @Override + public long fail() { + long delayTime = lastDelayTime; + + if (delayTime == 0) { + delayTime = delayTimeLowerBound; + } else { + delayTime = Math.min(lastDelayTime << 1, delayTimeUpperBound); + } + + lastDelayTime = delayTime; + + return delayTime; + } + + @Override + public void success() { + lastDelayTime = 0; + } +} diff --git a/apollo-core/src/main/java/com/ctrip/apollo/core/schedule/SchedulePolicy.java b/apollo-core/src/main/java/com/ctrip/apollo/core/schedule/SchedulePolicy.java new file mode 100644 index 000000000..665cb3738 --- /dev/null +++ b/apollo-core/src/main/java/com/ctrip/apollo/core/schedule/SchedulePolicy.java @@ -0,0 +1,11 @@ +package com.ctrip.apollo.core.schedule; + +/** + * Schedule policy + * @author Jason Song(song_s@ctrip.com) + */ +public interface SchedulePolicy { + long fail(); + + void success(); +} diff --git a/apollo-core/src/main/java/com/ctrip/apollo/core/utils/ByteUtil.java b/apollo-core/src/main/java/com/ctrip/apollo/core/utils/ByteUtil.java new file mode 100644 index 000000000..bfb9d11f2 --- /dev/null +++ b/apollo-core/src/main/java/com/ctrip/apollo/core/utils/ByteUtil.java @@ -0,0 +1,36 @@ +package com.ctrip.apollo.core.utils; + +/** + * @author Jason Song(song_s@ctrip.com) + */ +public class ByteUtil { + private static final char[] HEX_CHARS = new char[] { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + public static byte int3(final int x) { + return (byte) (x >> 24); + } + + public static byte int2(final int x) { + return (byte) (x >> 16); + } + + public static byte int1(final int x) { + return (byte) (x >> 8); + } + + public static byte int0(final int x) { + return (byte) (x); + } + + public static String toHexString(byte[] bytes) { + char[] chars = new char[bytes.length * 2]; + int i = 0; + for (byte b : bytes) { + chars[i++] = HEX_CHARS[b >> 4 & 0xF]; + chars[i++] = HEX_CHARS[b & 0xF]; + } + return new String(chars); + } +} diff --git a/apollo-core/src/main/java/com/ctrip/apollo/core/utils/MachineUtil.java b/apollo-core/src/main/java/com/ctrip/apollo/core/utils/MachineUtil.java new file mode 100644 index 000000000..4d76c58d3 --- /dev/null +++ b/apollo-core/src/main/java/com/ctrip/apollo/core/utils/MachineUtil.java @@ -0,0 +1,59 @@ +package com.ctrip.apollo.core.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.NetworkInterface; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.security.SecureRandom; +import java.util.Enumeration; + +/** + * @author Jason Song(song_s@ctrip.com) + */ +public class MachineUtil { + private static final Logger logger = LoggerFactory.getLogger(MachineUtil.class); + private static final int MACHINE_IDENTIFIER = createMachineIdentifier(); + + public static int getMachineIdentifier() { + return MACHINE_IDENTIFIER; + } + + /** + * Get the machine identifier from mac address + * + * @see ObjectId.java + */ + private static int createMachineIdentifier() { + // build a 2-byte machine piece based on NICs info + int machinePiece; + try { + StringBuilder sb = new StringBuilder(); + Enumeration e = NetworkInterface.getNetworkInterfaces(); + while (e.hasMoreElements()) { + NetworkInterface ni = e.nextElement(); + sb.append(ni.toString()); + byte[] mac = ni.getHardwareAddress(); + if (mac != null) { + ByteBuffer bb = ByteBuffer.wrap(mac); + try { + sb.append(bb.getChar()); + sb.append(bb.getChar()); + sb.append(bb.getChar()); + } catch (BufferUnderflowException shortHardwareAddressException) { //NOPMD + // mac with less than 6 bytes. continue + } + } + } + machinePiece = sb.toString().hashCode(); + } catch (Throwable ex) { + // exception sometimes happens with IBM JVM, use random + machinePiece = (new SecureRandom().nextInt()); + logger.warn( + "Failed to get machine identifier from network interface, using random number instead", + ex); + } + return machinePiece; + } +} -- GitLab