Compare commits

..
Author SHA1 Message Date
buildmaster aa45392a9a Update SNAPSHOT to 4.1.2 2024-05-30 11:22:59 +00:00
Olga Maciaszek-Sharma 307b93bf5a Upgrade antora-ui-spring to 0.4.13. 2024-05-16 12:01:12 +02:00
buildmaster f21d0046cd Bumping versions 2024-04-25 11:05:03 +00:00
WonChul Heo de6d506e9a Collect metrics for application instances (#4270) 2024-04-25 11:57:45 +02:00
buildmaster 2a8b7ed054 Bumping versions 2024-04-16 11:04:41 +00:00
Olga MaciaszekSharma 79a2eb8c4f Revert "Enable Connection Evictor in HttpClient (#4103)"
This reverts commit c071e6b01c.
2024-04-15 14:00:54 +02:00
WonChul Heo dbb36fb0ed Change the order in which events are issued (#4274) 2024-04-09 17:59:10 +02:00
Ryan Baxter f8ed5c4163 Update antora-playbook.yml 2024-04-04 14:30:51 -04:00
Puppy4C 47d3f7133c Improve EurekaConfigServerInstanceProvider (#4267) 2024-04-03 13:12:14 +02:00
ZIRAKrezovic a74d8f966e Fix basic auth with encoded characters (#3815) (#4260) 2024-04-03 12:38:07 +02:00
ZIRAKrezovic 5113f0abf3 Add support for configuring RestTemplate via RestTemplateBuilder (#4255) (#4272) 2024-04-02 15:28:23 +02:00
buildmaster 99b8d2c6d4 Bumping versions to 4.1.2-SNAPSHOT after release 2024-03-27 13:56:59 +00:00
buildmaster 3ef81ba9cd Going back to snapshots 2024-03-27 13:56:58 +00:00
buildmaster c7885631ed Update SNAPSHOT to 4.1.1 2024-03-27 13:55:43 +00:00
buildmaster bb15b7f40e Bumping versions 2024-03-27 11:05:01 +00:00
Olga MaciaszekSharma c97bd250af Revert "Add support for configuring RestTemplate via RestTemplateBuilder (#4255) (#4262)"
This reverts commit 6b6173de3a.
2024-03-26 19:21:46 +01:00
Olga MaciaszekSharma 4b101efe19 Revert "Update javadocs and license comments for recent changes."
This reverts commit 61d5f5f9fb.
2024-03-26 19:20:44 +01:00
Olga MaciaszekSharma 61d5f5f9fb Update javadocs and license comments for recent changes. 2024-03-26 18:45:31 +01:00
ZIRAKrezovic 6b6173de3a Add support for configuring RestTemplate via RestTemplateBuilder (#4255) (#4262) 2024-03-26 18:42:50 +01:00
ZIRAKrezovic 4536954aca Clean up eureka-client deprecations (#4261) 2024-03-26 17:06:55 +01:00
dependabot[bot] 817ea2c88a Bump testcontainers.version from 1.19.6 to 1.19.7 (#4271) 2024-03-26 15:30:22 +01:00
ZIRAKrezovic 1754585fc0 Enable Connection Evictor in HttpClient (#4103) (#4258) 2024-03-26 15:29:18 +01:00
dependabot[bot] 0bebfd1e3d Bump testcontainers.version from 1.19.4 to 1.19.6 (#4264) 2024-03-25 16:41:34 +01:00
dependabot[bot] a0c95db99e Bump org.glassfish.hk2:spring-bridge from 3.0.6 to 3.1.0 (#4265) 2024-03-25 16:40:43 +01:00
Olga MaciaszekSharma ad002b4da2 Upgrade antora-ui-spring to 0.4.11. 2024-03-07 17:47:04 +01:00
buildmaster fb088832db Bumping versions 2024-03-05 11:04:58 +00:00
buildmaster 3d30846108 Bumping versions 2024-02-23 11:05:23 +00:00
RobertBleyl 3c84f2ec04 Introduce asyncClientInitialization property. (#4239) 2024-02-22 12:48:52 +01:00
Olga MaciaszekSharma 479f10e379 Merge remote-tracking branch 'origin/main' 2024-02-15 18:40:55 +01:00
Olga MaciaszekSharma 1bc989c5ce Remove unnecessary dependency. 2024-02-15 18:40:45 +01:00
dependabot[bot] cf81e327ad Bump testcontainers.version from 1.19.3 to 1.19.4 (#4243)
Bumps `testcontainers.version` from 1.19.3 to 1.19.4.

Updates `org.testcontainers:mockserver` from 1.19.3 to 1.19.4
- [Release notes](https://github.com/testcontainers/testcontainers-java/releases)
- [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.19.3...1.19.4)

Updates `org.testcontainers:junit-jupiter` from 1.19.3 to 1.19.4
- [Release notes](https://github.com/testcontainers/testcontainers-java/releases)
- [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.19.3...1.19.4)

---
updated-dependencies:
- dependency-name: org.testcontainers:mockserver
  dependency-type: direct:development
  update-type: version-update:semver-patch
- dependency-name: org.testcontainers:junit-jupiter
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-14 18:22:43 +01:00
dependabot[bot] 35ef348fe9 Bump org.glassfish.hk2:spring-bridge from 3.0.5 to 3.0.6 (#4245)
Bumps [org.glassfish.hk2:spring-bridge](https://github.com/eclipse-ee4j/glassfish-hk2) from 3.0.5 to 3.0.6.
- [Release notes](https://github.com/eclipse-ee4j/glassfish-hk2/releases)
- [Changelog](https://github.com/eclipse-ee4j/glassfish-hk2/blob/master/CHANGELOG)
- [Commits](https://github.com/eclipse-ee4j/glassfish-hk2/commits)

---
updated-dependencies:
- dependency-name: org.glassfish.hk2:spring-bridge
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-14 18:21:05 +01:00
dependabot[bot] 45ddfdfb7c Bump codecov/codecov-action from 3 to 4 (#4249)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-14 18:20:30 +01:00
Olga MaciaszekSharma 8ae4950200 Upgrade Eureka Server to 2.0.2. Upgrade glassfish spring-bridge to 3.0.6. Fixes gh-4238. 2024-02-14 12:32:53 +01:00
buildmaster d7275b888b Bumping versions 2024-01-31 11:04:43 +00:00
Olga Maciaszek-Sharma ed9ca444c8 Improve documentation on HTTP clients used by EurekaClient. (#4242) 2024-01-25 16:26:18 +01:00
39 changed files with 974 additions and 140 deletions
+1 -1
View File
@@ -28,6 +28,6 @@ jobs:
cache: 'maven'
- name: Build with Maven
run: ./mvnw clean install -B -U -P sonar
- uses: codecov/codecov-action@v3
- uses: codecov/codecov-action@v4
with:
fail_ci_if_error: false
+3 -2
View File
@@ -1,10 +1,11 @@
antora:
extensions:
- '@springio/antora-extensions/partial-build-extension'
# atlas-extension must be before latest-version-extension so latest versions are applied to imported versions
- '@antora/atlas-extension'
- require: '@springio/antora-extensions/latest-version-extension'
- require: '@springio/antora-extensions/inject-collector-cache-config-extension'
- '@antora/collector-extension'
- '@antora/atlas-extension'
- require: '@springio/antora-extensions/root-component-extension'
root_component_name: 'cloud-netflix'
- '@springio/antora-extensions/static-page-extension'
@@ -36,4 +37,4 @@ runtime:
format: pretty
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.2/ui-bundle.zip
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.13/ui-bundle.zip
@@ -604,6 +604,17 @@ when running a Eureka server you must include these dependencies in your POM or
Spring Cloud Netflix Eureka Server does not support Spring AOT transformations or native images.
=== Metrics
`EurekaInstanceMonitor` listens to events related to Eureka instance registration and creates/updates `Gauge`s for Eureka instance information in Micrometer's `MeterRegistry`. By default, this behavior is disabled. If you want to enable it, you need to set `eureka.server.metrics.enabled` to `true`.
By default, the `Gauge`s are named `eureka.server.instances` and have the following tags:
- `application`: application name
- `status`: instance status (`UP`, `DOWN`, `STARTING`, `OUT_OF_SERVICE`, `UNKNOWN`, see: `com.netflix.appinfo.InstanceInfo.InstanceStatus`)
You can add additional tags by injecting your own implementation of `EurekaInstanceTagsProvider`.
== Configuration properties
To see the list of all Spring Cloud Netflix related configuration properties please check link:appendix.html[the Appendix page].
+3 -1
View File
@@ -68,6 +68,7 @@
|eureka.instance.a-s-g-name | | Gets the AWS autoscaling group name associated with this instance. This information is specifically used in an AWS environment to automatically put an instance out of service after the instance is launched and it has been disabled for traffic..
|eureka.instance.app-group-name | | Get the name of the application group to be registered with eureka.
|eureka.instance.appname | `+++unknown+++` | Get the name of the application to be registered with eureka.
|eureka.instance.async-client-initialization | `+++false+++` | If true the EurekaClient will be initialized asynchronously when the InstanceRegistry bean is created.
|eureka.instance.data-center-info | | Returns the data center this instance is deployed. This information is used to get some AWS specific instance information if the instance is deployed in AWS.
|eureka.instance.default-address-resolution-order | `+++[]+++` |
|eureka.instance.environment | |
@@ -127,6 +128,7 @@
|eureka.server.max-threads-for-peer-replication | `+++20+++` |
|eureka.server.max-threads-for-status-replication | `+++1+++` |
|eureka.server.max-time-for-replication | `+++30000+++` |
|eureka.server.metrics.enabled | `+++false+++` | Indicates whether the metrics should be enabled for eureka instances.
|eureka.server.min-available-instances-for-peer-replication | `+++-1+++` |
|eureka.server.min-threads-for-peer-replication | `+++5+++` |
|eureka.server.min-threads-for-status-replication | `+++1+++` |
@@ -173,7 +175,7 @@
|eureka.server.use-read-only-response-cache | `+++true+++` |
|eureka.server.wait-time-in-ms-when-sync-empty | `+++0+++` |
|eureka.server.xml-codec-name | |
|spring.cloud.compatibility-verifier.compatible-boot-versions | `+++3.2.x+++` | Default accepted versions for the Spring Boot dependency. You can set {@code x} for the patch version if you don't want to specify a concrete value. Example: {@code 3.4.x}
|spring.cloud.compatibility-verifier.compatible-boot-versions | | Default accepted versions for the Spring Boot dependency. You can set {@code x} for the patch version if you don't want to specify a concrete value. Example: {@code 3.4.x}
|spring.cloud.compatibility-verifier.enabled | `+++false+++` | Enables creation of Spring Cloud compatibility verification.
|spring.cloud.config.allow-override | `+++true+++` | Flag to indicate that {@link #isOverrideSystemProperties() systemPropertiesOverride} can be used. Set to false to prevent users from changing the default accidentally. Default true.
|spring.cloud.config.initialize-on-context-refresh | `+++false+++` | Flag to initialize bootstrap configuration on context refresh event. Default false.
+1 -1
View File
@@ -8,7 +8,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>4.1.1-SNAPSHOT</version>
<version>4.1.2</version>
<relativePath>..</relativePath>
</parent>
<packaging>jar</packaging>
+5 -5
View File
@@ -3,14 +3,14 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-netflix</artifactId>
<version>4.1.1-SNAPSHOT</version>
<version>4.1.2</version>
<packaging>pom</packaging>
<name>Spring Cloud Netflix</name>
<description>Spring Cloud Netflix</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build</artifactId>
<version>4.1.1-SNAPSHOT</version>
<version>4.1.2</version>
<relativePath />
</parent>
<scm>
@@ -21,9 +21,9 @@
</scm>
<properties>
<bintray.package>netflix</bintray.package>
<spring-cloud-commons.version>4.1.1-SNAPSHOT</spring-cloud-commons.version>
<spring-cloud-config.version>4.1.1-SNAPSHOT</spring-cloud-config.version>
<testcontainers.version>1.19.3</testcontainers.version>
<spring-cloud-commons.version>4.1.3</spring-cloud-commons.version>
<spring-cloud-config.version>4.1.2</spring-cloud-config.version>
<testcontainers.version>1.19.7</testcontainers.version>
<mockserverclient.version>5.15.0</mockserverclient.version>
<!-- Sonar -->
+3 -3
View File
@@ -5,16 +5,16 @@
<parent>
<artifactId>spring-cloud-dependencies-parent</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>4.1.1-SNAPSHOT</version>
<version>4.1.2</version>
<relativePath/>
</parent>
<artifactId>spring-cloud-netflix-dependencies</artifactId>
<version>4.1.1-SNAPSHOT</version>
<version>4.1.2</version>
<packaging>pom</packaging>
<name>spring-cloud-netflix-dependencies</name>
<description>Spring Cloud Netflix Dependencies</description>
<properties>
<eureka.version>2.0.1</eureka.version>
<eureka.version>2.0.2</eureka.version>
</properties>
<dependencyManagement>
<dependencies>
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>4.1.1-SNAPSHOT</version>
<version>4.1.2</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-netflix-eureka-client-tls-tests</artifactId>
@@ -24,8 +24,11 @@ import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.configuration.TlsProperties;
import org.springframework.cloud.netflix.eureka.config.DiscoveryClientOptionalArgsConfiguration;
import org.springframework.cloud.netflix.eureka.http.EurekaClientHttpRequestFactorySupplier;
@@ -76,7 +79,7 @@ public class RestTemplateEurekaClientTest extends BaseCertTest {
EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier)
throws GeneralSecurityException, IOException {
return configuration.restTemplateDiscoveryClientOptionalArgs(tlsProperties,
eurekaClientHttpRequestFactorySupplier);
eurekaClientHttpRequestFactorySupplier, new RestTemplateBuilderObjectProvider());
}
}
@@ -88,4 +91,30 @@ public class RestTemplateEurekaClientTest extends BaseCertTest {
}
private static class RestTemplateBuilderObjectProvider implements ObjectProvider<RestTemplateBuilder> {
private final RestTemplateBuilder builder = new RestTemplateBuilder();
@Override
public RestTemplateBuilder getObject(Object... args) throws BeansException {
return builder;
}
@Override
public RestTemplateBuilder getIfAvailable() throws BeansException {
return builder;
}
@Override
public RestTemplateBuilder getIfUnique() throws BeansException {
return builder;
}
@Override
public RestTemplateBuilder getObject() throws BeansException {
return builder;
}
}
}
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>4.1.1-SNAPSHOT</version>
<version>4.1.2</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-netflix-eureka-client</artifactId>
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2023 the original author or authors.
* Copyright 2013-2024 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.
@@ -103,6 +103,7 @@ import static org.springframework.cloud.commons.util.IdUtils.getDefaultInstanceI
* @author Daniel Lavoie
* @author Olga Maciaszek-Sharma
* @author Tim Ysewyn
* @author Robert Bleyl
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@@ -224,8 +225,8 @@ public class EurekaClientAutoConfiguration {
}
@Bean
public EurekaServiceRegistry eurekaServiceRegistry() {
return new EurekaServiceRegistry();
public EurekaServiceRegistry eurekaServiceRegistry(EurekaInstanceConfigBean eurekaInstanceConfigBean) {
return new EurekaServiceRegistry(eurekaInstanceConfigBean);
}
// @Bean
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* Copyright 2013-2024 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.
@@ -38,6 +38,7 @@ import org.springframework.util.StringUtils;
* @author Spencer Gibb
* @author Ryan Baxter
* @author Gregor Zurowski
* @author Robert Bleyl
*/
@ConfigurationProperties("eureka.instance")
public class EurekaInstanceConfigBean implements CloudEurekaInstanceConfig, EnvironmentAware {
@@ -269,6 +270,12 @@ public class EurekaInstanceConfigBean implements CloudEurekaInstanceConfig, Envi
*/
private boolean preferIpAddress = false;
/**
* If true the EurekaClient will be initialized asynchronously when the
* InstanceRegistry bean is created.
*/
private boolean asyncClientInitialization;
/**
* Initial status to register with remote Eureka server.
*/
@@ -547,6 +554,14 @@ public class EurekaInstanceConfigBean implements CloudEurekaInstanceConfig, Envi
this.preferIpAddress = preferIpAddress;
}
public boolean isAsyncClientInitialization() {
return asyncClientInitialization;
}
public void setAsyncClientInitialization(boolean asyncClientInitialization) {
this.asyncClientInitialization = asyncClientInitialization;
}
public InstanceStatus getInitialStatus() {
return initialStatus;
}
@@ -597,6 +612,7 @@ public class EurekaInstanceConfigBean implements CloudEurekaInstanceConfig, Envi
&& Objects.equals(namespace, that.namespace) && Objects.equals(hostname, that.hostname)
&& preferIpAddress == that.preferIpAddress && Objects.equals(initialStatus, that.initialStatus)
&& Arrays.equals(defaultAddressResolutionOrder, that.defaultAddressResolutionOrder)
&& asyncClientInitialization == that.asyncClientInitialization
&& Objects.equals(environment, that.environment);
}
@@ -607,7 +623,7 @@ public class EurekaInstanceConfigBean implements CloudEurekaInstanceConfig, Envi
leaseExpirationDurationInSeconds, virtualHostName, instanceId, secureVirtualHostName, aSGName,
metadataMap, dataCenterInfo, ipAddress, statusPageUrlPath, statusPageUrl, homePageUrlPath, homePageUrl,
healthCheckUrlPath, healthCheckUrl, secureHealthCheckUrl, namespace, hostname, preferIpAddress,
initialStatus, Arrays.hashCode(defaultAddressResolutionOrder), environment);
asyncClientInitialization, initialStatus, Arrays.hashCode(defaultAddressResolutionOrder), environment);
}
@Override
@@ -631,6 +647,7 @@ public class EurekaInstanceConfigBean implements CloudEurekaInstanceConfig, Envi
.append("', ").append("healthCheckUrl='").append(healthCheckUrl).append("', ")
.append("secureHealthCheckUrl='").append(secureHealthCheckUrl).append("', ").append("namespace='")
.append(namespace).append("', ").append("hostname='").append(hostname).append("', ")
.append("asyncClientInitialization=").append(asyncClientInitialization).append(", ")
.append("preferIpAddress=").append(preferIpAddress).append(", ").append("initialStatus=")
.append(initialStatus).append(", ").append("defaultAddressResolutionOrder=")
.append(Arrays.toString(defaultAddressResolutionOrder)).append(", ").append("environment=")
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2022 the original author or authors.
* Copyright 2017-2024 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.
@@ -34,6 +34,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.configuration.SSLContextFactory;
import org.springframework.cloud.configuration.TlsProperties;
import org.springframework.cloud.netflix.eureka.RestTemplateTimeoutProperties;
@@ -49,6 +50,7 @@ import org.springframework.web.reactive.function.client.WebClient;
/**
* @author Daniel Lavoie
* @author Armin Krezovic
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(RestTemplateTimeoutProperties.class)
@@ -69,11 +71,11 @@ public class DiscoveryClientOptionalArgsConfiguration {
@ConditionalOnProperty(prefix = "eureka.client", name = "webclient.enabled", matchIfMissing = true,
havingValue = "false")
public RestTemplateDiscoveryClientOptionalArgs restTemplateDiscoveryClientOptionalArgs(TlsProperties tlsProperties,
EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier)
throws GeneralSecurityException, IOException {
EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier,
ObjectProvider<RestTemplateBuilder> restTemplateBuilders) throws GeneralSecurityException, IOException {
logger.info("Eureka HTTP Client uses RestTemplate.");
RestTemplateDiscoveryClientOptionalArgs result = new RestTemplateDiscoveryClientOptionalArgs(
eurekaClientHttpRequestFactorySupplier);
eurekaClientHttpRequestFactorySupplier, restTemplateBuilders::getIfAvailable);
setupTLS(result, tlsProperties);
return result;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* Copyright 2013-2024 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.
@@ -29,10 +29,12 @@ import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration;
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.config.client.ConfigServerInstanceProvider;
import org.springframework.cloud.config.client.ConfigServicePropertySourceLocator;
import org.springframework.cloud.configuration.TlsProperties;
import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean;
import org.springframework.cloud.netflix.eureka.RestTemplateTimeoutProperties;
import org.springframework.cloud.netflix.eureka.http.DefaultEurekaClientHttpRequestFactorySupplier;
import org.springframework.cloud.netflix.eureka.http.EurekaClientHttpRequestFactorySupplier;
import org.springframework.cloud.netflix.eureka.http.RestTemplateEurekaHttpClient;
@@ -51,11 +53,12 @@ import org.springframework.web.reactive.function.client.WebClient;
* discovery.
*
* @author Dave Syer
* @author Armin Krezovic
*/
@ConditionalOnClass(ConfigServicePropertySourceLocator.class)
@Conditional(EurekaConfigServerBootstrapConfiguration.EurekaConfigServerBootstrapCondition.class)
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@EnableConfigurationProperties(RestTemplateTimeoutProperties.class)
public class EurekaConfigServerBootstrapConfiguration {
@Bean
@@ -70,16 +73,18 @@ public class EurekaConfigServerBootstrapConfiguration {
havingValue = "false")
public RestTemplateEurekaHttpClient configDiscoveryRestTemplateEurekaHttpClient(EurekaClientConfigBean config,
Environment env, @Nullable TlsProperties properties,
EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier) {
EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier,
ObjectProvider<RestTemplateBuilder> restTemplateBuilders) {
return (RestTemplateEurekaHttpClient) new RestTemplateTransportClientFactory(properties,
eurekaClientHttpRequestFactorySupplier)
eurekaClientHttpRequestFactorySupplier, restTemplateBuilders::getIfAvailable)
.newClient(HostnameBasedUrlRandomizer.randomEndpoint(config, env));
}
@Bean
@ConditionalOnMissingBean
EurekaClientHttpRequestFactorySupplier defaultEurekaClientHttpRequestFactorySupplier() {
return new DefaultEurekaClientHttpRequestFactorySupplier();
EurekaClientHttpRequestFactorySupplier defaultEurekaClientHttpRequestFactorySupplier(
RestTemplateTimeoutProperties restTemplateTimeoutProperties) {
return new DefaultEurekaClientHttpRequestFactorySupplier(restTemplateTimeoutProperties);
}
@Bean
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* Copyright 2013-2024 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,9 @@ import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.netflix.eureka.EurekaServiceInstance;
import org.springframework.http.HttpStatus;
/**
* @author Tang Xiong
*/
public class EurekaConfigServerInstanceProvider {
private final Log log;
@@ -53,7 +56,11 @@ public class EurekaConfigServerInstanceProvider {
if (log.isDebugEnabled()) {
log.debug("eurekaConfigServerInstanceProvider finding instances for " + serviceId);
}
EurekaHttpResponse<Applications> response = client.getApplications(config.getRegion());
String remoteRegionsStr = config.fetchRegistryForRemoteRegions();
String[] remoteRegions = remoteRegionsStr == null ? null : remoteRegionsStr.split(",");
EurekaHttpResponse<Applications> response = config.getRegistryRefreshSingleVipAddress() == null
? client.getApplications(remoteRegions)
: client.getVip(config.getRegistryRefreshSingleVipAddress(), remoteRegions);
List<ServiceInstance> instances = new ArrayList<>();
if (!isSuccessful(response) || response.getEntity() == null) {
return instances;
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2022 the original author or authors.
* Copyright 2017-2024 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.
@@ -16,18 +16,32 @@
package org.springframework.cloud.netflix.eureka.http;
import java.util.function.Supplier;
import com.netflix.discovery.AbstractDiscoveryClientOptionalArgs;
import org.springframework.boot.web.client.RestTemplateBuilder;
/**
* @author Daniel Lavoie
* @author Armin Krezovic
*/
public class RestTemplateDiscoveryClientOptionalArgs extends AbstractDiscoveryClientOptionalArgs<Void> {
protected final EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier;
protected final Supplier<RestTemplateBuilder> restTemplateBuilderSupplier;
public RestTemplateDiscoveryClientOptionalArgs(
EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier) {
this(eurekaClientHttpRequestFactorySupplier, RestTemplateBuilder::new);
}
public RestTemplateDiscoveryClientOptionalArgs(
EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier,
Supplier<RestTemplateBuilder> restTemplateBuilderSupplier) {
this.eurekaClientHttpRequestFactorySupplier = eurekaClientHttpRequestFactorySupplier;
this.restTemplateBuilderSupplier = restTemplateBuilderSupplier;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2022 the original author or authors.
* Copyright 2017-2024 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.
@@ -29,6 +29,7 @@ import com.netflix.discovery.shared.transport.jersey.TransportClientFactories;
/**
* @author Daniel Lavoie
* @author Armin Krezovic
*/
public class RestTemplateTransportClientFactories implements TransportClientFactories<Void> {
@@ -42,7 +43,7 @@ public class RestTemplateTransportClientFactories implements TransportClientFact
public TransportClientFactory newTransportClientFactory(EurekaClientConfig clientConfig,
Collection<Void> additionalFilters, InstanceInfo myInstanceInfo) {
return new RestTemplateTransportClientFactory(this.args.getSSLContext(), this.args.getHostnameVerifier(),
this.args.eurekaClientHttpRequestFactorySupplier);
this.args.eurekaClientHttpRequestFactorySupplier, this.args.restTemplateBuilderSupplier);
}
@Override
@@ -50,7 +51,7 @@ public class RestTemplateTransportClientFactories implements TransportClientFact
final Collection<Void> additionalFilters, final InstanceInfo myInstanceInfo,
final Optional<SSLContext> sslContext, final Optional<HostnameVerifier> hostnameVerifier) {
return new RestTemplateTransportClientFactory(this.args.getSSLContext(), this.args.getHostnameVerifier(),
this.args.eurekaClientHttpRequestFactorySupplier);
this.args.eurekaClientHttpRequestFactorySupplier, this.args.restTemplateBuilderSupplier);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2022 the original author or authors.
* Copyright 2017-2024 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.
@@ -21,6 +21,7 @@ import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Optional;
import java.util.function.Supplier;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
@@ -44,6 +45,7 @@ import com.netflix.discovery.shared.resolver.EurekaEndpoint;
import com.netflix.discovery.shared.transport.EurekaHttpClient;
import com.netflix.discovery.shared.transport.TransportClientFactory;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.configuration.SSLContextFactory;
import org.springframework.cloud.configuration.TlsProperties;
import org.springframework.http.HttpHeaders;
@@ -63,6 +65,7 @@ import org.springframework.web.util.UriComponentsBuilder;
* deserialization.
*
* @author Daniel Lavoie
* @author Armin Krezovic
*/
public class RestTemplateTransportClientFactory implements TransportClientFactory {
@@ -72,11 +75,20 @@ public class RestTemplateTransportClientFactory implements TransportClientFactor
private final EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier;
private final Supplier<RestTemplateBuilder> restTemplateBuilderSupplier;
public RestTemplateTransportClientFactory(TlsProperties tlsProperties,
EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier) {
EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier,
Supplier<RestTemplateBuilder> restTemplateBuilderSupplier) {
this.sslContext = context(tlsProperties);
this.hostnameVerifier = Optional.empty();
this.eurekaClientHttpRequestFactorySupplier = eurekaClientHttpRequestFactorySupplier;
this.restTemplateBuilderSupplier = restTemplateBuilderSupplier;
}
public RestTemplateTransportClientFactory(TlsProperties tlsProperties,
EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier) {
this(tlsProperties, eurekaClientHttpRequestFactorySupplier, RestTemplateBuilder::new);
}
private Optional<SSLContext> context(TlsProperties properties) {
@@ -93,16 +105,23 @@ public class RestTemplateTransportClientFactory implements TransportClientFactor
public RestTemplateTransportClientFactory(Optional<SSLContext> sslContext,
Optional<HostnameVerifier> hostnameVerifier,
EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier) {
EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier,
Supplier<RestTemplateBuilder> restTemplateBuilderSupplier) {
this.sslContext = sslContext;
this.hostnameVerifier = hostnameVerifier;
this.eurekaClientHttpRequestFactorySupplier = eurekaClientHttpRequestFactorySupplier;
this.restTemplateBuilderSupplier = restTemplateBuilderSupplier;
}
public RestTemplateTransportClientFactory(Optional<SSLContext> sslContext,
Optional<HostnameVerifier> hostnameVerifier,
EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier) {
this(sslContext, hostnameVerifier, eurekaClientHttpRequestFactorySupplier, RestTemplateBuilder::new);
}
public RestTemplateTransportClientFactory() {
this.sslContext = Optional.empty();
this.hostnameVerifier = Optional.empty();
this.eurekaClientHttpRequestFactorySupplier = new DefaultEurekaClientHttpRequestFactorySupplier();
this(Optional.empty(), Optional.empty(), new DefaultEurekaClientHttpRequestFactorySupplier());
}
@Override
@@ -120,7 +139,15 @@ public class RestTemplateTransportClientFactory implements TransportClientFactor
private RestTemplate restTemplate(String serviceUrl) {
ClientHttpRequestFactory requestFactory = this.eurekaClientHttpRequestFactorySupplier
.get(this.sslContext.orElse(null), this.hostnameVerifier.orElse(null));
RestTemplate restTemplate = new RestTemplate(requestFactory);
RestTemplate restTemplate;
if (restTemplateBuilderSupplier != null && restTemplateBuilderSupplier.get() != null) {
restTemplate = restTemplateBuilderSupplier.get().requestFactory(() -> requestFactory).build();
}
else {
restTemplate = new RestTemplate(requestFactory);
}
try {
URI serviceURI = new URI(serviceUrl);
@@ -209,11 +236,6 @@ public class RestTemplateTransportClientFactory implements TransportClientFactor
return response.getStatusCode();
}
@Override
public int getRawStatusCode() throws IOException {
return response.getRawStatusCode();
}
@Override
public String getStatusText() throws IOException {
return response.getStatusText();
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2022 the original author or authors.
* Copyright 2017-2024 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.
@@ -50,6 +50,7 @@ import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.ExchangeFilterFunctions;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriComponentsBuilder;
/**
* Provides the custom {@link WebClient.Builder} required by the
@@ -58,6 +59,7 @@ import org.springframework.web.reactive.function.client.WebClient;
*
* @author Daniel Lavoie
* @author Haytham Mohamed
* @author Armin Krezovic
*/
public class WebClientTransportClientFactory implements TransportClientFactory {
@@ -78,14 +80,14 @@ public class WebClientTransportClientFactory implements TransportClientFactory {
}
private WebClient.Builder setUrl(WebClient.Builder builder, String serviceUrl) {
String url = serviceUrl;
String url = UriComponentsBuilder.fromUriString(serviceUrl).userInfo(null).toUriString();
try {
URI serviceURI = new URI(serviceUrl);
if (serviceURI.getUserInfo() != null) {
String[] credentials = serviceURI.getUserInfo().split(":");
if (credentials.length == 2) {
builder.filter(ExchangeFilterFunctions.basicAuthentication(credentials[0], credentials[1]));
url = serviceUrl.replace(credentials[0] + ":" + credentials[1] + "@", "");
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* Copyright 2013-2024 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.
@@ -17,25 +17,54 @@
package org.springframework.cloud.netflix.eureka.serviceregistry;
import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.netflix.appinfo.InstanceInfo;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean;
import static com.netflix.appinfo.InstanceInfo.InstanceStatus.UNKNOWN;
/**
* @author Spencer Gibb
* @author Robert Bleyl
*/
public class EurekaServiceRegistry implements ServiceRegistry<EurekaRegistration> {
private static final Log log = LogFactory.getLog(EurekaServiceRegistry.class);
private EurekaInstanceConfigBean eurekaInstanceConfigBean;
public EurekaServiceRegistry() {
}
public EurekaServiceRegistry(EurekaInstanceConfigBean eurekaInstanceConfigBean) {
this.eurekaInstanceConfigBean = eurekaInstanceConfigBean;
}
@Override
public void register(EurekaRegistration reg) {
maybeInitializeClient(reg);
if (eurekaInstanceConfigBean != null && eurekaInstanceConfigBean.isAsyncClientInitialization()) {
if (log.isDebugEnabled()) {
log.debug("Initializing client asynchronously...");
}
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(() -> {
maybeInitializeClient(reg);
if (log.isDebugEnabled()) {
log.debug("Asynchronous client initialization done.");
}
});
}
else {
maybeInitializeClient(reg);
}
if (log.isInfoEnabled()) {
log.info("Registering application " + reg.getApplicationInfoManager().getInfo().getAppName()
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* Copyright 2013-2024 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.
@@ -51,6 +51,7 @@ import static org.mockito.Mockito.when;
/**
* @author Spencer Gibb
* @author Tang Xiong
*/
@RunWith(ModifiedClassPathRunner.class)
@ClassPathExclusions("spring-webflux-*")
@@ -163,7 +164,34 @@ public class EurekaConfigServerBootstrapConfigurationTests {
"eureka.client.enabled=true",
"spring.main.sources=" + TestConfigDiscoveryBootstrapConfiguration.class.getName(),
"logging.level.org.springframework.cloud.netflix.eureka.config=DEBUG")
.run();
.run().close();
assertThat(output).contains("eurekaConfigServerInstanceProvider finding instances for configserver")
.contains("eurekaConfigServerInstanceProvider found 1 instance(s) for configserver");
}
@Test
public void eurekaConfigServerInstanceProviderCalledWithRemoteRegions() {
TomcatURLStreamHandlerFactory.disable();
new SpringApplicationBuilder(TestConfigDiscoveryConfiguration.class)
.properties("spring.config.use-legacy-processing=true", "spring.cloud.config.discovery.enabled=true",
"eureka.client.enabled=true", "eureka.client.fetchRemoteRegionsRegistry=us-east-1,us-east-2",
"spring.main.sources=" + TestConfigDiscoveryBootstrapConfiguration.class.getName(),
"logging.level.org.springframework.cloud.netflix.eureka.config=DEBUG")
.run().close();
assertThat(output).contains("eurekaConfigServerInstanceProvider finding instances for configserver")
.contains("eurekaConfigServerInstanceProvider found 1 instance(s) for configserver");
}
@Test
public void eurekaConfigServerInstanceProviderCalledWithVipAddress() {
TomcatURLStreamHandlerFactory.disable();
new SpringApplicationBuilder(TestConfigDiscoveryConfiguration.class)
.properties("spring.config.use-legacy-processing=true", "spring.cloud.config.discovery.enabled=true",
"eureka.client.enabled=true", "eureka.client.registryRefreshSingleVipAddress=vip1",
"eureka.client.fetchRemoteRegionsRegistry=us-east-1,us-east-2",
"spring.main.sources=" + TestConfigDiscoveryBootstrapConfiguration.class.getName(),
"logging.level.org.springframework.cloud.netflix.eureka.config=DEBUG")
.run().close();
assertThat(output).contains("eurekaConfigServerInstanceProvider finding instances for configserver")
.contains("eurekaConfigServerInstanceProvider found 1 instance(s) for configserver");
}
@@ -208,7 +236,9 @@ public class EurekaConfigServerBootstrapConfigurationTests {
when(response.getEntity()).thenReturn(applications);
EurekaHttpClient client = mock(EurekaHttpClient.class);
when(client.getApplications("us-east-1")).thenReturn(response);
when(client.getApplications("us-east-1", "us-east-2")).thenReturn(response);
when(client.getApplications((String) null)).thenReturn(response);
when(client.getVip("vip1", "us-east-1", "us-east-2")).thenReturn(response);
return client;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* Copyright 2013-2024 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,11 +26,12 @@ import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.cloud.netflix.eureka.http.WebClientEurekaHttpClient;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import static org.assertj.core.api.Assertions.assertThat;
@@ -53,10 +54,12 @@ class EurekaConfigServerBootstrapConfigurationWebClientIntegrationTests {
@Test
void webClientRespectsCodecProperties() {
WebClient webClient = eurekaHttpClient.getWebClient();
ClientResponse response = webClient.get().uri("http://localhost:" + port).exchange().block();
ResponseEntity<String> response = webClient.get().uri("http://localhost:" + port).retrieve()
.toEntity(String.class).block();
assertThat(response).isNotNull();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.bodyToMono(String.class).block()).startsWith("....").hasSize(300000);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).startsWith("....").hasSize(300000);
}
@SpringBootConfiguration
@@ -71,8 +74,8 @@ class EurekaConfigServerBootstrapConfigurationWebClientIntegrationTests {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests().anyRequest().permitAll().and().csrf().disable();
return http.build();
return http.authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
.csrf(AbstractHttpConfigurer::disable).build();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2022 the original author or authors.
* Copyright 2017-2024 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.
@@ -16,23 +16,57 @@
package org.springframework.cloud.netflix.eureka.http;
import java.time.Duration;
import com.netflix.discovery.shared.resolver.DefaultEndpoint;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import org.springframework.web.reactive.function.client.WebClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
/**
* @author Daniel Lavoie
* @author Armin Krezovic
*/
@MockitoSettings(strictness = Strictness.LENIENT)
class WebClientTransportClientFactoryTest {
@Mock
private ExchangeFunction exchangeFunction;
@Captor
private ArgumentCaptor<ClientRequest> captor;
private WebClientTransportClientFactory transportClientFatory;
@BeforeEach
void setup() {
transportClientFatory = new WebClientTransportClientFactory(WebClient::builder);
ClientResponse mockResponse = mock();
when(mockResponse.statusCode()).thenReturn(HttpStatus.OK);
when(mockResponse.bodyToMono(Void.class)).thenReturn(Mono.empty());
given(exchangeFunction.exchange(captor.capture())).willReturn(Mono.just(mockResponse));
transportClientFatory = new WebClientTransportClientFactory(
() -> WebClient.builder().exchangeFunction(exchangeFunction));
}
@Test
@@ -45,6 +79,23 @@ class WebClientTransportClientFactoryTest {
transportClientFatory.newClient(new DefaultEndpoint("http://test@localhost:8761"));
}
@Test
void testUserInfoWithEncodedCharacters() {
String encodedBasicAuth = HttpHeaders.encodeBasicAuth("test", "MyPassword@", null);
String expectedAuthHeader = "Basic " + encodedBasicAuth;
String expectedUrl = "http://localhost:8761";
WebClientEurekaHttpClient client = (WebClientEurekaHttpClient) transportClientFatory
.newClient(new DefaultEndpoint("http://test:MyPassword%40@localhost:8761"));
client.getWebClient().get().retrieve().bodyToMono(Void.class).block(Duration.ofSeconds(10));
ClientRequest request = verifyAndGetRequest();
assertThat(request.headers().getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo(expectedAuthHeader);
assertThat(request.url().toString()).isEqualTo(expectedUrl);
}
@Test
void testUserInfo() {
transportClientFatory.newClient(new DefaultEndpoint("http://test:test@localhost:8761"));
@@ -55,4 +106,11 @@ class WebClientTransportClientFactoryTest {
transportClientFatory.shutdown();
}
private ClientRequest verifyAndGetRequest() {
ClientRequest request = captor.getValue();
verify(exchangeFunction).exchange(request);
verifyNoMoreInteractions(exchangeFunction);
return request;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* Copyright 2013-2024 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.
@@ -16,14 +16,20 @@
package org.springframework.cloud.netflix.eureka.serviceregistry;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import com.netflix.appinfo.ApplicationInfoManager;
import com.netflix.appinfo.InstanceInfo;
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.commons.util.InetUtils;
import org.springframework.cloud.commons.util.InetUtilsProperties;
import org.springframework.cloud.loadbalancer.support.SimpleObjectProvider;
import org.springframework.cloud.netflix.eureka.CloudEurekaClient;
import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean;
import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean;
@@ -33,16 +39,24 @@ import static com.netflix.appinfo.InstanceInfo.InstanceStatus.DOWN;
import static com.netflix.appinfo.InstanceInfo.InstanceStatus.OUT_OF_SERVICE;
import static com.netflix.appinfo.InstanceInfo.InstanceStatus.UNKNOWN;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
/**
* @author Spencer Gibb
* @author Tim Ysewyn
* @author Robert Bleyl
*/
@ExtendWith(MockitoExtension.class)
class EurekaServiceRegistryTests {
@Mock
private EurekaInstanceConfigBean eurekaInstanceConfigBean;
@Test
void eurekaClientNotShutdownInDeregister() {
EurekaServiceRegistry registry = new EurekaServiceRegistry();
@@ -110,8 +124,6 @@ class EurekaServiceRegistryTests {
CloudEurekaClient eurekaClient = mock(CloudEurekaClient.class);
when(eurekaClient.getInstanceInfo("myapp", "1234")).thenReturn(null);
ApplicationInfoManager applicationInfoManager = mock(ApplicationInfoManager.class);
when(applicationInfoManager.getInfo()).thenReturn(mock(InstanceInfo.class));
@@ -130,4 +142,54 @@ class EurekaServiceRegistryTests {
assertThat(map).hasSize(1).containsEntry("status", UNKNOWN.toString());
}
@Test
void eurekaClientInitializesClientAsynchronously() {
when(eurekaInstanceConfigBean.isAsyncClientInitialization()).thenReturn(true);
EurekaServiceRegistry registry = new EurekaServiceRegistry(eurekaInstanceConfigBean);
final AtomicBoolean applicationsFetched = new AtomicBoolean();
CloudEurekaClient eurekaClient = mock(CloudEurekaClient.class);
when(eurekaClient.getApplications()).thenAnswer((answer) -> {
applicationsFetched.set(true);
return answer;
});
ApplicationInfoManager applicationInfoManager = mock(ApplicationInfoManager.class);
when(applicationInfoManager.getInfo()).thenReturn(mock(InstanceInfo.class));
EurekaRegistration registration = EurekaRegistration
.builder(new EurekaInstanceConfigBean(new InetUtils(new InetUtilsProperties()))).with(eurekaClient)
.with(applicationInfoManager).with(new EurekaClientConfigBean(), mock(ApplicationEventPublisher.class))
.with(new SimpleObjectProvider<>(null)).build();
registry.register(registration);
await().atMost(Duration.ofSeconds(5)).pollInterval(Duration.ofMillis(500)).until(applicationsFetched::get);
verify(eurekaClient).getApplications();
assertThat(applicationsFetched).isTrue();
}
@Test
void eurekaClientInitializesClientSynchronously() {
EurekaServiceRegistry registry = new EurekaServiceRegistry(eurekaInstanceConfigBean);
CloudEurekaClient eurekaClient = mock(CloudEurekaClient.class);
ApplicationInfoManager applicationInfoManager = mock(ApplicationInfoManager.class);
when(applicationInfoManager.getInfo()).thenReturn(mock(InstanceInfo.class));
EurekaRegistration registration = EurekaRegistration
.builder(new EurekaInstanceConfigBean(new InetUtils(new InetUtilsProperties()))).with(eurekaClient)
.with(applicationInfoManager).with(new EurekaClientConfigBean(), mock(ApplicationEventPublisher.class))
.with(new SimpleObjectProvider<>(null)).build();
registry.register(registration);
verify(eurekaClient).getApplications();
verify(eurekaClient, never()).getEurekaClientConfig();
}
}
+2 -8
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>4.1.1-SNAPSHOT</version>
<version>4.1.2</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-netflix-eureka-server</artifactId>
@@ -76,13 +76,7 @@
<groupId>org.glassfish.hk2</groupId>
<artifactId>spring-bridge</artifactId>
<!-- FIXME: 4.0 -->
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>
<!-- FIXME: 4.0 -->
<version>2.0.1</version>
<version>3.1.0</version>
</dependency>
<!-- Eureka deps that are now optional in eureka -->
<dependency>
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* Copyright 2013-2024 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.
@@ -25,6 +25,8 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Pattern;
import com.netflix.appinfo.ApplicationInfoManager;
@@ -88,6 +90,7 @@ import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.cloud.client.actuator.HasFeatures;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.cloud.netflix.eureka.EurekaConstants;
import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
@@ -108,6 +111,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
* @author Biju Kunjummen
* @author Fahim Farook
* @author Weix Sun
* @author Robert Bleyl
*/
@Configuration(proxyBeanMethods = false)
@Import(EurekaServerInitializerConfiguration.class)
@@ -207,8 +211,25 @@ public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
@Bean
public PeerAwareInstanceRegistry peerAwareInstanceRegistry(ServerCodecs serverCodecs,
EurekaServerHttpClientFactory eurekaServerHttpClientFactory) {
this.eurekaClient.getApplications(); // force initialization
EurekaServerHttpClientFactory eurekaServerHttpClientFactory,
EurekaInstanceConfigBean eurekaInstanceConfigBean) {
if (eurekaInstanceConfigBean.isAsyncClientInitialization()) {
if (log.isDebugEnabled()) {
log.debug("Initializing client asynchronously...");
}
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(() -> {
this.eurekaClient.getApplications();
if (log.isDebugEnabled()) {
log.debug("Asynchronous client initialization done.");
}
});
}
else {
this.eurekaClient.getApplications(); // force initialization
}
return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig, serverCodecs, this.eurekaClient,
eurekaServerHttpClientFactory,
this.instanceRegistryProperties.getExpectedNumberOfClientsSendingRenews(),
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* Copyright 2013-2024 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.
@@ -39,6 +39,7 @@ import org.springframework.context.ApplicationEvent;
/**
* @author Spencer Gibb
* @author Wonchul Heo
*/
public class InstanceRegistry extends PeerAwareInstanceRegistryImpl implements ApplicationContextAware {
@@ -78,52 +79,65 @@ public class InstanceRegistry extends PeerAwareInstanceRegistryImpl implements A
@Override
public void register(InstanceInfo info, int leaseDuration, boolean isReplication) {
handleRegistration(info, leaseDuration, isReplication);
super.register(info, leaseDuration, isReplication);
handleRegistration(info, leaseDuration, isReplication);
}
@Override
public void register(final InstanceInfo info, final boolean isReplication) {
handleRegistration(info, resolveInstanceLeaseDuration(info), isReplication);
super.register(info, isReplication);
handleRegistration(info, resolveInstanceLeaseDuration(info), isReplication);
}
@Override
public boolean cancel(String appName, String serverId, boolean isReplication) {
handleCancelation(appName, serverId, isReplication);
return super.cancel(appName, serverId, isReplication);
final boolean cancelled = super.cancel(appName, serverId, isReplication);
if (cancelled) {
handleCancelation(appName, serverId, isReplication);
}
return cancelled;
}
@Override
public boolean renew(final String appName, final String serverId, boolean isReplication) {
log("renew " + appName + " serverId " + serverId + ", isReplication {}" + isReplication);
Application application = getApplication(appName);
if (application != null) {
InstanceInfo instanceInfo = application.getByInstanceId(serverId);
if (instanceInfo != null) {
publishEvent(new EurekaInstanceRenewedEvent(this, appName, serverId, instanceInfo, isReplication));
}
final boolean renewed = super.renew(appName, serverId, isReplication);
if (renewed) {
handleRenewal(appName, serverId, isReplication);
}
return super.renew(appName, serverId, isReplication);
return renewed;
}
@Override
protected boolean internalCancel(String appName, String id, boolean isReplication) {
handleCancelation(appName, id, isReplication);
return super.internalCancel(appName, id, isReplication);
final boolean cancelled = super.internalCancel(appName, id, isReplication);
if (cancelled) {
handleCancelation(appName, id, isReplication);
}
return cancelled;
}
private void handleCancelation(String appName, String id, boolean isReplication) {
log("cancel " + appName + ", serverId " + id + ", isReplication " + isReplication);
log("cancelled " + appName + ", serverId " + id + ", isReplication " + isReplication);
publishEvent(new EurekaInstanceCanceledEvent(this, appName, id, isReplication));
}
private void handleRegistration(InstanceInfo info, int leaseDuration, boolean isReplication) {
log("register " + info.getAppName() + ", vip " + info.getVIPAddress() + ", leaseDuration " + leaseDuration
log("registered " + info.getAppName() + ", vip " + info.getVIPAddress() + ", leaseDuration " + leaseDuration
+ ", isReplication " + isReplication);
publishEvent(new EurekaInstanceRegisteredEvent(this, info, leaseDuration, isReplication));
}
private void handleRenewal(final String appName, final String serverId, boolean isReplication) {
log("renewed " + appName + ", serverId " + serverId + ", isReplication " + isReplication);
final Application application = getApplication(appName);
if (application != null) {
final InstanceInfo instanceInfo = application.getByInstanceId(serverId);
if (instanceInfo != null) {
publishEvent(new EurekaInstanceRenewedEvent(this, appName, serverId, instanceInfo, isReplication));
}
}
}
private void log(String message) {
if (log.isDebugEnabled()) {
log.debug(message);
@@ -0,0 +1,37 @@
/*
* Copyright 2013-2024 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.netflix.eureka.server.metrics;
import com.netflix.appinfo.InstanceInfo;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
/**
* Default implementation for {@link EurekaInstanceTagsProvider}.
*
* @author Wonchul Heo
* @since 4.1.2
*/
class DefaultEurekaInstanceTagsProvider implements EurekaInstanceTagsProvider {
@Override
public Tags eurekaInstanceTags(InstanceInfo instanceInfo) {
return Tags.of(Tag.of("application", instanceInfo.getAppName()),
Tag.of("status", instanceInfo.getStatus().name()));
}
}
@@ -0,0 +1,58 @@
/*
* Copyright 2013-2024 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.netflix.eureka.server.metrics;
import com.netflix.eureka.registry.PeerAwareInstanceRegistry;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration;
import org.springframework.context.annotation.Bean;
/**
* Auto-configuration for Eureka Instance metrics.
*
* @author Wonchul Heo
* @since 4.1.2
*/
@ConditionalOnClass(MeterRegistry.class)
@ConditionalOnBean(MeterRegistry.class)
@AutoConfigureAfter({ MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class,
EurekaServerAutoConfiguration.class })
@ConditionalOnProperty(name = "eureka.server.metrics.enabled", havingValue = "true")
class EurekaInstanceMetricsAutoConfiguration {
@ConditionalOnMissingBean
@Bean
public EurekaInstanceTagsProvider eurekaInstanceTagProvider() {
return new DefaultEurekaInstanceTagsProvider();
}
@ConditionalOnMissingBean
@Bean
public EurekaInstanceMonitor eurekaInstanceMeterBinder(MeterRegistry meterRegistry,
PeerAwareInstanceRegistry instanceRegistry, EurekaInstanceTagsProvider tagProvider) {
return new EurekaInstanceMonitor(meterRegistry, instanceRegistry, tagProvider);
}
}
@@ -0,0 +1,76 @@
/*
* Copyright 2013-2024 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.netflix.eureka.server.metrics;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import com.netflix.eureka.registry.PeerAwareInstanceRegistry;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.MultiGauge;
import io.micrometer.core.instrument.Tags;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceCanceledEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRegisteredEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRenewedEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.SmartApplicationListener;
/**
* {@link SmartApplicationListener} for collecting event metrics from
* {@link PeerAwareInstanceRegistry}.
*
* @author Wonchul Heo
* @since 4.1.2
*/
public class EurekaInstanceMonitor implements SmartApplicationListener {
private final MultiGauge eurekaInstances;
private final PeerAwareInstanceRegistry instanceRegistry;
private final EurekaInstanceTagsProvider tagProvider;
EurekaInstanceMonitor(MeterRegistry meterRegistry, PeerAwareInstanceRegistry instanceRegistry,
EurekaInstanceTagsProvider tagProvider) {
Objects.requireNonNull(meterRegistry);
this.instanceRegistry = Objects.requireNonNull(instanceRegistry);
this.tagProvider = Objects.requireNonNull(tagProvider);
this.eurekaInstances = MultiGauge.builder("eureka.server.instances")
.description("Number of application instances registered with the Eureka server.")
.register(meterRegistry);
}
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
// If events that change state are added, an event class must be added.
return EurekaInstanceCanceledEvent.class.isAssignableFrom(eventType)
|| EurekaInstanceRegisteredEvent.class.isAssignableFrom(eventType)
|| EurekaInstanceRenewedEvent.class.isAssignableFrom(eventType);
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
final Map<Tags, Long> aggregatedCounts = instanceRegistry.getApplications().getRegisteredApplications().stream()
.flatMap(application -> application.getInstances().stream())
.collect(Collectors.groupingBy(tagProvider::eurekaInstanceTags, Collectors.counting()));
eurekaInstances.register(aggregatedCounts.entrySet().stream()
.map(entry -> MultiGauge.Row.of(entry.getKey(), entry.getValue())).collect(Collectors.toList()), true);
}
}
@@ -0,0 +1,32 @@
/*
* Copyright 2013-2024 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.netflix.eureka.server.metrics;
import com.netflix.appinfo.InstanceInfo;
import io.micrometer.core.instrument.Tags;
/**
* Provides {@link Tags} for Eureka instance metrics.
*
* @author Wonchul Heo
* @since 4.1.2
*/
public interface EurekaInstanceTagsProvider {
Tags eurekaInstanceTags(InstanceInfo instanceInfo);
}
@@ -0,0 +1,10 @@
{
"properties": [
{
"name": "eureka.server.metrics.enabled",
"type": "java.lang.Boolean",
"defaultValue": "false",
"description": "Indicates whether the metrics should be enabled for eureka instances."
}
]
}
@@ -1 +1,2 @@
org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration
org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration
org.springframework.cloud.netflix.eureka.server.metrics.EurekaInstanceMetricsAutoConfiguration
@@ -0,0 +1,45 @@
/*
* Copyright 2013-2024 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.netflix.eureka.server;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.LeaseInfo;
public final class EurekaInstanceFixture {
private EurekaInstanceFixture() {
}
public static LeaseInfo getLeaseInfo() {
LeaseInfo.Builder leaseBuilder = LeaseInfo.Builder.newBuilder();
leaseBuilder.setRenewalIntervalInSecs(10);
leaseBuilder.setDurationInSecs(15);
return leaseBuilder.build();
}
public static InstanceInfo getInstanceInfo(String appName, String hostName, String instanceId, int port,
LeaseInfo leaseInfo) {
InstanceInfo.Builder builder = InstanceInfo.Builder.newBuilder();
builder.setAppName(appName);
builder.setHostName(hostName);
builder.setInstanceId(instanceId);
builder.setPort(port);
builder.setLeaseInfo(leaseInfo);
return builder.build();
}
}
@@ -0,0 +1,166 @@
/*
* Copyright 2013-2024 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.netflix.eureka.server;
import java.util.Map;
import com.netflix.appinfo.InstanceInfo;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.cloud.netflix.eureka.server.EurekaInstanceFixture.getInstanceInfo;
import static org.springframework.cloud.netflix.eureka.server.EurekaInstanceFixture.getLeaseInfo;
/**
* @author Wonchul Heo
*/
@SpringBootTest(classes = EurekaInstanceMonitorTests.Application.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
value = { "spring.application.name=eureka", "eureka.server.metrics.enabled=true" })
class EurekaInstanceMonitorTests {
private static final String FOO_APP_NAME = "FOO-APP-NAME";
private static final String BAR_APP_NAME = "BAR-APP-NAME";
@Autowired
private InstanceRegistry instanceRegistry;
@Autowired
private MeterRegistry meterRegistry;
private InstanceInfo fooInstanceInfo;
private InstanceInfo fooInstanceInfo2;
private InstanceInfo barInstanceInfo;
private InstanceInfo barInstanceInfo2;
@BeforeEach
void setup() {
instanceRegistry.clearRegistry();
meterRegistry.clear();
fooInstanceInfo = getInstanceInfo(FOO_APP_NAME, "my-host-name", "my-host-name:8008", 8008, getLeaseInfo());
fooInstanceInfo2 = getInstanceInfo(FOO_APP_NAME, "my-host-name", "my-host-name:8009", 8009, getLeaseInfo());
barInstanceInfo = getInstanceInfo(BAR_APP_NAME, "my-host-name", "my-host-name:8010", 8010, getLeaseInfo());
barInstanceInfo2 = getInstanceInfo(BAR_APP_NAME, "my-host-name", "my-host-name:8011", 8011, getLeaseInfo());
}
@Test
void testNoRegistration() {
assertThat(meterRegistry.find("eureka.server.instances").gauge()).isNull();
}
@Test
void testMultipleRegistrations() {
instanceRegistry.register(fooInstanceInfo, false);
instanceRegistry.register(fooInstanceInfo2, false);
instanceRegistry.register(barInstanceInfo, false);
final Map<Tags, Long> counts = Map.of(tags(fooInstanceInfo), 2L, tags(barInstanceInfo), 1L);
assertEurekaInstance(counts);
}
@Test
void testPartialDeregistrationAfterMultipleRegistrations() {
instanceRegistry.register(fooInstanceInfo, false);
instanceRegistry.register(fooInstanceInfo2, false);
instanceRegistry.register(barInstanceInfo, false);
instanceRegistry.register(barInstanceInfo2, false);
instanceRegistry.internalCancel(fooInstanceInfo.getAppName(), fooInstanceInfo.getInstanceId(), false);
final Map<Tags, Long> counts = Map.of(tags(fooInstanceInfo), 1L, tags(barInstanceInfo), 2L);
assertEurekaInstance(counts);
}
@Test
void testPartialDeregistrationAndThenRegistrationAfterMultipleRegistrations() {
instanceRegistry.register(fooInstanceInfo, false);
instanceRegistry.register(fooInstanceInfo2, false);
instanceRegistry.register(barInstanceInfo, false);
instanceRegistry.register(barInstanceInfo2, false);
instanceRegistry.internalCancel(fooInstanceInfo.getAppName(), fooInstanceInfo.getInstanceId(), false);
instanceRegistry.register(fooInstanceInfo, false);
final Map<Tags, Long> counts = Map.of(tags(fooInstanceInfo), 2L, tags(barInstanceInfo), 2L);
assertEurekaInstance(counts);
}
@Test
void testPartialNonRenewalAfterMultipleRegistrations() {
instanceRegistry.register(fooInstanceInfo, false);
instanceRegistry.register(fooInstanceInfo2, false);
instanceRegistry.register(barInstanceInfo, false);
instanceRegistry.register(barInstanceInfo2, false);
instanceRegistry.statusUpdate(fooInstanceInfo.getAppName(), fooInstanceInfo.getInstanceId(),
InstanceInfo.InstanceStatus.DOWN, null, false);
final Map<Tags, Long> meterRegistryCounts = Map.of(tags(fooInstanceInfo2), 2L);
assertEurekaInstance(meterRegistryCounts);
}
@Test
void testPartialRenewalAfterMultipleRegistrations() {
instanceRegistry.register(fooInstanceInfo, false);
instanceRegistry.register(fooInstanceInfo2, false);
instanceRegistry.register(barInstanceInfo, false);
instanceRegistry.register(barInstanceInfo2, false);
instanceRegistry.statusUpdate(fooInstanceInfo.getAppName(), fooInstanceInfo.getInstanceId(),
InstanceInfo.InstanceStatus.DOWN, null, false);
instanceRegistry.statusUpdate(barInstanceInfo2.getAppName(), barInstanceInfo2.getInstanceId(),
InstanceInfo.InstanceStatus.STARTING, null, false);
instanceRegistry.renew(fooInstanceInfo.getAppName(), fooInstanceInfo.getInstanceId(), false);
instanceRegistry.renew(barInstanceInfo2.getAppName(), barInstanceInfo2.getInstanceId(), false);
final Map<Tags, Long> counts = Map.of(tags(fooInstanceInfo), 1L, tags(fooInstanceInfo2), 1L,
tags(barInstanceInfo), 1L, tags(barInstanceInfo2), 1L);
assertEurekaInstance(counts);
}
private static Tags tags(InstanceInfo instanceInfo) {
return Tags.of(Tag.of("application", instanceInfo.getAppName()),
Tag.of("status", instanceInfo.getStatus().name()));
}
private void assertEurekaInstance(Map<Tags, Long> meterRegistryCounts) {
meterRegistryCounts.forEach((tags,
count) -> assertThat((long) meterRegistry.get("eureka.server.instances").tags(tags).gauge().value())
.isEqualTo(count));
}
@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration
@EnableEurekaServer
protected static class Application {
}
}
@@ -0,0 +1,102 @@
/*
* Copyright 2013-2024 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.netflix.eureka.server;
import java.util.Map;
import com.netflix.appinfo.InstanceInfo;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.netflix.eureka.server.metrics.EurekaInstanceTagsProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.cloud.netflix.eureka.server.EurekaInstanceFixture.getInstanceInfo;
import static org.springframework.cloud.netflix.eureka.server.EurekaInstanceFixture.getLeaseInfo;
/**
* @author Wonchul Heo
*/
@SpringBootTest(classes = EurekaInstanceMonitorWithCustomTagsProviderTests.Application.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
value = { "spring.application.name=eureka", "eureka.server.metrics.enabled=true" })
class EurekaInstanceMonitorWithCustomTagsProviderTests {
private static final String APP_NAME = "FOO-APP-NAME";
@Autowired
private InstanceRegistry instanceRegistry;
@Autowired
private MeterRegistry meterRegistry;
private InstanceInfo firstInstanceInfo;
private InstanceInfo secondInstanceInfo;
@BeforeEach
void setup() {
instanceRegistry.clearRegistry();
meterRegistry.clear();
firstInstanceInfo = getInstanceInfo(APP_NAME, "my-host-name", "my-host-name:8008", 8008, getLeaseInfo());
secondInstanceInfo = getInstanceInfo(APP_NAME, "my-host-name", "my-host-name:8009", 8009, getLeaseInfo());
}
@Test
void testNoRegistration() {
assertThat(meterRegistry.find("eureka.server.instances").gauge()).isNull();
}
@Test
void testMultipleRegistrations() {
instanceRegistry.register(firstInstanceInfo, false);
instanceRegistry.register(secondInstanceInfo, false);
final Map<Tags, Long> counts = Map.of(tags(firstInstanceInfo), 1L, tags(secondInstanceInfo), 1L);
assertEurekaInstance(counts);
}
private static Tags tags(InstanceInfo instanceInfo) {
return Tags.of("port", String.valueOf(instanceInfo.getPort()));
}
private void assertEurekaInstance(Map<Tags, Long> meterRegistryCounts) {
meterRegistryCounts.forEach((tags,
count) -> assertThat((long) meterRegistry.get("eureka.server.instances").tags(tags).gauge().value())
.isEqualTo(count));
}
@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration
@EnableEurekaServer
protected static class Application {
@Bean
EurekaInstanceTagsProvider customEurekaInstanceTagsProvider() {
return instanceInfo -> Tags.of("port", String.valueOf(instanceInfo.getPort()));
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2022 the original author or authors.
* Copyright 2016-2024 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.
@@ -16,21 +16,17 @@
package org.springframework.cloud.netflix.eureka.server;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.LeaseInfo;
import com.netflix.discovery.shared.Application;
import com.netflix.eureka.registry.PeerAwareInstanceRegistry;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.cloud.netflix.eureka.server.InstanceRegistryTests.TestApplication;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceCanceledEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRegisteredEvent;
@@ -41,7 +37,8 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.SmartApplicationListener;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.springframework.cloud.netflix.eureka.server.EurekaInstanceFixture.getInstanceInfo;
import static org.springframework.cloud.netflix.eureka.server.EurekaInstanceFixture.getLeaseInfo;
/**
* @author Bartlomiej Slota
@@ -59,12 +56,13 @@ class InstanceRegistryTests {
private static final int PORT = 8008;
@SpyBean(PeerAwareInstanceRegistry.class)
@Autowired
private InstanceRegistry instanceRegistry;
@BeforeEach
void setup() {
this.testEvents.applicationEvents.clear();
this.instanceRegistry.clearRegistry();
}
@Autowired
@@ -103,39 +101,41 @@ class InstanceRegistryTests {
@Test
void testInternalCancel() {
// registering instance info
final InstanceInfo instanceInfo = getInstanceInfo(APP_NAME, HOST_NAME, INSTANCE_ID, PORT, null);
instanceRegistry.register(instanceInfo, false);
// calling tested method
instanceRegistry.internalCancel(APP_NAME, HOST_NAME, false);
instanceRegistry.internalCancel(APP_NAME, INSTANCE_ID, false);
// event of proper type is registered
assertThat(this.testEvents.applicationEvents.size()).isEqualTo(1);
assertThat(this.testEvents.applicationEvents.get(0) instanceof EurekaInstanceCanceledEvent).isTrue();
assertThat(this.testEvents.applicationEvents.size()).isEqualTo(2);
assertThat(this.testEvents.applicationEvents.get(0) instanceof EurekaInstanceRegisteredEvent).isTrue();
assertThat(this.testEvents.applicationEvents.get(1) instanceof EurekaInstanceCanceledEvent).isTrue();
// event details are correct
final EurekaInstanceCanceledEvent registeredEvent = (EurekaInstanceCanceledEvent) (this.testEvents.applicationEvents
.get(0));
.get(1));
assertThat(registeredEvent.getAppName()).isEqualTo(APP_NAME);
assertThat(registeredEvent.getServerId()).isEqualTo(HOST_NAME);
assertThat(registeredEvent.getServerId()).isEqualTo(INSTANCE_ID);
assertThat(registeredEvent.getSource()).isEqualTo(instanceRegistry);
assertThat(registeredEvent.isReplication()).isFalse();
}
@Test
void testRenew() {
// Creating two instances of the app
// registering two instances of the app
final InstanceInfo instanceInfo1 = getInstanceInfo(APP_NAME, HOST_NAME, INSTANCE_ID, PORT, null);
final InstanceInfo instanceInfo2 = getInstanceInfo(APP_NAME, HOST_NAME, "my-host-name:8009", 8009, null);
// creating application list with an app having two instances
final Application application = new Application(APP_NAME, Arrays.asList(instanceInfo1, instanceInfo2));
// stubbing application
doReturn(application).when(instanceRegistry).getApplication(APP_NAME);
instanceRegistry.register(instanceInfo1, false);
instanceRegistry.register(instanceInfo2, false);
// calling tested method
instanceRegistry.renew(APP_NAME, INSTANCE_ID, false);
instanceRegistry.renew(APP_NAME, "my-host-name:8009", false);
// event of proper type is registered
assertThat(this.testEvents.applicationEvents.size()).isEqualTo(2);
assertThat(this.testEvents.applicationEvents.get(0) instanceof EurekaInstanceRenewedEvent).isTrue();
assertThat(this.testEvents.applicationEvents.get(1) instanceof EurekaInstanceRenewedEvent).isTrue();
assertThat(this.testEvents.applicationEvents.size()).isEqualTo(4);
assertThat(this.testEvents.applicationEvents.get(2) instanceof EurekaInstanceRenewedEvent).isTrue();
assertThat(this.testEvents.applicationEvents.get(3) instanceof EurekaInstanceRenewedEvent).isTrue();
// event details are correct
final EurekaInstanceRenewedEvent event1 = (EurekaInstanceRenewedEvent) (this.testEvents.applicationEvents
.get(0));
.get(2));
assertThat(event1.getAppName()).isEqualTo(APP_NAME);
assertThat(event1.getServerId()).isEqualTo(INSTANCE_ID);
assertThat(event1.getSource()).isEqualTo(instanceRegistry);
@@ -143,28 +143,10 @@ class InstanceRegistryTests {
assertThat(event1.isReplication()).isFalse();
final EurekaInstanceRenewedEvent event2 = (EurekaInstanceRenewedEvent) (this.testEvents.applicationEvents
.get(1));
.get(3));
assertThat(event2.getInstanceInfo()).isEqualTo(instanceInfo2);
}
private LeaseInfo getLeaseInfo() {
LeaseInfo.Builder leaseBuilder = LeaseInfo.Builder.newBuilder();
leaseBuilder.setRenewalIntervalInSecs(10);
leaseBuilder.setDurationInSecs(15);
return leaseBuilder.build();
}
private InstanceInfo getInstanceInfo(String appName, String hostName, String instanceId, int port,
LeaseInfo leaseInfo) {
InstanceInfo.Builder builder = InstanceInfo.Builder.newBuilder();
builder.setAppName(appName);
builder.setHostName(hostName);
builder.setInstanceId(instanceId);
builder.setPort(port);
builder.setLeaseInfo(leaseInfo);
return builder.build();
}
@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration
@EnableEurekaServer
@@ -4,7 +4,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>4.1.1-SNAPSHOT</version>
<version>4.1.2</version>
</parent>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<name>Spring Cloud Starter Netflix Eureka Client</name>
@@ -3,7 +3,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>4.1.1-SNAPSHOT</version>
<version>4.1.2</version>
</parent>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<name>Spring Cloud Starter Netflix Eureka Server</name>