Merge branch '3.1.x'
This commit is contained in:
22
pom.xml
22
pom.xml
@@ -126,6 +126,26 @@
|
||||
<artifactId>spring-cloud-test-support</artifactId>
|
||||
<version>${spring-cloud-commons.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers</artifactId>
|
||||
<version>${testcontainers.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>mockserver</artifactId>
|
||||
<version>${testcontainers.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>${testcontainers.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mock-server</groupId>
|
||||
<artifactId>mockserver-client-java</artifactId>
|
||||
<version>${mockserverclient.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
@@ -134,6 +154,8 @@
|
||||
<spring-cloud-commons.version>4.0.4-SNAPSHOT</spring-cloud-commons.version>
|
||||
<spring-cloud-config.version>4.0.4-SNAPSHOT</spring-cloud-config.version>
|
||||
<spring-cloud-openfeign.version>4.0.4-SNAPSHOT</spring-cloud-openfeign.version>
|
||||
<testcontainers.version>1.17.6</testcontainers.version>
|
||||
<mockserverclient.version>5.15.0</mockserverclient.version>
|
||||
</properties>
|
||||
|
||||
<profiles>
|
||||
|
||||
@@ -156,6 +156,26 @@
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>mockserver</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mock-server</groupId>
|
||||
<artifactId>mockserver-client-java</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -17,23 +17,31 @@
|
||||
package org.springframework.cloud.zookeeper.discovery.configclient;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.curator.RetryPolicy;
|
||||
import org.apache.curator.framework.CuratorFramework;
|
||||
import org.apache.curator.retry.ExponentialBackoffRetry;
|
||||
import org.apache.curator.x.discovery.ServiceDiscovery;
|
||||
import org.apache.curator.x.discovery.ServiceDiscoveryBuilder;
|
||||
import org.apache.curator.x.discovery.details.InstanceSerializer;
|
||||
import org.apache.curator.x.discovery.details.JsonInstanceSerializer;
|
||||
|
||||
import org.springframework.boot.BootstrapContext;
|
||||
import org.springframework.boot.BootstrapRegistry;
|
||||
import org.springframework.boot.BootstrapRegistryInitializer;
|
||||
import org.springframework.boot.context.properties.bind.BindHandler;
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.commons.util.InetUtils;
|
||||
import org.springframework.cloud.commons.util.InetUtilsProperties;
|
||||
import org.springframework.cloud.config.client.ConfigClientProperties;
|
||||
import org.springframework.cloud.config.client.ConfigServerInstanceProvider;
|
||||
import org.springframework.cloud.zookeeper.CuratorFactory;
|
||||
import org.springframework.cloud.zookeeper.ZookeeperProperties;
|
||||
import org.springframework.cloud.zookeeper.discovery.ConditionalOnZookeeperDiscoveryEnabled;
|
||||
import org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryClient;
|
||||
import org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryProperties;
|
||||
@@ -45,6 +53,12 @@ import org.springframework.util.ClassUtils;
|
||||
|
||||
public class ZookeeperConfigServerBootstrapper implements BootstrapRegistryInitializer {
|
||||
|
||||
private static boolean isEnabled(Binder binder) {
|
||||
return binder.bind(ConfigClientProperties.CONFIG_DISCOVERY_ENABLED, Boolean.class).orElse(false) &&
|
||||
binder.bind(ConditionalOnZookeeperDiscoveryEnabled.PROPERTY, Boolean.class).orElse(true) &&
|
||||
binder.bind("spring.cloud.discovery.enabled", Boolean.class).orElse(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void initialize(BootstrapRegistry registry) {
|
||||
@@ -95,7 +109,7 @@ public class ZookeeperConfigServerBootstrapper implements BootstrapRegistryIniti
|
||||
}
|
||||
ServiceDiscovery<ZookeeperInstance> serviceDiscovery = context.get(ServiceDiscovery.class);
|
||||
ZookeeperDependencies dependencies = binder.bind(ZookeeperDependencies.PREFIX, Bindable
|
||||
.of(ZookeeperDependencies.class), getBindHandler(context))
|
||||
.of(ZookeeperDependencies.class), getBindHandler(context))
|
||||
.orElseGet(ZookeeperDependencies::new);
|
||||
ZookeeperDiscoveryProperties discoveryProperties = context.get(ZookeeperDiscoveryProperties.class);
|
||||
|
||||
@@ -103,12 +117,9 @@ public class ZookeeperConfigServerBootstrapper implements BootstrapRegistryIniti
|
||||
});
|
||||
|
||||
// create instance provider
|
||||
registry.registerIfAbsent(ConfigServerInstanceProvider.Function.class, context -> {
|
||||
if (!isEnabled(context.get(Binder.class))) {
|
||||
return (id) -> Collections.emptyList();
|
||||
}
|
||||
return context.get(ZookeeperDiscoveryClient.class)::getInstances;
|
||||
});
|
||||
// We need to pass the lambda here so we do not create a new instance of ConfigServerInstanceProvider.Function
|
||||
// which would result in a ClassNotFoundException when Spring Cloud Config is not on the classpath
|
||||
registry.registerIfAbsent(ConfigServerInstanceProvider.Function.class, ZookeeperFunction::create);
|
||||
|
||||
// promote beans to context
|
||||
registry.addCloseListener(event -> {
|
||||
@@ -124,10 +135,63 @@ public class ZookeeperConfigServerBootstrapper implements BootstrapRegistryIniti
|
||||
return context.getOrElse(BindHandler.class, null);
|
||||
}
|
||||
|
||||
private boolean isEnabled(Binder binder) {
|
||||
return binder.bind(ConfigClientProperties.CONFIG_DISCOVERY_ENABLED, Boolean.class).orElse(false) &&
|
||||
binder.bind(ConditionalOnZookeeperDiscoveryEnabled.PROPERTY, Boolean.class).orElse(true) &&
|
||||
binder.bind("spring.cloud.discovery.enabled", Boolean.class).orElse(true);
|
||||
/*
|
||||
* This Function is executed when loading config data. Because of this we cannot rely on the
|
||||
* BootstrapContext because Boot has not finished loading all the configuration data so if we
|
||||
* ask the BootstrapContext for configuration data it will not have it. The apply method in this function
|
||||
* is passed the Binder and BindHandler from the config data context which has the configuration properties that
|
||||
* have been loaded so far in the config data process.
|
||||
*
|
||||
* We will create many of the same beans in this function as we do above in the initializer above. We do both
|
||||
* to maintain compatibility since we are promoting those beans to the main application context.
|
||||
*/
|
||||
static final class ZookeeperFunction implements ConfigServerInstanceProvider.Function {
|
||||
|
||||
private final BootstrapContext context;
|
||||
|
||||
private ZookeeperFunction(BootstrapContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
static ZookeeperFunction create(BootstrapContext context) {
|
||||
return new ZookeeperFunction(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ServiceInstance> apply(String serviceId) {
|
||||
return apply(serviceId, null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ServiceInstance> apply(String serviceId, Binder binder, BindHandler bindHandler, Log log) {
|
||||
if (binder == null || !isEnabled(binder)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
ZookeeperProperties properties = binder.bind(ZookeeperProperties.PREFIX, Bindable.of(ZookeeperProperties.class))
|
||||
.orElse(new ZookeeperProperties());
|
||||
RetryPolicy retryPolicy = new ExponentialBackoffRetry(properties.getBaseSleepTimeMs(), properties.getMaxRetries(),
|
||||
properties.getMaxSleepMs());
|
||||
try {
|
||||
CuratorFramework curatorFramework = CuratorFactory.curatorFramework(properties, retryPolicy, Stream::of,
|
||||
() -> null, () -> null);
|
||||
InstanceSerializer<ZookeeperInstance> serializer = new JsonInstanceSerializer<>(ZookeeperInstance.class);
|
||||
ZookeeperDiscoveryProperties discoveryProperties = binder.bind(ZookeeperDiscoveryProperties.PREFIX, Bindable
|
||||
.of(ZookeeperDiscoveryProperties.class), bindHandler)
|
||||
.orElseGet(() -> new ZookeeperDiscoveryProperties(new InetUtils(new InetUtilsProperties())));
|
||||
DefaultServiceDiscoveryCustomizer customizer = new DefaultServiceDiscoveryCustomizer(curatorFramework, discoveryProperties, serializer);
|
||||
ServiceDiscovery<ZookeeperInstance> serviceDiscovery = customizer.customize(ServiceDiscoveryBuilder.builder(ZookeeperInstance.class));
|
||||
ZookeeperDependencies dependencies = binder.bind(ZookeeperDependencies.PREFIX, Bindable
|
||||
.of(ZookeeperDependencies.class), bindHandler)
|
||||
.orElseGet(ZookeeperDependencies::new);
|
||||
return new ZookeeperDiscoveryClient(serviceDiscovery, dependencies, discoveryProperties).getInstances(serviceId);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("Error fetching config server instance from Zookeeper", e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.cloud.zookeeper.discovery.configclient;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.curator.framework.CuratorFramework;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -31,6 +32,7 @@ import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.context.properties.bind.BindContext;
|
||||
import org.springframework.boot.context.properties.bind.BindHandler;
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
|
||||
import org.springframework.cloud.config.client.ConfigServerInstanceProvider;
|
||||
import org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryClient;
|
||||
@@ -39,6 +41,7 @@ import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class ZookeeperConfigServerBootstrapperTests {
|
||||
|
||||
@@ -59,7 +62,8 @@ public class ZookeeperConfigServerBootstrapperTests {
|
||||
.addBootstrapRegistryInitializer(registry -> registry.addCloseListener(event -> {
|
||||
ConfigServerInstanceProvider.Function providerFn = event.getBootstrapContext()
|
||||
.get(ConfigServerInstanceProvider.Function.class);
|
||||
assertThat(providerFn.apply("id")).as("ConfigServerInstanceProvider.Function should return empty list")
|
||||
Log log = mock(Log.class);
|
||||
assertThat(providerFn.apply("id", event.getBootstrapContext().get(Binder.class), event.getBootstrapContext().get(BindHandler.class), log)).as("ConfigServerInstanceProvider.Function should return empty list")
|
||||
.isEmpty();
|
||||
})).run();
|
||||
CuratorFramework curatorFramework = context.getBean("curatorFramework", CuratorFramework.class);
|
||||
@@ -80,7 +84,8 @@ public class ZookeeperConfigServerBootstrapperTests {
|
||||
.addBootstrapRegistryInitializer(registry -> registry.addCloseListener(event -> {
|
||||
ConfigServerInstanceProvider.Function providerFn = event.getBootstrapContext()
|
||||
.get(ConfigServerInstanceProvider.Function.class);
|
||||
assertThat(providerFn.apply("id")).as("ConfigServerInstanceProvider.Function should return empty list")
|
||||
Log log = mock(Log.class);
|
||||
assertThat(providerFn.apply("id", event.getBootstrapContext().get(Binder.class), event.getBootstrapContext().get(BindHandler.class), log)).as("ConfigServerInstanceProvider.Function should return empty list")
|
||||
.isEmpty();
|
||||
})).run();
|
||||
CuratorFramework curatorFramework = context.getBean("curatorFramework", CuratorFramework.class);
|
||||
@@ -101,7 +106,7 @@ public class ZookeeperConfigServerBootstrapperTests {
|
||||
.addBootstrapRegistryInitializer(registry -> registry.addCloseListener(event -> {
|
||||
ConfigServerInstanceProvider.Function providerFn = event.getBootstrapContext()
|
||||
.get(ConfigServerInstanceProvider.Function.class);
|
||||
assertThat(providerFn.apply("id")).as("ConfigServerInstanceProvider.Function should return empty list")
|
||||
assertThat(providerFn.apply("id", event.getBootstrapContext().get(Binder.class), event.getBootstrapContext().get(BindHandler.class), mock(Log.class))).as("ConfigServerInstanceProvider.Function should return empty list")
|
||||
.isEmpty();
|
||||
})).run();
|
||||
CuratorFramework curatorFramework = context.getBean("curatorFramework", CuratorFramework.class);
|
||||
@@ -124,7 +129,7 @@ public class ZookeeperConfigServerBootstrapperTests {
|
||||
.addBootstrapRegistryInitializer(registry -> registry.addCloseListener(event -> {
|
||||
ConfigServerInstanceProvider.Function providerFn = event.getBootstrapContext()
|
||||
.get(ConfigServerInstanceProvider.Function.class);
|
||||
assertThat(providerFn.apply("id")).as("Should return empty list.")
|
||||
assertThat(providerFn.apply("id", event.getBootstrapContext().get(Binder.class), event.getBootstrapContext().get(BindHandler.class), mock(Log.class))).as("Should return empty list.")
|
||||
.isNotNull();
|
||||
bootstrapDiscoveryClient.set(event.getBootstrapContext().get(ZookeeperDiscoveryClient.class));
|
||||
})).run();
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright 2015-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.zookeeper.discovery.configclient;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.curator.framework.CuratorFramework;
|
||||
import org.apache.curator.framework.CuratorFrameworkFactory;
|
||||
import org.apache.curator.retry.RetryOneTime;
|
||||
import org.apache.curator.x.discovery.ServiceDiscovery;
|
||||
import org.apache.curator.x.discovery.ServiceDiscoveryBuilder;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockserver.client.MockServerClient;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.MockServerContainer;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
import org.testcontainers.utility.DockerImageName;
|
||||
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.config.environment.Environment;
|
||||
import org.springframework.cloud.config.environment.PropertySource;
|
||||
import org.springframework.cloud.zookeeper.ZookeeperProperties;
|
||||
import org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryProperties;
|
||||
import org.springframework.cloud.zookeeper.discovery.ZookeeperInstance;
|
||||
import org.springframework.cloud.zookeeper.serviceregistry.ServiceInstanceRegistration;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockserver.model.HttpRequest.request;
|
||||
import static org.mockserver.model.HttpResponse.response;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@Testcontainers
|
||||
class ZookeeperConfigServerBootstrapperTestsIT {
|
||||
private static final int ZOOKEEPER_PORT = 2181;
|
||||
|
||||
public static final DockerImageName MOCKSERVER_IMAGE = DockerImageName.parse("mockserver/mockserver")
|
||||
.withTag("mockserver-" + MockServerClient.class.getPackage().getImplementationVersion());
|
||||
|
||||
@Container
|
||||
private static final GenericContainer<?> zookeeper = new GenericContainer<>("zookeeper:3.8.0")
|
||||
.withExposedPorts(ZOOKEEPER_PORT);
|
||||
|
||||
@Container
|
||||
static MockServerContainer mockServer = new MockServerContainer(MOCKSERVER_IMAGE);
|
||||
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
private ServiceDiscovery<ZookeeperInstance> serviceDiscovery;
|
||||
|
||||
private CuratorFramework curatorFramework;
|
||||
|
||||
@BeforeEach
|
||||
void before() {
|
||||
curatorFramework = CuratorFrameworkFactory.builder().connectString(zookeeper.getHost() + ":" + zookeeper.getMappedPort(ZOOKEEPER_PORT))
|
||||
.retryPolicy(new RetryOneTime(100)).build();
|
||||
|
||||
try {
|
||||
curatorFramework.start();
|
||||
serviceDiscovery = ServiceDiscoveryBuilder.builder(ZookeeperInstance.class).client(curatorFramework).basePath("/services")
|
||||
.build();
|
||||
serviceDiscovery.start();
|
||||
serviceDiscovery.registerService(ServiceInstanceRegistration.builder().id("zookeeper-configserver").name("zookeeper-configserver")
|
||||
.address(mockServer.getHost()).port(mockServer.getServerPort()).uriSpec(ZookeeperDiscoveryProperties.DEFAULT_URI_SPEC).build().getServiceInstance());
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void after() throws IOException {
|
||||
this.context.close();
|
||||
this.serviceDiscovery.close();
|
||||
this.curatorFramework.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contextLoads() throws JsonProcessingException {
|
||||
Environment environment = new Environment("test", "default");
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put("hello", "world");
|
||||
PropertySource p = new PropertySource("p1", properties);
|
||||
environment.add(p);
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
try (MockServerClient mockServerClient = new MockServerClient(mockServer.getHost(),
|
||||
mockServer.getMappedPort(MockServerContainer.PORT))) {
|
||||
mockServerClient.when(request().withPath("/application/default"))
|
||||
.respond(response().withBody(objectMapper.writeValueAsString(environment))
|
||||
.withHeader("content-type", "application/json"));
|
||||
this.context = setup().run();
|
||||
assertThat(this.context.getEnvironment().getProperty("hello")).isEqualTo("world");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SpringApplicationBuilder setup(String... env) {
|
||||
return new SpringApplicationBuilder(TestConfig.class)
|
||||
.properties(addDefaultEnv(env));
|
||||
}
|
||||
|
||||
private String[] addDefaultEnv(String[] env) {
|
||||
Set<String> set = new LinkedHashSet<>();
|
||||
if (env != null && env.length > 0) {
|
||||
set.addAll(Arrays.asList(env));
|
||||
}
|
||||
set.add("spring.config.import=classpath:bootstrapper.yaml");
|
||||
set.add("spring.cloud.config.enabled=true");
|
||||
set.add("spring.cloud.service-registry.auto-registration.enabled=false");
|
||||
set.add(ZookeeperProperties.PREFIX + ".connectString=" + zookeeper.getHost() + ":" + zookeeper.getMappedPort(ZOOKEEPER_PORT));
|
||||
return set.toArray(new String[0]);
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
static class TestConfig {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
spring:
|
||||
config:
|
||||
import: "optional:configserver:"
|
||||
cloud:
|
||||
config:
|
||||
discovery:
|
||||
service-id: zookeeper-configserver
|
||||
enabled: true
|
||||
Reference in New Issue
Block a user