Commit 60e4c259 by Johannes Edmeier

Use metadata for default service conversion

Use the metadata for health-/managament-url in the default service conversion. This allows custom health- and management-path per application. fixes #205
parent c37279e5
......@@ -270,9 +270,7 @@ The setup is explained <<discover-clients-via-spring-cloud-discovery,above>>.
The informations from the discovered services are converted by the `ServiceInstanceConverter`. Spring Boot Admin ships with a default and Eureka converter implementation. The correct one is selected by AutoConfiguration. You can use your own conversion by implementing the interface and adding the bean to your application context.
TIP: When *Eureka* discovery is active, 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.
TIP: If you want to customize the default conversion of services you can either add `health.path` and/or `mangament.context-path` entries to the services metadata. This allows you to set the health or management path per application. In case you want to configure this for all of your discovered services, you can use the `spring.boot.admin.discovery.converter.*`-properties for your Spring Boot Admin Server configuration. The services' metadata takes precedence over the server configuration.
.Discovery configuration options
|===
......@@ -282,10 +280,6 @@ TIP: When the default conversion kicks in, you can use the `spring.boot.admin.di
| Enables the DiscoveryClient-support for the admin server.
| `true`
| spring.boot.admin.discovery.management.context-path _(deprecated)_
| If set this will be appended to the service-url from the discovery information.
|
| spring.boot.admin.discovery.converter.management-context-path
| Will be appended to the service-url of the discovered service when the managment-url is converted by the `DefaultServiceInstanceConverter`.
|
......
......@@ -63,6 +63,7 @@ public class DiscoveryClientConfiguration {
public static class EurekaConverterConfiguration {
@Bean
@ConditionalOnMissingBean({ ServiceInstanceConverter.class })
@ConfigurationProperties(prefix = "spring.boot.admin.discovery.converter")
public EurekaServiceInstanceConverter serviceInstanceConverter() {
return new EurekaServiceInstanceConverter();
}
......
......@@ -18,6 +18,8 @@ package de.codecentric.boot.admin.discovery;
import java.util.HashSet;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
......@@ -35,6 +37,8 @@ import de.codecentric.boot.admin.registry.ApplicationRegistry;
* @author Johannes Edmeier
*/
public class ApplicationDiscoveryListener {
private static final Logger LOGGER = LoggerFactory
.getLogger(ApplicationDiscoveryListener.class);
private final DiscoveryClient discoveryClient;
private final ApplicationRegistry registry;
private final HeartbeatMonitor monitor = new HeartbeatMonitor();
......@@ -76,14 +80,23 @@ public class ApplicationDiscoveryListener {
for (String serviceId : discoveryClient.getServices()) {
for (ServiceInstance instance : discoveryClient.getInstances(serviceId)) {
if (!ignoredServices.contains(serviceId)) {
registry.register(convert(instance));
register(instance);
}
}
}
}
protected Application convert(ServiceInstance instance) {
return converter.convert(instance);
protected void register(ServiceInstance instance) {
try {
Application application = converter.convert(instance);
if (application != null) {
registry.register(application);
} else {
LOGGER.warn("No application for service {} registered", instance);
}
} catch (Exception ex) {
LOGGER.error("Couldn't register application for service {}", instance, ex);
}
}
public void setConverter(ServiceInstanceConverter converter) {
......@@ -93,4 +106,4 @@ public class ApplicationDiscoveryListener {
public void setIgnoredServices(Set<String> ignoredServices) {
this.ignoredServices = ignoredServices;
}
}
\ No newline at end of file
}
......@@ -15,43 +15,76 @@
*/
package de.codecentric.boot.admin.discovery;
import static org.apache.commons.lang.StringUtils.defaultIfEmpty;
import static org.apache.commons.lang.StringUtils.stripStart;
import java.net.URI;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponentsBuilder;
import de.codecentric.boot.admin.model.Application;
/**
* Converts any {@link ServiceInstance}s to {@link Application}s.
* Converts any {@link ServiceInstance}s to {@link Application}s. To customize the health- or
* management-url for all applications you can set healthEndpointPath or managementContextPath
* respectively. If you want to influence the url per service you can add
* <code>management.context-path</code> or <code>health.path</code> to the instances metadata.
*
* @author Johannes Edmeier
*/
public class DefaultServiceInstanceConverter implements ServiceInstanceConverter {
private static final String KEY_MANAGEMENT_PATH = "management.context-path";
private static final String KEY_HEALTH_PATH = "health.path";
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);
Application.Builder builder = Application.create(instance.getServiceId());
URI healthUrl = getHealthUrl(instance);
if (healthUrl != null) {
builder.withHealthUrl(healthUrl.toString());
}
return Application.create(instance.getServiceId()).withHealthUrl(healthUrl)
.withManagementUrl(managementUrl).withServiceUrl(serviceUrl).build();
}
URI managementUrl = getManagementUrl(instance);
if (managementUrl != null) {
builder.withManagementUrl(managementUrl.toString());
}
protected final String append(String uri, String path) {
String baseUri = uri.replaceFirst("/+$", "");
if (StringUtils.isEmpty(path)) {
return baseUri;
URI serviceUrl = getServiceUrl(instance);
if (serviceUrl != null) {
builder.withServiceUrl(serviceUrl.toString());
}
String normPath = path.replaceFirst("^/+", "").replaceFirst("/+$", "");
return baseUri + "/" + normPath;
return builder.build();
}
protected URI getHealthUrl(ServiceInstance instance) {
String healthPath = defaultIfEmpty(
instance.getMetadata().get(KEY_HEALTH_PATH), healthEndpointPath);
healthPath = stripStart(healthPath, "/");
return UriComponentsBuilder.fromUri(getManagementUrl(instance)).pathSegment(healthPath)
.build().toUri();
}
protected URI getManagementUrl(ServiceInstance instance) {
String managamentPath = defaultIfEmpty(
instance.getMetadata().get(KEY_MANAGEMENT_PATH), managementContextPath);
managamentPath = stripStart(managamentPath, "/");
return UriComponentsBuilder.fromUri(getServiceUrl(instance)).pathSegment(managamentPath)
.build().toUri();
}
protected URI getServiceUrl(ServiceInstance instance) {
return instance.getUri();
}
/**
* <code>management.context-path</code> to be appended to the url of the discovered service for
* the managment-url.
* Default <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.
*/
......@@ -60,7 +93,7 @@ public class DefaultServiceInstanceConverter implements ServiceInstanceConverter
}
/**
* path of the health-endpoint to be used for the health-url of the discovered service.
* Default path of the health-endpoint to be used for the health-url of the discovered service.
*
* @param healthEndpointPath the path for the health-endpoint.
*/
......
......@@ -15,12 +15,11 @@
*/
package de.codecentric.boot.admin.discovery;
import java.net.URI;
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;
......@@ -29,35 +28,13 @@ import de.codecentric.boot.admin.model.Application;
*
* @author Johannes Edmeier
*/
public class EurekaServiceInstanceConverter implements ServiceInstanceConverter {
public class EurekaServiceInstanceConverter extends DefaultServiceInstanceConverter {
@Override
public Application convert(ServiceInstance instance) {
protected URI getHealthUrl(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;
return URI.create(((EurekaServiceInstance) instance).getInstanceInfo().getHealthCheckUrl());
}
}
......@@ -24,14 +24,5 @@
"type": "java.lang.Boolean",
"description": "Enable Spring Cloud Discovery support.",
"defaultValue": "true"
},
{
"name": "spring.boot.admin.discovery.management.context-path",
"type": "java.lang.String",
"description": "management-path suffix for discovered applications",
"defaultValue": "",
"deprecation" : {
"replacement" : "spring.boot.admin.discovery.converter.management-context-path"
}
}
]}
......@@ -13,7 +13,19 @@ import de.codecentric.boot.admin.model.Application;
public class DefaultServiceInstanceConverterTest {
@Test
public void convert() {
public void test_convert_with_defaults() {
ServiceInstance service = new DefaultServiceInstance("test", "localhost", 80, false);
Application application = new DefaultServiceInstanceConverter().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"));
assertThat(application.getHealthUrl(), is("http://localhost:80/health"));
}
@Test
public void test_convert_with_custom_defaults() {
DefaultServiceInstanceConverter converter = new DefaultServiceInstanceConverter();
converter.setHealthEndpointPath("ping");
converter.setManagementContextPath("mgmt");
......@@ -28,4 +40,19 @@ public class DefaultServiceInstanceConverterTest {
assertThat(application.getHealthUrl(), is("http://localhost:80/mgmt/ping"));
}
@Test
public void test_convert_with_metadata() {
ServiceInstance service = new DefaultServiceInstance("test", "localhost", 80, false);
service.getMetadata().put("health.path", "ping");
service.getMetadata().put("management.context-path", "mgmt");
Application application = new DefaultServiceInstanceConverter().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"));
}
}
......@@ -7,6 +7,7 @@ import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.net.URI;
import java.util.Collections;
import org.junit.Test;
......@@ -20,17 +21,15 @@ 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);
when(service.getUri()).thenReturn(URI.create("http://localhost:80"));
when(service.getServiceId()).thenReturn("test");
when(service.getMetadata()).thenReturn(singletonMap("management.context-path", "/mgmt"));
Application application = converter.convert(service);
Application application = new EurekaServiceInstanceConverter().convert(service);
assertThat(application.getId(), nullValue());
assertThat(application.getName(), is("test"));
......@@ -39,8 +38,8 @@ public class EurekaServiceInstanceConverterTest {
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);
when(service.getMetadata()).thenReturn(Collections.<String, String> emptyMap());
application = new EurekaServiceInstanceConverter().convert(service);
assertThat(application.getManagementUrl(), is("http://localhost:80"));
}
}
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