Makes sure origin tracked values are ordered.

Moves from regular HashMap to LinkedHashMap to preserve ordering from the config server.

fixes gh-1535
This commit is contained in:
Spencer Gibb
2020-01-07 14:14:18 -05:00
parent 60b8547271
commit 2576c6cf4c
2 changed files with 72 additions and 14 deletions

View File

@@ -21,6 +21,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -189,7 +190,7 @@ public class ConfigServicePropertySourceLocator implements PropertySourceLocator
private Map<String, Object> translateOrigins(String name,
Map<String, Object> source) {
Map<String, Object> withOrigins = new HashMap<>();
Map<String, Object> withOrigins = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry : source.entrySet()) {
boolean hasOrigin = false;

View File

@@ -18,8 +18,10 @@ package org.springframework.cloud.config.client;
import java.io.ByteArrayInputStream;
import java.net.URI;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import org.hamcrest.core.IsInstanceOf;
@@ -27,12 +29,13 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.ArgumentCaptor;
import org.mockito.Matchers;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.cloud.config.client.ConfigServicePropertySourceLocator.GenericRequestHeaderInterceptor;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.http.HttpEntity;
@@ -77,7 +80,7 @@ public class ConfigServicePropertySourceLocatorTests {
ArgumentCaptor<HttpEntity> argumentCaptor = ArgumentCaptor
.forClass(HttpEntity.class);
assertThat(this.locator.locate(this.environment)).isNotNull();
assertThat(this.locator.locateCollection(this.environment)).isNotNull();
Mockito.verify(this.restTemplate).exchange(anyString(), any(HttpMethod.class),
argumentCaptor.capture(), any(Class.class), anyString(), anyString());
@@ -94,7 +97,7 @@ public class ConfigServicePropertySourceLocatorTests {
this.locator.setRestTemplate(this.restTemplate);
TestPropertyValues.of("spring.cloud.config.label:v1.0.0")
.applyTo(this.environment);
assertThat(this.locator.locate(this.environment)).isNotNull();
assertThat(this.locator.locateCollection(this.environment)).isNotNull();
}
@Test
@@ -105,7 +108,7 @@ public class ConfigServicePropertySourceLocatorTests {
this.locator.setRestTemplate(this.restTemplate);
TestPropertyValues.of("spring.cloud.config.label:release/v1.0.0")
.applyTo(this.environment);
assertThat(this.locator.locate(this.environment)).isNotNull();
assertThat(this.locator.locateCollection(this.environment)).isNotNull();
}
@Test
@@ -113,7 +116,7 @@ public class ConfigServicePropertySourceLocatorTests {
mockRequestResponseWithLabel(
new ResponseEntity<>((Void) null, HttpStatus.NOT_FOUND), "nosuchlabel");
this.locator.setRestTemplate(this.restTemplate);
assertThat(this.locator.locate(this.environment)).isNull();
assertThat(this.locator.locateCollection(this.environment)).isEmpty();
}
@Test
@@ -130,7 +133,7 @@ public class ConfigServicePropertySourceLocatorTests {
this.expected.expect(IsInstanceOf.instanceOf(IllegalStateException.class));
this.expected.expectMessage(
"Could not locate PropertySource and the fail fast property is set, failing: None of labels [release/v1.0.1] found");
this.locator.locate(this.environment);
this.locator.locateCollection(this.environment);
}
@Test
@@ -138,7 +141,7 @@ public class ConfigServicePropertySourceLocatorTests {
mockRequestResponseWithoutLabel(
new ResponseEntity<>("Wah!", HttpStatus.INTERNAL_SERVER_ERROR));
this.locator.setRestTemplate(this.restTemplate);
assertThat(this.locator.locate(this.environment)).isNull();
assertThat(this.locator.locateCollection(this.environment)).isEmpty();
}
@Test
@@ -166,7 +169,7 @@ public class ConfigServicePropertySourceLocatorTests {
this.expected
.expectCause(IsInstanceOf.instanceOf(IllegalArgumentException.class));
this.expected.expectMessage("fail fast property is set");
this.locator.locate(this.environment);
this.locator.locateCollection(this.environment);
}
@Test
@@ -193,7 +196,7 @@ public class ConfigServicePropertySourceLocatorTests {
this.expected
.expectCause(IsInstanceOf.instanceOf(IllegalArgumentException.class));
this.expected.expectMessage("fail fast property is set");
this.locator.locate(this.environment);
this.locator.locateCollection(this.environment);
}
@Test
@@ -212,7 +215,7 @@ public class ConfigServicePropertySourceLocatorTests {
this.expected.expect(IllegalStateException.class);
this.expected.expectMessage(
"Could not locate PropertySource and the fail fast property is set, failing");
this.locator.locate(this.environment);
this.locator.locateCollection(this.environment);
}
@Test
@@ -306,12 +309,66 @@ public class ConfigServicePropertySourceLocatorTests {
}
}
@SuppressWarnings({ "unchecked", "raw" })
@Test
public void shouldPreserveOrder() {
Environment body = new Environment("app", "master");
LinkedHashMap<Object, Object> properties = new LinkedHashMap<>();
properties.put("zuul.routes.specificproduct.path",
originValue("/v1/product/electronics/**",
"Config Server /config-repo/zuul-service/zuul-service.yml:5:13"));
properties.put("zuul.routes.specificproduct.service-id",
originValue("electronic-product-service",
"Config Server /config-repo/zuul-service/zuul-service.yml:6:19"));
properties.put("zuul.routes.specificproduct.strip-prefix", originValue("false",
"Config Server /config-repo/zuul-service/zuul-service.yml:7:21"));
properties.put("zuul.routes.specificproduct.sensitiveHeaders", originValue("",
"Config Server /config-repo/zuul-service/zuul-service.yml:8:24"));
properties.put("zuul.routes.genericproduct.path", originValue("/v1/product/**",
"Config Server /config-repo/zuul-service/zuul-service.yml:10:13"));
properties.put("zuul.routes.genericproduct.service-id", originValue(
"product-service",
"Config Server /config-repo/zuul-service/zuul-service.yml:11:19"));
properties.put("zuul.routes.genericproduct.strip-prefix", originValue("false",
"Config Server /config-repo/zuul-service/zuul-service.yml:12:21"));
properties.put("zuul.routes.genericproduct.sensitiveHeaders", originValue("",
"Config Server /config-repo/zuul-service/zuul-service.yml:13:24"));
body.add(new PropertySource("source1", properties));
mockRequestResponseWithoutLabel(new ResponseEntity<>(body, HttpStatus.OK));
this.locator.setRestTemplate(this.restTemplate);
Collection<org.springframework.core.env.PropertySource<?>> propertySources = this.locator
.locateCollection(this.environment);
assertThat(propertySources).hasSize(1);
org.springframework.core.env.PropertySource<?> propertySource = propertySources
.iterator().next();
Map source = (Map) propertySource.getSource();
Iterator iterator = source.keySet().iterator();
assertThat(iterator.next()).isEqualTo("zuul.routes.specificproduct.path");
assertThat(iterator.next()).isEqualTo("zuul.routes.specificproduct.service-id");
assertThat(source).isInstanceOf(LinkedHashMap.class);
}
private Map<String, Object> originValue(String value, String origin) {
HashMap<String, Object> map = new HashMap<>();
map.put("value", value);
map.put("origin", origin);
return map;
}
@SuppressWarnings("unchecked")
private void mockRequestResponseWithLabel(ResponseEntity<?> response, String label) {
Mockito.when(this.restTemplate.exchange(Mockito.any(String.class),
Mockito.any(HttpMethod.class), Mockito.any(HttpEntity.class),
Mockito.any(Class.class), anyString(), anyString(), Matchers.eq(label)))
.thenReturn(response);
Mockito.any(Class.class), anyString(), anyString(),
ArgumentMatchers.eq(label))).thenReturn(response);
}
@SuppressWarnings("unchecked")
@@ -326,7 +383,7 @@ public class ConfigServicePropertySourceLocatorTests {
ResponseEntity<?> response, String expectedName) {
Mockito.when(this.restTemplate.exchange(Mockito.any(String.class),
Mockito.any(HttpMethod.class), Mockito.any(HttpEntity.class),
Mockito.any(Class.class), Matchers.eq(expectedName), anyString()))
Mockito.any(Class.class), ArgumentMatchers.eq(expectedName), anyString()))
.thenReturn(response);
}