Commit 25b3e509 by Johannes Edmeier

Add option for fallback servers to register at

In case you have clustered admin servers, you can now specify multiple urls for the client to register at. So that in the case the first server is down it will register at the next one specified in the list. This allows you to do cluster- ing without the need for a load-balanced host.
parent 9250b263
...@@ -187,7 +187,7 @@ The Spring Boot Admin Client registers the application at the admin server. This ...@@ -187,7 +187,7 @@ The Spring Boot Admin Client registers the application at the admin server. This
| Property name |Description |Default value | Property name |Description |Default value
| spring.boot.admin.url | spring.boot.admin.url
| URL of the spring-boot-admin application to register at. This triggers the AutoConfiguration. *Mandatory*. | List of URLs of the Spring Boot Admin server to register at. This triggers the AutoConfiguration. *Mandatory*.
| |
| spring.boot.admin.api-path | spring.boot.admin.api-path
...@@ -204,19 +204,19 @@ spring.boot.admin.password ...@@ -204,19 +204,19 @@ spring.boot.admin.password
| `10.000` | `10.000`
| spring.boot.admin.auto-deregistration | spring.boot.admin.auto-deregistration
| Swtich to enable auto-deregistration at admin when context is closed. | Switch to enable auto-deregistration at Spring Boot Admin server when context is closed.
| `false` | `false`
| spring.boot.admin.client.health-url | spring.boot.admin.client.health-url
| Client-health-url to register with. Can be overriden in case the reachable URL is different (e.g. Docker). Must be unique in registry. | Client-health-url to register with. Can be overridden in case the reachable URL is different (e.g. Docker). Must be unique in registry.
| Guessed based on management-url and `endpoints.health.id`. | Guessed based on management-url and `endpoints.health.id`.
| spring.boot.admin.client.management-url | spring.boot.admin.client.management-url
| Client-management-url to register with. Can be overriden in case the reachable url is different (e.g. Docker). | Client-management-url to register with. Can be overridden in case the reachable url is different (e.g. Docker).
| Guessed based on service-url, `server.servlet-path`, `management.port` and `management.context-path`. | Guessed based on service-url, `server.servlet-path`, `management.port` and `management.context-path`.
| spring.boot.admin.client.service-url | spring.boot.admin.client.service-url
| Client-service-url to register with. Can be overriden in case the reachable url is different (e.g. Docker). | Client-service-url to register with. Can be overridden in case the reachable url is different (e.g. Docker).
| Guessed based on hostname, `server.port` and `server.context-path`. | Guessed based on hostname, `server.port` and `server.context-path`.
| spring.boot.admin.client.name | spring.boot.admin.client.name
...@@ -236,7 +236,7 @@ spring.boot.admin.password ...@@ -236,7 +236,7 @@ spring.boot.admin.password
| Property name |Description |Default value | Property name |Description |Default value
| spring.boot.admin.context-path | spring.boot.admin.context-path
| The context-path prefixes the path where the Admin Servers statics assets and api should be served. Relative to the Dispatcher-Servlet. | The context-path prefixes the path where the Admin Servers statics assets and API should be served. Relative to the Dispatcher-Servlet.
| |
| spring.boot.admin.monitor.period | spring.boot.admin.monitor.period
...@@ -244,7 +244,7 @@ spring.boot.admin.password ...@@ -244,7 +244,7 @@ spring.boot.admin.password
| 10.000 | 10.000
| spring.boot.admin.monitor.status-lifetime | spring.boot.admin.monitor.status-lifetime
| Lifetime of iapplication statuses in ms. The applications /health-endpoint will not be queried until the lifetime has expired. | Lifetime of application statuses in ms. The applications /health-endpoint will not be queried until the lifetime has expired.
| 10.000 | 10.000
|=== |===
...@@ -384,7 +384,7 @@ spring.boot.admin.notify.mail.to=admin@example.com ...@@ -384,7 +384,7 @@ spring.boot.admin.notify.mail.to=admin@example.com
[[pagerduty-notifications]] [[pagerduty-notifications]]
=== Pagerduty notifications === === Pagerduty notifications ===
To enable pagerduty notifications you just have to add a generic service to your pagerduty-account and set `spring.boot.admin.notify.pagerduty.service-key` to the service-key you recieved. To enable pagerduty notifications you just have to add a generic service to your pagerduty-account and set `spring.boot.admin.notify.pagerduty.service-key` to the service-key you received.
.Pagerduty notifications configuration options .Pagerduty notifications configuration options
|=== |===
...@@ -424,7 +424,7 @@ To enable pagerduty notifications you just have to add a generic service to your ...@@ -424,7 +424,7 @@ To enable pagerduty notifications you just have to add a generic service to your
[qanda] [qanda]
Can I include spring-boot-admin into my business application?:: Can I include spring-boot-admin into my business application?::
*tl;dr* You can, but you shouldn't. + *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 monitioring tool also does. 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?:: 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. 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.
......
...@@ -23,7 +23,7 @@ public class AdminProperties { ...@@ -23,7 +23,7 @@ public class AdminProperties {
/** /**
* The admin servers url to register at * The admin servers url to register at
*/ */
private String url; private String[] url;
/** /**
* The admin rest-apis path. * The admin rest-apis path.
...@@ -50,12 +50,12 @@ public class AdminProperties { ...@@ -50,12 +50,12 @@ public class AdminProperties {
*/ */
private boolean autoDeregistration; private boolean autoDeregistration;
public void setUrl(String url) { public void setUrl(String[] url) {
this.url = url; this.url = url.clone();
} }
public String getUrl() { public String[] getUrl() {
return url; return url.clone();
} }
public void setApiPath(String apiPath) { public void setApiPath(String apiPath) {
...@@ -66,8 +66,12 @@ public class AdminProperties { ...@@ -66,8 +66,12 @@ public class AdminProperties {
return apiPath; return apiPath;
} }
public String getAdminUrl() { public String[] getAdminUrl() {
return url + "/" + apiPath; String adminUrls[] = url.clone();
for (int i = 0; i < adminUrls.length; i++) {
adminUrls[i] += "/" + apiPath;
}
return adminUrls;
} }
public int getPeriod() { public int getPeriod() {
......
...@@ -69,31 +69,33 @@ public class ApplicationRegistrator { ...@@ -69,31 +69,33 @@ public class ApplicationRegistrator {
* @return true if successful * @return true if successful
*/ */
public boolean register() { public boolean register() {
Application self = null; Application self = createApplication();
try {
self = createApplication(); for (String adminUrl : admin.getAdminUrl()) {
try {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
ResponseEntity<Map> response = template.postForEntity(admin.getAdminUrl(), ResponseEntity<Map> response = template.postForEntity(adminUrl,
new HttpEntity<Application>(self, HTTP_HEADERS), Map.class); new HttpEntity<Application>(self, HTTP_HEADERS), Map.class);
if (response.getStatusCode().equals(HttpStatus.CREATED)) { if (response.getStatusCode().equals(HttpStatus.CREATED)) {
if (registeredId.get() == null) { if (registeredId.get() == null) {
if (registeredId.compareAndSet(null, response.getBody().get("id").toString())) { if (registeredId.compareAndSet(null,
LOGGER.info("Application registered itself as {}", response.getBody()); response.getBody().get("id").toString())) {
return true; LOGGER.info("Application registered itself as {}", response.getBody());
return true;
}
} }
}
LOGGER.debug("Application refreshed itself as {}", response.getBody()); LOGGER.debug("Application refreshed itself as {}", response.getBody());
return true; return true;
} else { } else {
LOGGER.warn("Application failed to registered itself as {}. Response: {}", self, LOGGER.warn("Application failed to registered itself as {}. Response: {}", self,
response.toString()); response.toString());
}
} catch (Exception ex) {
LOGGER.warn("Failed to register application as {} at spring-boot-admin ({}): {}",
self, admin.getAdminUrl(), ex.getMessage());
} }
} catch (Exception ex) {
LOGGER.warn("Failed to register application as {} at spring-boot-admin ({}): {}", self,
admin.getAdminUrl(), ex.getMessage());
} }
return false; return false;
...@@ -102,13 +104,16 @@ public class ApplicationRegistrator { ...@@ -102,13 +104,16 @@ public class ApplicationRegistrator {
public void deregister() { public void deregister() {
String id = registeredId.get(); String id = registeredId.get();
if (id != null) { if (id != null) {
try { for (String adminUrl : admin.getAdminUrl()) {
template.delete(admin.getAdminUrl() + "/" + id); try {
registeredId.set(null); template.delete(adminUrl + "/" + id);
} catch (Exception ex) { registeredId.set(null);
LOGGER.warn( return;
"Failed to deregister application (id={}) at spring-boot-admin ({}): {}", } catch (Exception ex) {
id, admin.getAdminUrl(), ex.getMessage()); LOGGER.warn(
"Failed to deregister application (id={}) at spring-boot-admin ({}): {}",
id, adminUrl, ex.getMessage());
}
} }
} }
} }
......
...@@ -51,7 +51,7 @@ public class ApplicationRegistratorTest { ...@@ -51,7 +51,7 @@ public class ApplicationRegistratorTest {
restTemplate = mock(RestTemplate.class); restTemplate = mock(RestTemplate.class);
AdminProperties adminProps = new AdminProperties(); AdminProperties adminProps = new AdminProperties();
adminProps.setUrl("http://sba:8080"); adminProps.setUrl(new String[] { "http://sba:8080", "http://sba2:8080" });
AdminClientProperties clientProps = new AdminClientProperties(); AdminClientProperties clientProps = new AdminClientProperties();
clientProps.setManagementUrl("http://localhost:8080/mgmt"); clientProps.setManagementUrl("http://localhost:8080/mgmt");
...@@ -73,9 +73,7 @@ public class ApplicationRegistratorTest { ...@@ -73,9 +73,7 @@ public class ApplicationRegistratorTest {
.thenReturn(new ResponseEntity<Map>(Collections.singletonMap("id", "-id-"), .thenReturn(new ResponseEntity<Map>(Collections.singletonMap("id", "-id-"),
HttpStatus.CREATED)); HttpStatus.CREATED));
boolean result = registrator.register(); assertTrue(registrator.register());
assertTrue(result);
verify(restTemplate) verify(restTemplate)
.postForEntity("http://sba:8080/api/applications", .postForEntity("http://sba:8080/api/applications",
new HttpEntity<Application>(Application.create("AppName") new HttpEntity<Application>(Application.create("AppName")
...@@ -90,9 +88,19 @@ public class ApplicationRegistratorTest { ...@@ -90,9 +88,19 @@ public class ApplicationRegistratorTest {
when(restTemplate.postForEntity(isA(String.class), isA(HttpEntity.class), when(restTemplate.postForEntity(isA(String.class), isA(HttpEntity.class),
eq(Application.class))).thenThrow(new RestClientException("Error")); eq(Application.class))).thenThrow(new RestClientException("Error"));
boolean result = registrator.register(); assertFalse(registrator.register());
}
@SuppressWarnings("rawtypes")
@Test
public void register_retry() {
when(restTemplate.postForEntity(isA(String.class), isA(HttpEntity.class),
eq(Application.class))).thenThrow(new RestClientException("Error"));
when(restTemplate.postForEntity(isA(String.class), isA(HttpEntity.class), eq(Map.class)))
.thenReturn(new ResponseEntity<Map>(Collections.singletonMap("id", "-id-"),
HttpStatus.CREATED));
assertFalse(result); assertTrue(registrator.register());
} }
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
......
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