Commit bd11afac by Johannes Stelzer

Support automatic discovery via Spring Clouds DiscoveryClient.

To enable, you just have to add a DiscoveryClient to your Spring Boot Admin Server. Also did some code polish. Closes #44
parent 912d4f73
...@@ -224,6 +224,11 @@ ...@@ -224,6 +224,11 @@
<dependency> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
<version>${spring-cloud.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId> <artifactId>spring-cloud-starter-zuul</artifactId>
<version>${spring-cloud.version}</version> <version>${spring-cloud.version}</version>
</dependency> </dependency>
...@@ -237,6 +242,11 @@ ...@@ -237,6 +242,11 @@
<artifactId>spring-cloud-commons</artifactId> <artifactId>spring-cloud-commons</artifactId>
<version>${spring-cloud.version}</version> <version>${spring-cloud.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>${spring-cloud.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
...@@ -262,6 +272,7 @@ ...@@ -262,6 +272,7 @@
</dependency> </dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
<repositories> <repositories>
<repository> <repository>
<id>spring-release</id> <id>spring-release</id>
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
</properties> </properties>
<modules> <modules>
<module>spring-boot-admin-sample</module> <module>spring-boot-admin-sample</module>
<module>spring-boot-admin-sample-discovery</module>
<module>spring-boot-admin-sample-hazelcast</module> <module>spring-boot-admin-sample-hazelcast</module>
</modules> </modules>
</project> </project>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-samples</artifactId>
<version>1.1.3-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>spring-boot-admin-sample-discovery</artifactId>
<dependencies>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-ui</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>de.codecentric.boot.admin.SpringBootAdminApplication</mainClass>
<addResources>false</addResources>
</configuration>
</plugin>
</plugins>
</build>
</project>
/*
* Copyright 2014 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 de.codecentric.boot.admin;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Configuration;
import de.codecentric.boot.admin.config.EnableAdminServer;
@Configuration
@EnableAutoConfiguration
@EnableDiscoveryClient
@EnableAdminServer
public class SpringBootAdminApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootAdminApplication.class, args);
}
}
info:
version: 1.0.0
spring:
application:
name: discovery-example
cloud:
config:
enabled: false
eureka:
instance:
leaseRenewalIntervalInSeconds: 5
metadataMap:
instanceId: ${spring.application.name}:${spring.application.instance_id:${random.value}}
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<jmxConfigurator/>
</configuration>
\ No newline at end of file
spring.resources.cachePeriod=3600
server.port=8080
info.version=@pom.version@
info.stage=test
logging.file=/tmp/log.log
spring.application.name=@pom.artifactId@
spring.boot.admin.url=http://localhost:8080
info:
version: @pom.version@
stage: test
spring:
application:
name: @pom.artifactId@
boot:
admin:
url=http://localhost:8080
cloud:
config:
enabled: false
endpoints:
health:
sensitive: false
\ No newline at end of file
...@@ -30,8 +30,18 @@ public class Application { ...@@ -30,8 +30,18 @@ public class Application {
} }
``` ```
## Hazelcast Support ## Spring Cloud DiscoveryClient support
The Spring Boot Admin Server is capable of using Spring Clouds DiscoveryClient to discover applications. When you do this the clients don't have to include the spring-boot-starter-admin-client. You just have to configure a DiscoveryClient - everything else is done by AutoConfiguration.
See the [discovery sample project](https://github.com/codecentric/spring-boot-admin/tree/master/spring-boot-admin-samples/spring-boot-admin-sample-discovery) in this repository.
One note: If you omit the Spring Boot Admin Client in you Client Applications you can't download the logfile (but hopefully my pull request will make it into Spring Boot 1.3.0);
### Further configuration
Since the DiscoveryClient doesn't tell the management.context-path you can suffix the url for all discovered clients by setting ``spring.boot.admin.discovery.management.context-path``.
Explictly disable DiscoveryClient support by setting ``spring.boot.admin.discover.enable=false``.
## Hazelcast Support
Spring Boot Admin Server supports cluster replication with Hazelcast. Spring Boot Admin Server supports cluster replication with Hazelcast.
It is automatically enabled when its found on the classpath. It is automatically enabled when its found on the classpath.
...@@ -47,7 +57,7 @@ Just add Hazelcast to your dependencies: ...@@ -47,7 +57,7 @@ Just add Hazelcast to your dependencies:
And thats it! The server is going to use the default Hazelcast configuration. And thats it! The server is going to use the default Hazelcast configuration.
### Custom Hazelcast configuration ### Custom Hazelcast configuration
To change the configuration add a com.hazelcast.config.Config bean to your application context (for example with hazelcast-spring): To change the configuration add a ``com.hazelcast.config.Config``-bean to your application context (for example with hazelcast-spring):
Add hazelcast-spring to dependencies: Add hazelcast-spring to dependencies:
```xml ```xml
...@@ -83,6 +93,6 @@ Write xml-config hazelcast-config.xml: ...@@ -83,6 +93,6 @@ Write xml-config hazelcast-config.xml:
``` ```
### Further configuration ### Further configuration
To disable Hazelcast support by setting ``spring.boot.admin.hazelcast.enable=false``. Disable Hazelcast support by setting ``spring.boot.admin.hazelcast.enable=false``.
To alter the name of the Hazelcast-Map set ``spring.boot.admin.hazelcast.map= my-own-map-name``. To alter the name of the Hazelcast-Map set ``spring.boot.admin.hazelcast.map= my-own-map-name``.
...@@ -59,8 +59,14 @@ ...@@ -59,8 +59,14 @@
<version>4.3.6</version> <version>4.3.6</version>
</dependency> </dependency>
<!-- Optional Discovery Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
<optional>true</optional>
</dependency>
<!-- Hazelcast-Support --> <!-- Optional Hazelcast-Support -->
<dependency> <dependency>
<groupId>com.hazelcast</groupId> <groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId> <artifactId>hazelcast</artifactId>
...@@ -71,6 +77,7 @@ ...@@ -71,6 +77,7 @@
<artifactId>hazelcast-spring</artifactId> <artifactId>hazelcast-spring</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<!-- Test --> <!-- Test -->
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
......
...@@ -22,12 +22,15 @@ import org.springframework.beans.factory.annotation.Autowired; ...@@ -22,12 +22,15 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.endpoint.Endpoint; import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.trace.TraceRepository; import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.noop.NoopDiscoveryClientAutoConfiguration;
import org.springframework.cloud.netflix.zuul.RoutesEndpoint; import org.springframework.cloud.netflix.zuul.RoutesEndpoint;
import org.springframework.cloud.netflix.zuul.ZuulFilterInitializer; import org.springframework.cloud.netflix.zuul.ZuulFilterInitializer;
import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper; import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
...@@ -43,6 +46,8 @@ import org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter ...@@ -43,6 +46,8 @@ import org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter
import org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter; import org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter;
import org.springframework.cloud.netflix.zuul.web.ZuulController; import org.springframework.cloud.netflix.zuul.web.ZuulController;
import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping; import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter;
...@@ -59,6 +64,7 @@ import com.hazelcast.core.MapEvent; ...@@ -59,6 +64,7 @@ import com.hazelcast.core.MapEvent;
import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.ZuulFilter;
import de.codecentric.boot.admin.controller.RegistryController; import de.codecentric.boot.admin.controller.RegistryController;
import de.codecentric.boot.admin.discovery.ApplicationDiscoveryListener;
import de.codecentric.boot.admin.model.Application; import de.codecentric.boot.admin.model.Application;
import de.codecentric.boot.admin.registry.ApplicationIdGenerator; import de.codecentric.boot.admin.registry.ApplicationIdGenerator;
import de.codecentric.boot.admin.registry.ApplicationRegistry; import de.codecentric.boot.admin.registry.ApplicationRegistry;
...@@ -81,7 +87,8 @@ public class WebappConfig extends WebMvcConfigurerAdapter { ...@@ -81,7 +87,8 @@ public class WebappConfig extends WebMvcConfigurerAdapter {
} }
/** /**
* Controller with REST-API for spring-boot applications to register itself. * @param registry the backing Application registry.
* @return Controller with REST-API for spring-boot applications to register itself.
*/ */
@Bean @Bean
public RegistryController registryController(ApplicationRegistry registry) { public RegistryController registryController(ApplicationRegistry registry) {
...@@ -89,7 +96,9 @@ public class WebappConfig extends WebMvcConfigurerAdapter { ...@@ -89,7 +96,9 @@ public class WebappConfig extends WebMvcConfigurerAdapter {
} }
/** /**
* Default registry for all registered application. * @param applicationStore the backing store
* @param applicationIdGenerator the id generator to use
* @return Default registry for all registered application.
*/ */
@Bean @Bean
public ApplicationRegistry applicationRegistry(ApplicationStore applicationStore, public ApplicationRegistry applicationRegistry(ApplicationStore applicationStore,
...@@ -98,7 +107,7 @@ public class WebappConfig extends WebMvcConfigurerAdapter { ...@@ -98,7 +107,7 @@ public class WebappConfig extends WebMvcConfigurerAdapter {
} }
/** /**
* Default applicationId Generator * @return Default applicationId Generator
*/ */
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
...@@ -107,7 +116,7 @@ public class WebappConfig extends WebMvcConfigurerAdapter { ...@@ -107,7 +116,7 @@ public class WebappConfig extends WebMvcConfigurerAdapter {
} }
@Configuration @Configuration
public static class SimpleConfig { public static class SimpleStoreConfig {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public ApplicationStore applicationStore() { public ApplicationStore applicationStore() {
...@@ -226,9 +235,9 @@ public class WebappConfig extends WebMvcConfigurerAdapter { ...@@ -226,9 +235,9 @@ public class WebappConfig extends WebMvcConfigurerAdapter {
@Configuration @Configuration
@ConditionalOnClass({ Hazelcast.class }) @ConditionalOnClass({ Hazelcast.class })
@ConditionalOnExpression("${spring.boot.admin.hazelcast.enable:true}") @ConditionalOnProperty(prefix = "spring.boot.admin.hazelcast", name = "enable", matchIfMissing = true)
@AutoConfigureBefore(SimpleConfig.class) @AutoConfigureBefore(SimpleStoreConfig.class)
public static class HazelcastConfig { public static class HazelcastStoreConfig {
@Value("${spring.boot.admin.hazelcast.map:spring-boot-admin-application-store}") @Value("${spring.boot.admin.hazelcast.map:spring-boot-admin-application-store}")
private String hazelcastMapName; private String hazelcastMapName;
...@@ -303,4 +312,29 @@ public class WebappConfig extends WebMvcConfigurerAdapter { ...@@ -303,4 +312,29 @@ public class WebappConfig extends WebMvcConfigurerAdapter {
} }
} }
@Configuration
@ConditionalOnClass({ DiscoveryClient.class })
@ConditionalOnProperty(prefix = "spring.boot.admin.discovery", name = "enabled", matchIfMissing = true)
@AutoConfigureAfter({ NoopDiscoveryClientAutoConfiguration.class })
public static class DiscoveryConfig {
@Value("${spring.boot.admin.discovery.management.context-path:}")
private String managementPath;
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private ApplicationRegistry registry;
@Bean
ApplicationListener<ApplicationEvent> applicationDiscoveryListener() {
ApplicationDiscoveryListener listener = new ApplicationDiscoveryListener(
discoveryClient,
registry);
listener.setManagementContextPath(managementPath);
return listener;
}
}
} }
...@@ -56,14 +56,14 @@ public class RegistryController { ...@@ -56,14 +56,14 @@ public class RegistryController {
@RequestMapping(method = RequestMethod.POST) @RequestMapping(method = RequestMethod.POST)
public ResponseEntity<Application> register(@RequestBody Application app) { public ResponseEntity<Application> register(@RequestBody Application app) {
LOGGER.debug("Register application {}", app.toString()); LOGGER.debug("Register application {}", app.toString());
Application registeredApp = registry.register(app); Application registeredApp = registry.register(app);
return new ResponseEntity<Application>(registeredApp, HttpStatus.CREATED); return new ResponseEntity<Application>(registeredApp, HttpStatus.CREATED);
} }
/** /**
* List all registered applications with name * List all registered applications with name
* * @param name the name to search for
* @return List. * @return List
*/ */
@RequestMapping(method = RequestMethod.GET) @RequestMapping(method = RequestMethod.GET)
public Collection<Application> applications(@RequestParam(value = "name", required = false) String name) { public Collection<Application> applications(@RequestParam(value = "name", required = false) String name) {
...@@ -97,6 +97,7 @@ public class RegistryController { ...@@ -97,6 +97,7 @@ public class RegistryController {
* Unregister an application within this admin application. * Unregister an application within this admin application.
* *
* @param id The application id. * @param id The application id.
* @return the unregistered application.
*/ */
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE) @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
public ResponseEntity<Application> unregister(@PathVariable String id) { public ResponseEntity<Application> unregister(@PathVariable String id) {
......
/*
* Copyright 2014 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 de.codecentric.boot.admin.discovery;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
import org.springframework.cloud.client.discovery.event.HeartbeatMonitor;
import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;
import org.springframework.cloud.client.discovery.event.ParentHeartbeatEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
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
*/
public class ApplicationDiscoveryListener implements ApplicationListener<ApplicationEvent> {
private final DiscoveryClient discoveryClient;
private final ApplicationRegistry registry;
private final HeartbeatMonitor monitor = new HeartbeatMonitor();
private String managementContextPath = "";
public ApplicationDiscoveryListener(DiscoveryClient discoveryClient, ApplicationRegistry registry) {
this.discoveryClient = discoveryClient;
this.registry = registry;
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof InstanceRegisteredEvent) {
discover();
}
else if (event instanceof ParentHeartbeatEvent) {
ParentHeartbeatEvent e = (ParentHeartbeatEvent) event;
discoverIfNeeded(e.getValue());
}
else if (event instanceof HeartbeatEvent) {
HeartbeatEvent e = (HeartbeatEvent) event;
discoverIfNeeded(e.getValue());
}
}
private void discoverIfNeeded(Object value) {
if (this.monitor.update(value)) {
discover();
}
}
public void discover() {
for (String serviceId : discoveryClient.getServices()) {
for (ServiceInstance instance : discoveryClient.getInstances(serviceId)) {
registry.register(convert(instance));
}
}
}
private Application convert(ServiceInstance instance) {
String url = instance.getUri()
.resolve(managementContextPath.startsWith("/") ? managementContextPath : "/" + managementContextPath)
.toString();
return new Application(url, instance.getServiceId());
}
public void setManagementContextPath(String managementContextPath) {
this.managementContextPath = managementContextPath;
}
}
\ No newline at end of file
/*
* Copyright 2014 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 de.codecentric.boot.admin.event; package de.codecentric.boot.admin.event;
import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEvent;
......
/*
* Copyright 2014 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 de.codecentric.boot.admin.event; package de.codecentric.boot.admin.event;
import de.codecentric.boot.admin.model.Application; import de.codecentric.boot.admin.model.Application;
/**
* * This event gets emitted when an application is registered.
* @author Johannes Stelzer
*/
public class ClientApplicationRegisteredEvent extends ClientApplicationEvent { public class ClientApplicationRegisteredEvent extends ClientApplicationEvent {
public ClientApplicationRegisteredEvent(Object source, Application application) { public ClientApplicationRegisteredEvent(Object source, Application application) {
super(source, application); super(source, application);
......
/*
* Copyright 2014 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 de.codecentric.boot.admin.event; package de.codecentric.boot.admin.event;
import de.codecentric.boot.admin.model.Application; import de.codecentric.boot.admin.model.Application;
/**
* This event gets emitted when an application is unregistered.
* @author Johannes Stelzer
*/
public class ClientApplicationUnregisteredEvent extends ClientApplicationEvent { public class ClientApplicationUnregisteredEvent extends ClientApplicationEvent {
public ClientApplicationUnregisteredEvent(Object source, Application application) { public ClientApplicationUnregisteredEvent(Object source, Application application) {
super(source, application); super(source, application);
......
...@@ -49,7 +49,8 @@ public class ApplicationRegistry implements ApplicationContextAware { ...@@ -49,7 +49,8 @@ public class ApplicationRegistry implements ApplicationContextAware {
/** /**
* Register application. * Register application.
* *
* @param app The Application. * @param app application to be registered.
* @return the registered application.
*/ */
public Application register(Application app) { public Application register(Application app) {
Validate.notNull(app, "Application must not be null"); Validate.notNull(app, "Application must not be null");
...@@ -93,7 +94,7 @@ public class ApplicationRegistry implements ApplicationContextAware { ...@@ -93,7 +94,7 @@ public class ApplicationRegistry implements ApplicationContextAware {
/** /**
* Get a list of all registered applications. * Get a list of all registered applications.
* *
* @return List. * @return List of all applications.
*/ */
public Collection<Application> getApplications() { public Collection<Application> getApplications() {
return store.findAll(); return store.findAll();
...@@ -102,7 +103,8 @@ public class ApplicationRegistry implements ApplicationContextAware { ...@@ -102,7 +103,8 @@ public class ApplicationRegistry implements ApplicationContextAware {
/** /**
* Get a list of all registered applications. * Get a list of all registered applications.
* *
* @return List. * @param name the name to search for.
* @return List of applications with the given name.
*/ */
public Collection<Application> getApplicationsByName(String name) { public Collection<Application> getApplicationsByName(String name) {
return store.findByName(name); return store.findByName(name);
...@@ -121,7 +123,7 @@ public class ApplicationRegistry implements ApplicationContextAware { ...@@ -121,7 +123,7 @@ public class ApplicationRegistry implements ApplicationContextAware {
/** /**
* Remove a specific application from registry * Remove a specific application from registry
* *
* @param id * @param id the applications id to unregister
* @return the unregistered Application * @return the unregistered Application
*/ */
public Application unregister(String id) { public Application unregister(String id) {
......
...@@ -17,6 +17,7 @@ package de.codecentric.boot.admin.registry; ...@@ -17,6 +17,7 @@ package de.codecentric.boot.admin.registry;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import de.codecentric.boot.admin.model.Application; import de.codecentric.boot.admin.model.Application;
...@@ -25,7 +26,7 @@ import de.codecentric.boot.admin.model.Application; ...@@ -25,7 +26,7 @@ import de.codecentric.boot.admin.model.Application;
*/ */
public class HashingApplicationUrlIdGenerator implements ApplicationIdGenerator { public class HashingApplicationUrlIdGenerator implements ApplicationIdGenerator {
private static final char[] HEX_CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', private static final char[] HEX_CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
'e', 'f' }; 'e', 'f' };
@Override @Override
public String generateId(Application a) { public String generateId(Application a) {
...@@ -33,7 +34,8 @@ public class HashingApplicationUrlIdGenerator implements ApplicationIdGenerator ...@@ -33,7 +34,8 @@ public class HashingApplicationUrlIdGenerator implements ApplicationIdGenerator
MessageDigest digest = MessageDigest.getInstance("SHA-1"); MessageDigest digest = MessageDigest.getInstance("SHA-1");
byte[] bytes = digest.digest(a.getUrl().getBytes(StandardCharsets.UTF_8)); byte[] bytes = digest.digest(a.getUrl().getBytes(StandardCharsets.UTF_8));
return new String(encodeHex(bytes, 0, 8)); return new String(encodeHex(bytes, 0, 8));
} catch (Exception e) { }
catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e); throw new IllegalStateException(e);
} }
} }
......
/*
* Copyright 2014 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 de.codecentric.boot.admin.zuul; package de.codecentric.boot.admin.zuul;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
...@@ -9,6 +24,10 @@ import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute; ...@@ -9,6 +24,10 @@ import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute;
import de.codecentric.boot.admin.model.Application; import de.codecentric.boot.admin.model.Application;
import de.codecentric.boot.admin.registry.ApplicationRegistry; import de.codecentric.boot.admin.registry.ApplicationRegistry;
/**
* RouteLocator to register all applications' routes to zuul
* @author Johannes Stelzer
*/
public class ApplicationRouteLocator extends ProxyRouteLocator { public class ApplicationRouteLocator extends ProxyRouteLocator {
private ApplicationRegistry registry; private ApplicationRegistry registry;
......
/*
* Copyright 2014 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 de.codecentric.boot.admin.zuul; package de.codecentric.boot.admin.zuul;
import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping; import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping;
...@@ -5,6 +20,11 @@ import org.springframework.context.ApplicationListener; ...@@ -5,6 +20,11 @@ import org.springframework.context.ApplicationListener;
import de.codecentric.boot.admin.event.ClientApplicationEvent; import de.codecentric.boot.admin.event.ClientApplicationEvent;
/**
* Listener to trigger recomputation of the applications' routes.
* @author Johannes Stelzer
*
*/
public class ApplicationRouteRefreshListener implements ApplicationListener<ClientApplicationEvent> { public class ApplicationRouteRefreshListener implements ApplicationListener<ClientApplicationEvent> {
private final ApplicationRouteLocator routeLocator; private final ApplicationRouteLocator routeLocator;
......
/*
* Copyright 2014 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 de.codecentric.boot.admin.discovery;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;
import org.springframework.cloud.client.discovery.event.ParentHeartbeatEvent;
import org.springframework.context.ApplicationContext;
import de.codecentric.boot.admin.model.Application;
import de.codecentric.boot.admin.registry.ApplicationRegistry;
import de.codecentric.boot.admin.registry.HashingApplicationUrlIdGenerator;
import de.codecentric.boot.admin.registry.store.SimpleApplicationStore;
public class ApplicationDiscoveryListenerTest {
ApplicationDiscoveryListener listener;
DiscoveryClient discovery;
ApplicationRegistry registry;
@Before
public void setup() {
registry = new ApplicationRegistry(new SimpleApplicationStore(), new HashingApplicationUrlIdGenerator());
registry.setApplicationContext(mock(ApplicationContext.class));
discovery = mock(DiscoveryClient.class);
listener = new ApplicationDiscoveryListener(discovery, registry);
}
@Test
public void test_register_and_convert() {
when(discovery.getServices()).thenReturn(Collections.singletonList("service"));
when(discovery.getInstances("service")).thenReturn(
Collections.<ServiceInstance> singletonList(new DefaultServiceInstance("service", "localhost", 80,
false)));
listener.onApplicationEvent(new InstanceRegisteredEvent<>(new Object(), null));
assertEquals(1, registry.getApplications().size());
Application application = registry.getApplications().iterator().next();
assertEquals("http://localhost:80", application.getUrl());
assertEquals("service", application.getName());
}
@Test
public void convert_mgmtContextPath() {
when(discovery.getServices()).thenReturn(Collections.singletonList("service"));
when(discovery.getInstances("service")).thenReturn(
Collections.<ServiceInstance> singletonList(new DefaultServiceInstance("service", "localhost", 80,
false)));
listener.setManagementContextPath("/mgmt");
listener.onApplicationEvent(new InstanceRegisteredEvent<>(new Object(), null));
assertEquals(1, registry.getApplications().size());
Application application = registry.getApplications().iterator().next();
assertEquals("http://localhost:80/mgmt", application.getUrl());
assertEquals("service", application.getName());
}
@Test
public void single_discovery_for_same_heartbeat() {
Object heartbeat = new Object();
listener.onApplicationEvent(new ParentHeartbeatEvent(new Object(), heartbeat));
when(discovery.getServices()).thenReturn(Collections.singletonList("service"));
when(discovery.getInstances("service")).thenReturn(
Collections.<ServiceInstance> singletonList(new DefaultServiceInstance("service", "localhost", 80,
false)));
listener.onApplicationEvent(new HeartbeatEvent(new Object(), heartbeat));
assertEquals(0, registry.getApplications().size());
listener.onApplicationEvent(new HeartbeatEvent(new Object(), new Object()));
assertEquals(1, registry.getApplications().size());
}
}
/*
* Copyright 2014 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 de.codecentric.boot.admin.zuul;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cloud.netflix.zuul.filters.ProxyRouteLocator;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import de.codecentric.boot.admin.model.Application;
import de.codecentric.boot.admin.registry.ApplicationRegistry;
public class ApplicationRouteLocatorTest {
ApplicationRouteLocator locator;
private ApplicationRegistry registry;
@Before
public void setup() {
registry = mock(ApplicationRegistry.class);
locator = new ApplicationRouteLocator("/", registry, new ZuulProperties(), "/api/applications");
}
@Test
public void locateRoutes() {
when(registry.getApplications()).thenReturn(
Collections.singletonList(new Application("http://localhost", "app1", "1234")));
locator.resetRoutes();
assertEquals(1, locator.getRoutes().size());
assertEquals("http://localhost", locator.getRoutes().get("/api/applications/1234/*/**"));
assertEquals(Collections.singleton("/api/applications/1234/*/**"), locator.getRoutePaths());
assertEquals(new ProxyRouteLocator.ProxyRouteSpec("api/applications/1234", "/*/**", "http://localhost",
"/api/applications/1234", null), locator.getMatchingRoute("/api/applications/1234/*/**"));
}
}
spring.resources.cachePeriod=3600
server.port=8080 server.port=8080
info.version=1.0.0 info.version=1.0.0
info.stage=test spring.application.name=spring-boot-admin-server-test
logging.file=/tmp/log.log
spring.application.name=spring-boot-admin-example
spring.boot.admin.url=http://localhost:8080 spring.boot.admin.url=http://localhost:8080
spring.boot.admin.hazelcast.enable=false spring.boot.admin.hazelcast.enable=false
\ No newline at end of file spring.boot.admin.discovery.enable=false
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment