diff --git a/apollo-mockserver/pom.xml b/apollo-mockserver/pom.xml index 48a4f238b341a4b3dd899184db3f4c64c01a1ab4..d2c09b551183d81f2a688f193fa86fb917025322 100644 --- a/apollo-mockserver/pom.xml +++ b/apollo-mockserver/pom.xml @@ -11,27 +11,38 @@ apollo-mockserver + + + + com.squareup.okhttp3 + mockwebserver + 3.11.0 + + + com.squareup.okhttp3 + okhttp + 3.11.0 + + + + com.ctrip.framework.apollo apollo-client - ${project.version} com.squareup.okhttp3 mockwebserver - 3.11.0 com.squareup.okhttp3 okhttp - 3.11.0 org.springframework.boot spring-boot-starter - 1.3.8.RELEASE - provided + test diff --git a/apollo-mockserver/src/main/java/com/ctrip/framework/apollo/mockserver/EmbeddedApollo.java b/apollo-mockserver/src/main/java/com/ctrip/framework/apollo/mockserver/EmbeddedApollo.java index 89d8aad994c7a642d94493b69fda9249b98f17f8..c616c4bb3e91dcd087a60dbd7df119674977dc2b 100644 --- a/apollo-mockserver/src/main/java/com/ctrip/framework/apollo/mockserver/EmbeddedApollo.java +++ b/apollo-mockserver/src/main/java/com/ctrip/framework/apollo/mockserver/EmbeddedApollo.java @@ -1,19 +1,19 @@ package com.ctrip.framework.apollo.mockserver; -import com.ctrip.framework.apollo.core.MetaDomainConsts; +import com.ctrip.framework.apollo.build.ApolloInjector; import com.ctrip.framework.apollo.core.dto.ApolloConfig; import com.ctrip.framework.apollo.core.dto.ApolloConfigNotification; -import com.ctrip.framework.apollo.core.dto.ServiceDTO; -import com.ctrip.framework.apollo.core.spi.MetaServerProvider; import com.ctrip.framework.apollo.core.utils.ResourceUtils; +import com.ctrip.framework.apollo.internals.ConfigServiceLocator; +import com.ctrip.framework.apollo.spring.config.PropertySourcesProcessor; +import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; -import java.io.IOException; +import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -29,40 +29,57 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Create by zhangzheng on 8/22/18 - * Email:zhangzheng@youzan.com + * Create by zhangzheng on 8/22/18 Email:zhangzheng@youzan.com */ public class EmbeddedApollo extends ExternalResource { - private Gson gson = new Gson(); - private Logger logger = LoggerFactory.getLogger(EmbeddedApollo.class); - private Type notificationType = new TypeToken>(){}.getType(); + private static final Logger logger = LoggerFactory.getLogger(EmbeddedApollo.class); + private static final Type notificationType = new TypeToken>() { + }.getType(); + + private static Method PROPERTY_SOURCES_PROCESSOR_CLEAR; + private static Method SPRING_VALUE_DEFINITION_PROCESS_CLEAR; + private static Method CONFIG_SERVICE_LOCATOR_CLEAR; + private static ConfigServiceLocator CONFIG_SERVICE_LOCATOR; + + private final Gson gson = new Gson(); + private final Map> addedOrModifiedPropertiesOfNamespace = new HashMap<>(); + private final Map> deletedKeysOfNamespace = new HashMap<>(); - private String listenningUrl; private MockWebServer server; + static { + try { + System.setProperty("apollo.longPollingInitialDelayInMills", "0"); + PROPERTY_SOURCES_PROCESSOR_CLEAR = PropertySourcesProcessor.class.getDeclaredMethod("reset"); + PROPERTY_SOURCES_PROCESSOR_CLEAR.setAccessible(true); + SPRING_VALUE_DEFINITION_PROCESS_CLEAR = SpringValueDefinitionProcessor.class.getDeclaredMethod("reset"); + SPRING_VALUE_DEFINITION_PROCESS_CLEAR.setAccessible(true); + CONFIG_SERVICE_LOCATOR = ApolloInjector.getInstance(ConfigServiceLocator.class); + CONFIG_SERVICE_LOCATOR_CLEAR = ConfigServiceLocator.class.getDeclaredMethod("initConfigServices"); + CONFIG_SERVICE_LOCATOR_CLEAR.setAccessible(true); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + } @Override protected void before() throws Throwable { + clear(); server = new MockWebServer(); final Dispatcher dispatcher = new Dispatcher() { @Override public MockResponse dispatch(RecordedRequest request) throws InterruptedException { - if (request.getPath().startsWith("/services/config")) { - - return new MockResponse().setResponseCode(200) - .setBody(mockConfigServiceAddr(listenningUrl)); - } else if (request.getPath().startsWith("/notifications/v2")) { + if (request.getPath().startsWith("/notifications/v2")) { String notifications = request.getRequestUrl().queryParameter("notifications"); - MockResponse response = new MockResponse().setResponseCode(200).setBody(mockLongPollBody(notifications)); - return response; + return new MockResponse().setResponseCode(200).setBody(mockLongPollBody(notifications)); } else if (request.getPath().startsWith("/configs")) { List pathSegments = request.getRequestUrl().pathSegments(); + // appId and cluster might be used in the future String appId = pathSegments.get(1); String cluster = pathSegments.get(2); String namespace = pathSegments.get(3); - return new MockResponse().setResponseCode(200) - .setBody(loadConfigFor(namespace)); + return new MockResponse().setResponseCode(200).setBody(loadConfigFor(namespace)); } return new MockResponse().setResponseCode(404); } @@ -70,13 +87,8 @@ public class EmbeddedApollo extends ExternalResource { server.setDispatcher(dispatcher); server.start(); - //指定apollo的metaserver地址为localhost - int port = server.getPort(); - this.listenningUrl = "http://localhost:"+port; - MockedMetaServerProvider.setAddress(listenningUrl); - - System.setProperty("apollo.longPollingInitialDelayInMills","1"); + mockConfigServiceUrl("http://localhost:" + server.getPort()); super.before(); } @@ -84,78 +96,90 @@ public class EmbeddedApollo extends ExternalResource { @Override protected void after() { try { + clear(); server.close(); - } catch (IOException e) { + } catch (Exception e) { logger.error("stop apollo server error", e); } } + private void clear() throws Exception { + resetOverriddenProperties(); + // clear Apollo states + PROPERTY_SOURCES_PROCESSOR_CLEAR.invoke(null); + SPRING_VALUE_DEFINITION_PROCESS_CLEAR.invoke(null); + } + + private void mockConfigServiceUrl(String url) throws Exception { + System.setProperty("apollo.configService", url); - private String loadConfigFor(String namespace){ + CONFIG_SERVICE_LOCATOR_CLEAR.invoke(CONFIG_SERVICE_LOCATOR); + } + + private String loadConfigFor(String namespace) { String filename = String.format("mockdata-%s.properties", namespace); final Properties prop = ResourceUtils.readConfigFile(filename, new Properties()); - Map configurations = prop.stringPropertyNames().stream().collect( - Collectors.toMap(key -> key, key -> prop.getProperty(key))); - ApolloConfig apolloConfig = new ApolloConfig("someAppId", "someCluster",namespace,"someReleaseKey"); + Map configurations = prop.stringPropertyNames().stream().collect( + Collectors.toMap(key -> key, prop::getProperty)); + ApolloConfig apolloConfig = new ApolloConfig("someAppId", "someCluster", namespace, "someReleaseKey"); - Map mergedConfigurations = mergeModifyByUser(namespace, configurations); + Map mergedConfigurations = mergeOverriddenProperties(namespace, configurations); apolloConfig.setConfigurations(mergedConfigurations); return gson.toJson(apolloConfig); } - - private String mockLongPollBody(String notificationsStr){ + private String mockLongPollBody(String notificationsStr) { List oldNotifications = gson.fromJson(notificationsStr, notificationType); List newNotifications = new ArrayList<>(); - for(ApolloConfigNotification noti: oldNotifications){ - newNotifications.add(new ApolloConfigNotification(noti.getNamespaceName(), noti.getNotificationId()+1)); + for (ApolloConfigNotification notification : oldNotifications) { + newNotifications + .add(new ApolloConfigNotification(notification.getNamespaceName(), notification.getNotificationId() + 1)); } return gson.toJson(newNotifications); } - private String mockConfigServiceAddr(String addr){ - ServiceDTO serviceDTO = new ServiceDTO(); - serviceDTO.setAppName("someAppName"); - serviceDTO.setInstanceId("someInstanceId"); - serviceDTO.setHomepageUrl(addr); - return gson.toJson(Arrays.asList(serviceDTO)); - } - /** * 合并用户对namespace的修改 - * @param configurations - * @return */ - private Map mergeModifyByUser(String namespace - , Map configurations){ - if(addedPropertyOfNamespace.containsKey(namespace)){ - configurations.putAll(addedPropertyOfNamespace.get(namespace)); + private Map mergeOverriddenProperties(String namespace, Map configurations) { + if (addedOrModifiedPropertiesOfNamespace.containsKey(namespace)) { + configurations.putAll(addedOrModifiedPropertiesOfNamespace.get(namespace)); } - if(deletedKeysOfNamespace.containsKey(namespace)){ - for(String k: deletedKeysOfNamespace.get(namespace)){ + if (deletedKeysOfNamespace.containsKey(namespace)) { + for (String k : deletedKeysOfNamespace.get(namespace)) { configurations.remove(k); } } return configurations; } - - private Map> addedPropertyOfNamespace = new HashMap<>(); - public void addOrModifyPropery(String namespace, String someKey, String someValue) { - if(addedPropertyOfNamespace.containsKey(namespace)){ - addedPropertyOfNamespace.get(namespace).put(someKey, someValue); - }else{ - addedPropertyOfNamespace.put(namespace, ImmutableMap.of(someKey, someValue)); + /** + * Add new property or update existed property + */ + public void addOrModifyProperty(String namespace, String someKey, String someValue) { + if (addedOrModifiedPropertiesOfNamespace.containsKey(namespace)) { + addedOrModifiedPropertiesOfNamespace.get(namespace).put(someKey, someValue); + } else { + addedOrModifiedPropertiesOfNamespace.put(namespace, ImmutableMap.of(someKey, someValue)); } } - private Map> deletedKeysOfNamespace = new HashMap<>(); - public void delete(String namespace, String someKey) { - if(deletedKeysOfNamespace.containsKey(namespace)){ + + /** + * Delete existed property + */ + public void deleteProperty(String namespace, String someKey) { + if (deletedKeysOfNamespace.containsKey(namespace)) { deletedKeysOfNamespace.get(namespace).add(someKey); - }else{ + } else { deletedKeysOfNamespace.put(namespace, ImmutableSet.of(someKey)); } } - + /** + * reset overridden properties + */ + public void resetOverriddenProperties() { + addedOrModifiedPropertiesOfNamespace.clear(); + deletedKeysOfNamespace.clear(); + } } diff --git a/apollo-mockserver/src/main/java/com/ctrip/framework/apollo/mockserver/MockedMetaServerProvider.java b/apollo-mockserver/src/main/java/com/ctrip/framework/apollo/mockserver/MockedMetaServerProvider.java deleted file mode 100644 index 2c5fedafbcc943ff0fea7402bdf04b1bca502037..0000000000000000000000000000000000000000 --- a/apollo-mockserver/src/main/java/com/ctrip/framework/apollo/mockserver/MockedMetaServerProvider.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.ctrip.framework.apollo.mockserver; - -import com.ctrip.framework.apollo.core.enums.Env; -import com.ctrip.framework.apollo.core.spi.MetaServerProvider; - -/** - * Create by zhangzheng on 8/23/18 - * Email:zhangzheng@youzan.com - */ -public class MockedMetaServerProvider implements MetaServerProvider{ - - private static String address; - - public static void setAddress(String addr){ - address = addr; - } - - @Override - public String getMetaServerAddress(Env targetEnv) { - return address; - } - - @Override - public int getOrder() { - return HIGHEST_PRECEDENCE; - } -} diff --git a/apollo-mockserver/src/main/resources/META-INF/services/com.ctrip.framework.apollo.core.spi.MetaServerProvider b/apollo-mockserver/src/main/resources/META-INF/services/com.ctrip.framework.apollo.core.spi.MetaServerProvider deleted file mode 100644 index a3fe43116b984be01a94e148c03875cb78b7bf66..0000000000000000000000000000000000000000 --- a/apollo-mockserver/src/main/resources/META-INF/services/com.ctrip.framework.apollo.core.spi.MetaServerProvider +++ /dev/null @@ -1 +0,0 @@ -com.ctrip.framework.apollo.mockserver.MockedMetaServerProvider diff --git a/apollo-mockserver/src/test/java/com/ctrip/framework/apollo/mockserver/ApolloMockServerApiTest.java b/apollo-mockserver/src/test/java/com/ctrip/framework/apollo/mockserver/ApolloMockServerApiTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7f62cb8dc88dbd8b918efc71c3e2df2167341c29 --- /dev/null +++ b/apollo-mockserver/src/test/java/com/ctrip/framework/apollo/mockserver/ApolloMockServerApiTest.java @@ -0,0 +1,51 @@ +package com.ctrip.framework.apollo.mockserver; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.ctrip.framework.apollo.Config; +import com.ctrip.framework.apollo.ConfigService; +import com.ctrip.framework.apollo.model.ConfigChangeEvent; +import com.google.common.util.concurrent.SettableFuture; +import java.util.concurrent.TimeUnit; +import org.junit.ClassRule; +import org.junit.Test; + +public class ApolloMockServerApiTest { + + private static final String otherNamespace = "otherNamespace"; + + @ClassRule + public static EmbeddedApollo embeddedApollo = new EmbeddedApollo(); + + @Test + public void testGetProperty() throws Exception { + Config applicationConfig = ConfigService.getAppConfig(); + + assertEquals("value1", applicationConfig.getProperty("key1", null)); + assertEquals("value2", applicationConfig.getProperty("key2", null)); + } + + @Test + public void testUpdateProperties() throws Exception { + String someNewValue = "someNewValue"; + + Config otherConfig = ConfigService.getConfig(otherNamespace); + + SettableFuture future = SettableFuture.create(); + + otherConfig.addChangeListener(future::set); + + assertEquals("otherValue1", otherConfig.getProperty("key1", null)); + assertEquals("otherValue2", otherConfig.getProperty("key2", null)); + + embeddedApollo.addOrModifyProperty(otherNamespace, "key1", someNewValue); + + ConfigChangeEvent changeEvent = future.get(5, TimeUnit.SECONDS); + + assertEquals(someNewValue, otherConfig.getProperty("key1", null)); + assertEquals("otherValue2", otherConfig.getProperty("key2", null)); + + assertTrue(changeEvent.isChanged("key1")); + } +} diff --git a/apollo-mockserver/src/test/java/com/ctrip/framework/apollo/mockserver/SpringIntegrationTest.java b/apollo-mockserver/src/test/java/com/ctrip/framework/apollo/mockserver/ApolloMockServerSpringIntegrationTest.java similarity index 73% rename from apollo-mockserver/src/test/java/com/ctrip/framework/apollo/mockserver/SpringIntegrationTest.java rename to apollo-mockserver/src/test/java/com/ctrip/framework/apollo/mockserver/ApolloMockServerSpringIntegrationTest.java index 525fe356433b3ac9a637cb21821bca0670fecd6c..ab24de4372a0519bc515671095b49e20beef8f12 100644 --- a/apollo-mockserver/src/test/java/com/ctrip/framework/apollo/mockserver/SpringIntegrationTest.java +++ b/apollo-mockserver/src/test/java/com/ctrip/framework/apollo/mockserver/ApolloMockServerSpringIntegrationTest.java @@ -1,12 +1,12 @@ package com.ctrip.framework.apollo.mockserver; +import static org.junit.Assert.assertEquals; + import com.ctrip.framework.apollo.enums.PropertyChangeType; -import com.ctrip.framework.apollo.mockserver.SpringIntegrationTest.TestConfiguration; +import com.ctrip.framework.apollo.mockserver.ApolloMockServerSpringIntegrationTest.TestConfiguration; import com.ctrip.framework.apollo.model.ConfigChangeEvent; import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener; import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig; -import static org.junit.Assert.*; - import com.google.common.util.concurrent.SettableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -23,21 +23,23 @@ import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** - * Create by zhangzheng on 8/16/18 - * Email:zhangzheng@youzan.com + * Create by zhangzheng on 8/16/18 Email:zhangzheng@youzan.com */ @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = TestConfiguration.class) -public class SpringIntegrationTest { - @Autowired - TestBean testBean; +public class ApolloMockServerSpringIntegrationTest { + + private static final String otherNamespace = "otherNamespace"; @ClassRule public static EmbeddedApollo embeddedApollo = new EmbeddedApollo(); + @Autowired + private TestBean testBean; + @Test @DirtiesContext - public void testPropertyInject(){ + public void testPropertyInject() { assertEquals("value1", testBean.key1); assertEquals("value2", testBean.key2); } @@ -45,8 +47,7 @@ public class SpringIntegrationTest { @Test @DirtiesContext public void testListenerTriggeredByAdd() throws InterruptedException, ExecutionException, TimeoutException { - String otherNamespace = "othernamespace"; - embeddedApollo.addOrModifyPropery(otherNamespace,"someKey","someValue"); + embeddedApollo.addOrModifyProperty(otherNamespace, "someKey", "someValue"); ConfigChangeEvent changeEvent = testBean.futureData.get(5000, TimeUnit.MILLISECONDS); assertEquals(otherNamespace, changeEvent.getNamespace()); assertEquals("someValue", changeEvent.getChange("someKey").getNewValue()); @@ -56,42 +57,34 @@ public class SpringIntegrationTest { @DirtiesContext public void testListenerTriggeredByDel() throws InterruptedException, ExecutionException, TimeoutException { - String otherNamespace = "othernamespace"; - embeddedApollo.delete(otherNamespace, "key1"); + embeddedApollo.deleteProperty(otherNamespace, "key1"); ConfigChangeEvent changeEvent = testBean.futureData.get(5000, TimeUnit.MILLISECONDS); assertEquals(otherNamespace, changeEvent.getNamespace()); assertEquals(PropertyChangeType.DELETED, changeEvent.getChange("key1").getChangeType()); } - - - - - - - - @EnableApolloConfig("application") + @EnableApolloConfig @Configuration - static class TestConfiguration{ + static class TestConfiguration { + @Bean - public TestBean testBean(){ + public TestBean testBean() { return new TestBean(); } } - static class TestBean{ + private static class TestBean { + @Value("${key1:default}") - String key1; + private String key1; @Value("${key2:default}") - String key2; + private String key2; - SettableFuture futureData = SettableFuture.create(); + private SettableFuture futureData = SettableFuture.create(); - @ApolloConfigChangeListener("othernamespace") + @ApolloConfigChangeListener(otherNamespace) private void onChange(ConfigChangeEvent changeEvent) { futureData.set(changeEvent); } } - - } diff --git a/apollo-mockserver/src/test/resources/META-INF/app.properties b/apollo-mockserver/src/test/resources/META-INF/app.properties new file mode 100644 index 0000000000000000000000000000000000000000..dca1d42b34fbce3488ecdcd0579d13677b192068 --- /dev/null +++ b/apollo-mockserver/src/test/resources/META-INF/app.properties @@ -0,0 +1 @@ +app.id=someAppId diff --git a/apollo-mockserver/src/test/resources/mockdata-otherNamespace.properties b/apollo-mockserver/src/test/resources/mockdata-otherNamespace.properties new file mode 100644 index 0000000000000000000000000000000000000000..9f6e4ac5dc1d1f3507de220f1ffaa512cc780eca --- /dev/null +++ b/apollo-mockserver/src/test/resources/mockdata-otherNamespace.properties @@ -0,0 +1,2 @@ +key1=otherValue1 +key2=otherValue2 diff --git a/apollo-mockserver/src/test/resources/mockdata-othernamespace.properties b/apollo-mockserver/src/test/resources/mockdata-othernamespace.properties deleted file mode 100644 index 26190d146cddba9395020b60589b47d9c046f66f..0000000000000000000000000000000000000000 --- a/apollo-mockserver/src/test/resources/mockdata-othernamespace.properties +++ /dev/null @@ -1,2 +0,0 @@ -key1=othervalue1 -key2=othervalue2 diff --git a/pom.xml b/pom.xml index ee5d0b68d88840b0faa07de84756395dd947b285..c693eebfd7ae6886ad57e15ad975841cb15496cf 100644 --- a/pom.xml +++ b/pom.xml @@ -113,6 +113,11 @@ apollo-core ${project.version} + + com.ctrip.framework.apollo + apollo-client + ${project.version} + com.ctrip.framework.apollo apollo-common