Skip to content

Commit

Permalink
Update to Brixton and add Eureka-Service converter
Browse files Browse the repository at this point in the history
Update to Spring Cloud Brixton and add the concept of ServiceInstanceConverters.
A ServiceConverter is used to convert discovered ServiceInstances to
Applications. The old converter and one for Eureka is provided. It is also
possible to provide your own implementation.

closes #154
  • Loading branch information
joshiste committed Apr 27, 2016
1 parent f8b9d82 commit 272633e
Show file tree
Hide file tree
Showing 24 changed files with 545 additions and 412 deletions.
17 changes: 12 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<java.version>1.7</java.version>
<main.basedir>${basedir}</main.basedir>
<spring-boot.version>1.3.3.RELEASE</spring-boot.version>
<spring-cloud.version>Angel.SR6</spring-cloud.version>
<spring-cloud.version>Brixton.RC2</spring-cloud.version>
<build-plugin.jacoco.version>0.7.5.201505241946</build-plugin.jacoco.version>
<build-plugin.coveralls.version>4.0.0</build-plugin.coveralls.version>
<build-plugin.gpg.version>1.6</build-plugin.gpg.version>
Expand Down Expand Up @@ -226,21 +226,21 @@
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- Removed for release
<!-- Remove for release -->
<repositories>
<repository>
<id>spring-release</id>
Expand All @@ -256,6 +256,13 @@
</snapshots>
<url>http://repo.spring.io/milestone</url>
</repository>
<repository>
<id>spring-snapshot</id>
<snapshots>
<enabled>true</enabled>
</snapshots>
<url>http://repo.spring.io/snapshot</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
Expand All @@ -273,5 +280,5 @@
<url>http://repo.spring.io/milestone</url>
</pluginRepository>
</pluginRepositories>
-->
<!-- -->
</project>
25 changes: 18 additions & 7 deletions spring-boot-admin-docs/src/main/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ The Spring Boot Admin Client registers the application at the admin server. This

| spring.boot.admin.client.enabled
| Enables the Spring Boot Admin Client.
| true
| `true`

| spring.boot.admin.url
| List of URLs of the Spring Boot Admin server to register at. This triggers the AutoConfiguration. *Mandatory*.
Expand Down Expand Up @@ -266,6 +266,14 @@ spring.boot.admin.password
The Spring Boot Admin Server is capable of using Spring Clouds `DiscoveryClient` to discover applications. The advantage is that the clients don't have to include the `spring-boot-admin-starter-client`. You just have to add a DiscoveryClient to your admin server - everything else is done by AutoConfiguration.
The setup is explained <<discover-clients-via-spring-cloud-discovery,above>>.

==== Usage of discovery informations ====

The informations of the discovered services is converted by the `ServiceInstanceConverter`. Spring Boot Admin ships with a default and Eureka converter implementation. The corresponding one is selected by AutoConfiguration. You can use your own conversion by simply implementing the interface and adding the bean to your application context.

TIP: When Eureka discovery is used, the `EurekaServiceInstanceConverter` will use the discovered instances homePageUrl and healthCheckUrl. In case the instances managment.context-path is different from the homePageUrl you should add an entry `management.context-path` to the instances metadata-map with the corresponding value.

TIP: When the default conversion kicks in, you can use the `spring.boot.admin.discovery.converter.*` properties to control the conversion for all your instances.

.Discovery configuration options
|===
| Property name |Description |Default value
Expand All @@ -274,12 +282,18 @@ The setup is explained <<discover-clients-via-spring-cloud-discovery,above>>.
| Enables the DiscoveryClient-support for the admin server.
| `true`

| spring.boot.admin.discovery.management.context-path
| spring.boot.admin.discovery.management.context-path _(deprecated)_
| If set this will be appended to the service-url from the discovery information.
|
|===

NOTE: Currently the implementation is not specific to any DiscoveryClient implementation. Unfortunately the DiscoveryClient doesn't expose the health- or management-url, so if you're not sticking to the defaults and setting `spring.boot.admin.discovery.management.context-path` doesn't suffice, you can add a bean extending `ApplicationDiscoveryListener` to your admin server and provide your own conversion.
| spring.boot.admin.discovery.converter.management-context-path
| Will be appended to the service-url of the discovered service when the managment-url is converter by the `DefaultServiceInstanceConverter`.
|

| spring.boot.admin.discovery.converter.health-endpoint
| Will be appended to the management-url of the discovered service when the health-url is converter by the `DefaultServiceInstanceConverter`.
| `"health"`
|===

[[hazelcast-support]]
=== Hazelcast support ===
Expand Down Expand Up @@ -545,8 +559,5 @@ Can I include spring-boot-admin into my business application?::
*tl;dr* You can, but you shouldn't. +
You can set `spring.boot.admin.context-path` to alter the path where the UI and REST-API is served, but depending on the complexity of your application you might get in trouble. On the other hand in my opinion it makes no sense for an application to monitor itself. In case your application goes down your monitoring tool also does.

How do I disable Spring Boot Admin Client for my unit tests?::
The AutoConfiguration is triggered by the presence of the `spring.boot.admin.url`. So if you don't set this property for your tests, the Spring Boot Admin Client is not active.

How do I customize the UI?::
You can only customize the UI by copying and modifying the source of `spring-boot-admin-ui` and adding your own module to your classpath.
12 changes: 10 additions & 2 deletions spring-boot-admin-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-core</artifactId>
<!-- for fix of serverproeprties -->
<version>1.1.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
Expand All @@ -38,18 +40,24 @@
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<!-- Optional Mail Starter for mail-notfications -->
<!-- Optional Mail Starter for mail-notfications -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
<optional>true</optional>
<optional>true</optional>
</dependency>
<!-- Optional Discovery Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
<optional>true</optional>
</dependency>
<!-- Optional Eureka Discovery Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<optional>true</optional>
</dependency>
<!-- Optional Hazelcast-Support -->
<dependency>
<groupId>com.hazelcast</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,23 @@
package de.codecentric.boot.admin.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.noop.NoopDiscoveryClientAutoConfiguration;
import org.springframework.cloud.netflix.eureka.EurekaDiscoveryClient.EurekaServiceInstance;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import de.codecentric.boot.admin.discovery.ApplicationDiscoveryListener;
import de.codecentric.boot.admin.discovery.DefaultServiceInstanceConverter;
import de.codecentric.boot.admin.discovery.EurekaServiceInstanceConverter;
import de.codecentric.boot.admin.discovery.ServiceInstanceConverter;
import de.codecentric.boot.admin.registry.ApplicationRegistry;

@Configuration
Expand All @@ -35,9 +41,6 @@
@AutoConfigureAfter({ NoopDiscoveryClientAutoConfiguration.class })
public class DiscoveryClientConfiguration {

@Value("${spring.boot.admin.discovery.management.context-path:}")
private String managementPath;

@Autowired
private DiscoveryClient discoveryClient;

Expand All @@ -46,10 +49,32 @@ public class DiscoveryClientConfiguration {

@Bean
@ConditionalOnMissingBean
public ApplicationDiscoveryListener applicationDiscoveryListener() {
public ApplicationDiscoveryListener applicationDiscoveryListener(
ServiceInstanceConverter serviceInstanceConverter) {
ApplicationDiscoveryListener listener = new ApplicationDiscoveryListener(discoveryClient,
registry);
listener.setManagementContextPath(managementPath);
listener.setConverter(serviceInstanceConverter);
return listener;
}

@Configuration
@ConditionalOnClass({ EurekaServiceInstance.class })
@AutoConfigureBefore(DefaultConverterConfiguration.class)
public static class EurekaConverterConfiguration {
@Bean
@ConditionalOnMissingBean
public EurekaServiceInstanceConverter serviceInstanceConverter() {
return new EurekaServiceInstanceConverter();
}
}

@Configuration
@ConfigurationProperties(prefix = "spring.boot.admin.discovery.converter")
public static class DefaultConverterConfiguration {
@Bean
@ConditionalOnMissingBean
public DefaultServiceInstanceConverter serviceInstanceConverter() {
return new DefaultServiceInstanceConverter();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,17 @@
package de.codecentric.boot.admin.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.cloud.netflix.zuul.RoutesEndpoint;
import org.springframework.cloud.netflix.zuul.ZuulConfiguration;
import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter;
import org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter;
import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping;
import org.springframework.context.ApplicationEvent;
Expand All @@ -32,7 +38,6 @@
import de.codecentric.boot.admin.event.RoutesOutdatedEvent;
import de.codecentric.boot.admin.registry.ApplicationRegistry;
import de.codecentric.boot.admin.zuul.ApplicationRouteLocator;
import de.codecentric.boot.admin.zuul.PreDecorationFilter;

@Configuration
@AutoConfigureAfter({ AdminServerWebConfiguration.class })
Expand All @@ -50,16 +55,21 @@ public class RevereseZuulProxyConfiguration extends ZuulConfiguration {
@Autowired
private AdminServerProperties adminServer;

@Autowired
private ZuulHandlerMapping zuulHandlerMapping;

@Bean
@Override
public ApplicationRouteLocator routeLocator() {
return new ApplicationRouteLocator(this.server.getServletPrefix(), registry,
adminServer.getContextPath() + "/api/applications");
adminServer.getContextPath() + "/api/applications/");
}

// pre filters
@Bean
public PreDecorationFilter preDecorationFilter() {
return new PreDecorationFilter(routeLocator(), true);
return new PreDecorationFilter(routeLocator(), this.server.getServletPrefix(),
new ZuulProperties());
}

@Bean
Expand All @@ -68,28 +78,36 @@ public SimpleHostRoutingFilter simpleHostRoutingFilter() {
if (this.traces != null) {
helper.setTraces(this.traces);
}
return new SimpleHostRoutingFilter(helper);
return new SimpleHostRoutingFilter(helper, new ZuulProperties());
}

@Bean
@Override
public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {
return new ZuulRefreshListener();
return new ZuulRefreshListener(zuulHandlerMapping);
}

@Configuration
@ConditionalOnClass(Endpoint.class)
protected static class RoutesEndpointConfiguration {
@Bean
public RoutesEndpoint zuulEndpoint(RouteLocator routeLocator) {
return new RoutesEndpoint(routeLocator);
}
}

private static class ZuulRefreshListener implements ApplicationListener<ApplicationEvent> {
@Autowired
private ZuulHandlerMapping zuulHandlerMapping;

@Autowired
private ApplicationRouteLocator routeLocator;
public ZuulRefreshListener(ZuulHandlerMapping zuulHandlerMapping) {
this.zuulHandlerMapping = zuulHandlerMapping;
}

@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof PayloadApplicationEvent && ((PayloadApplicationEvent<?>) event)
.getPayload() instanceof RoutesOutdatedEvent) {
routeLocator.resetRoutes();
zuulHandlerMapping.registerHandlers();
zuulHandlerMapping.setDirty(true);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,21 @@
import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;
import org.springframework.cloud.client.discovery.event.ParentHeartbeatEvent;
import org.springframework.context.event.EventListener;
import org.springframework.util.StringUtils;

import de.codecentric.boot.admin.model.Application;
import de.codecentric.boot.admin.registry.ApplicationRegistry;

/**
* Listener for Heartbeats events to publish all services to the application registry.
*
* @author Johannes Stelzer
* @author Johannes Edmeier
*/
public class ApplicationDiscoveryListener {

private final DiscoveryClient discoveryClient;

private final ApplicationRegistry registry;

private final HeartbeatMonitor monitor = new HeartbeatMonitor();

private String managementContextPath = "";

private String serviceContextPath = "";

private String healthEndpoint = "health";
private ServiceInstanceConverter converter = new DefaultServiceInstanceConverter();

public ApplicationDiscoveryListener(DiscoveryClient discoveryClient,
ApplicationRegistry registry) {
Expand Down Expand Up @@ -82,33 +74,10 @@ protected void discover() {
}

protected Application convert(ServiceInstance instance) {
String serviceUrl = append(instance.getUri().toString(), serviceContextPath);
String managementUrl = append(instance.getUri().toString(), managementContextPath);
String healthUrl = append(managementUrl, healthEndpoint);

return Application.create(instance.getServiceId()).withHealthUrl(healthUrl)
.withManagementUrl(managementUrl).withServiceUrl(serviceUrl).build();
}

public void setManagementContextPath(String managementContextPath) {
this.managementContextPath = managementContextPath;
}

public void setServiceContextPath(String serviceContextPath) {
this.serviceContextPath = serviceContextPath;
return converter.convert(instance);
}

public void setHealthEndpoint(String healthEndpoint) {
this.healthEndpoint = healthEndpoint;
}

protected final String append(String uri, String path) {
String baseUri = uri.replaceFirst("/+$", "");
if (StringUtils.isEmpty(path)) {
return baseUri;
}

String normPath = path.replaceFirst("^/+", "").replaceFirst("/+$", "");
return baseUri + "/" + normPath;
public void setConverter(ServiceInstanceConverter converter) {
this.converter = converter;
}
}

0 comments on commit 272633e

Please sign in to comment.