Commit ba760e20 by Mike Liu Committed by Johannes Edmeier

Allow registration on multiple servers

With this commit you can from now on register the application on multiple servers. You need to set spring.boot.admin.register-once to false and assign a list of urls to spring.boot.admin.url
parent c6e45d74
...@@ -196,7 +196,7 @@ The Spring Boot Admin Client registers the application at the admin server. This ...@@ -196,7 +196,7 @@ The Spring Boot Admin Client registers the application at the admin server. This
| `true` | `true`
| spring.boot.admin.url | spring.boot.admin.url
| List of URLs of the Spring Boot Admin server to register at. This triggers the AutoConfiguration. *Mandatory*. | Comma separated ordered 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
...@@ -220,6 +220,10 @@ spring.boot.admin.password ...@@ -220,6 +220,10 @@ spring.boot.admin.password
| Switch to enable auto-deregistration at Spring Boot Admin server when context is closed. | Switch to enable auto-deregistration at Spring Boot Admin server when context is closed.
| `false` | `false`
| spring.boot.admin.register-once
| If set to true the client will only register against one admin server (in order defined by `spring.boot.admin.url`); if that admin server goes down, will automatically register against the next admin server. If false, will register against all admin servers.
| `true`
| spring.boot.admin.client.health-url | spring.boot.admin.client.health-url
| Client-health-url to register with. Can be overridden 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`.
......
...@@ -21,7 +21,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; ...@@ -21,7 +21,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
public class AdminProperties { public class AdminProperties {
/** /**
* The admin servers url to register at * The admin server urls to register at
*/ */
private String[] url; private String[] url;
...@@ -55,6 +55,11 @@ public class AdminProperties { ...@@ -55,6 +55,11 @@ public class AdminProperties {
*/ */
private boolean autoRegistration = true; private boolean autoRegistration = true;
/**
* Enable registration against one or all admin servers
*/
private boolean registerOnce = true;
public void setUrl(String[] url) { public void setUrl(String[] url) {
this.url = url.clone(); this.url = url.clone();
} }
...@@ -118,4 +123,12 @@ public class AdminProperties { ...@@ -118,4 +123,12 @@ public class AdminProperties {
public void setAutoRegistration(boolean autoRegistration) { public void setAutoRegistration(boolean autoRegistration) {
this.autoRegistration = autoRegistration; this.autoRegistration = autoRegistration;
} }
public boolean isRegisterOnce() {
return registerOnce;
}
public void setRegisterOnce(boolean registerOnce) {
this.registerOnce = registerOnce;
}
} }
...@@ -41,7 +41,7 @@ public class ApplicationRegistrator { ...@@ -41,7 +41,7 @@ public class ApplicationRegistrator {
private static HttpHeaders HTTP_HEADERS = createHttpHeaders(); private static HttpHeaders HTTP_HEADERS = createHttpHeaders();
private final AtomicReference<String> registeredId = new AtomicReference<String>(); private final AtomicReference<String> registeredId = new AtomicReference<>();
private AdminClientProperties client; private AdminClientProperties client;
...@@ -66,26 +66,28 @@ public class ApplicationRegistrator { ...@@ -66,26 +66,28 @@ public class ApplicationRegistrator {
/** /**
* Registers the client application at spring-boot-admin-server. * Registers the client application at spring-boot-admin-server.
* *
* @return true if successful * @return true if successful registration on at least one admin server
*/ */
public boolean register() { public boolean register() {
boolean isRegistrationSuccessful = false;
Application self = createApplication(); Application self = createApplication();
for (String adminUrl : admin.getAdminUrl()) { for (String adminUrl : admin.getAdminUrl()) {
try { try {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
ResponseEntity<Map> response = template.postForEntity(adminUrl, ResponseEntity<Map> response = template.postForEntity(adminUrl,
new HttpEntity<Application>(self, HTTP_HEADERS), Map.class); new HttpEntity<>(self, HTTP_HEADERS), Map.class);
if (response.getStatusCode().equals(HttpStatus.CREATED)) { if (response.getStatusCode().equals(HttpStatus.CREATED)) {
if (registeredId.get() == null && registeredId.compareAndSet(null, if (registeredId.compareAndSet(null, response.getBody().get("id").toString())) {
response.getBody().get("id").toString())) {
LOGGER.info("Application registered itself as {}", response.getBody()); LOGGER.info("Application registered itself as {}", response.getBody());
return true; } else {
LOGGER.debug("Application refreshed itself as {}", response.getBody());
} }
LOGGER.debug("Application refreshed itself as {}", response.getBody()); isRegistrationSuccessful = true;
return true; if (admin.isRegisterOnce()) {
break;
}
} 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());
...@@ -96,7 +98,7 @@ public class ApplicationRegistrator { ...@@ -96,7 +98,7 @@ public class ApplicationRegistrator {
} }
} }
return false; return isRegistrationSuccessful;
} }
public void deregister() { public void deregister() {
...@@ -105,8 +107,10 @@ public class ApplicationRegistrator { ...@@ -105,8 +107,10 @@ public class ApplicationRegistrator {
for (String adminUrl : admin.getAdminUrl()) { for (String adminUrl : admin.getAdminUrl()) {
try { try {
template.delete(adminUrl + "/" + id); template.delete(adminUrl + "/" + id);
registeredId.set(null); registeredId.compareAndSet(id, null);
return; if (admin.isRegisterOnce()) {
break;
}
} catch (Exception ex) { } catch (Exception ex) {
LOGGER.warn( LOGGER.warn(
"Failed to deregister application (id={}) at spring-boot-admin ({}): {}", "Failed to deregister application (id={}) at spring-boot-admin ({}): {}",
......
...@@ -42,6 +42,7 @@ import de.codecentric.boot.admin.model.Application; ...@@ -42,6 +42,7 @@ import de.codecentric.boot.admin.model.Application;
public class ApplicationRegistratorTest { public class ApplicationRegistratorTest {
private AdminProperties adminProps;
private ApplicationRegistrator registrator; private ApplicationRegistrator registrator;
private RestTemplate restTemplate; private RestTemplate restTemplate;
private HttpHeaders headers; private HttpHeaders headers;
...@@ -50,7 +51,7 @@ public class ApplicationRegistratorTest { ...@@ -50,7 +51,7 @@ public class ApplicationRegistratorTest {
public void setup() { public void setup() {
restTemplate = mock(RestTemplate.class); restTemplate = mock(RestTemplate.class);
AdminProperties adminProps = new AdminProperties(); adminProps = new AdminProperties();
adminProps.setUrl(new String[] { "http://sba:8080", "http://sba2:8080" }); adminProps.setUrl(new String[] { "http://sba:8080", "http://sba2:8080" });
AdminClientProperties clientProps = new AdminClientProperties(); AdminClientProperties clientProps = new AdminClientProperties();
...@@ -76,7 +77,7 @@ public class ApplicationRegistratorTest { ...@@ -76,7 +77,7 @@ public class ApplicationRegistratorTest {
assertTrue(registrator.register()); assertTrue(registrator.register());
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.create("AppName")
.withHealthUrl("http://localhost:8080/health") .withHealthUrl("http://localhost:8080/health")
.withManagementUrl("http://localhost:8080/mgmt") .withManagementUrl("http://localhost:8080/mgmt")
.withServiceUrl("http://localhost:8080").build(), headers), .withServiceUrl("http://localhost:8080").build(), headers),
...@@ -114,4 +115,72 @@ public class ApplicationRegistratorTest { ...@@ -114,4 +115,72 @@ public class ApplicationRegistratorTest {
verify(restTemplate).delete("http://sba:8080/api/applications/-id-"); verify(restTemplate).delete("http://sba:8080/api/applications/-id-");
} }
@SuppressWarnings("rawtypes")
@Test
public void register_multiple() {
adminProps.setRegisterOnce(false);
when(restTemplate.postForEntity(isA(String.class), isA(HttpEntity.class), eq(Map.class)))
.thenReturn(new ResponseEntity<Map>(Collections.singletonMap("id", "-id-"),
HttpStatus.CREATED));
assertTrue(registrator.register());
verify(restTemplate)
.postForEntity("http://sba:8080/api/applications",
new HttpEntity<>(Application.create("AppName")
.withHealthUrl("http://localhost:8080/health")
.withManagementUrl("http://localhost:8080/mgmt")
.withServiceUrl("http://localhost:8080").build(), headers),
Map.class);
verify(restTemplate)
.postForEntity("http://sba2:8080/api/applications",
new HttpEntity<>(Application.create("AppName")
.withHealthUrl("http://localhost:8080/health")
.withManagementUrl("http://localhost:8080/mgmt")
.withServiceUrl("http://localhost:8080").build(), headers),
Map.class);
}
@SuppressWarnings("rawtypes")
@Test
public void register_multiple_one_failure() {
adminProps.setRegisterOnce(false);
when(restTemplate.postForEntity(isA(String.class), isA(HttpEntity.class), eq(Map.class)))
.thenReturn(new ResponseEntity<Map>(Collections.singletonMap("id", "-id-"),
HttpStatus.CREATED))
.thenThrow(new RestClientException("Error"));
assertTrue(registrator.register());
verify(restTemplate)
.postForEntity("http://sba:8080/api/applications",
new HttpEntity<>(Application.create("AppName")
.withHealthUrl("http://localhost:8080/health")
.withManagementUrl("http://localhost:8080/mgmt")
.withServiceUrl("http://localhost:8080").build(), headers),
Map.class);
verify(restTemplate)
.postForEntity("http://sba2:8080/api/applications",
new HttpEntity<>(Application.create("AppName")
.withHealthUrl("http://localhost:8080/health")
.withManagementUrl("http://localhost:8080/mgmt")
.withServiceUrl("http://localhost:8080").build(), headers),
Map.class);
}
@Test
public void register_multiple_all_failures() {
adminProps.setRegisterOnce(false);
when(restTemplate.postForEntity(isA(String.class), isA(HttpEntity.class), eq(Map.class)))
.thenThrow(new RestClientException("Error"))
.thenThrow(new RestClientException("Error"));
assertFalse(registrator.register());
}
} }
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