Features actuator endpoint

Allows modules to declare features and display if they enabled or not.
This commit is contained in:
Spencer Gibb
2015-09-04 00:30:38 -06:00
parent a582e4e1bf
commit 93455532f9
5 changed files with 242 additions and 0 deletions

View File

@@ -16,8 +16,12 @@
package org.springframework.cloud.client;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
@@ -25,10 +29,13 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.actuator.FeaturesEndpoint;
import org.springframework.cloud.client.actuator.HasFeatures;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.health.DiscoveryClientHealthIndicator;
import org.springframework.cloud.client.discovery.health.DiscoveryCompositeHealthIndicator;
import org.springframework.cloud.client.discovery.health.DiscoveryHealthIndicator;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -56,4 +63,22 @@ public class CommonsClientAutoConfiguration {
return new DiscoveryCompositeHealthIndicator(aggregator, indicators);
}
@Bean
public HasFeatures commonsFeatures() {
return HasFeatures.abstractFeatures(DiscoveryClient.class,
LoadBalancerClient.class);
}
@Configuration
@ConditionalOnClass(Endpoint.class)
protected static class ActuatorConfiguration {
@Autowired(required = false)
private List<HasFeatures> hasFeatures = new ArrayList<>();
@Bean
public FeaturesEndpoint featuresEndpoint() {
return new FeaturesEndpoint(hasFeatures);
}
}
}

View File

@@ -0,0 +1,87 @@
package org.springframework.cloud.client.actuator;
import java.util.ArrayList;
import java.util.List;
import lombok.Value;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* @author Spencer Gibb
*/
@ConfigurationProperties(prefix = "endpoints.features", ignoreUnknownFields = false)
public class FeaturesEndpoint extends AbstractEndpoint<FeaturesEndpoint.Features> implements ApplicationContextAware {
private final List<HasFeatures> hasFeaturesList;
private ApplicationContext context;
public FeaturesEndpoint(List<HasFeatures> hasFeaturesList) {
super("features", false);
this.hasFeaturesList = hasFeaturesList;
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.context = context;
}
@Override
public Features invoke() {
Features features = new Features();
for (HasFeatures hasFeatures : hasFeaturesList) {
List<Class> abstractFeatures = hasFeatures.getAbstractFeatures();
if (abstractFeatures != null) {
for (Class clazz : abstractFeatures) {
addAbstractFeature(features, clazz);
}
}
List<NamedFeature> namedFeatures = hasFeatures.getNamedFeatures();
if (namedFeatures != null) {
for (NamedFeature namedFeature : namedFeatures) {
addFeature(features, namedFeature);
}
}
}
return features;
}
private void addAbstractFeature(Features features, Class type) {
addFeature(features, new NamedFeature(type.getSimpleName(), type));
}
private void addFeature(Features features, NamedFeature feature) {
try {
Object bean = context.getBean(feature.getType());
Class<?> beanClass = bean.getClass();
features.getEnabled().add(new Feature(feature.getName(),
beanClass.getCanonicalName(),
beanClass.getPackage().getImplementationVersion(),
beanClass.getPackage().getImplementationVendor()));
} catch (NoSuchBeanDefinitionException e) {
features.getDisabled().add(feature.getName());
}
}
@Value
class Features {
List<Feature> enabled = new ArrayList<>();
List<String> disabled = new ArrayList<>();
}
@Value
class Feature {
final String type;
final String name;
final String version;
final String vendor;
}
}

View File

@@ -0,0 +1,29 @@
package org.springframework.cloud.client.actuator;
import lombok.Builder;
import lombok.Singular;
import lombok.Value;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* @author Spencer Gibb
*/
@Value
@Builder
public class HasFeatures {
@Singular
private final List<Class> abstractFeatures;
@Singular
private final List<NamedFeature> namedFeatures;
public static HasFeatures abstractFeatures(Class... abstractFeatures) {
return new HasFeatures(Arrays.asList(abstractFeatures), Collections.<NamedFeature>emptyList());
}
public static HasFeatures namedFeatures(NamedFeature... namedFeatures) {
return new HasFeatures(Collections.<Class>emptyList(), Arrays.asList(namedFeatures));
}
}

View File

@@ -0,0 +1,12 @@
package org.springframework.cloud.client.actuator;
import lombok.Value;
/**
* @author Spencer Gibb
*/
@Value
public class NamedFeature {
private final String name;
private final Class<?> type;
}

View File

@@ -0,0 +1,89 @@
package org.springframework.cloud.client.actuator;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
/**
* @author Spencer Gibb
*/
public class FeaturesEndpointTests {
private AnnotationConfigApplicationContext context;
@Before
public void setup() {
this.context = new AnnotationConfigApplicationContext();
this.context.register(JacksonAutoConfiguration.class, FeaturesConfig.class, Config.class);
this.context.refresh();
}
@After
public void close() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void invokeWorks() {
FeaturesEndpoint.Features features = this.context.getBean(FeaturesEndpoint.class).invoke();
assertThat(features, is(notNullValue()));
assertThat(features.getEnabled().size(), is(equalTo(2)));
assertThat(features.getDisabled().size(), is(equalTo(1)));
}
@Configuration
public static class FeaturesConfig {
@Bean
Foo foo() {
return new Foo();
}
@Bean
Bar bar() {
return new Bar();
}
@Bean
HasFeatures localFeatures() {
return HasFeatures.builder()
.abstractFeature(Foo.class)
.namedFeature(new NamedFeature("Bar Feature", Bar.class))
.abstractFeature(Baz.class)
.build();
}
}
@Configuration
@EnableConfigurationProperties
public static class Config {
@Autowired(required = false)
private List<HasFeatures> hasFeatures = new ArrayList<>();
@Bean
public FeaturesEndpoint cloudEndpoint() {
return new FeaturesEndpoint(hasFeatures);
}
}
public static class Foo {}
public static class Bar {}
public static class Baz {}
}