Skip to content

Commit

Permalink
Merge pull request #2130 from AxonFramework/feature/1964
Browse files Browse the repository at this point in the history
[#1964] Include `AxonServerHealthIndicator`
  • Loading branch information
smcvb committed Mar 14, 2022
2 parents ee80f99 + f3a927c commit e635fc6
Show file tree
Hide file tree
Showing 21 changed files with 559 additions and 81 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2020. Axon Framework
* Copyright (c) 2010-2022. Axon Framework
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -29,14 +29,15 @@
import org.axonframework.lifecycle.Lifecycle;
import org.axonframework.lifecycle.Phase;

import javax.net.ssl.SSLException;
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import javax.net.ssl.SSLException;

import static org.axonframework.common.BuilderUtils.assertNonNull;

Expand All @@ -47,7 +48,7 @@
* @author Marc Gathier
* @since 4.0
*/
public class AxonServerConnectionManager implements Lifecycle {
public class AxonServerConnectionManager implements Lifecycle, ConnectionManager {

private final Map<String, AxonServerConnection> connections = new ConcurrentHashMap<>();
private final AxonServerConnectionFactory connectionFactory;
Expand Down Expand Up @@ -196,6 +197,13 @@ public Channel getChannel(String context) {
return ((ContextConnection) getConnection(context)).getManagedChannel();
}

@Override
public Map<String, Boolean> connections() {
return connections.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().isConnected()));
}

/**
* Builder class to instantiate an {@link AxonServerConnectionManager}.
* <p>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright (c) 2010-2022. Axon Framework
*
* 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 org.axonframework.axonserver.connector;

import java.util.Map;

/**
* Interface describing functionality for connection managers. A connection manager typically deals with a collection of
* connections per context the application is wired with.
*
* @author Steven van Beelen
* @author Milan Savic
* @author Sara Pelligrini
* @since 4.6.0
*/
public interface ConnectionManager {

/**
* Return the connections this instances manages. Consists of key-value pairs where the key resembles the context
* name and the value describes whether the connection is active at this moment.
*
* @return Return the connections this instances manages.
*/
Map<String, Boolean> connections();
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2020. Axon Framework
* Copyright (c) 2010-2022. Axon Framework
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -29,9 +29,7 @@
import org.axonframework.axonserver.connector.util.TcpUtil;
import org.axonframework.common.AxonConfigurationException;
import org.axonframework.config.TagsConfiguration;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.*;

import java.io.IOException;
import java.util.Collections;
Expand All @@ -41,11 +39,7 @@
import java.util.concurrent.atomic.AtomicBoolean;

import static org.axonframework.axonserver.connector.utils.AssertUtils.assertWithin;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;

/**
* Unit tests for {@link AxonServerConnectionManager}.
Expand Down Expand Up @@ -313,6 +307,31 @@ void testDisconnectSingleConnection() {
assertEquals(1, secondNode.getPlatformService().getNumberOfCompletedStreams());
}

@Test
void testConnectionsReturnsConnectionStatus() {
AxonServerConnectionManager testSubject = AxonServerConnectionManager.builder()
.axonServerConfiguration(testConfig)
.build();

AxonServerConnection channelOne = testSubject.getConnection(TEST_CONTEXT);
AxonServerConnection channelTwo = testSubject.getConnection("some-other-context");

assertWithin(250, TimeUnit.MILLISECONDS, () -> {
assertTrue(channelOne.isReady());
assertTrue(channelTwo.isReady());
});

Map<String, Boolean> results = testSubject.connections();

Boolean testContextConnection = results.get(TEST_CONTEXT);
assertNotNull(testContextConnection);
assertTrue(testContextConnection);

Boolean someOtherContextConnection = results.get("some-other-context");
assertNotNull(someOtherContextConnection);
assertTrue(someOtherContextConnection);
}

@Test
void testBuildWithNullAxonServerConfigurationThrowsAxonConfigurationException() {
AxonServerConnectionManager.Builder builderTestSubject = AxonServerConnectionManager.builder();
Expand Down
9 changes: 8 additions & 1 deletion spring-boot-autoconfigure/pom.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2010-2021. Axon Framework
~ Copyright (c) 2010-2022. Axon Framework
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -94,6 +94,13 @@
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
<version>${spring.boot.version}</version>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2010-2022. Axon Framework
*
* 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 org.axonframework.actuator;

import org.springframework.boot.actuate.health.Status;

/**
* Utility class holding additional {@link Status} instances.
*
* @author Steven van Beelen
* @author Marc Gathier
* @since 4.6.0
*/
public abstract class HealthStatus {

/**
* A {@link Status} suggesting the connection is still working but not at full capacity.
*/
public static final Status WARN = new Status("WARN");

private HealthStatus() {
// Utility class
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright (c) 2010-2022. Axon Framework
*
* 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 org.axonframework.actuator.axonserver;

import org.axonframework.actuator.HealthStatus;
import org.axonframework.axonserver.connector.AxonServerConnectionManager;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status;

import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

/**
* An {@link AbstractHealthIndicator} implementation exposing the health of the connections made through the {@link
* AxonServerConnectionManager}. This status is exposed through the {@code "axonServer"} component.
* <p>
* The status is regarded as {@link Status#UP} if <b>all</b> {@link AxonServerConnectionManager#connections()} are up.
* If one of them is down, the status is {@link HealthStatus#WARN}. If all of them are down the status will be {@link
* Status#DOWN}. This {@link org.springframework.boot.actuate.health.HealthIndicator} also shares connection details per
* context under {@code "{context-name}.connection.active"}.
*
* @author Steven van Beelen
* @since 4.6.0
*/
public class AxonServerHealthIndicator extends AbstractHealthIndicator {

private static final String CONNECTION = "%s.connection.active";

private final AxonServerConnectionManager connectionManager;

/**
* Constructs this health indicator, extracting health information from the given {@code connectionManager}.
*
* @param connectionManager The Axon Server connection manager to extract health information from.
*/
public AxonServerHealthIndicator(AxonServerConnectionManager connectionManager) {
this.connectionManager = connectionManager;
}

@Override
protected void doHealthCheck(Health.Builder builder) {
builder.up();

AtomicBoolean anyConnectionUp = new AtomicBoolean(false);
Map<String, Boolean> connections = connectionManager.connections();

connections.forEach((context, connectionStatus) -> {
String contextStatusCode;
if (Boolean.FALSE.equals(connectionStatus)) {
contextStatusCode = Status.DOWN.getCode();
builder.status(HealthStatus.WARN);
} else {
contextStatusCode = Status.UP.getCode();
anyConnectionUp.compareAndSet(false, true);
}
builder.withDetail(String.format(CONNECTION, context), contextStatusCode);
});

if (!connections.isEmpty() && !anyConnectionUp.get()) {
builder.down();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2010-2022. Axon Framework
*
* 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 org.axonframework.actuator.axonserver;

import org.axonframework.actuator.HealthStatus;
import org.springframework.boot.actuate.health.SimpleStatusAggregator;
import org.springframework.boot.actuate.health.Status;

/**
* A {@link SimpleStatusAggregator} implementation determining the overall health of an Axon Framework application using
* Axon Server. Adds the {@link HealthStatus#WARN} status to the regular set of {@link Status statuses}.
*
* @author Steven van Beelen
* @author Marc Gathier
* @since 4.6.0
*/
public class AxonServerStatusAggregator extends SimpleStatusAggregator {

/**
* Constructs a default Axon Server specific {@link SimpleStatusAggregator}. Adds the {@link HealthStatus#WARN}
* after {@link Status#OUT_OF_SERVICE} and before {@link Status#UP}.
*/
public AxonServerStatusAggregator() {
super(Status.DOWN, Status.OUT_OF_SERVICE, HealthStatus.WARN, Status.UP, Status.UNKNOWN);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2010-2022. Axon Framework
*
* 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 org.axonframework.springboot.autoconfig;

import org.axonframework.actuator.axonserver.AxonServerHealthIndicator;
import org.axonframework.actuator.axonserver.AxonServerStatusAggregator;
import org.axonframework.axonserver.connector.AxonServerConnectionManager;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* Autoconfiguration class for Spring Boot Actuator monitoring tools around Axon Server.
*
* @author Steven van Beelen
* @since 4.6.0
*/
@Configuration
@AutoConfigureAfter(AxonServerAutoConfiguration.class)
@ConditionalOnClass(name = {"org.springframework.boot.actuate.health.AbstractHealthIndicator"})
@ConditionalOnProperty(name = "axon.axonserver.enabled", matchIfMissing = true)
public class AxonServerActuatorAutoConfiguration {

@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
@Bean
public AxonServerHealthIndicator axonServerHealthIndicator(AxonServerConnectionManager connectionManager) {
return new AxonServerHealthIndicator(connectionManager);
}

@Bean
public AxonServerStatusAggregator axonServerStatusAggregator() {
return new AxonServerStatusAggregator();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.axonframework.springboot.autoconfig.InfraConfiguration,\
org.axonframework.springboot.autoconfig.ObjectMapperAutoConfiguration,\
org.axonframework.springboot.autoconfig.AxonServerAutoConfiguration,\
org.axonframework.springboot.autoconfig.XStreamAutoConfiguration
org.axonframework.springboot.autoconfig.XStreamAutoConfiguration,\
org.axonframework.springboot.autoconfig.AxonServerActuatorAutoConfiguration

0 comments on commit e635fc6

Please sign in to comment.