diff --git a/docs/src/main/asciidoc/spring-cloud-consul.adoc b/docs/src/main/asciidoc/spring-cloud-consul.adoc index 7f094838..992f7f4c 100644 --- a/docs/src/main/asciidoc/spring-cloud-consul.adoc +++ b/docs/src/main/asciidoc/spring-cloud-consul.adoc @@ -83,6 +83,21 @@ spring: ---- +==== Metadata and Consul tags + +Consul does not yet support metadata on services. Spring Cloud's `ServiceInstance` has a `Map metadata` field. Spring Cloud Consul uses Consul tags to approximate metadata until Consul officially supports metadata. Tags with the form `key=value` will be split and used as a `Map` key and value respectively. Tags without the equal `=` sign, will be used as both the key and value. + +.application.yml +---- +spring: + cloud: + consul: + discovery: + tags: foo=bar, baz +---- + +The above configuration will result in a map with `foo->bar` and `baz->baz`. + ==== Making the Consul Instance ID Unique By default a consul instance is registered with an ID that is equal to its Spring Application Context ID. By default, the Spring Application Context ID is `${spring.application.name}:comma,separated,profiles:${server.port}`. For most cases, this will allow multiple instances of one service to run on one machine. If further uniqueness is required, Using Spring Cloud you can override this by providing a unique identifier in `spring.cloud.consul.discovery.instanceId`. For example: diff --git a/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/discovery/ConsulDiscoveryClient.java b/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/discovery/ConsulDiscoveryClient.java index 72ebc663..775a4c98 100644 --- a/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/discovery/ConsulDiscoveryClient.java +++ b/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/discovery/ConsulDiscoveryClient.java @@ -20,6 +20,12 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.util.StringUtils; + import com.ecwid.consul.v1.ConsulClient; import com.ecwid.consul.v1.QueryParams; import com.ecwid.consul.v1.Response; @@ -28,15 +34,10 @@ import com.ecwid.consul.v1.agent.model.Self; import com.ecwid.consul.v1.agent.model.Service; import com.ecwid.consul.v1.health.model.HealthService; -import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.cloud.client.DefaultServiceInstance; -import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.discovery.DiscoveryClient; -import org.springframework.util.StringUtils; - import lombok.extern.apachecommons.CommonsLog; import static org.springframework.cloud.consul.discovery.ConsulServerUtils.findHost; +import static org.springframework.cloud.consul.discovery.ConsulServerUtils.getMetadata; /** * @author Spencer Gibb @@ -74,6 +75,7 @@ public class ConsulDiscoveryClient implements DiscoveryClient { Service service = agentServices.getValue().get(lifecycle.getServiceId()); String serviceId; Integer port; + Map metadata; if (service == null) { // possibly called before registration log.warn("Unable to locate service in consul agent: " @@ -85,10 +87,12 @@ public class ConsulDiscoveryClient implements DiscoveryClient { && serverProperties.getPort() != null) { port = serverProperties.getPort(); } + metadata = getMetadata(this.properties.getTags()); } else { serviceId = service.getId(); port = service.getPort(); + metadata = getMetadata(service.getTags()); } String host = "localhost"; Response agentSelf = client.getAgentSelf(); @@ -101,7 +105,7 @@ public class ConsulDiscoveryClient implements DiscoveryClient { host = member.getName(); } } - return new DefaultServiceInstance(serviceId, host, port, false); + return new DefaultServiceInstance(serviceId, host, port, false, metadata); } @Override @@ -119,7 +123,7 @@ public class ConsulDiscoveryClient implements DiscoveryClient { for (HealthService service : services.getValue()) { String host = findHost(service); instances.add(new DefaultServiceInstance(serviceId, host, service - .getService().getPort(), false)); + .getService().getPort(), false, getMetadata(service))); } } diff --git a/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/discovery/ConsulServer.java b/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/discovery/ConsulServer.java index 468de14f..69f3bd5c 100644 --- a/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/discovery/ConsulServer.java +++ b/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/discovery/ConsulServer.java @@ -16,12 +16,14 @@ package org.springframework.cloud.consul.discovery; -import static org.springframework.cloud.consul.discovery.ConsulServerUtils.findHost; +import java.util.Map; import com.ecwid.consul.v1.health.model.Check; import com.ecwid.consul.v1.health.model.HealthService; import com.netflix.loadbalancer.Server; +import static org.springframework.cloud.consul.discovery.ConsulServerUtils.findHost; + /** * @author Spencer Gibb */ @@ -67,6 +69,10 @@ public class ConsulServer extends Server { return this.service; } + public Map getMetadata() { + return ConsulServerUtils.getMetadata(this.service); + } + public boolean isPassingChecks() { for (Check check : this.service.getChecks()) { if (check.getStatus() != Check.CheckStatus.PASSING) { diff --git a/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/discovery/ConsulServerUtils.java b/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/discovery/ConsulServerUtils.java index 57e50f15..7a8a4bf1 100644 --- a/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/discovery/ConsulServerUtils.java +++ b/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/discovery/ConsulServerUtils.java @@ -16,6 +16,11 @@ package org.springframework.cloud.consul.discovery; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + import com.ecwid.consul.v1.health.model.HealthService; import org.springframework.util.StringUtils; @@ -39,4 +44,34 @@ public class ConsulServerUtils { } return node.getNode(); } + + public static Map getMetadata(HealthService healthService) { + return getMetadata(healthService.getService().getTags()); + } + + public static Map getMetadata(List tags) { + LinkedHashMap metadata = new LinkedHashMap<>(); + if (tags != null) { + for (String tag : tags) { + String[] parts = StringUtils.delimitedListToStringArray(tag, "="); + switch (parts.length) { + case 0: + break; + case 1: + metadata.put(parts[0], parts[0]); + break; + case 2: + metadata.put(parts[0], parts[1]); + break; + default: + String[] end = Arrays.copyOfRange(parts, 1, parts.length); + metadata.put(parts[0], StringUtils.arrayToDelimitedString(end, "=")); + break; + } + + } + } + + return metadata; + } } diff --git a/spring-cloud-consul-discovery/src/test/java/org/springframework/cloud/consul/discovery/ConsulDiscoveryClientCustomizedTests.java b/spring-cloud-consul-discovery/src/test/java/org/springframework/cloud/consul/discovery/ConsulDiscoveryClientCustomizedTests.java index 0cbffb41..61b3fec5 100644 --- a/spring-cloud-consul-discovery/src/test/java/org/springframework/cloud/consul/discovery/ConsulDiscoveryClientCustomizedTests.java +++ b/spring-cloud-consul-discovery/src/test/java/org/springframework/cloud/consul/discovery/ConsulDiscoveryClientCustomizedTests.java @@ -16,11 +16,8 @@ package org.springframework.cloud.consul.discovery; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; - import java.util.List; +import java.util.Map; import org.apache.http.conn.util.InetAddressUtils; import org.junit.Test; @@ -36,12 +33,18 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + /** * @author Spencer Gibb */ @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = ConsulDiscoveryClientCustomizedTests.MyTestConfig.class) -@WebIntegrationTest(value = {"spring.application.name=testConsulDiscovery2", "spring.cloud.consul.discovery.instanceId=testConsulDiscovery2Id"}, randomPort = true) +@WebIntegrationTest(value = { "spring.application.name=testConsulDiscovery2", + "spring.cloud.consul.discovery.instanceId=testConsulDiscovery2Id", + "spring.cloud.consul.discovery.tags=plaintag,foo=bar,foo2=bar2=baz2" }, randomPort = true) public class ConsulDiscoveryClientCustomizedTests { @Autowired @@ -55,7 +58,33 @@ public class ConsulDiscoveryClientCustomizedTests { } private void assertNotIpAddress(ServiceInstance instance) { - assertFalse("host is an ip address", InetAddressUtils.isIPv4Address(instance.getHost())); + assertFalse("host is an ip address", + InetAddressUtils.isIPv4Address(instance.getHost())); + } + + @Test + public void getMetadataWorks() throws InterruptedException { + List instances = discoveryClient + .getInstances("testConsulDiscovery2"); + assertNotNull("instances was null", instances); + assertFalse("instances was empty", instances.isEmpty()); + + ServiceInstance instance = instances.get(0); + assertInstance(instance); + } + + private void assertInstance(ServiceInstance instance) { + Map metadata = instance.getMetadata(); + assertNotNull("metadata was null", metadata); + + String foo = metadata.get("foo"); + assertEquals("metadata key foo was wrong", "bar", foo); + + String plaintag = metadata.get("plaintag"); + assertEquals("metadata key plaintag was wrong", "plaintag", plaintag); + + String foo2 = metadata.get("foo2"); + assertEquals("metadata key foo2 was wrong", "bar2=baz2", foo2); } @Test @@ -63,7 +92,9 @@ public class ConsulDiscoveryClientCustomizedTests { ServiceInstance instance = discoveryClient.getLocalServiceInstance(); assertNotNull("instance was null", instance); assertNotIpAddress(instance); - assertEquals("instance id was wrong", "testConsulDiscovery2Id", instance.getServiceId()); + assertEquals("instance id was wrong", "testConsulDiscovery2Id", + instance.getServiceId()); + assertInstance(instance); } @Configuration