Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/modules/ROOT/partials/_configprops.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
|spring.cloud.consul.config.default-context | `+++application+++` |
|spring.cloud.consul.config.enabled | `+++true+++` |
|spring.cloud.consul.config.fail-fast | `+++true+++` | Throw exceptions during config lookup if true, otherwise, log warnings.
|spring.cloud.consul.config.consistency-mode | `+++DEFAULT+++` | Consistency mode for Consul KV requests. Supported values are `DEFAULT`, `STALE`, and `CONSISTENT`.
|spring.cloud.consul.config.format | `+++key-value+++` |
|spring.cloud.consul.config.name | | Alternative to spring.application.name to use in looking up values in consul KV.
|spring.cloud.consul.config.prefix | |
Expand Down Expand Up @@ -81,4 +82,4 @@
|spring.cloud.consul.tls.key-store-password | | Password to an external keystore.
|spring.cloud.consul.tls.key-store-path | | Path to an external keystore.

|===
|===
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.apache.commons.logging.LogFactory;

import org.springframework.cloud.consul.ConsulClient;
import org.springframework.cloud.consul.ConsulClient.QueryParams;
import org.springframework.cloud.consul.model.http.ConsulHeaders;
import org.springframework.cloud.consul.model.http.kv.GetValue;
import org.springframework.cloud.endpoint.event.RefreshEvent;
Expand Down Expand Up @@ -157,7 +158,8 @@ public void watchConfigKeyValues() {
}

ResponseEntity<List<GetValue>> response = this.consul.getKVValues(context, aclToken,
(long) properties.getWatch().getWaitTime(), currentIndex);
new QueryParams(this.properties.getConsistencyMode(),
(long) this.properties.getWatch().getWaitTime(), currentIndex));

// if response.value == null, response was a 404, otherwise it was a
// 200, reducing churn if there wasn't anything
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
import org.springframework.cloud.consul.ConsulClient.ConsistencyMode;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.annotation.Validated;
Expand Down Expand Up @@ -68,6 +69,12 @@ public class ConsulConfigProperties {
@Value("${consul.token:${CONSUL_TOKEN:${spring.cloud.consul.token:${SPRING_CLOUD_CONSUL_TOKEN:}}}}")
private String aclToken;

/**
* Consistency mode for Consul KV requests.
*/
@NotNull
private ConsistencyMode consistencyMode = ConsistencyMode.DEFAULT;

private Watch watch = new Watch();

/**
Expand Down Expand Up @@ -165,6 +172,14 @@ public void setAclToken(String aclToken) {
this.aclToken = aclToken;
}

public ConsistencyMode getConsistencyMode() {
return this.consistencyMode;
}

public void setConsistencyMode(ConsistencyMode consistencyMode) {
this.consistencyMode = consistencyMode;
}

public Watch getWatch() {
return this.watch;
}
Expand Down Expand Up @@ -198,6 +213,7 @@ public String toString() {
.append("format", this.format)
.append("dataKey", this.dataKey)
.append("aclToken", this.aclToken != null ? "******" : null)
.append("consistencyMode", this.consistencyMode)
.append("watch", this.watch)
.append("failFast", this.failFast)
.append("name", this.name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.cloud.consul.ConsulClient;
import org.springframework.cloud.consul.ConsulClient.QueryParams;
import org.springframework.cloud.consul.model.http.ConsulHeaders;
import org.springframework.cloud.consul.model.http.kv.GetValue;
import org.springframework.core.env.EnumerablePropertySource;
Expand Down Expand Up @@ -55,7 +56,6 @@ public ConsulPropertySource(String context, ConsulClient source, ConsulConfigPro
super(context, source);
this.context = context;
this.configProperties = configProperties;

}

public void init() {
Expand All @@ -70,7 +70,7 @@ public void init() {
}

ResponseEntity<List<GetValue>> response = this.source.getKVValues(this.context,
this.configProperties.getAclToken());
this.configProperties.getAclToken(), new QueryParams(this.configProperties.getConsistencyMode()));

this.initialIndex = ConsulHeaders.getConsulIndex(response);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.apache.commons.logging.Log;

import org.springframework.cloud.consul.ConsulClient;
import org.springframework.cloud.consul.ConsulClient.QueryParams;
import org.springframework.cloud.consul.ConsulException;
import org.springframework.cloud.consul.model.http.ConsulHeaders;
import org.springframework.cloud.consul.model.http.kv.GetValue;
Expand Down Expand Up @@ -127,7 +128,7 @@ public ConsulPropertySource createPropertySource(String propertySourceContext, C

if (properties.getFormat() == FILES) {
ResponseEntity<List<GetValue>> response = consul.getKVValue(propertySourceContext,
properties.getAclToken());
properties.getAclToken(), new QueryParams(properties.getConsistencyMode()));

GetValue value = null;
if (response.getStatusCode().is2xxSuccessful()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.junit.Test;

import org.springframework.cloud.consul.ConsulClient;
import org.springframework.cloud.consul.ConsulClient.QueryParams;
import org.springframework.cloud.consul.model.http.ConsulHeaders;
import org.springframework.cloud.consul.model.http.kv.GetValue;
import org.springframework.cloud.endpoint.event.RefreshEvent;
Expand All @@ -36,7 +37,6 @@
import org.springframework.util.StringUtils;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
Expand Down Expand Up @@ -119,7 +119,8 @@ private void setupWatch(ApplicationEventPublisher eventPublisher, GetValue getVa
headers.add(ConsulHeaders.INDEX_HEADER, "1");
headers.add(ConsulHeaders.LAST_CONTACT_HEADER, "1");
response.headers(headers);
when(consul.getKVValues(eq(context), nullable(String.class), anyLong(), anyLong()))

when(consul.getKVValues(eq(context), nullable(String.class), any(QueryParams.class)))
.thenReturn(response.body(getValues));

if (StringUtils.hasText(aclToken)) {
Expand All @@ -146,7 +147,7 @@ public void firstCallDoesNotPublishEvent() {
List<GetValue> getValues = Collections.singletonList(getValue);

ResponseEntity<List<GetValue>> response = ResponseEntity.ok(getValues);
when(consul.getKVValues(eq(context), anyString(), anyLong(), anyLong())).thenReturn(response);
when(consul.getKVValues(eq(context), anyString(), any(QueryParams.class))).thenReturn(response);

ConfigWatch watch = new ConfigWatch(this.configProperties, consul, new LinkedHashMap<>());
watch.setApplicationEventPublisher(eventPublisher);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import org.junit.jupiter.api.Test;

import org.springframework.cloud.consul.ConsulClient.ConsistencyMode;

import static org.assertj.core.api.Assertions.assertThat;

public class ConsulConfigPropertiesTests {
Expand All @@ -29,4 +31,20 @@ public void aclTokenToStringMasked() {
assertThat(properties.toString()).doesNotContain("myAclToken").contains("******");
}

@Test
public void consistencyModeDefaultsToDefault() {
ConsulConfigProperties properties = new ConsulConfigProperties();

assertThat(properties.getConsistencyMode()).isEqualTo(ConsistencyMode.DEFAULT);
}

@Test
public void consistencyModeCanBeConfigured() {
ConsulConfigProperties properties = new ConsulConfigProperties();

properties.setConsistencyMode(ConsistencyMode.CONSISTENT);

assertThat(properties.getConsistencyMode()).isEqualTo(ConsistencyMode.CONSISTENT);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@
import org.junit.Test;

import org.springframework.cloud.consul.ConsulClient;
import org.springframework.cloud.consul.ConsulClient.QueryParams;
import org.springframework.cloud.consul.model.http.kv.GetValue;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.mock;
Expand All @@ -50,7 +52,8 @@ public class ConsulPropertySourcesTests {
public void createPropertySourceStoresNormalizedContextInIndex() {
ConsulClient consul = mock(ConsulClient.class);
ResponseEntity<List<GetValue>> response = new ResponseEntity<List<GetValue>>(HttpStatus.OK);
when(consul.getKVValues(eq("test/"), nullable(String.class))).thenReturn(response);

when(consul.getKVValues(eq("test/"), nullable(String.class), any(QueryParams.class))).thenReturn(response);

ConsulConfigProperties properties = new ConsulConfigProperties();
ConsulPropertySources sources = new ConsulPropertySources(properties,
Expand All @@ -70,7 +73,8 @@ public void createPropertySourceFilesFormatStoresNormalizedContextInIndex() {
when(response.getStatusCode()).thenReturn(HttpStatus.OK);
when(response.getBody()).thenReturn(Collections.emptyList());
when(response.getHeaders()).thenReturn(new HttpHeaders());
when(consul.getKVValue(eq("test.yml"), nullable(String.class))).thenReturn(response);

when(consul.getKVValue(eq("test.yml"), nullable(String.class), any(QueryParams.class))).thenReturn(response);

ConsulConfigProperties properties = new ConsulConfigProperties();
properties.setFormat(ConsulConfigProperties.Format.FILES);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,44 @@ ResponseEntity<List<HealthService>> getHealthServices(@PathVariable String servi
ResponseEntity<Void> deleteKVValues(@PathVariable String context,
@RequestHeader(name = ACL_TOKEN_HEADER, required = false) String aclToken);

/**
* Retrieves a single KV entry using the default query parameters.
*/
@GetExchange("/v1/kv/{context}")
ResponseEntity<List<GetValue>> getKVValue(@PathVariable String context,
@RequestHeader(name = ACL_TOKEN_HEADER, required = false) String aclToken);

/**
* Retrieves a single KV entry using the supplied query parameters.
*/
@GetExchange("/v1/kv/{context}")
ResponseEntity<List<GetValue>> getKVValue(@PathVariable String context,
@RequestHeader(name = ACL_TOKEN_HEADER, required = false) String aclToken, QueryParams queryParams);

/**
* Retrieves KV entries recursively using the default query parameters.
*/
@GetExchange("/v1/kv/{context}?recurse")
ResponseEntity<List<GetValue>> getKVValues(@PathVariable String context,
@RequestHeader(name = ACL_TOKEN_HEADER, required = false) String aclToken);

/**
* Retrieves KV entries recursively using the supplied query parameters.
*
* The QueryParams argument allows callers to specify the datacenter, consistency
* mode, and other supported Consul query parameters.
*/
@GetExchange("/v1/kv/{context}?recurse")
ResponseEntity<List<GetValue>> getKVValues(@PathVariable String context,
@RequestHeader(name = ACL_TOKEN_HEADER, required = false) String aclToken, QueryParams queryParams);

/**
* Retrieves KV entries recursively using blocking queries.
*
* This existing method is retained for compatibility. Callers requiring a consistency
* mode for blocking queries should use the QueryParams overload and include waitTime
* and index in QueryParams.
*/
@GetExchange("/v1/kv/{context}?recurse")
ResponseEntity<List<GetValue>> getKVValues(@PathVariable String context,
@RequestHeader(name = ACL_TOKEN_HEADER, required = false) String aclToken,
Expand Down Expand Up @@ -150,6 +180,9 @@ ResponseEntity<List<Event>> eventList(@RequestParam("wait") @WaitTimeFormat Long
ResponseEntity<Event> eventFire(@PathVariable String name, @RequestBody String payload,
@RequestHeader(name = ACL_TOKEN_HEADER, required = false) String aclToken);

/**
* Query parameters supported by Consul HTTP API requests.
*/
class QueryParams {

/**
Expand Down Expand Up @@ -187,6 +220,13 @@ public QueryParams(long waitTime, long index) {
this(null, ConsistencyMode.DEFAULT, waitTime, index);
}

/**
* Creates query parameters with a consistency mode and blocking-query settings.
*/
public QueryParams(ConsistencyMode consistencyMode, long waitTime, long index) {
this(null, consistencyMode, waitTime, index);
}

public QueryParams(String datacenter, long waitTime, long index) {
this(datacenter, ConsistencyMode.DEFAULT, waitTime, index, null);
}
Expand Down Expand Up @@ -226,31 +266,34 @@ public String getNear() {

}

/**
* Consistency modes supported by the Consul HTTP API.
*/
enum ConsistencyMode {

/**
* Default Consul Consistency Mode.
* Default Consul consistency mode.
*/
DEFAULT("default"),

/**
* Stale Consul Consistency Mode.
* Stale Consul consistency mode.
*/
STALE("stale"),

/**
* Consistent Consul Consistency Mode.
* Consistent Consul consistency mode.
*/
CONSISTENT("consistent");

private String paramName;
private final String paramName;

ConsistencyMode(String paramName) {
this.paramName = paramName;
}

public String getParamName() {
return paramName;
return this.paramName;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ private URI resolve(QueryParams params) throws Exception {
values.getUriVariables());
}

@Test
public void consistentModePreservesBlockingQueryParameters() throws Exception {
URI uri = resolve(new QueryParams(ConsistencyMode.CONSISTENT, 30, 123));

assertThat(uri.getQuery()).contains("consistent=");
assertThat(uri.getQuery()).contains("wait=30s");
assertThat(uri.getQuery()).contains("index=123");
}

void target(QueryParams params) {
}

Expand Down
Loading