Commit b533fb61 authored by Jason Song's avatar Jason Song Committed by GitHub

Merge pull request #1403 from nobodyiam/refactor-mock-server

refactor a little bit
parents d7621107 71fca12e
...@@ -11,27 +11,38 @@ ...@@ -11,27 +11,38 @@
<artifactId>apollo-mockserver</artifactId> <artifactId>apollo-mockserver</artifactId>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<version>3.11.0</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.11.0</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>com.ctrip.framework.apollo</groupId> <groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId> <artifactId>apollo-client</artifactId>
<version>${project.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.squareup.okhttp3</groupId> <groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId> <artifactId>mockwebserver</artifactId>
<version>3.11.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.squareup.okhttp3</groupId> <groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId> <artifactId>okhttp</artifactId>
<version>3.11.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId> <artifactId>spring-boot-starter</artifactId>
<version>1.3.8.RELEASE</version> <scope>test</scope>
<scope>provided</scope>
</dependency> </dependency>
</dependencies> </dependencies>
......
package com.ctrip.framework.apollo.mockserver; 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.ApolloConfig;
import com.ctrip.framework.apollo.core.dto.ApolloConfigNotification; 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.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.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import java.io.IOException; import java.lang.reflect.Method;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
...@@ -29,40 +29,57 @@ import org.slf4j.Logger; ...@@ -29,40 +29,57 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
* Create by zhangzheng on 8/22/18 * Create by zhangzheng on 8/22/18 Email:zhangzheng@youzan.com
* Email:zhangzheng@youzan.com
*/ */
public class EmbeddedApollo extends ExternalResource { public class EmbeddedApollo extends ExternalResource {
private Gson gson = new Gson(); private static final Logger logger = LoggerFactory.getLogger(EmbeddedApollo.class);
private Logger logger = LoggerFactory.getLogger(EmbeddedApollo.class); private static final Type notificationType = new TypeToken<List<ApolloConfigNotification>>() {
private Type notificationType = new TypeToken<List<ApolloConfigNotification>>(){}.getType(); }.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<String, Map<String, String>> addedOrModifiedPropertiesOfNamespace = new HashMap<>();
private final Map<String, Set<String>> deletedKeysOfNamespace = new HashMap<>();
private String listenningUrl;
private MockWebServer server; 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 @Override
protected void before() throws Throwable { protected void before() throws Throwable {
clear();
server = new MockWebServer(); server = new MockWebServer();
final Dispatcher dispatcher = new Dispatcher() { final Dispatcher dispatcher = new Dispatcher() {
@Override @Override
public MockResponse dispatch(RecordedRequest request) throws InterruptedException { public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
if (request.getPath().startsWith("/services/config")) { if (request.getPath().startsWith("/notifications/v2")) {
return new MockResponse().setResponseCode(200)
.setBody(mockConfigServiceAddr(listenningUrl));
} else if (request.getPath().startsWith("/notifications/v2")) {
String notifications = request.getRequestUrl().queryParameter("notifications"); String notifications = request.getRequestUrl().queryParameter("notifications");
MockResponse response = new MockResponse().setResponseCode(200).setBody(mockLongPollBody(notifications)); return new MockResponse().setResponseCode(200).setBody(mockLongPollBody(notifications));
return response;
} else if (request.getPath().startsWith("/configs")) { } else if (request.getPath().startsWith("/configs")) {
List<String> pathSegments = request.getRequestUrl().pathSegments(); List<String> pathSegments = request.getRequestUrl().pathSegments();
// appId and cluster might be used in the future
String appId = pathSegments.get(1); String appId = pathSegments.get(1);
String cluster = pathSegments.get(2); String cluster = pathSegments.get(2);
String namespace = pathSegments.get(3); String namespace = pathSegments.get(3);
return new MockResponse().setResponseCode(200) return new MockResponse().setResponseCode(200).setBody(loadConfigFor(namespace));
.setBody(loadConfigFor(namespace));
} }
return new MockResponse().setResponseCode(404); return new MockResponse().setResponseCode(404);
} }
...@@ -70,13 +87,8 @@ public class EmbeddedApollo extends ExternalResource { ...@@ -70,13 +87,8 @@ public class EmbeddedApollo extends ExternalResource {
server.setDispatcher(dispatcher); server.setDispatcher(dispatcher);
server.start(); server.start();
//指定apollo的metaserver地址为localhost
int port = server.getPort();
this.listenningUrl = "http://localhost:"+port;
MockedMetaServerProvider.setAddress(listenningUrl); mockConfigServiceUrl("http://localhost:" + server.getPort());
System.setProperty("apollo.longPollingInitialDelayInMills","1");
super.before(); super.before();
} }
...@@ -84,78 +96,90 @@ public class EmbeddedApollo extends ExternalResource { ...@@ -84,78 +96,90 @@ public class EmbeddedApollo extends ExternalResource {
@Override @Override
protected void after() { protected void after() {
try { try {
clear();
server.close(); server.close();
} catch (IOException e) { } catch (Exception e) {
logger.error("stop apollo server error", 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); String filename = String.format("mockdata-%s.properties", namespace);
final Properties prop = ResourceUtils.readConfigFile(filename, new Properties()); final Properties prop = ResourceUtils.readConfigFile(filename, new Properties());
Map<String,String> configurations = prop.stringPropertyNames().stream().collect( Map<String, String> configurations = prop.stringPropertyNames().stream().collect(
Collectors.toMap(key -> key, key -> prop.getProperty(key))); Collectors.toMap(key -> key, prop::getProperty));
ApolloConfig apolloConfig = new ApolloConfig("someAppId", "someCluster",namespace,"someReleaseKey"); ApolloConfig apolloConfig = new ApolloConfig("someAppId", "someCluster", namespace, "someReleaseKey");
Map<String,String> mergedConfigurations = mergeModifyByUser(namespace, configurations); Map<String, String> mergedConfigurations = mergeOverriddenProperties(namespace, configurations);
apolloConfig.setConfigurations(mergedConfigurations); apolloConfig.setConfigurations(mergedConfigurations);
return gson.toJson(apolloConfig); return gson.toJson(apolloConfig);
} }
private String mockLongPollBody(String notificationsStr) {
private String mockLongPollBody(String notificationsStr){
List<ApolloConfigNotification> oldNotifications = gson.fromJson(notificationsStr, notificationType); List<ApolloConfigNotification> oldNotifications = gson.fromJson(notificationsStr, notificationType);
List<ApolloConfigNotification> newNotifications = new ArrayList<>(); List<ApolloConfigNotification> newNotifications = new ArrayList<>();
for(ApolloConfigNotification noti: oldNotifications){ for (ApolloConfigNotification notification : oldNotifications) {
newNotifications.add(new ApolloConfigNotification(noti.getNamespaceName(), noti.getNotificationId()+1)); newNotifications
.add(new ApolloConfigNotification(notification.getNamespaceName(), notification.getNotificationId() + 1));
} }
return gson.toJson(newNotifications); 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的修改 * 合并用户对namespace的修改
* @param configurations
* @return
*/ */
private Map<String,String> mergeModifyByUser(String namespace private Map<String, String> mergeOverriddenProperties(String namespace, Map<String, String> configurations) {
, Map<String,String> configurations){ if (addedOrModifiedPropertiesOfNamespace.containsKey(namespace)) {
if(addedPropertyOfNamespace.containsKey(namespace)){ configurations.putAll(addedOrModifiedPropertiesOfNamespace.get(namespace));
configurations.putAll(addedPropertyOfNamespace.get(namespace));
} }
if(deletedKeysOfNamespace.containsKey(namespace)){ if (deletedKeysOfNamespace.containsKey(namespace)) {
for(String k: deletedKeysOfNamespace.get(namespace)){ for (String k : deletedKeysOfNamespace.get(namespace)) {
configurations.remove(k); configurations.remove(k);
} }
} }
return configurations; return configurations;
} }
/**
private Map<String,Map<String,String>> addedPropertyOfNamespace = new HashMap<>(); * Add new property or update existed property
public void addOrModifyPropery(String namespace, String someKey, String someValue) { */
if(addedPropertyOfNamespace.containsKey(namespace)){ public void addOrModifyProperty(String namespace, String someKey, String someValue) {
addedPropertyOfNamespace.get(namespace).put(someKey, someValue); if (addedOrModifiedPropertiesOfNamespace.containsKey(namespace)) {
}else{ addedOrModifiedPropertiesOfNamespace.get(namespace).put(someKey, someValue);
addedPropertyOfNamespace.put(namespace, ImmutableMap.of(someKey, someValue)); } else {
addedOrModifiedPropertiesOfNamespace.put(namespace, ImmutableMap.of(someKey, someValue));
} }
} }
private Map<String,Set<String>> 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); deletedKeysOfNamespace.get(namespace).add(someKey);
}else{ } else {
deletedKeysOfNamespace.put(namespace, ImmutableSet.of(someKey)); deletedKeysOfNamespace.put(namespace, ImmutableSet.of(someKey));
} }
} }
/**
* reset overridden properties
*/
public void resetOverriddenProperties() {
addedOrModifiedPropertiesOfNamespace.clear();
deletedKeysOfNamespace.clear();
}
} }
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;
}
}
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<ConfigChangeEvent> 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"));
}
}
package com.ctrip.framework.apollo.mockserver; package com.ctrip.framework.apollo.mockserver;
import static org.junit.Assert.assertEquals;
import com.ctrip.framework.apollo.enums.PropertyChangeType; 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.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener; import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig; import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import static org.junit.Assert.*;
import com.google.common.util.concurrent.SettableFuture; import com.google.common.util.concurrent.SettableFuture;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
...@@ -23,21 +23,23 @@ import org.springframework.test.annotation.DirtiesContext; ...@@ -23,21 +23,23 @@ import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/** /**
* Create by zhangzheng on 8/16/18 * Create by zhangzheng on 8/16/18 Email:zhangzheng@youzan.com
* Email:zhangzheng@youzan.com
*/ */
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestConfiguration.class) @SpringApplicationConfiguration(classes = TestConfiguration.class)
public class SpringIntegrationTest { public class ApolloMockServerSpringIntegrationTest {
@Autowired
TestBean testBean; private static final String otherNamespace = "otherNamespace";
@ClassRule @ClassRule
public static EmbeddedApollo embeddedApollo = new EmbeddedApollo(); public static EmbeddedApollo embeddedApollo = new EmbeddedApollo();
@Autowired
private TestBean testBean;
@Test @Test
@DirtiesContext @DirtiesContext
public void testPropertyInject(){ public void testPropertyInject() {
assertEquals("value1", testBean.key1); assertEquals("value1", testBean.key1);
assertEquals("value2", testBean.key2); assertEquals("value2", testBean.key2);
} }
...@@ -45,8 +47,7 @@ public class SpringIntegrationTest { ...@@ -45,8 +47,7 @@ public class SpringIntegrationTest {
@Test @Test
@DirtiesContext @DirtiesContext
public void testListenerTriggeredByAdd() throws InterruptedException, ExecutionException, TimeoutException { public void testListenerTriggeredByAdd() throws InterruptedException, ExecutionException, TimeoutException {
String otherNamespace = "othernamespace"; embeddedApollo.addOrModifyProperty(otherNamespace, "someKey", "someValue");
embeddedApollo.addOrModifyPropery(otherNamespace,"someKey","someValue");
ConfigChangeEvent changeEvent = testBean.futureData.get(5000, TimeUnit.MILLISECONDS); ConfigChangeEvent changeEvent = testBean.futureData.get(5000, TimeUnit.MILLISECONDS);
assertEquals(otherNamespace, changeEvent.getNamespace()); assertEquals(otherNamespace, changeEvent.getNamespace());
assertEquals("someValue", changeEvent.getChange("someKey").getNewValue()); assertEquals("someValue", changeEvent.getChange("someKey").getNewValue());
...@@ -56,42 +57,34 @@ public class SpringIntegrationTest { ...@@ -56,42 +57,34 @@ public class SpringIntegrationTest {
@DirtiesContext @DirtiesContext
public void testListenerTriggeredByDel() public void testListenerTriggeredByDel()
throws InterruptedException, ExecutionException, TimeoutException { throws InterruptedException, ExecutionException, TimeoutException {
String otherNamespace = "othernamespace"; embeddedApollo.deleteProperty(otherNamespace, "key1");
embeddedApollo.delete(otherNamespace, "key1");
ConfigChangeEvent changeEvent = testBean.futureData.get(5000, TimeUnit.MILLISECONDS); ConfigChangeEvent changeEvent = testBean.futureData.get(5000, TimeUnit.MILLISECONDS);
assertEquals(otherNamespace, changeEvent.getNamespace()); assertEquals(otherNamespace, changeEvent.getNamespace());
assertEquals(PropertyChangeType.DELETED, changeEvent.getChange("key1").getChangeType()); assertEquals(PropertyChangeType.DELETED, changeEvent.getChange("key1").getChangeType());
} }
@EnableApolloConfig
@EnableApolloConfig("application")
@Configuration @Configuration
static class TestConfiguration{ static class TestConfiguration {
@Bean @Bean
public TestBean testBean(){ public TestBean testBean() {
return new TestBean(); return new TestBean();
} }
} }
static class TestBean{ private static class TestBean {
@Value("${key1:default}") @Value("${key1:default}")
String key1; private String key1;
@Value("${key2:default}") @Value("${key2:default}")
String key2; private String key2;
SettableFuture<ConfigChangeEvent> futureData = SettableFuture.create(); private SettableFuture<ConfigChangeEvent> futureData = SettableFuture.create();
@ApolloConfigChangeListener("othernamespace") @ApolloConfigChangeListener(otherNamespace)
private void onChange(ConfigChangeEvent changeEvent) { private void onChange(ConfigChangeEvent changeEvent) {
futureData.set(changeEvent); futureData.set(changeEvent);
} }
} }
} }
...@@ -113,6 +113,11 @@ ...@@ -113,6 +113,11 @@
<artifactId>apollo-core</artifactId> <artifactId>apollo-core</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>com.ctrip.framework.apollo</groupId> <groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-common</artifactId> <artifactId>apollo-common</artifactId>
......
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