Make the source of Service Metadata configurable (#270)
Make service metadata configurable, so that users can tune it however they see fit. Specifically we add: * Add flag to enable/disable addition of labels to metadata (default: true) * Add optional prefix to label keys (default none) * Add flag to enable/disable addition of annotation to metadata (default: true) * Add optional prefix to annotation keys (default none) Fixes: gh-260
This commit is contained in:
committed by
Spencer Gibb
parent
0b8e1c9810
commit
b39997a820
@@ -23,6 +23,7 @@ import io.fabric8.kubernetes.api.model.Service;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
@@ -35,6 +36,7 @@ import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.SimpleEvaluationContext;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
public class KubernetesDiscoveryClient implements DiscoveryClient {
|
||||
|
||||
@@ -72,19 +74,35 @@ public class KubernetesDiscoveryClient implements DiscoveryClient {
|
||||
public List<ServiceInstance> getInstances(String serviceId) {
|
||||
Assert.notNull(serviceId,
|
||||
"[Assertion failed] - the object argument must be null");
|
||||
final Map<String, String> labels = getLabels(serviceId);
|
||||
|
||||
Endpoints endpoints = client.endpoints().withName(serviceId).get();
|
||||
List<EndpointSubset> subsets = null != endpoints ? endpoints.getSubsets() : new ArrayList<>();
|
||||
List<ServiceInstance> instances = new ArrayList<>();
|
||||
if (!subsets.isEmpty()) {
|
||||
|
||||
final Service service = client.services().withName(serviceId).get();
|
||||
|
||||
final Map<String, String> metadata = new HashMap<>();
|
||||
if(properties.isEnabledAdditionOfLabelsAsMetadata()) {
|
||||
metadata.putAll(
|
||||
getMapWithPrefixedKeys(
|
||||
service.getMetadata().getLabels(), properties.getLabelKeysPrefix())
|
||||
);
|
||||
}
|
||||
if(properties.isEnabledAdditionOfAnnotationsAsMetadata()) {
|
||||
metadata.putAll(
|
||||
getMapWithPrefixedKeys(
|
||||
service.getMetadata().getAnnotations(), properties.getAnnotationKeysPrefix())
|
||||
);
|
||||
}
|
||||
|
||||
for (EndpointSubset s : subsets) {
|
||||
List<EndpointAddress> addresses = s.getAddresses();
|
||||
for (EndpointAddress a : addresses) {
|
||||
instances.add(new KubernetesServiceInstance(serviceId,
|
||||
a,
|
||||
s.getPorts().stream().findFirst().orElseThrow(IllegalStateException::new),
|
||||
labels,
|
||||
metadata,
|
||||
false));
|
||||
}
|
||||
}
|
||||
@@ -93,12 +111,19 @@ public class KubernetesDiscoveryClient implements DiscoveryClient {
|
||||
return instances;
|
||||
}
|
||||
|
||||
private Map<String, String> getLabels(String serviceName) {
|
||||
final Service service = client.services().withName(serviceName).get();
|
||||
if (service != null) {
|
||||
return service.getMetadata().getLabels();
|
||||
// returns a new map that contain all the entries of the original map
|
||||
// but with the keys prefixed
|
||||
// if the prefix is null or empty, the map itself is returned (unchanged of course)
|
||||
private Map<String, String> getMapWithPrefixedKeys(Map<String, String> map, String prefix) {
|
||||
// when the prefix is empty just return an map with the same entries
|
||||
if (!StringUtils.hasText(prefix)) {
|
||||
return map;
|
||||
}
|
||||
return Collections.emptyMap();
|
||||
|
||||
final Map<String, String> result = new HashMap<>();
|
||||
map.forEach((k, v) -> result.put(prefix + k, v));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -29,11 +29,36 @@ public class KubernetesDiscoveryProperties extends AutoServiceRegistrationProper
|
||||
@Value("${spring.application.name:unknown}")
|
||||
private String serviceName = "unknown";
|
||||
|
||||
/**
|
||||
* SpEL expression to filter services
|
||||
/**
|
||||
* SpEL expression to filter services
|
||||
**/
|
||||
private String filter;
|
||||
|
||||
/**
|
||||
* When set, the Kubernetes labels of the services will be included as metadata
|
||||
* of the returned ServiceInstance
|
||||
*/
|
||||
private boolean enabledAdditionOfLabelsAsMetadata = true;
|
||||
|
||||
/**
|
||||
* When enabledAdditionOfLabelsAsMetadata is set, then the value labelKeysPrefix
|
||||
* will be used as a prefix to the key names in the metadata map
|
||||
*/
|
||||
private String labelKeysPrefix;
|
||||
|
||||
|
||||
/**
|
||||
* When set, the Kubernetes annotations of the services will be included as metadata
|
||||
* of the returned ServiceInstance
|
||||
*/
|
||||
private boolean enabledAdditionOfAnnotationsAsMetadata = true;
|
||||
|
||||
/**
|
||||
* When enabledAdditionOfAnnotationsAsMetadata is set, then the value annotationKeysPrefix
|
||||
* will be used as a prefix to the key names in the metadata map
|
||||
*/
|
||||
private String annotationKeysPrefix;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
@@ -49,11 +74,44 @@ public class KubernetesDiscoveryProperties extends AutoServiceRegistrationProper
|
||||
public String getFilter() {
|
||||
return filter;
|
||||
}
|
||||
|
||||
|
||||
public void setFilter(String filter){
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
public boolean isEnabledAdditionOfLabelsAsMetadata() {
|
||||
return enabledAdditionOfLabelsAsMetadata;
|
||||
}
|
||||
|
||||
public void setEnabledAdditionOfLabelsAsMetadata(boolean enabledAdditionOfLabelsAsMetadata) {
|
||||
this.enabledAdditionOfLabelsAsMetadata = enabledAdditionOfLabelsAsMetadata;
|
||||
}
|
||||
|
||||
public String getLabelKeysPrefix() {
|
||||
return labelKeysPrefix;
|
||||
}
|
||||
|
||||
public void setLabelKeysPrefix(String labelKeysPrefix) {
|
||||
this.labelKeysPrefix = labelKeysPrefix;
|
||||
}
|
||||
|
||||
public boolean isEnabledAdditionOfAnnotationsAsMetadata() {
|
||||
return enabledAdditionOfAnnotationsAsMetadata;
|
||||
}
|
||||
|
||||
public void setEnabledAdditionOfAnnotationsAsMetadata(
|
||||
boolean enabledAdditionOfAnnotationsAsMetadata) {
|
||||
this.enabledAdditionOfAnnotationsAsMetadata = enabledAdditionOfAnnotationsAsMetadata;
|
||||
}
|
||||
|
||||
public String getAnnotationKeysPrefix() {
|
||||
return annotationKeysPrefix;
|
||||
}
|
||||
|
||||
public void setAnnotationKeysPrefix(String annotationKeysPrefix) {
|
||||
this.annotationKeysPrefix = annotationKeysPrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KubernetesDiscoveryProperties{" +
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* 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.springframework.cloud.kubernetes.discovery;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.DoneableEndpoints;
|
||||
import io.fabric8.kubernetes.api.model.DoneableService;
|
||||
import io.fabric8.kubernetes.api.model.Endpoints;
|
||||
import io.fabric8.kubernetes.api.model.EndpointsBuilder;
|
||||
import io.fabric8.kubernetes.api.model.EndpointsList;
|
||||
import io.fabric8.kubernetes.api.model.Service;
|
||||
import io.fabric8.kubernetes.api.model.ServiceBuilder;
|
||||
import io.fabric8.kubernetes.api.model.ServiceList;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.dsl.MixedOperation;
|
||||
import io.fabric8.kubernetes.client.dsl.Resource;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class KubernetesDiscoveryClientFilterMetadataTest {
|
||||
|
||||
@Mock
|
||||
private KubernetesClient kubernetesClient;
|
||||
|
||||
@Mock
|
||||
private KubernetesDiscoveryProperties properties;
|
||||
|
||||
@Mock
|
||||
private MixedOperation<Service, ServiceList, DoneableService, Resource<Service, DoneableService>> serviceOperation;
|
||||
|
||||
@Mock
|
||||
private MixedOperation<Endpoints, EndpointsList, DoneableEndpoints, Resource<Endpoints, DoneableEndpoints>> endpointsOperation;
|
||||
|
||||
@Mock
|
||||
private Resource<Service, DoneableService> serviceResource;
|
||||
|
||||
@Mock
|
||||
private Resource<Endpoints, DoneableEndpoints> endpointsResource;
|
||||
|
||||
@InjectMocks
|
||||
private KubernetesDiscoveryClient underTest;
|
||||
|
||||
@Test
|
||||
public void testBothLabelsAndAnnotationsDisabled() {
|
||||
final String serviceId = "s";
|
||||
|
||||
when(properties.isEnabledAdditionOfLabelsAsMetadata()).thenReturn(false);
|
||||
when(properties.isEnabledAdditionOfAnnotationsAsMetadata()).thenReturn(false);
|
||||
|
||||
setupServiceWithLabelsAndAnnotations(
|
||||
serviceId,
|
||||
new HashMap<String, String>() {{
|
||||
put("l1", "lab");
|
||||
}},
|
||||
new HashMap<String, String>() {{
|
||||
put("l1", "lab");
|
||||
}}
|
||||
);
|
||||
|
||||
final List<ServiceInstance> instances = underTest.getInstances(serviceId);
|
||||
assertThat(instances).hasSize(1);
|
||||
assertThat(instances.get(0).getMetadata()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLabelsEnabledAndAnnotationsDisabled() {
|
||||
final String serviceId = "s";
|
||||
|
||||
when(properties.isEnabledAdditionOfLabelsAsMetadata()).thenReturn(true);
|
||||
when(properties.isEnabledAdditionOfAnnotationsAsMetadata()).thenReturn(false);
|
||||
|
||||
setupServiceWithLabelsAndAnnotations(
|
||||
serviceId,
|
||||
new HashMap<String, String>() {{
|
||||
put("l1", "v1");
|
||||
put("l2", "v2");
|
||||
}},
|
||||
new HashMap<String, String>() {{
|
||||
put("l1", "lab");
|
||||
}}
|
||||
);
|
||||
|
||||
final List<ServiceInstance> instances = underTest.getInstances(serviceId);
|
||||
assertThat(instances).hasSize(1);
|
||||
assertThat(instances.get(0).getMetadata()).containsOnly(entry("l1", "v1"), entry("l2", "v2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLabelsEnabledAndAnnotationsDisabledWithPrefix() {
|
||||
final String serviceId = "s";
|
||||
|
||||
when(properties.isEnabledAdditionOfLabelsAsMetadata()).thenReturn(true);
|
||||
when(properties.getLabelKeysPrefix()).thenReturn("l_");
|
||||
when(properties.isEnabledAdditionOfAnnotationsAsMetadata()).thenReturn(false);
|
||||
|
||||
setupServiceWithLabelsAndAnnotations(
|
||||
serviceId,
|
||||
new HashMap<String, String>() {{
|
||||
put("l1", "v1");
|
||||
put("l2", "v2");
|
||||
}},
|
||||
new HashMap<String, String>() {{
|
||||
put("l1", "lab");
|
||||
}}
|
||||
);
|
||||
|
||||
final List<ServiceInstance> instances = underTest.getInstances(serviceId);
|
||||
assertThat(instances).hasSize(1);
|
||||
assertThat(instances.get(0).getMetadata()).containsOnly(entry("l_l1", "v1"), entry("l_l2", "v2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLabelsDisabledAndAnnotationsEnabled() {
|
||||
final String serviceId = "s";
|
||||
|
||||
when(properties.isEnabledAdditionOfLabelsAsMetadata()).thenReturn(false);
|
||||
when(properties.isEnabledAdditionOfAnnotationsAsMetadata()).thenReturn(true);
|
||||
|
||||
setupServiceWithLabelsAndAnnotations(
|
||||
serviceId,
|
||||
new HashMap<String, String>() {{
|
||||
put("l1", "v1");
|
||||
}},
|
||||
new HashMap<String, String>() {{
|
||||
put("a1", "v1");
|
||||
put("a2", "v2");
|
||||
}}
|
||||
);
|
||||
|
||||
final List<ServiceInstance> instances = underTest.getInstances(serviceId);
|
||||
assertThat(instances).hasSize(1);
|
||||
assertThat(instances.get(0).getMetadata()).containsOnly(entry("a1", "v1"), entry("a2", "v2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLabelsDisabledAndAnnotationsEnabledWithPrefix() {
|
||||
final String serviceId = "s";
|
||||
|
||||
when(properties.isEnabledAdditionOfLabelsAsMetadata()).thenReturn(false);
|
||||
when(properties.isEnabledAdditionOfAnnotationsAsMetadata()).thenReturn(true);
|
||||
when(properties.getAnnotationKeysPrefix()).thenReturn("a_");
|
||||
|
||||
setupServiceWithLabelsAndAnnotations(
|
||||
serviceId,
|
||||
new HashMap<String, String>() {{
|
||||
put("l1", "v1");
|
||||
}},
|
||||
new HashMap<String, String>() {{
|
||||
put("a1", "v1");
|
||||
put("a2", "v2");
|
||||
}}
|
||||
);
|
||||
|
||||
final List<ServiceInstance> instances = underTest.getInstances(serviceId);
|
||||
assertThat(instances).hasSize(1);
|
||||
assertThat(instances.get(0).getMetadata()).containsOnly(entry("a_a1", "v1"), entry("a_a2", "v2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBothLabelsAndAnnotationsEnabledWithPrefix() {
|
||||
final String serviceId = "s";
|
||||
|
||||
when(properties.isEnabledAdditionOfLabelsAsMetadata()).thenReturn(true);
|
||||
when(properties.getLabelKeysPrefix()).thenReturn("l_");
|
||||
when(properties.isEnabledAdditionOfAnnotationsAsMetadata()).thenReturn(true);
|
||||
when(properties.getAnnotationKeysPrefix()).thenReturn("a_");
|
||||
|
||||
setupServiceWithLabelsAndAnnotations(
|
||||
serviceId,
|
||||
new HashMap<String, String>() {{
|
||||
put("l1", "la1");
|
||||
}},
|
||||
new HashMap<String, String>() {{
|
||||
put("a1", "an1");
|
||||
put("a2", "an2");
|
||||
}}
|
||||
);
|
||||
|
||||
final List<ServiceInstance> instances = underTest.getInstances(serviceId);
|
||||
assertThat(instances).hasSize(1);
|
||||
assertThat(instances.get(0).getMetadata()).containsOnly(
|
||||
entry("a_a1", "an1"), entry("a_a2", "an2"), entry("l_l1", "la1"));
|
||||
}
|
||||
|
||||
private void setupServiceWithLabelsAndAnnotations(String serviceId,
|
||||
Map<String, String> labels, Map<String, String> annotations) {
|
||||
final Service service =
|
||||
new ServiceBuilder()
|
||||
.withNewMetadata()
|
||||
.withLabels(labels)
|
||||
.withAnnotations(annotations)
|
||||
.endMetadata()
|
||||
.build();
|
||||
when(serviceOperation.withName(serviceId)).thenReturn(serviceResource);
|
||||
when(serviceResource.get()).thenReturn(service);
|
||||
when(kubernetesClient.services()).thenReturn(serviceOperation);
|
||||
|
||||
final Endpoints endpoints =
|
||||
new EndpointsBuilder()
|
||||
.addNewSubset()
|
||||
.addNewPort()
|
||||
.endPort()
|
||||
.addNewAddress()
|
||||
.endAddress()
|
||||
.endSubset()
|
||||
.build();
|
||||
|
||||
when(endpointsResource.get()).thenReturn(endpoints);
|
||||
when(endpointsOperation.withName(serviceId)).thenReturn(endpointsResource);
|
||||
when(kubernetesClient.endpoints()).thenReturn(endpointsOperation);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -37,7 +37,7 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class KubernetesDiscoveryClientTest {
|
||||
public class KubernetesDiscoveryClientFilterTest {
|
||||
|
||||
@Mock
|
||||
private KubernetesClient kubernetesClient;
|
||||
Reference in New Issue
Block a user