Adds ConfigData support for s.c.config.discover.enabled=true

adds ZookeeperConfigServerBootstrapper to optionally instantiate ZookeeperDiscoveryClient and register a instance provider for config client
This commit is contained in:
spencergibb
2020-09-22 11:25:14 -04:00
parent 4cf27efdbe
commit 8dbb178f4b
7 changed files with 344 additions and 5 deletions

View File

@@ -85,9 +85,7 @@ public class ZookeeperConfigDataLocationResolver implements ConfigDataLocationRe
String location, boolean optional, Profiles profiles) throws ConfigDataLocationNotFoundException {
UriComponents locationUri = parseLocation(location);
ZookeeperConfigProperties properties = loadConfigProperties(context.getBinder());
context.getBootstrapContext().register(ZookeeperConfigProperties.class, InstanceSupplier.of(properties));
// create curator
ZookeeperProperties zookeeperProperties = loadProperties(context.getBinder(), locationUri);
context.getBootstrapContext().register(ZookeeperProperties.class, InstanceSupplier.of(zookeeperProperties));
@@ -97,11 +95,16 @@ public class ZookeeperConfigDataLocationResolver implements ConfigDataLocationRe
context.getBootstrapContext().registerIfAbsent(CuratorFramework.class, InstanceSupplier
.from(() -> curatorFramework(context.getBootstrapContext(), zookeeperProperties, optional)));
// create locations
ZookeeperConfigProperties properties = loadConfigProperties(context.getBinder());
context.getBootstrapContext().register(ZookeeperConfigProperties.class, InstanceSupplier.of(properties));
ZookeeperPropertySources sources = new ZookeeperPropertySources(properties, log);
List<String> contexts = (locationUri == null || CollectionUtils.isEmpty(locationUri.getPathSegments()))
? sources.getAutomaticContexts(profiles.getAccepted()) : getCustomContexts(locationUri);
// promote beans to context
context.getBootstrapContext().addCloseListener(event -> {
CuratorFramework curatorFramework = event.getBootstrapContext().get(CuratorFramework.class);
event.getApplicationContext().getBeanFactory().registerSingleton("configDataCuratorFramework",

View File

@@ -30,9 +30,14 @@ import org.springframework.util.StringUtils;
* @author Spencer Gibb
* @since 1.0.0
*/
@ConfigurationProperties("spring.cloud.zookeeper.discovery")
@ConfigurationProperties(ZookeeperDiscoveryProperties.PREFIX)
public class ZookeeperDiscoveryProperties {
/**
* Zookeeper Discovery Properties prefix.
*/
public static final String PREFIX = "spring.cloud.zookeeper.discovery";
/**
* Default URI spec.
*/

View File

@@ -0,0 +1,160 @@
/*
* Copyright 2015-2020 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.util.function.Supplier;
import java.util.stream.Stream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.curator.RetryPolicy;
import org.apache.curator.drivers.TracerDriver;
import org.apache.curator.ensemble.EnsembleProvider;
import org.apache.curator.framework.CuratorFramework;
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.Bootstrapper;
import org.springframework.boot.context.properties.bind.Binder;
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.CuratorFrameworkCustomizer;
import org.springframework.cloud.zookeeper.ZookeeperProperties;
import org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryClient;
import org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryProperties;
import org.springframework.cloud.zookeeper.discovery.ZookeeperInstance;
import org.springframework.cloud.zookeeper.discovery.dependency.ZookeeperDependencies;
import org.springframework.cloud.zookeeper.support.DefaultServiceDiscoveryCustomizer;
import org.springframework.cloud.zookeeper.support.ServiceDiscoveryCustomizer;
import org.springframework.util.ClassUtils;
public class ZookeeperConfigServerBootstrapper implements Bootstrapper {
private final Log log = LogFactory.getLog(getClass());
@Override
@SuppressWarnings("unchecked")
public void intitialize(BootstrapRegistry registry) {
if (!ClassUtils.isPresent("org.springframework.cloud.config.client.ConfigServerInstanceProvider", null)) {
return;
}
// create curator
registry.registerIfAbsent(ZookeeperProperties.class, context -> context.get(Binder.class)
.bind(ZookeeperProperties.PREFIX, ZookeeperProperties.class).orElseGet(ZookeeperProperties::new));
registry.registerIfAbsent(RetryPolicy.class, context -> {
ZookeeperProperties properties = context.get(ZookeeperProperties.class);
return CuratorFactory.retryPolicy(properties);
});
registry.registerIfAbsent(CuratorFramework.class, context -> {
ZookeeperProperties zookeeperProperties = context.get(ZookeeperProperties.class);
return curatorFramework(context, zookeeperProperties);
});
// create discovery
registry.registerIfAbsent(ZookeeperDiscoveryProperties.class,
context -> context.get(Binder.class)
.bind(ZookeeperDiscoveryProperties.PREFIX, ZookeeperDiscoveryProperties.class)
.orElseGet(() -> new ZookeeperDiscoveryProperties(new InetUtils(new InetUtilsProperties()))));
registry.registerIfAbsent(InstanceSerializer.class,
context -> new JsonInstanceSerializer<>(ZookeeperInstance.class));
registry.registerIfAbsent(ServiceDiscoveryCustomizer.class, context -> {
CuratorFramework curator = context.get(CuratorFramework.class);
ZookeeperDiscoveryProperties properties = context.get(ZookeeperDiscoveryProperties.class);
InstanceSerializer<ZookeeperInstance> serializer = context.get(InstanceSerializer.class);
return new DefaultServiceDiscoveryCustomizer(curator, properties, serializer);
});
registry.registerIfAbsent(ServiceDiscovery.class, context -> {
ServiceDiscoveryCustomizer customizer = context.get(ServiceDiscoveryCustomizer.class);
return customizer.customize(ServiceDiscoveryBuilder.builder(ZookeeperInstance.class));
});
registry.registerIfAbsent(ZookeeperDiscoveryClient.class, context -> {
Binder binder = context.get(Binder.class);
if (!isEnabled(binder)) {
return null;
}
ServiceDiscovery<ZookeeperInstance> serviceDiscovery = context.get(ServiceDiscovery.class);
ZookeeperDependencies dependencies = binder.bind(ZookeeperDependencies.PREFIX, ZookeeperDependencies.class)
.orElseGet(ZookeeperDependencies::new);
ZookeeperDiscoveryProperties discoveryProperties = context.get(ZookeeperDiscoveryProperties.class);
return new ZookeeperDiscoveryClient(serviceDiscovery, dependencies, discoveryProperties);
});
// create instance provider
registry.registerIfAbsent(ConfigServerInstanceProvider.Function.class, context -> {
if (!isEnabled(context.get(Binder.class))) {
return null;
}
return context.get(ZookeeperDiscoveryClient.class)::getInstances;
});
// promote beans to context
registry.addCloseListener(event -> {
ZookeeperDiscoveryClient discoveryClient = event.getBootstrapContext().get(ZookeeperDiscoveryClient.class);
if (discoveryClient != null) {
event.getApplicationContext().getBeanFactory().registerSingleton("zookeeperServiceDiscovery",
discoveryClient);
}
});
}
private boolean isEnabled(Binder binder) {
return binder.bind(ConfigClientProperties.CONFIG_DISCOVERY_ENABLED, Boolean.class).orElse(false);
}
protected CuratorFramework curatorFramework(BootstrapContext context, ZookeeperProperties properties) {
Supplier<Stream<CuratorFrameworkCustomizer>> customizers;
// TODO: use new apis after milestone release
try {
CuratorFrameworkCustomizer customizer = context.get(CuratorFrameworkCustomizer.class);
customizers = () -> Stream.of(customizer);
}
catch (IllegalStateException e) {
customizers = () -> null;
}
try {
return CuratorFactory.curatorFramework(properties, context.get(RetryPolicy.class), customizers,
supplier(context, EnsembleProvider.class), supplier(context, TracerDriver.class));
}
catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug("Unable to connect to zookeeper", e);
}
}
return null;
}
private <T> Supplier<T> supplier(BootstrapContext context, Class<T> type) {
try {
// TODO: use new apis after milestone release
T instance = context.get(type);
return () -> instance;
}
catch (IllegalStateException e) {
return () -> null;
}
}
}

View File

@@ -37,9 +37,14 @@ import static org.springframework.cloud.zookeeper.discovery.DependencyPathUtils.
* @author Olga Maciaszek-Sharma
* @since 1.0.0
*/
@ConfigurationProperties("spring.cloud.zookeeper")
@ConfigurationProperties(ZookeeperDependencies.PREFIX)
public class ZookeeperDependencies {
/**
* Zookeeper Dependencies prefix.
*/
public static final String PREFIX = "spring.cloud.zookeeper";
/**
* Common prefix that will be applied to all Zookeeper dependencies' paths.
*/

View File

@@ -18,3 +18,6 @@ org.springframework.cloud.zookeeper.discovery.dependency.DependencyEnvironmentPo
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.zookeeper.discovery.configclient.ZookeeperDiscoveryClientConfigServiceBootstrapConfiguration
org.springframework.boot.Bootstrapper=\
org.springframework.cloud.zookeeper.discovery.configclient.ZookeeperConfigServerBootstrapper

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2015-2020 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 org.apache.catalina.webresources.TomcatURLStreamHandlerFactory;
import org.apache.curator.test.TestingServer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.test.ClassPathExclusions;
import org.springframework.cloud.test.ModifiedClassPathRunner;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.util.SocketUtils;
@RunWith(ModifiedClassPathRunner.class)
@ClassPathExclusions({ "spring-cloud-config-client-*.jar", "spring-cloud-config-server-*.jar" })
public class ZookeeperConfigServerBootstrapperNoConfigClientTests {
@Test
public void contextLoads() throws Exception {
TestingServer testingServer = null;
ConfigurableApplicationContext context = null;
try {
TomcatURLStreamHandlerFactory.disable();
int port = SocketUtils.findAvailableTcpPort();
testingServer = new TestingServer(port);
context = new SpringApplicationBuilder(TestConfig.class).properties("--server.port=0",
"spring.cloud.config.discovery.enabled=true", "spring.cloud.zookeeper.connect-string=localhost:" + port,
"spring.cloud.service-registry.auto-registration.enabled=false")
.run();
}
finally {
if (context != null) {
context.close();
}
if (testingServer != null) {
testingServer.close();
}
}
}
@SpringBootConfiguration
@EnableAutoConfiguration
static class TestConfig {
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2015-2020 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.util.concurrent.atomic.AtomicReference;
import org.apache.curator.test.TestingServer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.config.client.ConfigServerInstanceProvider;
import org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryClient;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.util.SocketUtils;
import static org.assertj.core.api.Assertions.assertThat;
public class ZookeeperConfigServerBootstrapperTests {
private TestingServer testingServer;
private ConfigurableApplicationContext context;
private int port;
@BeforeEach
public void init() throws Exception {
port = SocketUtils.findAvailableTcpPort();
testingServer = new TestingServer(port);
}
@AfterEach
public void after() throws Exception {
if (context != null) {
context.close();
}
if (testingServer != null) {
testingServer.close();
}
}
@Test
public void notEnabledDoesNotAddInstanceProviderFn() {
new SpringApplicationBuilder(TestConfig.class)
.properties("--server.port=0", "spring.cloud.service-registry.auto-registration.enabled=false",
"spring.cloud.zookeeper.connect-string=localhost:" + port)
.addBootstrapper(registry -> registry.addCloseListener(event -> {
ConfigServerInstanceProvider.Function providerFn = event.getBootstrapContext()
.get(ConfigServerInstanceProvider.Function.class);
assertThat(providerFn).as("ConfigServerInstanceProvider.Function was created when it shouldn't")
.isNull();
})).run().close();
}
@Test
public void enabledAddsInstanceProviderFn() {
AtomicReference<ZookeeperDiscoveryClient> bootstrapDiscoveryClient = new AtomicReference<>();
context = new SpringApplicationBuilder(TestConfig.class)
.properties("--server.port=0", "spring.cloud.config.discovery.enabled=true",
"spring.cloud.zookeeper.connect-string=localhost:" + port,
"spring.cloud.service-registry.auto-registration.enabled=false")
.addBootstrapper(registry -> registry.addCloseListener(event -> {
ConfigServerInstanceProvider.Function providerFn = event.getBootstrapContext()
.get(ConfigServerInstanceProvider.Function.class);
assertThat(providerFn).as("ConfigServerInstanceProvider.Function was not created when it should.")
.isNotNull();
bootstrapDiscoveryClient.set(event.getBootstrapContext().get(ZookeeperDiscoveryClient.class));
})).run();
ZookeeperDiscoveryClient discoveryClient = context.getBean(ZookeeperDiscoveryClient.class);
assertThat(discoveryClient == bootstrapDiscoveryClient.get()).isTrue();
}
@SpringBootConfiguration
@EnableAutoConfiguration
static class TestConfig {
}
}