diff --git a/docs/pom.xml b/docs/pom.xml index 76c31c36..c322b347 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -8,7 +8,7 @@ org.springframework.cloud spring-cloud-commons-parent - 4.0.3-SNAPSHOT + 4.0.4-SNAPSHOT jar Spring Cloud Commons Docs diff --git a/docs/src/main/asciidoc/spring-cloud-commons.adoc b/docs/src/main/asciidoc/spring-cloud-commons.adoc index 62d6273b..0a5dc7ff 100644 --- a/docs/src/main/asciidoc/spring-cloud-commons.adoc +++ b/docs/src/main/asciidoc/spring-cloud-commons.adoc @@ -248,6 +248,10 @@ For a Spring Boot Actuator application, some additional management endpoints are * `/actuator/restart` to close the `ApplicationContext` and restart it (disabled by default). * `/actuator/pause` and `/actuator/resume` for calling the `Lifecycle` methods (`stop()` and `start()` on the `ApplicationContext`). +NOTE: While enabling the `POST` method for `/actuator/env` endpoint can provide flexibility and convenience in managing your application environment variables, +it's critical to ensure that the endpoint is secured and monitored to prevent potential security risks. +Add a `spring-boot-starter-security` dependency to configure access control for the actuator’s endpoint. + NOTE: If you disable the `/actuator/restart` endpoint then the `/actuator/pause` and `/actuator/resume` endpoints will also be disabled since they are just a special case of `/actuator/restart`. diff --git a/pom.xml b/pom.xml index 7df7cdc4..f990ebac 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.springframework.cloud spring-cloud-commons-parent - 4.0.3-SNAPSHOT + 4.0.4-SNAPSHOT pom Spring Cloud Commons Parent Spring Cloud Commons Parent @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 4.0.3-SNAPSHOT + 4.0.4-SNAPSHOT diff --git a/spring-cloud-commons-dependencies/pom.xml b/spring-cloud-commons-dependencies/pom.xml index c07ea103..b86e33f2 100644 --- a/spring-cloud-commons-dependencies/pom.xml +++ b/spring-cloud-commons-dependencies/pom.xml @@ -6,11 +6,11 @@ spring-cloud-dependencies-parent org.springframework.cloud - 4.0.3-SNAPSHOT + 4.0.4-SNAPSHOT spring-cloud-commons-dependencies - 4.0.3-SNAPSHOT + 4.0.4-SNAPSHOT pom spring-cloud-commons-dependencies Spring Cloud Commons Dependencies diff --git a/spring-cloud-commons/pom.xml b/spring-cloud-commons/pom.xml index 50af0db2..04d7acfc 100644 --- a/spring-cloud-commons/pom.xml +++ b/spring-cloud-commons/pom.xml @@ -7,7 +7,7 @@ org.springframework.cloud spring-cloud-commons-parent - 4.0.3-SNAPSHOT + 4.0.4-SNAPSHOT .. spring-cloud-commons diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/composite/CompositeDiscoveryClient.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/composite/CompositeDiscoveryClient.java index bc5f40a6..e44be5c0 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/composite/CompositeDiscoveryClient.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/composite/CompositeDiscoveryClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2023 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. @@ -31,6 +31,7 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator; * * @author Biju Kunjummen * @author Olga Maciaszek-Sharma + * @author Sean Ruffatti */ public class CompositeDiscoveryClient implements DiscoveryClient { @@ -73,6 +74,15 @@ public class CompositeDiscoveryClient implements DiscoveryClient { return new ArrayList<>(services); } + @Override + public void probe() { + if (this.discoveryClients != null) { + for (DiscoveryClient discoveryClient : this.discoveryClients) { + discoveryClient.probe(); + } + } + } + public List getDiscoveryClients() { return this.discoveryClients; } diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/composite/CompositeDiscoveryClientUnitTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/composite/CompositeDiscoveryClientUnitTests.java new file mode 100644 index 00000000..953cfbeb --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/composite/CompositeDiscoveryClientUnitTests.java @@ -0,0 +1,101 @@ +/* + * Copyright 2012-2023 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 org.springframework.cloud.client.discovery.composite; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; + +import static org.assertj.core.api.BDDAssertions.then; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Mockito tests for Composite Discovery Client + * + * @author Sean Ruffatti + */ +@ExtendWith(MockitoExtension.class) +public class CompositeDiscoveryClientUnitTests { + + private CompositeDiscoveryClient underTest; + + @Mock + private DiscoveryClient client1; + + @Mock + private DiscoveryClient client2; + + @BeforeEach + void setUp() { + underTest = new CompositeDiscoveryClient(Arrays.asList(client1, client2)); + } + + @Test + void shouldRetrieveInstancesByServiceId() { + ServiceInstance serviceInstance1 = new DefaultServiceInstance("instance1", "serviceId", "https://s1", 8443, + true); + when(client1.getInstances("serviceId")).thenReturn(Collections.singletonList(serviceInstance1)); + + List serviceInstances = underTest.getInstances("serviceId"); + + then(serviceInstances.get(0).getInstanceId()).isEqualTo("instance1"); + then(serviceInstances.get(0).getServiceId()).isEqualTo("serviceId"); + then(serviceInstances.get(0).getHost()).isEqualTo("https://s1"); + then(serviceInstances.get(0).getPort()).isEqualTo(8443); + } + + @Test + void shouldReturnServiceIds() { + when(client1.getServices()).thenReturn(Collections.singletonList("serviceId1")); + when(client2.getServices()).thenReturn(Collections.singletonList("serviceId2")); + + List services = underTest.getServices(); + + then(services.size()).isEqualTo(2); + then(services).containsOnlyOnce("serviceId1", "serviceId2"); + } + + @Test + void shouldReturnAllDiscoveryClients() { + then(underTest.getDiscoveryClients()).containsOnlyOnce(client1, client2); + } + + @Test + void shouldCallProbeOnAllDiscoveryClients() { + underTest.probe(); + + // Every DiscoveryClient bean should invoke DiscoveryClient.probe() when + // CompositeDiscoveryClient.probe() is invoked. + verify(client1, times(1)).probe(); + verify(client1, times(0)).getServices(); + verify(client2, times(1)).probe(); + verify(client2, times(0)).getServices(); + } + +} diff --git a/spring-cloud-context-integration-tests/pom.xml b/spring-cloud-context-integration-tests/pom.xml index 2bbda182..c38beef5 100644 --- a/spring-cloud-context-integration-tests/pom.xml +++ b/spring-cloud-context-integration-tests/pom.xml @@ -7,7 +7,7 @@ org.springframework.cloud spring-cloud-commons-parent - 4.0.3-SNAPSHOT + 4.0.4-SNAPSHOT .. spring-cloud-context-integration-tests diff --git a/spring-cloud-context-webflux-integration-tests/pom.xml b/spring-cloud-context-webflux-integration-tests/pom.xml index e006cc25..66a2fdb9 100644 --- a/spring-cloud-context-webflux-integration-tests/pom.xml +++ b/spring-cloud-context-webflux-integration-tests/pom.xml @@ -7,7 +7,7 @@ org.springframework.cloud spring-cloud-commons-parent - 4.0.3-SNAPSHOT + 4.0.4-SNAPSHOT .. spring-cloud-context-webflux-integration-tests diff --git a/spring-cloud-context/pom.xml b/spring-cloud-context/pom.xml index c43aa36d..b734c716 100644 --- a/spring-cloud-context/pom.xml +++ b/spring-cloud-context/pom.xml @@ -7,7 +7,7 @@ org.springframework.cloud spring-cloud-commons-parent - 4.0.3-SNAPSHOT + 4.0.4-SNAPSHOT .. spring-cloud-context diff --git a/spring-cloud-loadbalancer/pom.xml b/spring-cloud-loadbalancer/pom.xml index 40c9db91..1e123a94 100644 --- a/spring-cloud-loadbalancer/pom.xml +++ b/spring-cloud-loadbalancer/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-commons-parent - 4.0.3-SNAPSHOT + 4.0.4-SNAPSHOT .. spring-cloud-loadbalancer diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/DelegatingServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/DelegatingServiceInstanceListSupplier.java index 50ba6a18..80b6600f 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/DelegatingServiceInstanceListSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/DelegatingServiceInstanceListSupplier.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 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. @@ -18,6 +18,7 @@ package org.springframework.cloud.loadbalancer.core; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; +import org.springframework.cloud.client.ServiceInstance; import org.springframework.util.Assert; /** @@ -26,9 +27,10 @@ import org.springframework.util.Assert; * * @author Spencer Gibb * @author Olga Maciaszek-Sharma + * @author Jürgen Kreitler */ public abstract class DelegatingServiceInstanceListSupplier - implements ServiceInstanceListSupplier, InitializingBean, DisposableBean { + implements ServiceInstanceListSupplier, SelectedInstanceCallback, InitializingBean, DisposableBean { protected final ServiceInstanceListSupplier delegate; @@ -46,6 +48,13 @@ public abstract class DelegatingServiceInstanceListSupplier return this.delegate.getServiceId(); } + @Override + public void selectedServiceInstance(ServiceInstance serviceInstance) { + if (delegate instanceof SelectedInstanceCallback selectedInstanceCallbackDelegate) { + selectedInstanceCallbackDelegate.selectedServiceInstance(serviceInstance); + } + } + @Override public void afterPropertiesSet() throws Exception { if (delegate instanceof InitializingBean) { diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SameInstancePreferenceServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SameInstancePreferenceServiceInstanceListSupplier.java index d198051a..8d8e63f2 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SameInstancePreferenceServiceInstanceListSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SameInstancePreferenceServiceInstanceListSupplier.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2023 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. @@ -30,6 +30,7 @@ import org.springframework.cloud.client.ServiceInstance; * chosen instance if it's available. * * @author Olga Maciaszek-Sharma + * @author Jürgen Kreitler * @since 2.2.7 */ public class SameInstancePreferenceServiceInstanceListSupplier extends DelegatingServiceInstanceListSupplier @@ -72,6 +73,7 @@ public class SameInstancePreferenceServiceInstanceListSupplier extends Delegatin @Override public void selectedServiceInstance(ServiceInstance serviceInstance) { + super.selectedServiceInstance(serviceInstance); if (previouslyReturnedInstance == null || !previouslyReturnedInstance.equals(serviceInstance)) { previouslyReturnedInstance = serviceInstance; } diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/RetryAwareServiceInstanceListSupplierTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/RetryAwareServiceInstanceListSupplierTests.java index b8882630..c8befdab 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/RetryAwareServiceInstanceListSupplierTests.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/RetryAwareServiceInstanceListSupplierTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2023 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. @@ -27,11 +27,16 @@ import org.springframework.cloud.client.loadbalancer.RetryableRequestContext; import org.springframework.cloud.loadbalancer.support.ServiceInstanceListSuppliers; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; /** * Tests for {@link RetryAwareServiceInstanceListSupplier}. * * @author Olga Maciaszek-Sharma + * @author Jürgen Kreitler */ class RetryAwareServiceInstanceListSupplierTests { @@ -78,4 +83,13 @@ class RetryAwareServiceInstanceListSupplierTests { assertThat(returnedInstances).containsExactly(firstInstance); } + @Test + void shouldCallSelectedServiceInstanceOnItsDelegate() { + ServiceInstance firstInstance = instance(serviceId, "1host", false); + TestSelectedServiceInstanceSupplier delegate = mock(TestSelectedServiceInstanceSupplier.class); + DelegatingServiceInstanceListSupplier supplier = new RetryAwareServiceInstanceListSupplier(delegate); + supplier.selectedServiceInstance(firstInstance); + verify(delegate, times(1)).selectedServiceInstance(any(ServiceInstance.class)); + } + } diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/RoundRobinLoadBalancerTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/RoundRobinLoadBalancerTests.java index 50aa8e41..48d70d08 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/RoundRobinLoadBalancerTests.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/RoundRobinLoadBalancerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -32,10 +32,13 @@ import static java.lang.Integer.MIN_VALUE; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** * @author Zhuozhi JI + * @author Jürgen Kreitler */ class RoundRobinLoadBalancerTests { @@ -68,6 +71,18 @@ class RoundRobinLoadBalancerTests { assertThat(loadBalancer.position).hasValue(0); } + @Test + void shouldCallSelectedServiceInstanceIfSupplierOrItsDelegateIsInstanceOf() { + TestSelectedServiceInstanceSupplier delegate = mock(TestSelectedServiceInstanceSupplier.class); + DelegatingServiceInstanceListSupplier supplier = new RetryAwareServiceInstanceListSupplier(delegate); + when(delegate.get(any())).thenReturn(Flux.just(Collections.singletonList(new DefaultServiceInstance()))); + RoundRobinLoadBalancer loadBalancer = new RoundRobinLoadBalancer(new SimpleObjectProvider<>(supplier), + "shouldNotMovePositionIfOnlyOneInstance", 0); + + loadBalancer.choose().block(); + verify(delegate, times(1)).selectedServiceInstance(any(ServiceInstance.class)); + } + @SuppressWarnings("all") void assertOrderEnforced(int seed) { List instances = new ArrayList<>(); diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SameInstancePreferenceServiceInstanceListSupplierTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SameInstancePreferenceServiceInstanceListSupplierTests.java index fd66a251..e7b58b2f 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SameInstancePreferenceServiceInstanceListSupplierTests.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SameInstancePreferenceServiceInstanceListSupplierTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2023 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. @@ -26,13 +26,17 @@ import org.springframework.cloud.client.DefaultServiceInstance; import org.springframework.cloud.client.ServiceInstance; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** * Tests for {@link SameInstancePreferenceServiceInstanceListSupplier}. * * @author Olga Maciaszek-Sharma + * @author Jürgen Kreitler */ class SameInstancePreferenceServiceInstanceListSupplierTests { @@ -78,6 +82,16 @@ class SameInstancePreferenceServiceInstanceListSupplierTests { assertThat(instances).hasSize(2); } + @Test + void shouldCallSelectedServiceInstanceOnItsDelegate() { + ServiceInstance firstInstance = serviceInstance("test-4"); + TestSelectedServiceInstanceSupplier delegate = mock(TestSelectedServiceInstanceSupplier.class); + DelegatingServiceInstanceListSupplier supplier = new SameInstancePreferenceServiceInstanceListSupplier( + delegate); + supplier.selectedServiceInstance(firstInstance); + verify(delegate, times(1)).selectedServiceInstance(any(ServiceInstance.class)); + } + private DefaultServiceInstance serviceInstance(String instanceId) { return new DefaultServiceInstance(instanceId, "test", "http://test.test", 9080, false); } diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/TestSelectedServiceInstanceSupplier.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/TestSelectedServiceInstanceSupplier.java new file mode 100644 index 00000000..be834f63 --- /dev/null +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/TestSelectedServiceInstanceSupplier.java @@ -0,0 +1,28 @@ +/* + * Copyright 2012-2023 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 org.springframework.cloud.loadbalancer.core; + +/** + * Test supplier interface extending {@link ServiceInstanceListSupplier} and + * {@link SelectedInstanceCallback}. Useful if you need to verify certain behavior + * happened on a mock for example. + * + * @author Jürgen Kreitler + */ +public interface TestSelectedServiceInstanceSupplier extends ServiceInstanceListSupplier, SelectedInstanceCallback { + +} diff --git a/spring-cloud-loadbalancer/src/test/resources/logback-test.xml b/spring-cloud-loadbalancer/src/test/resources/logback-test.xml new file mode 100644 index 00000000..6531f0e6 --- /dev/null +++ b/spring-cloud-loadbalancer/src/test/resources/logback-test.xml @@ -0,0 +1,11 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + \ No newline at end of file diff --git a/spring-cloud-starter-bootstrap/pom.xml b/spring-cloud-starter-bootstrap/pom.xml index 65a0053c..b8aaf038 100644 --- a/spring-cloud-starter-bootstrap/pom.xml +++ b/spring-cloud-starter-bootstrap/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-commons-parent - 4.0.3-SNAPSHOT + 4.0.4-SNAPSHOT .. jar diff --git a/spring-cloud-starter-loadbalancer/pom.xml b/spring-cloud-starter-loadbalancer/pom.xml index ff9b93a8..109bbf5a 100644 --- a/spring-cloud-starter-loadbalancer/pom.xml +++ b/spring-cloud-starter-loadbalancer/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-commons-parent - 4.0.3-SNAPSHOT + 4.0.4-SNAPSHOT .. 4.0.0 diff --git a/spring-cloud-starter/pom.xml b/spring-cloud-starter/pom.xml index bb851622..54915753 100644 --- a/spring-cloud-starter/pom.xml +++ b/spring-cloud-starter/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-commons-parent - 4.0.3-SNAPSHOT + 4.0.4-SNAPSHOT spring-cloud-starter spring-cloud-starter diff --git a/spring-cloud-test-support/pom.xml b/spring-cloud-test-support/pom.xml index 0ded8cd2..1a15432f 100644 --- a/spring-cloud-test-support/pom.xml +++ b/spring-cloud-test-support/pom.xml @@ -7,7 +7,7 @@ org.springframework.cloud spring-cloud-commons-parent - 4.0.3-SNAPSHOT + 4.0.4-SNAPSHOT .. spring-cloud-test-support