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

This commit is contained in:
Ryan Baxter
2024-10-24 09:09:01 -04:00
committed by GitHub
parent 68745f4f8c
commit 764022d514
6 changed files with 151 additions and 5 deletions

View File

@@ -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

View File

@@ -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() {
}
@@ -338,6 +345,14 @@ public class ConfigClientProperties {
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;
@@ -427,7 +442,7 @@ public class ConfigClientProperties {
+ 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 + "]";
}
/**

View File

@@ -113,12 +113,19 @@ public class ConfigServerConfigDataLoader implements ConfigDataLoader<ConfigServ
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) {

View File

@@ -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");
}
}

View File

@@ -0,0 +1 @@
my.prop.label1: my value from config server label1

View File

@@ -0,0 +1 @@
my.prop.label2: my value from config server label2