Commit 272633e1 by Johannes Edmeier

Update to Brixton and add Eureka-Service converter

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
parent f8b9d82f
......@@ -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>
......@@ -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>
......@@ -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>
......@@ -273,5 +280,5 @@
<url>http://repo.spring.io/milestone</url>
</pluginRepository>
</pluginRepositories>
-->
<!-- -->
</project>
......@@ -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*.
......@@ -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
......@@ -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 ===
......@@ -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.
......@@ -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>
......@@ -38,11 +40,11 @@
<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>
......@@ -50,6 +52,12 @@
<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>
......
......@@ -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
......@@ -35,9 +41,6 @@ import de.codecentric.boot.admin.registry.ApplicationRegistry;
@AutoConfigureAfter({ NoopDiscoveryClientAutoConfiguration.class })
public class DiscoveryClientConfiguration {
@Value("${spring.boot.admin.discovery.management.context-path:}")
private String managementPath;
@Autowired
private DiscoveryClient discoveryClient;
......@@ -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();
}
}
}
......@@ -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;
......@@ -32,7 +38,6 @@ import org.springframework.context.annotation.Configuration;
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 })
......@@ -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
......@@ -68,28 +78,36 @@ public class RevereseZuulProxyConfiguration extends ZuulConfiguration {
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);
}
}
}
......
......@@ -22,7 +22,6 @@ 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.event.EventListener;
import org.springframework.util.StringUtils;
import de.codecentric.boot.admin.model.Application;
import de.codecentric.boot.admin.registry.ApplicationRegistry;
......@@ -30,21 +29,14 @@ 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) {
......@@ -82,33 +74,10 @@ public class ApplicationDiscoveryListener {
}
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;
}
}
\ 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.discovery;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.util.StringUtils;
import de.codecentric.boot.admin.model.Application;
/**
* Converts any {@link ServiceInstance}s to {@link Application}s.
*
* @author Johannes Edmeier
*/
public class DefaultServiceInstanceConverter implements ServiceInstanceConverter {
private String managementContextPath = "";
private String healthEndpointPath = "health";
@Override
public Application convert(ServiceInstance instance) {
String serviceUrl = instance.getUri().toString();
String managementUrl = append(serviceUrl, managementContextPath);
String healthUrl = append(managementUrl, healthEndpointPath);
return Application.create(instance.getServiceId()).withHealthUrl(healthUrl)
.withManagementUrl(managementUrl).withServiceUrl(serviceUrl).build();
}
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;
}
/**
* <code>management.context-path</code> to be appended to the url of the discovered service for
* the managment-url.
*
* @param managementContextPath the management context-path.
*/
public void setManagementContextPath(String managementContextPath) {
this.managementContextPath = managementContextPath;
}
/**
* path of the health-endpoint to be used for the health-url of the discovered service.
*
* @param healthEndpointPath the path for the health-endpoint.
*/
public void setHealthEndpointPath(String healthEndpointPath) {
this.healthEndpointPath = healthEndpointPath;
}
}
/*
* 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.netflix.eureka.EurekaDiscoveryClient.EurekaServiceInstance;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.netflix.appinfo.InstanceInfo;
import de.codecentric.boot.admin.model.Application;
/**
* Converts {@link EurekaServiceInstance}s to {@link Application}s
*
* @author Johannes Edmeier
*/
public class EurekaServiceInstanceConverter implements ServiceInstanceConverter {
@Override
public Application convert(ServiceInstance instance) {
Assert.isInstanceOf(EurekaServiceInstance.class, instance,
"serviceInstance must be of type EurekaServiceInstance");
return convert(((EurekaServiceInstance) instance).getInstanceInfo());
}
private Application convert(InstanceInfo instanceInfo) {
String mgmtUrl = instanceInfo.getHomePageUrl();
String mgmtPath = instanceInfo.getMetadata().get("management.context-path");
if (StringUtils.hasText(mgmtPath)) {
mgmtUrl = append(mgmtUrl, mgmtPath);
}
return Application.create(instanceInfo.getAppName())
.withHealthUrl(instanceInfo.getHealthCheckUrl()).withManagementUrl(mgmtUrl)
.withServiceUrl(instanceInfo.getHomePageUrl()).build();
}
private String append(String mgmtUrl, String mgmtPath) {
if (mgmtUrl.endsWith("/")) {
mgmtUrl = mgmtUrl.substring(0, mgmtUrl.length() - 1);
}
if (!mgmtPath.startsWith("/")) {
mgmtPath = "/" + mgmtPath;
}
return mgmtUrl + mgmtPath;
}
}
/*
* 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 de.codecentric.boot.admin.model.Application;
/**
* Converts {@link ServiceInstance}s to {@link Application}s.
*
* @author Johannes Edmeier
*/
public interface ServiceInstanceConverter {
/**
* Converts a service instance to a application to be registered.
*
* @param instance the service instance.
* @return Application
*/
Application convert(ServiceInstance instance);
}
......@@ -22,7 +22,7 @@ import de.codecentric.boot.admin.model.Application;
/**
* Abstract Event regearding spring boot admin clients
*
* @author Johannes Stelzer
* @author Johannes Edmeier
*/
public abstract class ClientApplicationEvent implements Serializable {
private static final long serialVersionUID = 1L;
......@@ -39,21 +39,21 @@ public abstract class ClientApplicationEvent implements Serializable {
}
/**
* Return the system time in milliseconds when the event happened.
* @return system time in milliseconds when the event happened.
*/
public final long getTimestamp() {
return this.timestamp;
}
/**
* Return the affected application.
* @return affected application.
*/
public Application getApplication() {
return application;
}
/**
* Return the event type (for JSON).
* @return event type (for JSON).
*/
public String getType() {
return type;
......
......@@ -15,17 +15,16 @@
*/
package de.codecentric.boot.admin.zuul;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute;
import org.springframework.cloud.netflix.zuul.filters.RefreshableRouteLocator;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.PathMatcher;
......@@ -37,17 +36,20 @@ import de.codecentric.boot.admin.registry.ApplicationRegistry;
/**
* RouteLocator to register all applications' routes to zuul
*
* @author Johannes Stelzer
* @author Johannes Edmeier
*/
public class ApplicationRouteLocator implements RouteLocator {
public class ApplicationRouteLocator implements RefreshableRouteLocator {
private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationRouteLocator.class);
private AtomicReference<Map<String, ZuulRoute>> routes = new AtomicReference<>();
private ApplicationRegistry registry;
private String prefix;
private PathMatcher pathMatcher = new AntPathMatcher();
private AtomicReference<List<Route>> routes = new AtomicReference<>();
private String prefix;
private String servletPath;
private String[] proxyEndpoints = { "/env", "/metrics", "/trace", "/dump", "/jolokia", "/info",
"/configprops", "/trace", "/activiti", "/logfile", "/refresh" };
private String[] proxyEndpoints = { "env", "metrics", "trace", "dump", "jolokia", "info",
"configprops", "trace", "activiti", "logfile", "refresh" };
public ApplicationRouteLocator(String servletPath, ApplicationRegistry registry,
String prefix) {
......@@ -56,68 +58,82 @@ public class ApplicationRouteLocator implements RouteLocator {
this.prefix = prefix;
}
private LinkedHashMap<String, ZuulRoute> locateRoutes() {
LinkedHashMap<String, ZuulRoute> locateRoutes = new LinkedHashMap<String, ZuulRoute>();
for (Application application : registry.getApplications()) {
String appPath = prefix + "/" + application.getId();
addRoute(locateRoutes, appPath + "/health/**", application.getHealthUrl());
protected List<Route> locateRoutes() {
Collection<Application> applications = registry.getApplications();
List<Route> locateRoutes = new ArrayList<Route>(
applications.size() * (proxyEndpoints.length + 1));
for (Application application : applications) {
addRoute(locateRoutes, application.getId(), "health", application.getHealthUrl());
if (!StringUtils.isEmpty(application.getManagementUrl())) {
for (String endpoint : proxyEndpoints) {
addRoute(locateRoutes, appPath + endpoint + "/**",
application.getManagementUrl() + endpoint);
addRoute(locateRoutes, application.getId(), endpoint,
application.getManagementUrl() + "/" + endpoint);
}
}
}
return locateRoutes;
}
private void addRoute(LinkedHashMap<String, ZuulRoute> locateRoutes, String path, String url) {
locateRoutes.put(path, new ZuulRoute(path, url));
private void addRoute(List<Route> locateRoutes, String applicationId, String endpoint,
String targetUrl) {
Route route = new Route(applicationId + "-" + endpoint, "/**", targetUrl,
prefix + applicationId + "/" + endpoint, false, null);
locateRoutes.add(route);
}
public ProxyRouteSpec getMatchingRoute(String path) {
@Override
public Route getMatchingRoute(final String path) {
LOGGER.debug("Finding route for path: {}", path);
LOGGER.debug("servletPath={}", this.servletPath);
if (StringUtils.hasText(this.servletPath) && !this.servletPath.equals("/")
&& path.startsWith(this.servletPath)) {
path = path.substring(this.servletPath.length());
if (this.routes.get() == null) {
this.routes.set(locateRoutes());
}
for (Entry<String, ZuulRoute> entry : this.routes.get().entrySet()) {
String pattern = entry.getKey();
LOGGER.debug("servletPath= {}", this.servletPath);
String adjustedPath = stripServletPath(path);
for (Route route : this.routes.get()) {
String pattern = route.getFullPath();
LOGGER.debug("Matching pattern: {}", pattern);
if (this.pathMatcher.match(pattern, path)) {
ZuulRoute route = entry.getValue();
int index = route.getPath().indexOf("*") - 1;
String routePrefix = route.getPath().substring(0, index);
String targetPath = path.substring(index, path.length());
return new ProxyRouteSpec(route.getId(), targetPath, route.getLocation(),
routePrefix);
if (this.pathMatcher.match(pattern, adjustedPath)) {
LOGGER.debug("route matched= {}", route);
return adjustPathRoute(route, adjustedPath);
}
}
return null;
}
@Override
public Collection<String> getRoutePaths() {
return getRoutes().keySet();
return null;
}
public void resetRoutes() {
this.routes.set(locateRoutes());
private Route adjustPathRoute(Route route, String path) {
String adjustedPath;
if (path.startsWith(route.getPrefix())) {
adjustedPath = path.substring(route.getPrefix().length());
} else {
adjustedPath = path;
}
return new Route(route.getId(), adjustedPath, route.getLocation(), route.getPrefix(),
route.getRetryable(), null);
}
public Map<String, String> getRoutes() {
@Override
public List<Route> getRoutes() {
if (this.routes.get() == null) {
this.routes.set(locateRoutes());
}
Map<String, String> values = new LinkedHashMap<>();
for (String key : this.routes.get().keySet()) {
String url = key;
values.put(url, this.routes.get().get(key).getLocation());
}
return values;
return new ArrayList<>(routes.get());
}
@Override
public void refresh() {
routes.set(locateRoutes());
}
@Override
public Collection<String> getIgnoredPaths() {
return Collections.emptyList();
}
......@@ -125,38 +141,21 @@ public class ApplicationRouteLocator implements RouteLocator {
public void setProxyEndpoints(String[] proxyEndpoints) {
for (String endpoint : proxyEndpoints) {
Assert.hasText(endpoint, "The proxyEndpoints must not contain null");
Assert.isTrue(endpoint.startsWith("/"), "All proxyEndpoints must start with '/'");
Assert.isTrue(!endpoint.startsWith("/"), "All proxyEndpoints must not start with '/'");
}
this.proxyEndpoints = proxyEndpoints.clone();
}
public static class ProxyRouteSpec {
private final String id;
private final String path;
private final String location;
private final String prefix;
public ProxyRouteSpec(String id, String path, String location, String prefix) {
this.id = id;
this.path = path;
this.location = location;
this.prefix = prefix;
}
public String getId() {
return id;
}
public String getPath() {
return path;
}
private String stripServletPath(final String path) {
String adjustedPath = path;
public String getLocation() {
return location;
if (StringUtils.hasText(servletPath)) {
if (!servletPath.equals("/")) {
adjustedPath = path.substring(this.servletPath.length());
}
}
public String getPrefix() {
return prefix;
}
LOGGER.debug("adjustedPath={}", path);
return adjustedPath;
}
}
/*
* 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 java.net.MalformedURLException;
import java.net.URL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UrlPathHelper;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.constants.ZuulHeaders;
import com.netflix.zuul.context.RequestContext;
import de.codecentric.boot.admin.zuul.ApplicationRouteLocator.ProxyRouteSpec;
/**
* @author Johannes Edmeier
*/
public class PreDecorationFilter extends ZuulFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(PreDecorationFilter.class);
private ApplicationRouteLocator routeLocator;
private boolean addProxyHeaders;
private UrlPathHelper urlPathHelper = new UrlPathHelper();
public PreDecorationFilter(ApplicationRouteLocator routeLocator, boolean addProxyHeaders) {
this.routeLocator = routeLocator;
this.addProxyHeaders = addProxyHeaders;
}
@Override
public int filterOrder() {
return 5;
}
@Override
public String filterType() {
return "pre";
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
ProxyRouteSpec route = this.routeLocator.getMatchingRoute(requestURI);
if (route != null) {
ctx.put("requestURI", route.getPath());
ctx.put("proxy", route.getId());
ctx.setRouteHost(getUrl(route.getLocation()));
ctx.addOriginResponseHeader("X-Zuul-Service", route.getLocation());
if (this.addProxyHeaders) {
ctx.addZuulRequestHeader("X-Forwarded-Host", ctx.getRequest().getServerName() + ":"
+ String.valueOf(ctx.getRequest().getServerPort()));
ctx.addZuulRequestHeader(ZuulHeaders.X_FORWARDED_PROTO,
ctx.getRequest().getScheme());
if (StringUtils.hasText(route.getPrefix())) {
ctx.addZuulRequestHeader("X-Forwarded-Prefix", route.getPrefix());
}
}
} else {
LOGGER.warn("No route found for uri: " + requestURI);
ctx.set("forward.to", requestURI);
}
return null;
}
private URL getUrl(String target) {
try {
return new URL(target);
} catch (MalformedURLException ex) {
throw new IllegalStateException("Target URL is malformed", ex);
}
}
}
......@@ -29,6 +29,9 @@
"name": "spring.boot.admin.discovery.management.context-path",
"type": "java.lang.String",
"description": "management-path suffix for discovered applications",
"defaultValue": ""
"defaultValue": "",
"deprecation" : {
"replacement" : "spring.boot.admin.discovery.converter.management-context-path"
}
}
]}
......@@ -51,21 +51,6 @@ import de.codecentric.boot.admin.model.Application;
* @author Dennis Schulte
*/
public class AdminApplicationHazelcastTest {
@Configuration
@EnableAutoConfiguration
@EnableAdminServer
public static class TestAdminApplication {
@Bean
public Config hazelcastConfig() {
return new Config()
.addMapConfig(new MapConfig("spring-boot-admin-application-store")
.setBackupCount(1).setEvictionPolicy(EvictionPolicy.NONE))
.addListConfig(new ListConfig("spring-boot-admin-application-store")
.setBackupCount(1).setMaxSize(1000));
}
}
private RestTemplate template = new TestRestTemplate();
private EmbeddedWebApplicationContext instance1;
private EmbeddedWebApplicationContext instance2;
......@@ -146,6 +131,20 @@ public class AdminApplicationHazelcastTest {
return (ResponseEntity<Collection<Application>>) getResponse;
}
@Configuration
@EnableAutoConfiguration
@EnableAdminServer
public static class TestAdminApplication {
@Bean
public Config hazelcastConfig() {
return new Config()
.addMapConfig(new MapConfig("spring-boot-admin-application-store")
.setBackupCount(1).setEvictionPolicy(EvictionPolicy.NONE))
.addListConfig(new ListConfig("spring-boot-admin-application-store")
.setBackupCount(1).setMaxSize(1000));
}
}
public static class ApplicationList extends ArrayList<Application> {
private static final long serialVersionUID = 1L;
// needed for JSON deserialization
......
......@@ -116,7 +116,7 @@ public class AdminServerWebConfigurationTest {
@Configuration
static class TestHazelcastConfig {
@Bean
Config config() {
public Config config() {
return new Config();
}
}
......
......@@ -36,11 +36,10 @@ import de.codecentric.boot.admin.registry.store.SimpleApplicationStore;
public class RegistryControllerTest {
private RegistryController controller;
private ApplicationRegistry registry;
@Before
public void setup() {
registry = new ApplicationRegistry(new SimpleApplicationStore(),
ApplicationRegistry registry = new ApplicationRegistry(new SimpleApplicationStore(),
new HashingApplicationUrlIdGenerator());
registry.setApplicationEventPublisher(Mockito.mock(ApplicationEventPublisher.class));
controller = new RegistryController(registry);
......
......@@ -37,10 +37,9 @@ import de.codecentric.boot.admin.registry.HashingApplicationUrlIdGenerator;
import de.codecentric.boot.admin.registry.store.SimpleApplicationStore;
public class ApplicationDiscoveryListenerTest {
ApplicationDiscoveryListener listener;
DiscoveryClient discovery;
ApplicationRegistry registry;
private ApplicationDiscoveryListener listener;
private DiscoveryClient discovery;
private ApplicationRegistry registry;
@Before
public void setup() {
......@@ -54,9 +53,8 @@ public class ApplicationDiscoveryListenerTest {
@Test
public void test_register_and_convert() {
when(discovery.getServices()).thenReturn(Collections.singletonList("service"));
when(discovery.getInstances("service")).thenReturn(
Collections.singletonList((ServiceInstance) new DefaultServiceInstance("service",
"localhost", 80, false)));
when(discovery.getInstances("service")).thenReturn(Collections.singletonList(
(ServiceInstance) new DefaultServiceInstance("service", "localhost", 80, false)));
listener.onInstanceRegistered(new InstanceRegisteredEvent<>(new Object(), null));
......@@ -70,35 +68,13 @@ public class ApplicationDiscoveryListenerTest {
}
@Test
public void convert_mgmtContextPath() {
when(discovery.getServices()).thenReturn(Collections.singletonList("service"));
when(discovery.getInstances("service")).thenReturn(
Collections.singletonList((ServiceInstance) new DefaultServiceInstance("service",
"localhost", 80, false)));
listener.setManagementContextPath("/mgmt");
listener.setServiceContextPath("/service");
listener.setHealthEndpoint("alive");
listener.onInstanceRegistered(new InstanceRegisteredEvent<>(new Object(), null));
assertEquals(1, registry.getApplications().size());
Application application = registry.getApplications().iterator().next();
assertEquals("http://localhost:80/mgmt/alive", application.getHealthUrl());
assertEquals("http://localhost:80/mgmt", application.getManagementUrl());
assertEquals("http://localhost:80/service", application.getServiceUrl());
assertEquals("service", application.getName());
}
@Test
public void single_discovery_for_same_heartbeat() {
Object heartbeat = new Object();
listener.onParentHeartbeat(new ParentHeartbeatEvent(new Object(), heartbeat));
when(discovery.getServices()).thenReturn(Collections.singletonList("service"));
when(discovery.getInstances("service")).thenReturn(
Collections.singletonList((ServiceInstance) new DefaultServiceInstance("service",
"localhost", 80, false)));
when(discovery.getInstances("service")).thenReturn(Collections.singletonList(
(ServiceInstance) new DefaultServiceInstance("service", "localhost", 80, false)));
listener.onApplicationEvent(new HeartbeatEvent(new Object(), heartbeat));
assertEquals(0, registry.getApplications().size());
......
package de.codecentric.boot.admin.discovery;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import de.codecentric.boot.admin.model.Application;
public class DefaultServiceInstanceConverterTest {
@Test
public void convert() {
DefaultServiceInstanceConverter converter = new DefaultServiceInstanceConverter();
converter.setHealthEndpointPath("ping");
converter.setManagementContextPath("mgmt");
ServiceInstance service = new DefaultServiceInstance("test", "localhost", 80, false);
Application application = converter.convert(service);
assertThat(application.getId(), nullValue());
assertThat(application.getName(), is("test"));
assertThat(application.getServiceUrl(), is("http://localhost:80"));
assertThat(application.getManagementUrl(), is("http://localhost:80/mgmt"));
assertThat(application.getHealthUrl(), is("http://localhost:80/mgmt/ping"));
}
}
package de.codecentric.boot.admin.discovery;
import static java.util.Collections.singletonMap;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Collections;
import org.junit.Test;
import org.springframework.cloud.netflix.eureka.EurekaDiscoveryClient.EurekaServiceInstance;
import com.netflix.appinfo.InstanceInfo;
import de.codecentric.boot.admin.model.Application;
public class EurekaServiceInstanceConverterTest {
@Test
public void convert() {
EurekaServiceInstanceConverter converter = new EurekaServiceInstanceConverter();
InstanceInfo instanceInfo = mock(InstanceInfo.class);
when(instanceInfo.getAppName()).thenReturn("test");
when(instanceInfo.getHomePageUrl()).thenReturn("http://localhost:80");
when(instanceInfo.getHealthCheckUrl()).thenReturn("http://localhost:80/mgmt/ping");
when(instanceInfo.getMetadata())
.thenReturn(singletonMap("management.context-path", "/mgmt"));
EurekaServiceInstance service = mock(EurekaServiceInstance.class);
when(service.getInstanceInfo()).thenReturn(instanceInfo);
Application application = converter.convert(service);
assertThat(application.getId(), nullValue());
assertThat(application.getName(), is("test"));
assertThat(application.getServiceUrl(), is("http://localhost:80"));
assertThat(application.getManagementUrl(), is("http://localhost:80/mgmt"));
assertThat(application.getHealthUrl(), is("http://localhost:80/mgmt/ping"));
// no management url in metadata
when(instanceInfo.getMetadata()).thenReturn(Collections.<String, String> emptyMap());
application = converter.convert(service);
assertThat(application.getManagementUrl(), is("http://localhost:80"));
}
}
......@@ -15,80 +15,96 @@
*/
package de.codecentric.boot.admin.zuul;
import static java.util.Collections.singleton;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.hamcrest.CoreMatchers.hasItems;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
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.Route;
import de.codecentric.boot.admin.model.Application;
import de.codecentric.boot.admin.registry.ApplicationRegistry;
import de.codecentric.boot.admin.zuul.ApplicationRouteLocator.ProxyRouteSpec;
public class ApplicationRouteLocatorTest {
ApplicationRouteLocator locator;
private ApplicationRouteLocator locator;
private ApplicationRegistry registry;
@Before
public void setup() {
registry = mock(ApplicationRegistry.class);
locator = new ApplicationRouteLocator("/", registry, "/api/applications");
locator.setProxyEndpoints(new String[] { "/env" });
locator = new ApplicationRouteLocator("/", registry, "/api/applications/");
locator.setProxyEndpoints(new String[] { "env" });
}
@Test
public void locateRoutes_healthOnly() {
public void getRoutes_healthOnly() {
when(registry.getApplications()).thenReturn(singletonList(Application.create("app1")
.withHealthUrl("http://localhost/health").withId("1234").build()));
locator.resetRoutes();
assertEquals(1, locator.getRoutes().size());
assertEquals(singleton("/api/applications/1234/health/**"), locator.getRoutePaths());
assertEquals("http://localhost/health",
locator.getRoutes().get("/api/applications/1234/health/**"));
ProxyRouteSpec route = locator.getMatchingRoute("/api/applications/1234/health");
assertEquals("api/applications/1234/health", route.getId());
assertEquals("", route.getPath());
assertEquals("http://localhost/health", route.getLocation());
assertEquals("/api/applications/1234/health", route.getPrefix());
assertEquals(asList(new Route("1234-health", "/**", "http://localhost/health",
"/api/applications/1234/health", false, null)), locator.getRoutes());
Route matchingRoute = locator.getMatchingRoute("/api/applications/1234/health");
assertEquals(new Route("1234-health", "", "http://localhost/health",
"/api/applications/1234/health", false, null), matchingRoute);
assertNull(locator.getMatchingRoute("/api/applications/1234/danger"));
}
@Test
public void locateRoutes() {
public void getRoutes() {
when(registry.getApplications()).thenReturn(
singletonList(Application.create("app1").withHealthUrl("http://localhost/health")
.withManagementUrl("http://localhost").withId("1234").build()));
locator.resetRoutes();
assertEquals(2, locator.getRoutes().size());
assertThat(locator.getRoutePaths(),
hasItems("/api/applications/1234/health/**", "/api/applications/1234/env/**"));
assertEquals(asList(
new Route("1234-health", "/**", "http://localhost/health",
"/api/applications/1234/health", false, null),
new Route("1234-env", "/**", "http://localhost/env", "/api/applications/1234/env",
false, null)),
locator.getRoutes());
assertEquals("http://localhost/health",
locator.getRoutes().get("/api/applications/1234/health/**"));
ProxyRouteSpec route = locator.getMatchingRoute("/api/applications/1234/health");
assertEquals("api/applications/1234/health", route.getId());
assertEquals("", route.getPath());
assertEquals("http://localhost/health", route.getLocation());
assertEquals("/api/applications/1234/health", route.getPrefix());
Route matchingHealth = locator.getMatchingRoute("/api/applications/1234/health");
assertEquals(new Route("1234-health", "", "http://localhost/health",
"/api/applications/1234/health", false, null), matchingHealth);
route = locator.getMatchingRoute("/api/applications/1234/env/reset");
assertEquals("api/applications/1234/env", route.getId());
assertEquals("/reset", route.getPath());
assertEquals("http://localhost/env", route.getLocation());
assertEquals("/api/applications/1234/env", route.getPrefix());
Route matchingEnv = locator.getMatchingRoute("/api/applications/1234/env/reset");
assertEquals(new Route("1234-env", "/reset", "http://localhost/env",
"/api/applications/1234/env", false, null), matchingEnv);
assertNull(locator.getMatchingRoute("/api/applications/1234/danger"));
}
@Test
public void ignoredPaths() {
assertEquals(emptyList(), locator.getIgnoredPaths());
}
@Test
public void refresh() {
when(registry.getApplications()).thenReturn(Collections.<Application> emptyList());
locator.refresh();
assertTrue(locator.getRoutes().isEmpty());
when(registry.getApplications()).thenReturn(singletonList(Application.create("app1")
.withHealthUrl("http://localhost/health").withId("1234").build()));
locator.refresh();
assertEquals(1, locator.getRoutes().size());
when(registry.getApplications()).thenReturn(Collections.<Application> emptyList());
locator.refresh();
assertTrue(locator.getRoutes().isEmpty());
}
}
/*
* 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 org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import com.netflix.zuul.context.RequestContext;
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.ApplicationStore;
import de.codecentric.boot.admin.registry.store.SimpleApplicationStore;
public class PreDecorationFilterTest {
private PreDecorationFilter filter;
private ApplicationRouteLocator routeLocator;
private MockHttpServletRequest request = new MockHttpServletRequest();
private ApplicationRegistry registry;
private ApplicationStore store;
@Before
public void init() {
store = new SimpleApplicationStore();
store.save(Application.create("test").withId("-id-").withHealthUrl("http://test/health")
.withManagementUrl("http://mgmt").build());
registry = new ApplicationRegistry(store, new HashingApplicationUrlIdGenerator());
routeLocator = new ApplicationRouteLocator("/", registry, "/proxied");
routeLocator.setProxyEndpoints(new String[] { "/foo" });
routeLocator.resetRoutes();
filter = new PreDecorationFilter(routeLocator, true);
RequestContext ctx = RequestContext.getCurrentContext();
ctx.clear();
ctx.setRequest(request);
}
@Test
public void basicProperties() throws Exception {
assertEquals(5, this.filter.filterOrder());
assertEquals(true, this.filter.shouldFilter());
assertEquals("pre", this.filter.filterType());
}
@Test
public void prefixRouteAddsHeader() throws Exception {
request.setRequestURI("/proxied/-id-/foo");
filter.run();
RequestContext ctx = RequestContext.getCurrentContext();
assertEquals("", ctx.get("requestURI"));
assertEquals("http://mgmt/foo", ctx.getRouteHost().toString());
assertEquals("localhost:80", ctx.getZuulRequestHeaders().get("x-forwarded-host"));
assertEquals("http", ctx.getZuulRequestHeaders().get("x-forwarded-proto"));
assertEquals("/proxied/-id-/foo", ctx.getZuulRequestHeaders().get("x-forwarded-prefix"));
}
}
server.port=8080
info.version=1.0.0
spring.application.name=spring-boot-admin-server-test
spring.boot.admin.url=http://localhost:8080
\ No newline at end of file
server:
port: 8080
spring:
application:
name: spring-boot-admin-server-test
info:
version: 1.0.0
---
spring:
profiles: default
boot:
admin:
url: http://localhost:8080
---
server:
port: 8761
spring:
profiles: eurekaServer
jmx:
enabled: false
boot:
admin:
contextPath: /admin
eureka:
client:
registerWithEureka: true
fetchRegistry: true
registry-fetch-interval-seconds: 5
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
nonSecurePort: 8761
home-page-url: http://${spring.cloud.client.hostname}:3333
health-check-url: http://${spring.cloud.client.hostname}:4444/customHealth
......@@ -78,12 +78,10 @@ public class ApplicationRegistrator {
new HttpEntity<Application>(self, HTTP_HEADERS), Map.class);
if (response.getStatusCode().equals(HttpStatus.CREATED)) {
if (registeredId.get() == null) {
if (registeredId.compareAndSet(null,
response.getBody().get("id").toString())) {
LOGGER.info("Application registered itself as {}", response.getBody());
return true;
}
if (registeredId.get() == null && registeredId.compareAndSet(null,
response.getBody().get("id").toString())) {
LOGGER.info("Application registered itself as {}", response.getBody());
return true;
}
LOGGER.debug("Application refreshed itself as {}", response.getBody());
......
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