From bf4b6e32938745cb57ee795c4f7544cd417eb9fa Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Fri, 1 Jul 2016 23:16:00 -0600 Subject: [PATCH] Create a ServiceRegistry interface. Allow service discovery systems to register more than one instance. Automatic registration will still take place. Add boolean autoRegister() to EnableDiscoveryClient, defaults to true. Add ServiceRegistryEndpoint. fixes gh-9 --- .../main/asciidoc/spring-cloud-commons.adoc | 14 ++ spring-cloud-commons-dependencies/pom.xml | 2 +- .../discovery/AbstractDiscoveryLifecycle.java | 21 ++- .../discovery/EnableDiscoveryClient.java | 4 + .../EnableDiscoveryClientImportSelector.java | 24 +++ .../AbstractAutoServiceRegistration.java | 74 +++++++++ .../AutoServiceRegistration.java | 7 + .../AutoServiceRegistrationConfiguration.java | 28 ++++ .../AutoServiceRegistrationProperties.java | 21 +++ .../client/serviceregistry/Registration.java | 7 + .../serviceregistry/ServiceRegistry.java | 19 +++ .../ServiceRegistryAutoConfiguration.java | 28 ++++ .../endpoint/ServiceRegistryEndpoint.java | 95 +++++++++++ .../main/resources/META-INF/spring.factories | 1 + ...bleDiscoveryClientImportSelectorTests.java | 58 +++++++ .../AbstractAutoServiceRegistrationTests.java | 150 ++++++++++++++++++ ...ServiceRegistrationConfigurationTests.java | 81 ++++++++++ ...ceRegistryEndpointNoRegistrationTests.java | 70 ++++++++ .../ServiceRegistryEndpointTests.java | 124 +++++++++++++++ 19 files changed, 823 insertions(+), 5 deletions(-) create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistration.java create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistration.java create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistrationConfiguration.java create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistrationProperties.java create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/Registration.java create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/ServiceRegistry.java create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/ServiceRegistryAutoConfiguration.java create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/endpoint/ServiceRegistryEndpoint.java create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/EnableDiscoveryClientImportSelectorTests.java create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistrationTests.java create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistrationConfigurationTests.java create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/endpoint/ServiceRegistryEndpointNoRegistrationTests.java create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/endpoint/ServiceRegistryEndpointTests.java diff --git a/docs/src/main/asciidoc/spring-cloud-commons.adoc b/docs/src/main/asciidoc/spring-cloud-commons.adoc index 1571e09c..fd45c41d 100644 --- a/docs/src/main/asciidoc/spring-cloud-commons.adoc +++ b/docs/src/main/asciidoc/spring-cloud-commons.adoc @@ -311,6 +311,20 @@ For a Spring Boot Actuator application there are some additional management endp Patterns such as service discovery, load balancing and circuit breakers lend themselves to a common abstraction layer that can be consumed by all Spring Cloud clients, independent of the implementation (e.g. discovery via Eureka or Consul). +=== @EnableDiscoveryClient + +Commons provides the `@EnableDiscoveryClient` annotation. This looks for implementations of the `DiscoveryClient` interface via `META-INF/spring.factories`. Implementations of Discovery Client will add a configuration class to `spring.factories` under the `org.springframework.cloud.client.discovery.EnableDiscoveryClient` key. Examples of `DiscoveryClient` implementations: are http://cloud.spring.io/spring-cloud-netflix/[Spring Cloud Netflix Eureka], http://cloud.spring.io/spring-cloud-consul/[Spring Cloud Consul Discovery] and http://cloud.spring.io/spring-cloud-zookeeper/[Spring Cloud Zookeeper Discovery]. + +By default, implementations of `DiscoveryClient` will auto-register the local Spring Boot server with the remote discovery server. This can be disabled by setting `autoRegister=false` in `@EnableDiscoveryClient`. + +=== ServiceRegistry + +Commons now provides a `ServiceRegistry` interface which provides methods like `register(Registration)` and `deregister(Registration)` which allow you to provide custom registered services. `Registration` is a marker interface. + +==== Service Registry Actuator Endpoint + +A `/service-registry` actuator endpoint is provided by Commons. This endpoint relys on a `Registration` bean in the Spring Application Context. Calling `/service-registry/instance-status` via a GET will return the status of the `Registration`. A POST to the same endpoint with a `String` body will change the status of the current `Registration` to the new value. Please see the documentation of the `ServiceRegistry` implementation you are using for the allowed values for updating the status and the values retured for the status. + === Spring RestTemplate as a Load Balancer Client `RestTemplate` can be automatically configured to use ribbon. To create a load balanced `RestTemplate` create a `RestTemplate` `@Bean` and use the `@LoadBalanced` qualifier. diff --git a/spring-cloud-commons-dependencies/pom.xml b/spring-cloud-commons-dependencies/pom.xml index 01c18553..4f8a142f 100644 --- a/spring-cloud-commons-dependencies/pom.xml +++ b/spring-cloud-commons-dependencies/pom.xml @@ -5,7 +5,7 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.1.2.RELEASE + 1.2.2.BUILD-SNAPSHOT spring-cloud-commons-dependencies diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/AbstractDiscoveryLifecycle.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/AbstractDiscoveryLifecycle.java index 07f99a5a..0ebc456f 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/AbstractDiscoveryLifecycle.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/AbstractDiscoveryLifecycle.java @@ -26,6 +26,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent; import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent; +import org.springframework.cloud.client.serviceregistry.ServiceRegistry; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; @@ -33,8 +34,12 @@ import org.springframework.core.env.Environment; /** * Lifecycle methods that may be useful and common to various DiscoveryClient implementations. + * + * @deprecated use {@link org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration} instead. This class will be removed in the next release train. + * * @author Spencer Gibb */ +@Deprecated public abstract class AbstractDiscoveryLifecycle implements DiscoveryLifecycle, ApplicationContextAware, ApplicationListener { @@ -89,6 +94,9 @@ public abstract class AbstractDiscoveryLifecycle implements DiscoveryLifecycle, @Override public void start() { if (!isEnabled()) { + if (logger.isDebugEnabled()) { + logger.debug("Discovery Lifecycle disabled. Not starting"); + } return; } @@ -103,7 +111,7 @@ public abstract class AbstractDiscoveryLifecycle implements DiscoveryLifecycle, if (shouldRegisterManagement()) { registerManagement(); } - this.context .publishEvent(new InstanceRegisteredEvent<>(this, + this.context.publishEvent(new InstanceRegisteredEvent<>(this, getConfiguration())); this.running.compareAndSet(false, true); } @@ -113,17 +121,18 @@ public abstract class AbstractDiscoveryLifecycle implements DiscoveryLifecycle, protected abstract void setConfiguredPort(int port); /** - * @return if the management service should be registered with the DiscoveryService + * @return if the management service should be registered with the {@link ServiceRegistry} */ protected boolean shouldRegisterManagement() { return getManagementPort() != null && ManagementServerPortUtils.isDifferent(this.context); } /** - * @return the object used to configure the DiscoveryClient + * @return the object used to configure the registration */ protected abstract Object getConfiguration(); + /** * Register the local service with the DiscoveryClient */ @@ -147,7 +156,7 @@ public abstract class AbstractDiscoveryLifecycle implements DiscoveryLifecycle, } /** - * @return if the DiscoveryClient is enabled + * @return true, if the {@link DiscoveryLifecycle} is enabled */ protected abstract boolean isEnabled(); @@ -201,6 +210,10 @@ public abstract class AbstractDiscoveryLifecycle implements DiscoveryLifecycle, return this.running.get(); } + protected AtomicBoolean getRunning() { + return running; + } + @Override public int getOrder() { return this.order; diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/EnableDiscoveryClient.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/EnableDiscoveryClient.java index 60b76d45..efa96caa 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/EnableDiscoveryClient.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/EnableDiscoveryClient.java @@ -36,4 +36,8 @@ import org.springframework.context.annotation.Import; @Import(EnableDiscoveryClientImportSelector.class) public @interface EnableDiscoveryClient { + /** + * If true, the ServiceRegistry will automatically register the local server. + */ + boolean autoRegister() default true; } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/EnableDiscoveryClientImportSelector.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/EnableDiscoveryClientImportSelector.java index 84700a77..2d4bf09b 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/EnableDiscoveryClientImportSelector.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/EnableDiscoveryClientImportSelector.java @@ -19,7 +19,13 @@ package org.springframework.cloud.client.discovery; import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.cloud.commons.util.SpringFactoryImportSelector; import org.springframework.core.Ordered; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.Order; +import org.springframework.core.type.AnnotationMetadata; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; /** * @author Spencer Gibb @@ -28,6 +34,24 @@ import org.springframework.core.annotation.Order; public class EnableDiscoveryClientImportSelector extends SpringFactoryImportSelector { + @Override + public String[] selectImports(AnnotationMetadata metadata) { + String[] imports = super.selectImports(metadata); + + AnnotationAttributes attributes = AnnotationAttributes.fromMap( + metadata.getAnnotationAttributes(getAnnotationClass().getName(), true)); + + boolean autoRegister = attributes.getBoolean("autoRegister"); + + if (autoRegister) { + List importsList = new ArrayList<>(Arrays.asList(imports)); + importsList.add("org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration"); + imports = importsList.toArray(new String[0]); + } + + return imports; + } + @Override protected boolean isEnabled() { return new RelaxedPropertyResolver(getEnvironment()).getProperty( diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistration.java new file mode 100644 index 00000000..960ad191 --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistration.java @@ -0,0 +1,74 @@ +package org.springframework.cloud.client.serviceregistry; + +import org.springframework.cloud.client.discovery.AbstractDiscoveryLifecycle; + +/** + * Lifecycle methods that may be useful and common to {@link ServiceRegistry} implementations. + * + * TODO: document the lifecycle + * + * @param registration type passed to the {@link ServiceRegistry}. + * + * @author Spencer Gibb + */ +@SuppressWarnings("deprecation") +public abstract class AbstractAutoServiceRegistration extends AbstractDiscoveryLifecycle implements AutoServiceRegistration { + + private ServiceRegistry serviceRegistry; + + protected AbstractAutoServiceRegistration(ServiceRegistry serviceRegistry) { + this.serviceRegistry = serviceRegistry; + } + + protected ServiceRegistry getServiceRegistry() { + return this.serviceRegistry; + } + + protected abstract R getRegistration(); + + protected abstract R getManagementRegistration(); + + /** + * Register the local service with the {@link ServiceRegistry} + */ + @Override + protected void register() { + this.serviceRegistry.register(getRegistration()); + } + + /** + * Register the local management service with the {@link ServiceRegistry} + */ + @Override + protected void registerManagement() { + this.serviceRegistry.register(getManagementRegistration()); + } + + /** + * De-register the local service with the {@link ServiceRegistry} + */ + @Override + protected void deregister() { + this.serviceRegistry.deregister(getRegistration()); + } + + /** + * De-register the local management service with the {@link ServiceRegistry} + */ + @Override + protected void deregisterManagement() { + this.serviceRegistry.deregister(getManagementRegistration()); + } + + @Override + public void stop() { + if (this.getRunning().compareAndSet(true, false) && isEnabled()) { + deregister(); + if (shouldRegisterManagement()) { + deregisterManagement(); + } + this.serviceRegistry.close(); + } + } + +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistration.java new file mode 100644 index 00000000..3450804f --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistration.java @@ -0,0 +1,7 @@ +package org.springframework.cloud.client.serviceregistry; + +/** + * @author Spencer Gibb + */ +public interface AutoServiceRegistration { +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistrationConfiguration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistrationConfiguration.java new file mode 100644 index 00000000..45a95d54 --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistrationConfiguration.java @@ -0,0 +1,28 @@ +package org.springframework.cloud.client.serviceregistry; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; + +/** + * @author Spencer Gibb + */ +@Configuration +@EnableConfigurationProperties(AutoServiceRegistrationProperties.class) +public class AutoServiceRegistrationConfiguration { + + @Autowired(required = false) + private AutoServiceRegistration autoServiceRegistration; + + @Autowired + private AutoServiceRegistrationProperties properties; + + @PostConstruct + protected void init() { + if (autoServiceRegistration == null && this.properties.isFailFast()) { + throw new IllegalStateException("Auto Service Registration has been requested, but there is no AutoServiceRegistration bean"); + } + } +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistrationProperties.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistrationProperties.java new file mode 100644 index 00000000..5a8b22d5 --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistrationProperties.java @@ -0,0 +1,21 @@ +package org.springframework.cloud.client.serviceregistry; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author Spencer Gibb + */ +@ConfigurationProperties("spring.cloud.service-registry.auto-registration") +public class AutoServiceRegistrationProperties { + + /** Should startup fail if there is no AutoServiceRegistration, default to false. */ + private boolean failFast = false; + + public boolean isFailFast() { + return failFast; + } + + public void setFailFast(boolean failFast) { + this.failFast = failFast; + } +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/Registration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/Registration.java new file mode 100644 index 00000000..625e275b --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/Registration.java @@ -0,0 +1,7 @@ +package org.springframework.cloud.client.serviceregistry; + +/** + * @author Spencer Gibb + */ +public interface Registration { +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/ServiceRegistry.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/ServiceRegistry.java new file mode 100644 index 00000000..eb3d73ee --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/ServiceRegistry.java @@ -0,0 +1,19 @@ +package org.springframework.cloud.client.serviceregistry; + +/** + * TODO: write javadoc + * @author Spencer Gibb + */ +public interface ServiceRegistry { + void register(R registration); + + void deregister(R registration); + + void close(); + + // TODO: return value for success? + void setStatus(R registration, String status); + + // TODO: concrete return value? Interface? + Object getStatus(R registration); +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/ServiceRegistryAutoConfiguration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/ServiceRegistryAutoConfiguration.java new file mode 100644 index 00000000..7fbf7d72 --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/ServiceRegistryAutoConfiguration.java @@ -0,0 +1,28 @@ +package org.springframework.cloud.client.serviceregistry; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.endpoint.Endpoint; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Spencer Gibb + */ +@ConditionalOnBean(ServiceRegistry.class) +@Configuration +public class ServiceRegistryAutoConfiguration { + + @Autowired(required = false) + private Registration registration; + + @ConditionalOnClass(Endpoint.class) + @Bean + public ServiceRegistryEndpoint serviceRegistryEndpoint(ServiceRegistry serviceRegistry) { + ServiceRegistryEndpoint endpoint = new ServiceRegistryEndpoint(serviceRegistry); + endpoint.setRegistration(registration); + return endpoint; + } +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/endpoint/ServiceRegistryEndpoint.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/endpoint/ServiceRegistryEndpoint.java new file mode 100644 index 00000000..166621ff --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/endpoint/ServiceRegistryEndpoint.java @@ -0,0 +1,95 @@ +/* + * Copyright 2013-2016 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 + * + * http://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.client.serviceregistry.endpoint; + +import org.springframework.boot.actuate.endpoint.Endpoint; +import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; +import org.springframework.cloud.client.serviceregistry.Registration; +import org.springframework.cloud.client.serviceregistry.ServiceRegistry; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.jmx.export.annotation.ManagedAttribute; +import org.springframework.jmx.export.annotation.ManagedOperation; +import org.springframework.jmx.export.annotation.ManagedResource; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * Endpoint to display and set the service instance status using the service registry. + * + * @author Spencer Gibb + */ +@ManagedResource(description = "Can be used to display and set the service instance status using the service registry") +@SuppressWarnings("unchecked") +public class ServiceRegistryEndpoint implements MvcEndpoint { + + private final ServiceRegistry serviceRegistry; + + private Registration registration; + + public ServiceRegistryEndpoint(ServiceRegistry serviceRegistry) { + this.serviceRegistry = serviceRegistry; + } + + public void setRegistration(Registration registration) { + this.registration = registration; + } + + @RequestMapping(path = "instance-status", method = RequestMethod.POST) + @ResponseBody + @ManagedOperation + public ResponseEntity setStatus(@RequestBody String status) { + Assert.notNull(status, "status may not by null"); + + if (this.registration == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body("no registration found"); + } + + this.serviceRegistry.setStatus(this.registration, status); + return ResponseEntity.ok().build(); + } + + @RequestMapping(path = "instance-status", method = RequestMethod.GET) + @ResponseBody + @ManagedAttribute + public ResponseEntity getStatus() { + if (this.registration == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body("no registration found"); + } + + return ResponseEntity.ok().body(this.serviceRegistry.getStatus(this.registration)); + } + + @Override + public String getPath() { + return "/service-registry"; + } + + @Override + public boolean isSensitive() { + return true; + } + + @Override + public Class> getEndpointType() { + return null; + } +} diff --git a/spring-cloud-commons/src/main/resources/META-INF/spring.factories b/spring-cloud-commons/src/main/resources/META-INF/spring.factories index 2f8e25f5..4f996299 100644 --- a/spring-cloud-commons/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-commons/src/main/resources/META-INF/spring.factories @@ -4,6 +4,7 @@ org.springframework.cloud.client.CommonsClientAutoConfiguration,\ org.springframework.cloud.client.discovery.noop.NoopDiscoveryClientAutoConfiguration,\ org.springframework.cloud.client.hypermedia.CloudHypermediaAutoConfiguration,\ org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\ +org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration,\ org.springframework.cloud.commons.util.UtilAutoConfiguration diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/EnableDiscoveryClientImportSelectorTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/EnableDiscoveryClientImportSelectorTests.java new file mode 100644 index 00000000..de7d7288 --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/EnableDiscoveryClientImportSelectorTests.java @@ -0,0 +1,58 @@ +package org.springframework.cloud.client.discovery; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.mock.env.MockEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; + +/** + * @author Spencer Gibb + */ +public class EnableDiscoveryClientImportSelectorTests { + + private final EnableDiscoveryClientImportSelector importSelector = new EnableDiscoveryClientImportSelector(); + + private final MockEnvironment environment = new MockEnvironment(); + + @Mock + private AnnotationMetadata annotationMetadata; + + @Mock + private AnnotationAttributes annotationAttributes; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + this.importSelector.setBeanClassLoader(getClass().getClassLoader()); + this.importSelector.setEnvironment(this.environment); + } + + @Test + public void autoRegistrationIsEnabled() { + configureAnnotation(true); + String[] imports = this.importSelector.selectImports(this.annotationMetadata); + assertThat(imports).hasSize(1); + } + + @Test + public void autoRegistrationIsDisabled() { + configureAnnotation(false); + String[] imports = this.importSelector.selectImports(this.annotationMetadata); + assertThat(imports).isEmpty(); + } + + private void configureAnnotation(boolean autoRegistration) { + String annotationName = EnableDiscoveryClient.class.getName(); + given(this.annotationMetadata.isAnnotated(annotationName)).willReturn(true); + given(this.annotationMetadata.getAnnotationAttributes(annotationName, true)) + .willReturn(this.annotationAttributes); + given(this.annotationAttributes.getBoolean("autoRegister")) + .willReturn(autoRegistration); + } +} diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistrationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistrationTests.java new file mode 100644 index 00000000..ae0886c9 --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistrationTests.java @@ -0,0 +1,150 @@ +package org.springframework.cloud.client.serviceregistry; + +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author Spencer Gibb + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = AbstractAutoServiceRegistrationTests.Config.class) +@WebIntegrationTest(randomPort = true, value = "management.port=0") +public class AbstractAutoServiceRegistrationTests { + + @Autowired + private TestAutoServiceRegistration autoRegistration; + + @Value("${local.server.port}") + private int port; + + @Value("${local.management.port}") + private int managementPort; + + @Test + public void portsWork() { + assertNotEquals("Lifecycle port is zero", 0, autoRegistration.getPort().get()); + assertNotEquals("Lifecycle port is management port", managementPort, autoRegistration.getPort().get()); + assertEquals("Lifecycle port is wrong", port, autoRegistration.getPort().get()); + assertTrue("Lifecycle not running", autoRegistration.isRunning()); + assertThat("ServiceRegistry is wrong type", autoRegistration.getServiceRegistry(), is(instanceOf(TestServiceRegistry.class))); + TestServiceRegistry serviceRegistry = (TestServiceRegistry) autoRegistration.getServiceRegistry(); + assertTrue("Lifecycle not registered", serviceRegistry.isRegistered()); + assertEquals("Lifecycle appName is wrong", "application", autoRegistration.getAppName()); + } + + @EnableAutoConfiguration + @Configuration + public static class Config { + @Bean + public TestAutoServiceRegistration testAutoServiceRegistration() { + return new TestAutoServiceRegistration(); + } + } + + public static class TestRegistration implements Registration { + } + + public static class TestServiceRegistry implements ServiceRegistry { + private boolean registered = false; + private boolean deregistered = false; + + @Override + public void register(TestRegistration registration) { + this.registered = true; + } + + @Override + public void deregister(TestRegistration registration) { + this.deregistered = true; + } + + @Override + public void close() { } + + @Override + public void setStatus(TestRegistration registration, String status) { + //TODO: test setStatus + } + + @Override + public Object getStatus(TestRegistration registration) { + //TODO: test getStatus + return null; + } + + boolean isRegistered() { + return registered; + } + + boolean isDeregistered() { + return deregistered; + } + } + + public static class TestAutoServiceRegistration extends AbstractAutoServiceRegistration { + private int port = 0; + + @Override + protected AtomicInteger getPort() { + return super.getPort(); + } + + @Override + protected String getAppName() { + return super.getAppName(); + } + + protected TestAutoServiceRegistration() { + super(new TestServiceRegistry()); + } + + @Override + protected int getConfiguredPort() { + return port; + } + + @Override + protected void setConfiguredPort(int port) { + this.port = port; + } + + @Override + protected TestRegistration getRegistration() { + return null; + } + + @Override + protected TestRegistration getManagementRegistration() { + return null; + } + + @Override + protected Object getConfiguration() { + return null; + } + + @Override + protected boolean isEnabled() { + return true; + } + + + } +} diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistrationConfigurationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistrationConfigurationTests.java new file mode 100644 index 00000000..31476e79 --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistrationConfigurationTests.java @@ -0,0 +1,81 @@ +package org.springframework.cloud.client.serviceregistry; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Map; + +import org.hamcrest.Matchers; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.boot.test.util.EnvironmentTestUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Spencer Gibb + */ +public class AutoServiceRegistrationConfigurationTests { + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Test + public void hasAutoServiceRegistration() { + try(AnnotationConfigApplicationContext context = setup(HasAutoServiceRegistrationConfiguration.class)) { + AutoServiceRegistration autoServiceRegistration = context.getBean(AutoServiceRegistration.class); + assertThat(autoServiceRegistration).isNotNull(); + } + } + + @Test + public void noAutoServiceRegistrationAndFailFast() { + this.exception.expect(BeanCreationException.class); + this.exception.expectMessage(Matchers.containsString("no AutoServiceRegistration")); + try(AnnotationConfigApplicationContext context = setup("spring.cloud.service-registry.auto-registration.failFast=true")) { + assertNoBean(context); + } + } + + @Test + public void noAutoServiceRegistrationAndFailFastFalse() { + try(AnnotationConfigApplicationContext context = setup()) { + assertNoBean(context); + } + } + + private void assertNoBean(AnnotationConfigApplicationContext context) { + Map beans = context.getBeansOfType(AutoServiceRegistration.class); + assertThat(beans).isEmpty(); + } + + @Configuration + static class HasAutoServiceRegistrationConfiguration { + @Bean + public AutoServiceRegistration autoServiceRegistration() { + return new AutoServiceRegistration() {}; + } + } + + private AnnotationConfigApplicationContext setup(Class... classes) { + return setup(null, classes); + } + + private AnnotationConfigApplicationContext setup(String property, Class... classes) { + ArrayList list = new ArrayList<>(); + list.add(AutoServiceRegistrationConfiguration.class); + list.addAll(Arrays.asList(classes)); + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(list.toArray(new Class[0])); + if (StringUtils.hasText(property)) { + EnvironmentTestUtils.addEnvironment(context, property); + } + context.refresh(); + return context; + } +} diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/endpoint/ServiceRegistryEndpointNoRegistrationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/endpoint/ServiceRegistryEndpointNoRegistrationTests.java new file mode 100644 index 00000000..ede5645b --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/endpoint/ServiceRegistryEndpointNoRegistrationTests.java @@ -0,0 +1,70 @@ +package org.springframework.cloud.client.serviceregistry.endpoint; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.client.serviceregistry.ServiceRegistry; +import org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpointTests.TestServiceRegistry; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author Spencer Gibb + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = ServiceRegistryEndpointNoRegistrationTests.TestConfiguration.class) +public class ServiceRegistryEndpointNoRegistrationTests { + + @Autowired + private WebApplicationContext context; + + private MockMvc mvc; + + @Before + public void setUp() { + this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + } + + @Test + public void testGet() throws Exception { + this.mvc.perform(get("/service-registry/instance-status")).andExpect(status().isNotFound()); + } + + @Test + public void testPost() throws Exception { + this.mvc.perform(post("/service-registry/instance-status").content("newstatus")).andExpect(status().isNotFound()); + } + + @Import({JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class, + ManagementServerPropertiesAutoConfiguration.class}) + @Configuration + public static class TestConfiguration { + @Bean + ServiceRegistryEndpoint serviceRegistryEndpoint() { + return new ServiceRegistryEndpoint(serviceRegistry()); + } + + @Bean + ServiceRegistry serviceRegistry() { + return new TestServiceRegistry() ; + } + } +} diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/endpoint/ServiceRegistryEndpointTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/endpoint/ServiceRegistryEndpointTests.java new file mode 100644 index 00000000..45e88575 --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/endpoint/ServiceRegistryEndpointTests.java @@ -0,0 +1,124 @@ +package org.springframework.cloud.client.serviceregistry.endpoint; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.client.serviceregistry.Registration; +import org.springframework.cloud.client.serviceregistry.ServiceRegistry; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import java.util.concurrent.atomic.AtomicReference; + +import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Spencer Gibb + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = ServiceRegistryEndpointTests.TestConfiguration.class) +public class ServiceRegistryEndpointTests { + private static final String UPDATED_STATUS = "updatedstatus"; + private static final String MYSTATUS = "mystatus"; + + @Autowired + private WebApplicationContext context; + + @Autowired + private TestServiceRegistry serviceRegistry; + + private MockMvc mvc; + + @Before + public void setUp() { + this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + } + + @Test + public void testGet() throws Exception { + this.mvc.perform(get("/service-registry/instance-status")).andExpect(status().isOk()) + .andExpect(content().string(containsString(MYSTATUS))); + } + + @Test + public void testPost() throws Exception { + this.mvc.perform(post("/service-registry/instance-status").content(UPDATED_STATUS)).andExpect(status().isOk()); + assertThat(this.serviceRegistry.getUpdatedStatus().get()).isEqualTo(UPDATED_STATUS); + } + + @Import({JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class, + ManagementServerPropertiesAutoConfiguration.class}) + @Configuration + public static class TestConfiguration { + @Bean + Registration registration() { + return new Registration() { + }; + } + + @Bean + ServiceRegistryEndpoint serviceRegistryEndpoint(Registration reg) { + ServiceRegistryEndpoint endpoint = new ServiceRegistryEndpoint(serviceRegistry()); + endpoint.setRegistration(reg); + return endpoint; + } + + @Bean + ServiceRegistry serviceRegistry() { + return new TestServiceRegistry() ; + } + } + + static class TestServiceRegistry implements ServiceRegistry { + + AtomicReference updatedStatus = new AtomicReference<>(); + + @Override + public void register(Registration registration) { + + } + + @Override + public void deregister(Registration registration) { + + } + + @Override + public void close() { + + } + + @Override + public void setStatus(Registration registration, String status) { + updatedStatus.compareAndSet(null, status); + } + + @Override + public Object getStatus(Registration registration) { + return MYSTATUS; + } + + public AtomicReference getUpdatedStatus() { + return updatedStatus; + } + } +}