From 3a9d423bbc42d4f2c76377831110355be76c479d Mon Sep 17 00:00:00 2001 From: Lomesh Patel Date: Mon, 17 Sep 2018 09:35:02 -0400 Subject: [PATCH] Added Header support for Service Checks - Upgraded com.ecwid.consul:consul-api to 1.4.1, which adds support for Headers in NewService model - Added new property healthCheckHeaders in ConsulDiscoveryProperties - Updated docs with instructions on how to set headers Fixes gh-391 --- .../main/asciidoc/spring-cloud-consul.adoc | 28 +++++++ spring-cloud-consul-dependencies/pom.xml | 2 +- .../discovery/ConsulDiscoveryProperties.java | 17 ++++- .../ConsulAutoRegistration.java | 5 +- .../ConsulDiscoveryPropertiesTests.java | 16 ++-- ...toRegistrationHealthCheckHeadersTests.java | 73 +++++++++++++++++++ 6 files changed, 128 insertions(+), 13 deletions(-) create mode 100644 spring-cloud-consul-discovery/src/test/java/org/springframework/cloud/consul/serviceregistry/ConsulAutoRegistrationHealthCheckHeadersTests.java diff --git a/docs/src/main/asciidoc/spring-cloud-consul.adoc b/docs/src/main/asciidoc/spring-cloud-consul.adoc index ca2bf052..0267cef2 100644 --- a/docs/src/main/asciidoc/spring-cloud-consul.adoc +++ b/docs/src/main/asciidoc/spring-cloud-consul.adoc @@ -122,6 +122,34 @@ spring: With this metadata, and multiple service instances deployed on localhost, the random value will kick in there to make the instance unique. In Cloudfoundry the `vcap.application.instance_id` will be populated automatically in a Spring Boot application, so the random value will not be needed. +==== Applying Headers to Health Check Requests + +Headers can be applied to health check requests. For example, if you're trying to register a https://cloud.spring.io/spring-cloud-config/[Spring Cloud Config] server that uses https://github.com/spring-cloud/spring-cloud-config/blob/master/docs/src/main/asciidoc/spring-cloud-config.adoc#vault-backend[Vault Backend]: + +.application.yml +---- +spring: + cloud: + consul: + discovery: + health-check-headers: + X-Config-Token: 6442e58b-d1ea-182e-cfa5-cf9cddef0722 +---- + +According to the HTTP standard, each header can have more than one values, in which case, an array can be supplied: + +.application.yml +---- +spring: + cloud: + consul: + discovery: + health-check-headers: + X-Config-Token: + - "6442e58b-d1ea-182e-cfa5-cf9cddef0722" + - "Some other value" +---- + === Looking up services ==== Using Ribbon diff --git a/spring-cloud-consul-dependencies/pom.xml b/spring-cloud-consul-dependencies/pom.xml index 6daae860..9bf76274 100644 --- a/spring-cloud-consul-dependencies/pom.xml +++ b/spring-cloud-consul-dependencies/pom.xml @@ -14,7 +14,7 @@ spring-cloud-consul-dependencies Spring Cloud Consul Dependencies - 1.3.1 + 1.4.1 diff --git a/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/discovery/ConsulDiscoveryProperties.java b/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/discovery/ConsulDiscoveryProperties.java index eee4b33a..eedce00c 100644 --- a/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/discovery/ConsulDiscoveryProperties.java +++ b/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/discovery/ConsulDiscoveryProperties.java @@ -60,6 +60,9 @@ public class ConsulDiscoveryProperties { /** Custom health check url to override default */ private String healthCheckUrl; + /** Headers to be applied to the Health Check calls */ + private Map> healthCheckHeaders = new HashMap<>(); + /** How often to perform the health check (e.g. 10s), defaults to 10s. */ private String healthCheckInterval = "10s"; @@ -88,7 +91,7 @@ public class ConsulDiscoveryProperties { /** Use ip address rather than hostname during registration */ private boolean preferIpAddress = false; - + /** Source of how we will determine the address to use */ private boolean preferAgentAddress = false; @@ -110,6 +113,7 @@ public class ConsulDiscoveryProperties { /** Service instance group*/ private String instanceGroup; + /** * Service instance zone comes from metadata. * This allows changing the metadata tag name. @@ -181,7 +185,7 @@ public class ConsulDiscoveryProperties { /** * @param serviceId The service who's filtering tag is being looked up * @return The tag the given service id should be filtered by, or null. - */ + */ public String getQueryTagForService(String serviceId){ String tag = serverListQueryTags.get(serviceId); return tag != null ? tag : defaultQueryTag; @@ -257,6 +261,14 @@ public class ConsulDiscoveryProperties { this.healthCheckUrl = healthCheckUrl; } + public Map> getHealthCheckHeaders() { + return healthCheckHeaders; + } + + public void setHealthCheckHeaders(Map> healthCheckHeaders) { + this.healthCheckHeaders = healthCheckHeaders; + } + public String getHealthCheckInterval() { return healthCheckInterval; } @@ -487,6 +499,7 @@ public class ConsulDiscoveryProperties { ", managementTags=" + managementTags + ", healthCheckPath='" + healthCheckPath + '\'' + ", healthCheckUrl='" + healthCheckUrl + '\'' + + ", healthCheckHeaders='" + healthCheckHeaders + '\'' + ", healthCheckInterval='" + healthCheckInterval + '\'' + ", healthCheckTimeout='" + healthCheckTimeout + '\'' + ", healthCheckCriticalTimeout='" + healthCheckCriticalTimeout + '\'' + diff --git a/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/serviceregistry/ConsulAutoRegistration.java b/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/serviceregistry/ConsulAutoRegistration.java index 4fc8da51..7a1dcbc0 100644 --- a/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/serviceregistry/ConsulAutoRegistration.java +++ b/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/serviceregistry/ConsulAutoRegistration.java @@ -206,10 +206,10 @@ public class ConsulAutoRegistration extends ConsulRegistration { if (!StringUtils.isEmpty(properties.getInstanceGroup())) { tags.add("group=" + properties.getInstanceGroup()); } - + //store the secure flag in the tags so that clients will be able to figure out whether to use http or https automatically tags.add("secure=" + Boolean.toString(properties.getScheme().equalsIgnoreCase("https"))); - + return tags; } @@ -231,6 +231,7 @@ public class ConsulAutoRegistration extends ConsulRegistration { properties.getHostname(), port, properties.getHealthCheckPath())); } + check.setHeader(properties.getHealthCheckHeaders()); check.setInterval(properties.getHealthCheckInterval()); check.setTimeout(properties.getHealthCheckTimeout()); if (StringUtils.hasText(properties.getHealthCheckCriticalTimeout())) { diff --git a/spring-cloud-consul-discovery/src/test/java/org/springframework/cloud/consul/discovery/ConsulDiscoveryPropertiesTests.java b/spring-cloud-consul-discovery/src/test/java/org/springframework/cloud/consul/discovery/ConsulDiscoveryPropertiesTests.java index 466b3692..fae4c4cc 100644 --- a/spring-cloud-consul-discovery/src/test/java/org/springframework/cloud/consul/discovery/ConsulDiscoveryPropertiesTests.java +++ b/spring-cloud-consul-discovery/src/test/java/org/springframework/cloud/consul/discovery/ConsulDiscoveryPropertiesTests.java @@ -19,11 +19,11 @@ public class ConsulDiscoveryPropertiesTests { private static final String SERVICE_NAME_IN_MAP = "serviceNameInMap"; private static final String SERVICE_NAME_NOT_IN_MAP = "serviceNameNotInMap"; private ConsulDiscoveryProperties properties; - private Map serverListQueryTags = Collections.singletonMap(SERVICE_NAME_IN_MAP, MAP_TAG); - private Map datacenters = Collections.singletonMap(SERVICE_NAME_IN_MAP, MAP_DC); + private final Map serverListQueryTags = Collections.singletonMap(SERVICE_NAME_IN_MAP, MAP_TAG); + private final Map datacenters = Collections.singletonMap(SERVICE_NAME_IN_MAP, MAP_DC); @Before - public void setUp() throws Exception { + public void setUp() { properties = new ConsulDiscoveryProperties(new InetUtils(new InetUtilsProperties())); properties.setDefaultQueryTag(DEFAULT_TAG); properties.setServerListQueryTags(serverListQueryTags); @@ -31,29 +31,29 @@ public class ConsulDiscoveryPropertiesTests { } @Test - public void testReturnsNullWhenNoDefaultAndNotInMap() throws Exception { + public void testReturnsNullWhenNoDefaultAndNotInMap() { properties.setDefaultQueryTag(null); assertNull(properties.getQueryTagForService(SERVICE_NAME_NOT_IN_MAP)); } @Test - public void testGetTagReturnsDefaultWhenNotInMap() throws Exception { + public void testGetTagReturnsDefaultWhenNotInMap() { assertEquals(DEFAULT_TAG, properties.getQueryTagForService(SERVICE_NAME_NOT_IN_MAP)); } @Test - public void testGetTagReturnsMapValueWhenInMap() throws Exception { + public void testGetTagReturnsMapValueWhenInMap() { assertEquals(MAP_TAG, properties.getQueryTagForService(SERVICE_NAME_IN_MAP)); } @Test - public void testGetDcReturnsNullWhenNotInMap() throws Exception { + public void testGetDcReturnsNullWhenNotInMap() { assertNull(properties.getDatacenters().get(SERVICE_NAME_NOT_IN_MAP)); } @Test - public void testGetDcReturnsMapValueWhenInMap() throws Exception { + public void testGetDcReturnsMapValueWhenInMap() { assertEquals(MAP_DC, properties.getDatacenters().get(SERVICE_NAME_IN_MAP)); } } diff --git a/spring-cloud-consul-discovery/src/test/java/org/springframework/cloud/consul/serviceregistry/ConsulAutoRegistrationHealthCheckHeadersTests.java b/spring-cloud-consul-discovery/src/test/java/org/springframework/cloud/consul/serviceregistry/ConsulAutoRegistrationHealthCheckHeadersTests.java new file mode 100644 index 00000000..9ff390db --- /dev/null +++ b/spring-cloud-consul-discovery/src/test/java/org/springframework/cloud/consul/serviceregistry/ConsulAutoRegistrationHealthCheckHeadersTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2013-2017 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.consul.serviceregistry; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration; +import org.springframework.cloud.consul.ConsulAutoConfiguration; +import org.springframework.cloud.consul.discovery.ConsulDiscoveryProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; + +import com.ecwid.consul.v1.agent.model.NewService; + +/** + * @author Lomesh Patel + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = ConsulAutoRegistrationHealthCheckHeadersTests.TestConfig.class, properties = { + "spring.application.name=myTestService-DiscoveryHealthCheckTlsSkipVerify", + "spring.cloud.consul.discovery.health-check-headers.X-Config-Token=ACCESSTOKEN" }, webEnvironment = RANDOM_PORT) +public class ConsulAutoRegistrationHealthCheckHeadersTests { + + @Autowired + private ConsulAutoRegistration registration; + + @Autowired + private ConsulDiscoveryProperties properties; + + @Test + public void contextLoads() { + NewService service = this.registration.getService(); + assertThat(service).as("service was null").isNotNull(); + + NewService.Check check = service.getCheck(); + assertThat(check).as("check was null").isNotNull(); + assertThat(check.getHeader()).as("header is null").isNotNull(); + assertThat(check.getHeader()).as("header is empty").isNotEmpty(); + assertThat(check.getHeader().get("X-Config-Token").get(0)) + .as("expected header value not found").isEqualTo("ACCESSTOKEN"); + + // unable to call consul api to get health check details + } + + @Configuration + @EnableAutoConfiguration + @ImportAutoConfiguration({ AutoServiceRegistrationConfiguration.class, + ConsulAutoConfiguration.class, + ConsulAutoServiceRegistrationAutoConfiguration.class }) + public static class TestConfig { + } +}