Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add The Ability For The Config Client To Send All Labels To The Config Server #2602

Merged
Merged
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
19 changes: 19 additions & 0 deletions docs/modules/ROOT/pages/client.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,25 @@ Label can also be provided as a comma-separated list.
This behavior can be useful when working on a feature branch.
For instance, you might want to align the config label with your branch but make it optional (in that case, use `spring.cloud.config.label=myfeature,develop`).

[[requesting-multiple-labels]]
== Requesting Multiple Labels

Prior to Spring Cloud Config 4.2.0, if you set `spring.cloud.config.label` to a comma-separated list of labels, the Config Client would
try each label by making a request to the Config Server until it found one that worked. This meant that if the first label was found, subsequent labels would not be tried.

As of Spring Cloud Config 4.2.0 if you set `spring.cloud.config.label` to a comma-separated list of labels **AND** set
`spring.cloud.config.send-all-labels` the Config Client will make a single request to the Config Server with the comma-separated list of labels
and if **THE CONFIG SERVER IS USING VERSION 4.2.0 OR LATER** it will return a single response with property sources for all the labels.

NOTE: Setting `spring.cloud-config.send-all-labels` to `true`, setting `spring.cloud.config.label` to a comma-separated list of labels,
and using a Config Server version prior to 4.2.0 will result in unexpected behavior because the Config Server will try and find a label
that matches the comma-separated list value and will not try and split apart the labels.

By sending all labels in a single request you can reduce the number of requests made to the Config Server.

`spring.cloud.config.send-all-labels` is set to `false` by default so the old behavior is still the default, and it also maintains
compatibility with older versions of the Config Server.

[[specifying-multiple-urls-for-the-config-server]]
== Specifying Multiple URLs for the Config Server

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,13 @@ public class ConfigClientProperties {
*/
private Map<String, String> headers = new HashMap<>();

/**
* If set to true the client will send all labels to the server instead of sending one
* at a time. Support for this would require a config server version of 4.2.0 or
* higher.
*/
private boolean sendAllLabels = false;

ConfigClientProperties() {
}

Expand Down Expand Up @@ -338,6 +345,14 @@ public void setHeaders(Map<String, String> headers) {
this.headers = headers;
}

public boolean isSendAllLabels() {
return sendAllLabels;
}

public void setSendAllLabels(boolean sendAllLabels) {
this.sendAllLabels = sendAllLabels;
}

private Credentials extractCredentials(int index) {
Credentials result = new Credentials();
int noOfUrl = this.uri.length;
Expand Down Expand Up @@ -427,7 +442,7 @@ public String toString() {
+ Arrays.toString(this.uri) + ", mediaType=" + this.mediaType + ", discovery=" + this.discovery
+ ", failFast=" + this.failFast + ", token=" + this.token + ", requestConnectTimeout="
+ this.requestConnectTimeout + ", requestReadTimeout=" + this.requestReadTimeout + ", sendState="
+ this.sendState + ", headers=" + this.headers + "]";
+ this.sendState + ", headers=" + this.headers + ", sendAllLabels=" + this.sendAllLabels + "]";
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,19 @@ public ConfigData doLoad(ConfigDataLoaderContext context, ConfigServerConfigData
Exception error = null;
String errorBody = null;
try {
String[] labels = new String[] { "" };
if (StringUtils.hasText(properties.getLabel())) {
labels = StringUtils.commaDelimitedListToStringArray(properties.getLabel());
String labelProperty = properties.getLabel();
String[] labels;
if (!properties.isSendAllLabels() && StringUtils.hasText(labelProperty)) {
labels = StringUtils.commaDelimitedListToStringArray(labelProperty);
}
else {
// This could contain a comma separated list of labels sent directly to
// the config server
// For this to work you would need to be using a config server version of
// 4.2.0 or later
labels = new String[] { StringUtils.hasText(labelProperty) ? labelProperty : "" };
}
String state = ConfigClientStateHolder.getState();
// Try all the labels until one works
for (String label : labels) {
Environment result = getRemoteEnvironment(context, resource, label.trim(), state);
if (result != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright 2018-2019 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
*
* https://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 sample;

import java.util.Map;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.util.TestSocketUtils;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;

@SpringBootTest(classes = Application.class,
// Normally spring.cloud.config.enabled:true is the default but since we have the
// config server on the classpath we need to set it explicitly
// spring.config.import needs to come from orderingtest.yml to test this issue
// hence no spring.config.import here and config name change
properties = { "spring.application.name=profilesample", "spring.cloud.config.enabled=true",
"spring.config.name=orderingtest", "management.security.enabled=false",
"management.endpoints.web.exposure.include=*", "management.endpoint.env.show-values=ALWAYS",
"spring.cloud.config.label=label1,label2", "spring.cloud.config.send-all-labels=true" },
webEnvironment = RANDOM_PORT)
public class ConfigServerAndClientMultiLabelTests {

private static final String BASE_PATH = new WebEndpointProperties().getBasePath();

private static final int configPort = TestSocketUtils.findAvailableTcpPort();

private static ConfigurableApplicationContext server;

@LocalServerPort
private int port;

@BeforeAll
public static void startConfigServer() {
server = SpringApplication.run(org.springframework.cloud.config.server.test.TestConfigServerApplication.class,
"--spring.profiles.active=native", "--server.port=" + configPort, "--spring.config.name=server");

System.setProperty("spring.cloud.config.uri", "http://localhost:" + configPort);
}

@AfterAll
public static void close() {
System.clearProperty("spring.cloud.config.uri");
if (server != null) {
server.close();
}
}

@Test
@SuppressWarnings({ "unchecked", "rawtypes" })
public void contextLoads() {
ResponseEntity<Map> response = new TestRestTemplate()
.getForEntity("http://localhost:" + this.port + BASE_PATH + "/env/my.prop", Map.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
Map res = response.getBody();
assertThat(res).containsKey("propertySources");
Map<String, Object> property = (Map<String, Object>) res.get("property");
assertThat(property).containsEntry("value", "my value from config server default profile");

response = new TestRestTemplate()
.getForEntity("http://localhost:" + this.port + BASE_PATH + "/env/my.prop.label1", Map.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
res = response.getBody();
assertThat(res).containsKey("propertySources");
property = (Map<String, Object>) res.get("property");
assertThat(property).containsEntry("value", "my value from config server label1");

response = new TestRestTemplate()
.getForEntity("http://localhost:" + this.port + BASE_PATH + "/env/my.prop.label2", Map.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
res = response.getBody();
assertThat(res).containsKey("propertySources");
property = (Map<String, Object>) res.get("property");
assertThat(property).containsEntry("value", "my value from config server label2");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
my.prop.label1: my value from config server label1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
my.prop.label2: my value from config server label2
Loading