diff --git a/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperConfigDataLocationResolver.java b/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperConfigDataLocationResolver.java index e2c086c2..b528e370 100644 --- a/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperConfigDataLocationResolver.java +++ b/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperConfigDataLocationResolver.java @@ -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 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", diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryProperties.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryProperties.java index 5526c4af..22cbf614 100644 --- a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryProperties.java +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryProperties.java @@ -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. */ diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/configclient/ZookeeperConfigServerBootstrapper.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/configclient/ZookeeperConfigServerBootstrapper.java new file mode 100644 index 00000000..632b8d31 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/configclient/ZookeeperConfigServerBootstrapper.java @@ -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 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 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> 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 Supplier supplier(BootstrapContext context, Class type) { + try { + // TODO: use new apis after milestone release + T instance = context.get(type); + return () -> instance; + } + catch (IllegalStateException e) { + return () -> null; + } + } + +} diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDependencies.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDependencies.java index 9a49f89d..7eef4710 100644 --- a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDependencies.java +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDependencies.java @@ -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. */ diff --git a/spring-cloud-zookeeper-discovery/src/main/resources/META-INF/spring.factories b/spring-cloud-zookeeper-discovery/src/main/resources/META-INF/spring.factories index 00d5a830..db7b0926 100644 --- a/spring-cloud-zookeeper-discovery/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-zookeeper-discovery/src/main/resources/META-INF/spring.factories @@ -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 \ No newline at end of file diff --git a/spring-cloud-zookeeper-discovery/src/test/java/org/springframework/cloud/zookeeper/discovery/configclient/ZookeeperConfigServerBootstrapperNoConfigClientTests.java b/spring-cloud-zookeeper-discovery/src/test/java/org/springframework/cloud/zookeeper/discovery/configclient/ZookeeperConfigServerBootstrapperNoConfigClientTests.java new file mode 100644 index 00000000..7b2fcb85 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/test/java/org/springframework/cloud/zookeeper/discovery/configclient/ZookeeperConfigServerBootstrapperNoConfigClientTests.java @@ -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 { + + } + +} diff --git a/spring-cloud-zookeeper-discovery/src/test/java/org/springframework/cloud/zookeeper/discovery/configclient/ZookeeperConfigServerBootstrapperTests.java b/spring-cloud-zookeeper-discovery/src/test/java/org/springframework/cloud/zookeeper/discovery/configclient/ZookeeperConfigServerBootstrapperTests.java new file mode 100644 index 00000000..c384a5db --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/test/java/org/springframework/cloud/zookeeper/discovery/configclient/ZookeeperConfigServerBootstrapperTests.java @@ -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 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 { + + } + +}