diff --git a/spring-cloud-aws-autoconfigure/pom.xml b/spring-cloud-aws-autoconfigure/pom.xml index d4131c45c..1ce9c13e5 100644 --- a/spring-cloud-aws-autoconfigure/pom.xml +++ b/spring-cloud-aws-autoconfigure/pom.xml @@ -75,6 +75,11 @@ s3-transfer-manager true + + software.amazon.awssdk + cloudwatch-metric-publisher + true + io.awspring.cloud spring-cloud-aws-s3-cross-region-client diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/AwsClientProperties.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/AwsClientProperties.java index 6bee7a541..a7c85207e 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/AwsClientProperties.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/AwsClientProperties.java @@ -15,6 +15,7 @@ */ package io.awspring.cloud.autoconfigure; +import io.awspring.cloud.autoconfigure.core.CloudWatchMetricsPublisherProperties; import java.net.URI; import org.springframework.lang.Nullable; @@ -37,6 +38,12 @@ public abstract class AwsClientProperties { @Nullable private String region; + /** + * Overrides the global enablement of the CloudWatch MetricsPublisher. + */ + @Nullable + private CloudWatchMetricsPublisherProperties metrics; + @Nullable public URI getEndpoint() { return this.endpoint; @@ -54,4 +61,13 @@ public String getRegion() { public void setRegion(String region) { this.region = region; } + + @Nullable + public CloudWatchMetricsPublisherProperties getMetrics() { + return metrics; + } + + public void setMetrics(@Nullable CloudWatchMetricsPublisherProperties metrics) { + this.metrics = metrics; + } } diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/AbstractAwsConfigDataLocationResolver.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/AbstractAwsConfigDataLocationResolver.java index 50a32999b..0c000c9a4 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/AbstractAwsConfigDataLocationResolver.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/AbstractAwsConfigDataLocationResolver.java @@ -22,6 +22,8 @@ import io.awspring.cloud.autoconfigure.core.RegionProperties; import io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration; import io.awspring.cloud.core.SpringCloudClientConfiguration; +import java.net.URI; +import java.time.Duration; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -34,14 +36,19 @@ import org.springframework.boot.context.config.ConfigDataLocationResolverContext; import org.springframework.boot.context.config.ConfigDataResource; import org.springframework.boot.context.config.ConfigDataResourceNotFoundException; +import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.metrics.publishers.cloudwatch.CloudWatchMetricPublisher; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.regions.providers.AwsRegionProvider; +import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClient; /** * Base class for AWS specific {@link ConfigDataLocationResolver}s. @@ -55,6 +62,10 @@ public abstract class AbstractAwsConfigDataLocationResolver> CloudWatchMetricPublisher createMetricPublisher( + AwsProperties awsProperties, AwsClientProperties awsClientProperties, AwsRegionProvider regionProvider, + T clientBuilder, AwsCredentialsProvider credentialsProvider) { + CloudWatchMetricPublisher.Builder builder = CloudWatchMetricPublisher.builder(); + PropertyMapper map = PropertyMapper.get(); + if ((awsClientProperties.getMetrics() != null && awsClientProperties.getMetrics().getEnabled())) { + CloudWatchAsyncClient cloudwatchAsyncClient = createCloudwatchAsyncClient(awsClientProperties.getRegion(), + awsClientProperties.getEndpoint(), credentialsProvider); + + map.from(awsClientProperties.getMetrics()::getNamespace).whenNonNull().to(builder::namespace); + map.from(awsClientProperties.getMetrics()::getUploadFrequencyInSeconds).whenNonNull() + .to(v -> builder.uploadFrequency(Duration.ofSeconds(v))); + builder.cloudWatchClient(cloudwatchAsyncClient); + } + else if (awsProperties.getMetrics() == null + || (awsProperties.getMetrics() != null && awsProperties.getMetrics().getEnabled())) { + CloudWatchAsyncClient cloudwatchAsyncClient = createCloudwatchAsyncClient(regionProvider.getRegion().id(), + awsProperties.getEndpoint(), credentialsProvider); + if (awsProperties.getMetrics() != null) { + map.from(awsProperties.getMetrics()::getNamespace).whenNonNull().to(builder::namespace); + map.from(awsProperties.getMetrics()::getUploadFrequencyInSeconds).whenNonNull() + .to(v -> builder.uploadFrequency(Duration.ofSeconds(v))); + } + builder.cloudWatchClient(cloudwatchAsyncClient); + } + return builder.build(); + } + + private > CloudWatchAsyncClient createCloudwatchAsyncClient(String region, + URI endpoint, AwsCredentialsProvider credentialsProvider) { + return CloudWatchAsyncClient.builder().region(Region.of(region)).endpointOverride(endpoint) + .credentialsProvider(credentialsProvider).build(); + } } diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreConfigDataLocationResolver.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreConfigDataLocationResolver.java index 569c47d53..311c7872c 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreConfigDataLocationResolver.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreConfigDataLocationResolver.java @@ -60,12 +60,15 @@ protected String getPrefix() { public List resolveProfileSpecific( ConfigDataLocationResolverContext resolverContext, ConfigDataLocation location, Profiles profiles) throws ConfigDataLocationNotFoundException { - registerBean(resolverContext, AwsProperties.class, loadAwsProperties(resolverContext.getBinder())); - registerBean(resolverContext, ParameterStoreProperties.class, loadProperties(resolverContext.getBinder())); + AwsProperties awsProperties = loadAwsProperties(resolverContext.getBinder()); + ParameterStoreProperties parameterStoreProperties = loadProperties(resolverContext.getBinder()); + RegionProperties regionProperties = loadRegionProperties(resolverContext.getBinder()); + + registerBean(resolverContext, AwsProperties.class, awsProperties); + registerBean(resolverContext, ParameterStoreProperties.class, parameterStoreProperties); registerBean(resolverContext, CredentialsProperties.class, loadCredentialsProperties(resolverContext.getBinder())); - registerBean(resolverContext, RegionProperties.class, loadRegionProperties(resolverContext.getBinder())); - + registerBean(resolverContext, RegionProperties.class, regionProperties); registerAndPromoteBean(resolverContext, SsmClient.class, this::createSimpleSystemManagementClient); ParameterStorePropertySources sources = new ParameterStorePropertySources(); diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLocationResolver.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLocationResolver.java index 4c40cff61..76fe08a00 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLocationResolver.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLocationResolver.java @@ -69,9 +69,7 @@ public List resolveProfileSpecific( registerBean(resolverContext, CredentialsProperties.class, loadCredentialsProperties(resolverContext.getBinder())); registerBean(resolverContext, RegionProperties.class, loadRegionProperties(resolverContext.getBinder())); - registerAndPromoteBean(resolverContext, SecretsManagerClient.class, this::createAwsSecretsManagerClient); - SecretsManagerPropertySources propertySources = new SecretsManagerPropertySources(); List contexts = getCustomContexts(location.getNonPrefixedValue(PREFIX)); diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/AwsClientBuilderConfigurer.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/AwsClientBuilderConfigurer.java index f55569c2f..e0f2c75a5 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/AwsClientBuilderConfigurer.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/AwsClientBuilderConfigurer.java @@ -16,16 +16,24 @@ package io.awspring.cloud.autoconfigure.core; import io.awspring.cloud.autoconfigure.AwsClientProperties; +import io.awspring.cloud.autoconfigure.metrics.CloudWatchProperties; import io.awspring.cloud.core.SpringCloudClientConfiguration; +import java.time.Duration; import java.util.Optional; +import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.metrics.MetricPublisher; +import software.amazon.awssdk.metrics.publishers.cloudwatch.CloudWatchMetricPublisher; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.regions.providers.AwsRegionProvider; +import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClient; +import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClientBuilder; /** * Provides a convenience method to apply common configuration to any {@link AwsClientBuilder}. @@ -34,6 +42,8 @@ * @since 3.0 */ public class AwsClientBuilderConfigurer { + public static final boolean IS_CLOUDWATCH_METRIC_PUBLISHER_PRESENT = ClassUtils + .isPresent("software.amazon.awssdk.metrics.publishers.cloudwatch.CloudWatchMetricPublisher", null); private final AwsCredentialsProvider credentialsProvider; private final AwsRegionProvider regionProvider; private final AwsProperties awsProperties; @@ -48,16 +58,20 @@ public class AwsClientBuilderConfigurer { } public > T configure(T builder) { - return configure(builder, null, null); + return configure(builder, null, null, null); } public > T configure(T builder, @Nullable AwsClientProperties clientProperties, - @Nullable AwsClientCustomizer customizer) { + @Nullable AwsClientCustomizer customizer, @Nullable MetricPublisher metricPublisher) { + ClientOverrideConfiguration.Builder clientOverrideConfigurationBuilder = clientOverrideConfiguration + .toBuilder(); Assert.notNull(builder, "builder is required"); Assert.notNull(clientProperties, "clientProperties are required"); - + if (metricPublisher != null) { + clientOverrideConfigurationBuilder.addMetricPublisher(metricPublisher).build(); + } builder.credentialsProvider(this.credentialsProvider).region(resolveRegion(clientProperties)) - .overrideConfiguration(this.clientOverrideConfiguration); + .overrideConfiguration(clientOverrideConfigurationBuilder.build()); Optional.ofNullable(this.awsProperties.getEndpoint()).ifPresent(builder::endpointOverride); Optional.ofNullable(clientProperties).map(AwsClientProperties::getEndpoint) .ifPresent(builder::endpointOverride); @@ -68,6 +82,7 @@ public class AwsClientBuilderConfigurer { if (customizer != null) { AwsClientCustomizer.apply(customizer, builder); } + return builder; } @@ -76,4 +91,33 @@ public Region resolveRegion(@Nullable AwsClientProperties clientProperties) { ? Region.of(clientProperties.getRegion()) : this.regionProvider.getRegion(); } + + public static @Nullable MetricPublisher createSpecificMetricPublisher(MetricPublisher metricPublisher, + AwsClientProperties properties, AwsClientBuilderConfigurer awsClientBuilderConfigurer) { + + if (IS_CLOUDWATCH_METRIC_PUBLISHER_PRESENT && properties.getMetrics() != null) { + if (properties.getMetrics().getEnabled()) { + PropertyMapper propertyMapper = PropertyMapper.get(); + + CloudWatchAsyncClientBuilder cloudWatchAsyncClientBuilder = CloudWatchAsyncClient.builder(); + CloudWatchProperties cloudWatchProperties = new CloudWatchProperties(); + propertyMapper.from(properties.getEndpoint()).whenNonNull().to(cloudWatchProperties::setEndpoint); + propertyMapper.from(properties.getRegion()).whenNonNull().to(cloudWatchProperties::setRegion); + CloudWatchAsyncClient cloudWatchAsyncClient = awsClientBuilderConfigurer + .configure(cloudWatchAsyncClientBuilder, cloudWatchProperties, null, null).build(); + + CloudWatchMetricPublisher.Builder builder = CloudWatchMetricPublisher.builder(); + builder.cloudWatchClient(cloudWatchAsyncClient); + propertyMapper.from(properties.getMetrics()::getNamespace).whenNonNull().to(builder::namespace); + propertyMapper.from(properties.getMetrics()::getUploadFrequencyInSeconds).whenNonNull() + .to(v -> builder.uploadFrequency(Duration.ofSeconds(v))); + return builder.build(); + } + else { + return null; + } + + } + return metricPublisher; + } } diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/AwsProperties.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/AwsProperties.java index 014ca6834..62e8d606f 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/AwsProperties.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/AwsProperties.java @@ -19,6 +19,7 @@ import java.net.URI; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.lang.Nullable; import software.amazon.awssdk.awscore.defaultsmode.DefaultsMode; @@ -54,6 +55,13 @@ public class AwsProperties { @Nullable private Boolean dualstackEnabled; + /** + * Configure metrics properties to send to CloudWatch if needed + */ + @NestedConfigurationProperty + @Nullable + private CloudWatchMetricsPublisherProperties metrics; + /** * Configure whether the SDK should use the AWS fips endpoints. */ @@ -95,4 +103,13 @@ public Boolean getFipsEnabled() { public void setFipsEnabled(@Nullable Boolean fipsEnabled) { this.fipsEnabled = fipsEnabled; } + + @Nullable + public CloudWatchMetricsPublisherProperties getMetrics() { + return metrics; + } + + public void setMetrics(@Nullable CloudWatchMetricsPublisherProperties metrics) { + this.metrics = metrics; + } } diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/CloudWatchMetricsPublisherAutoConfiguration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/CloudWatchMetricsPublisherAutoConfiguration.java new file mode 100644 index 000000000..6729cec4b --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/CloudWatchMetricsPublisherAutoConfiguration.java @@ -0,0 +1,73 @@ +/* + * Copyright 2013-2022 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 io.awspring.cloud.autoconfigure.core; + +import io.awspring.cloud.autoconfigure.metrics.CloudWatchProperties; +import java.time.Duration; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import software.amazon.awssdk.metrics.MetricPublisher; +import software.amazon.awssdk.metrics.publishers.cloudwatch.CloudWatchMetricPublisher; +import software.amazon.awssdk.regions.providers.AwsRegionProvider; +import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClient; +import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClientBuilder; + +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(AwsProperties.class) +@ConditionalOnClass({ CloudWatchMetricPublisher.class }) +@AutoConfigureAfter({ CredentialsProviderAutoConfiguration.class, RegionProviderAutoConfiguration.class }) +public class CloudWatchMetricsPublisherAutoConfiguration { + + private final AwsProperties awsProperties; + + public CloudWatchMetricsPublisherAutoConfiguration(AwsProperties awsProperties) { + this.awsProperties = awsProperties; + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(name = "spring.cloud.aws.metrics.enabled", havingValue = "true", matchIfMissing = true) + MetricPublisher cloudWatchMetricPublisher(AwsClientBuilderConfigurer awsClientBuilderConfigurer, + AwsRegionProvider awsRegionProvider, + ObjectProvider> configurer) { + PropertyMapper propertyMapper = PropertyMapper.get(); + + CloudWatchAsyncClientBuilder cloudWatchAsyncClientBuilder = CloudWatchAsyncClient.builder(); + CloudWatchProperties cloudWatchProperties = new CloudWatchProperties(); + propertyMapper.from(awsProperties.getEndpoint()).whenNonNull().to(cloudWatchProperties::setEndpoint); + propertyMapper.from(awsRegionProvider.getRegion().id()).whenNonNull().to(cloudWatchProperties::setRegion); + CloudWatchAsyncClient cloudWatchAsyncClient = awsClientBuilderConfigurer + .configure(cloudWatchAsyncClientBuilder, cloudWatchProperties, configurer.getIfAvailable(), null) + .build(); + + CloudWatchMetricPublisher.Builder builder = CloudWatchMetricPublisher.builder(); + builder.cloudWatchClient(cloudWatchAsyncClient); + + if (awsProperties.getMetrics() != null) { + propertyMapper.from(awsProperties.getMetrics()::getNamespace).whenNonNull().to(builder::namespace); + propertyMapper.from(awsProperties.getMetrics()::getUploadFrequencyInSeconds).whenNonNull() + .to(v -> builder.uploadFrequency(Duration.ofSeconds(v))); + } + return builder.build(); + } +} diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/CloudWatchMetricsPublisherProperties.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/CloudWatchMetricsPublisherProperties.java new file mode 100644 index 000000000..9e48a4e1b --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/CloudWatchMetricsPublisherProperties.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013-2022 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 io.awspring.cloud.autoconfigure.core; + +import org.springframework.lang.Nullable; + +public class CloudWatchMetricsPublisherProperties { + @Nullable + private boolean enabled; + @Nullable + private String namespace; + @Nullable + private Long uploadFrequencyInSeconds; + + @Nullable + public boolean getEnabled() { + return enabled; + } + + public void setEnabled(@Nullable boolean enabled) { + this.enabled = enabled; + } + + @Nullable + public String getNamespace() { + return namespace; + } + + public void setNamespace(@Nullable String namespace) { + this.namespace = namespace; + } + + @Nullable + public Long getUploadFrequencyInSeconds() { + return uploadFrequencyInSeconds; + } + + public void setUploadFrequencyInSeconds(@Nullable Long uploadFrequencyInSeconds) { + this.uploadFrequencyInSeconds = uploadFrequencyInSeconds; + } +} diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/dynamodb/DynamoDbAutoConfiguration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/dynamodb/DynamoDbAutoConfiguration.java index 246a7f71b..96e214821 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/dynamodb/DynamoDbAutoConfiguration.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/dynamodb/DynamoDbAutoConfiguration.java @@ -15,6 +15,8 @@ */ package io.awspring.cloud.autoconfigure.dynamodb; +import static io.awspring.cloud.autoconfigure.core.AwsClientBuilderConfigurer.createSpecificMetricPublisher; + import io.awspring.cloud.autoconfigure.core.AwsClientBuilderConfigurer; import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer; import io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration; @@ -31,6 +33,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; +import software.amazon.awssdk.metrics.MetricPublisher; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.DynamoDbClientBuilder; @@ -55,10 +58,19 @@ public DynamoDbAutoConfiguration(DynamoDbProperties properties) { @ConditionalOnMissingBean @Bean - public DynamoDbClient dynamoDbClient(AwsClientBuilderConfigurer awsClientBuilderConfigurer, - ObjectProvider> configurer) { - return awsClientBuilderConfigurer.configure(DynamoDbClient.builder(), properties, configurer.getIfAvailable()) - .build(); + public DynamoDbClientBuilder dynamoDbClientBuilder(AwsClientBuilderConfigurer awsClientBuilderConfigurer, + ObjectProvider> configurer, + ObjectProvider metricPublisherObjectProvider) { + MetricPublisher metricPublisher = createSpecificMetricPublisher(metricPublisherObjectProvider.getIfAvailable(), + properties, awsClientBuilderConfigurer); + return awsClientBuilderConfigurer.configure(DynamoDbClient.builder(), properties, configurer.getIfAvailable(), + metricPublisher); + } + + @ConditionalOnMissingBean + @Bean + public DynamoDbClient dynamoDbClient(DynamoDbClientBuilder dynamoDbClientBuilder) { + return dynamoDbClientBuilder.build(); } @ConditionalOnMissingBean diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/metrics/CloudWatchExportAutoConfiguration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/metrics/CloudWatchExportAutoConfiguration.java index a3881c9ee..28c0aa024 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/metrics/CloudWatchExportAutoConfiguration.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/metrics/CloudWatchExportAutoConfiguration.java @@ -34,6 +34,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import software.amazon.awssdk.metrics.MetricPublisher; import software.amazon.awssdk.regions.providers.AwsRegionProvider; import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClient; import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClientBuilder; @@ -67,9 +68,10 @@ public CloudWatchMeterRegistry cloudWatchMeterRegistry(CloudWatchConfig config, @ConditionalOnMissingBean public CloudWatchAsyncClient cloudWatchAsyncClient(CloudWatchProperties properties, AwsClientBuilderConfigurer awsClientBuilderConfigurer, - ObjectProvider> configurer) { - return awsClientBuilderConfigurer - .configure(CloudWatchAsyncClient.builder(), properties, configurer.getIfAvailable()).build(); + ObjectProvider> configurer, + ObjectProvider metricPublisherObjectProvider) { + return awsClientBuilderConfigurer.configure(CloudWatchAsyncClient.builder(), properties, + configurer.getIfAvailable(), metricPublisherObjectProvider.getIfAvailable()).build(); } @Bean diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3AutoConfiguration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3AutoConfiguration.java index c670caeda..4f8c847c3 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3AutoConfiguration.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3AutoConfiguration.java @@ -15,6 +15,8 @@ */ package io.awspring.cloud.autoconfigure.s3; +import static io.awspring.cloud.autoconfigure.core.AwsClientBuilderConfigurer.createSpecificMetricPublisher; + import com.fasterxml.jackson.databind.ObjectMapper; import io.awspring.cloud.autoconfigure.core.AwsClientBuilderConfigurer; import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer; @@ -43,6 +45,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import software.amazon.awssdk.metrics.MetricPublisher; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.S3ClientBuilder; import software.amazon.awssdk.services.s3.S3Configuration; @@ -68,9 +71,12 @@ public S3AutoConfiguration(S3Properties properties) { @Bean @ConditionalOnMissingBean S3ClientBuilder s3ClientBuilder(AwsClientBuilderConfigurer awsClientBuilderConfigurer, - ObjectProvider> configurer) { + ObjectProvider> configurer, + ObjectProvider metricPublisherObjectProvider) { + MetricPublisher metricPublisher = createSpecificMetricPublisher(metricPublisherObjectProvider.getIfAvailable(), + properties, awsClientBuilderConfigurer); S3ClientBuilder builder = awsClientBuilderConfigurer.configure(S3Client.builder(), this.properties, - configurer.getIfAvailable()); + configurer.getIfAvailable(), metricPublisher); builder.serviceConfiguration(s3ServiceConfiguration()); return builder; } @@ -137,5 +143,4 @@ S3OutputStreamProvider inMemoryBufferingS3StreamProvider(S3Client s3Client, return new InMemoryBufferingS3OutputStreamProvider(s3Client, contentTypeResolver.orElseGet(PropertiesS3ObjectContentTypeResolver::new)); } - } diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/ses/SesAutoConfiguration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/ses/SesAutoConfiguration.java index 5fb865fcf..373a25acd 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/ses/SesAutoConfiguration.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/ses/SesAutoConfiguration.java @@ -15,6 +15,8 @@ */ package io.awspring.cloud.autoconfigure.ses; +import static io.awspring.cloud.autoconfigure.core.AwsClientBuilderConfigurer.createSpecificMetricPublisher; + import io.awspring.cloud.autoconfigure.core.AwsClientBuilderConfigurer; import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer; import io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration; @@ -33,6 +35,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.mail.MailSender; import org.springframework.mail.javamail.JavaMailSender; +import software.amazon.awssdk.metrics.MetricPublisher; import software.amazon.awssdk.services.ses.SesClient; import software.amazon.awssdk.services.ses.SesClientBuilder; @@ -53,10 +56,20 @@ public class SesAutoConfiguration { @Bean @ConditionalOnMissingBean - public SesClient sesClient(SesProperties properties, AwsClientBuilderConfigurer awsClientBuilderConfigurer, - ObjectProvider> configurer) { - return awsClientBuilderConfigurer.configure(SesClient.builder(), properties, configurer.getIfAvailable()) - .build(); + public SesClientBuilder sesClientBuilder(SesProperties properties, + AwsClientBuilderConfigurer awsClientBuilderConfigurer, + ObjectProvider> configurer, + ObjectProvider metricPublisherObjectProvider) { + MetricPublisher metricPublisher = createSpecificMetricPublisher(metricPublisherObjectProvider.getIfAvailable(), + properties, awsClientBuilderConfigurer); + return awsClientBuilderConfigurer.configure(SesClient.builder(), properties, configurer.getIfAvailable(), + metricPublisher); + } + + @Bean + @ConditionalOnMissingBean + public SesClient sesClient(SesClientBuilder sesClientBuilder) { + return sesClientBuilder.build(); } @Bean diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sns/SnsAutoConfiguration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sns/SnsAutoConfiguration.java index 1708095a4..96a2e41a7 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sns/SnsAutoConfiguration.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sns/SnsAutoConfiguration.java @@ -15,6 +15,7 @@ */ package io.awspring.cloud.autoconfigure.sns; +import static io.awspring.cloud.autoconfigure.core.AwsClientBuilderConfigurer.createSpecificMetricPublisher; import static io.awspring.cloud.sns.configuration.NotificationHandlerMethodArgumentResolverConfigurationUtils.getNotificationHandlerMethodArgumentResolver; import com.fasterxml.jackson.databind.ObjectMapper; @@ -38,6 +39,7 @@ import org.springframework.messaging.converter.MappingJackson2MessageConverter; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import software.amazon.awssdk.metrics.MetricPublisher; import software.amazon.awssdk.services.sns.SnsClient; import software.amazon.awssdk.services.sns.SnsClientBuilder; @@ -60,10 +62,20 @@ public class SnsAutoConfiguration { @ConditionalOnMissingBean @Bean - public SnsClient snsClient(SnsProperties properties, AwsClientBuilderConfigurer awsClientBuilderConfigurer, - ObjectProvider> configurer) { - return awsClientBuilderConfigurer.configure(SnsClient.builder(), properties, configurer.getIfAvailable()) - .build(); + public SnsClientBuilder snsClientBuilder(SnsProperties properties, + AwsClientBuilderConfigurer awsClientBuilderConfigurer, + ObjectProvider> configurer, + ObjectProvider metricPublisherObjectProvider) { + MetricPublisher metricPublisher = createSpecificMetricPublisher(metricPublisherObjectProvider.getIfAvailable(), + properties, awsClientBuilderConfigurer); + return awsClientBuilderConfigurer.configure(SnsClient.builder(), properties, configurer.getIfAvailable(), + metricPublisher); + } + + @ConditionalOnMissingBean + @Bean + public SnsClient snsClient(SnsClientBuilder snsClientBuilder) { + return snsClientBuilder.build(); } @ConditionalOnMissingBean diff --git a/spring-cloud-aws-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-cloud-aws-autoconfigure/src/main/resources/META-INF/spring.factories index d8cb47a45..7e2068df8 100644 --- a/spring-cloud-aws-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-aws-autoconfigure/src/main/resources/META-INF/spring.factories @@ -3,6 +3,7 @@ io.awspring.cloud.autoconfigure.core.AwsAutoConfiguration,\ io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration,\ io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration,\ io.awspring.cloud.autoconfigure.metrics.CloudWatchExportAutoConfiguration,\ +io.awspring.cloud.autoconfigure.core.CloudWatchMetricsPublisherAutoConfiguration,\ io.awspring.cloud.autoconfigure.ses.SesAutoConfiguration,\ io.awspring.cloud.autoconfigure.s3.S3TransferManagerAutoConfiguration,\ io.awspring.cloud.autoconfigure.s3.S3AutoConfiguration,\ diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreConfigDataLoaderIntegrationTests.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreConfigDataLoaderIntegrationTests.java index 16c0e79b4..d2a7bb56a 100644 --- a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreConfigDataLoaderIntegrationTests.java +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreConfigDataLoaderIntegrationTests.java @@ -37,6 +37,7 @@ import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.test.util.ReflectionTestUtils; import org.testcontainers.containers.localstack.LocalStackContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -46,6 +47,8 @@ import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.http.SdkHttpClient; import software.amazon.awssdk.http.apache.ApacheHttpClient; +import software.amazon.awssdk.metrics.publishers.cloudwatch.CloudWatchMetricPublisher; +import software.amazon.awssdk.metrics.publishers.cloudwatch.internal.transform.MetricCollectionAggregator; import software.amazon.awssdk.services.ssm.SsmClient; import software.amazon.awssdk.services.ssm.model.GetParametersByPathRequest; import software.amazon.awssdk.services.ssm.model.GetParametersByPathResponse; @@ -210,6 +213,37 @@ void parameterStoreClientUsesGlobalRegion() { } } + @Test + void clientIsConfiguredWithConfigurerProvidedToBootstrapRegistry2() { + SpringApplication application = new SpringApplication(App.class); + application.setWebApplicationType(WebApplicationType.NONE); + application.addBootstrapRegistryInitializer(new AwsConfigurerClientConfiguration()); + + try (ConfigurableApplicationContext context = application.run( + "--spring.cloud.aws.parameterstore.metrics.namespace=custom", + "--spring.config.import=aws-parameterstore:/config/spring/", + "--spring.cloud.aws.parameterstore.region=" + REGION, + "--spring.cloud.aws.endpoint=http://non-existing-host/", + "--spring.cloud.aws.parameterstore.endpoint=" + localstack.getEndpointOverride(SSM).toString(), + "--spring.cloud.aws.credentials.access-key=noop", "--spring.cloud.aws.credentials.secret-key=noop", + "--spring.cloud.aws.region.static=eu-west-1", + "--logging.level.io.awspring.cloud.parameterstore=debug")) { + + CloudWatchMetricPublisher metricPublisher = (CloudWatchMetricPublisher) context.getBean(SsmClient.class) + .overrideConfiguration().metricPublishers().get(0); + MetricCollectionAggregator metricAggregator = (MetricCollectionAggregator) ReflectionTestUtils + .getField(metricPublisher, "metricAggregator"); + + String namespace = (String) ReflectionTestUtils.getField(metricAggregator, "namespace"); + assertThat(namespace).isEqualTo("custom"); + } + } + + @Test + void parameterStoreUsesCustomEndpointForMetricsIfDefined() { + + } + private ConfigurableApplicationContext runApplication(SpringApplication application, String springConfigImport, String endpointProperty) { return application.run("--spring.config.import=" + springConfigImport, diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/core/AwsAutoConfigurationTests.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/core/AwsAutoConfigurationTests.java new file mode 100644 index 000000000..5e41536bb --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/core/AwsAutoConfigurationTests.java @@ -0,0 +1,46 @@ +/* + * Copyright 2013-2022 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 io.awspring.cloud.autoconfigure.core; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import software.amazon.awssdk.metrics.MetricPublisher; +import software.amazon.awssdk.metrics.publishers.cloudwatch.CloudWatchMetricPublisher; + +class AwsAutoConfigurationTests { + + private static ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration( + AutoConfigurations.of(CredentialsProviderAutoConfiguration.class, AwsAutoConfiguration.class)) + .withBean(CustomRegionProvider.class); + + @Test + void doesNotCreateMetricsPublisherWhenDisabledExplicitlyAndDependencyInClasspath() { + contextRunner.withPropertyValues("spring.cloud.aws.metrics.enabled:false") + .run((context) -> assertThat(context).doesNotHaveBean(MetricPublisher.class)); + } + + @Test + void doesNotCreateMetricsPublisherWhenDependencyNotInClasspath() { + contextRunner.withClassLoader(new FilteredClassLoader(CloudWatchMetricPublisher.class)) + .run((context) -> assertThat(context).doesNotHaveBean(MetricPublisher.class)); + } + +} diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/core/CloudWatchMetricsPublisherAutoConfigurationTests.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/core/CloudWatchMetricsPublisherAutoConfigurationTests.java new file mode 100644 index 000000000..b9627f8c0 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/core/CloudWatchMetricsPublisherAutoConfigurationTests.java @@ -0,0 +1,44 @@ +/* + * Copyright 2013-2022 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 io.awspring.cloud.autoconfigure.core; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import software.amazon.awssdk.metrics.MetricPublisher; + +class CloudWatchMetricsPublisherAutoConfigurationTests { + + private static ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("spring.cloud.aws.region.static:eu-west-1") + .withConfiguration(AutoConfigurations.of(RegionProviderAutoConfiguration.class, + CredentialsProviderAutoConfiguration.class, CloudWatchMetricsPublisherAutoConfiguration.class, + AwsAutoConfiguration.class)); + + @Test + void createsMetricsPublisherByDefaultWhenDependencyInClasspath() { + contextRunner.run((context) -> assertThat(context).hasSingleBean(MetricPublisher.class)); + } + + @Test + void createsMetricsPublisherWhenSpecifiedExplicitlyAndDependencyInClasspath() { + contextRunner.withPropertyValues("spring.cloud.aws.metrics.enabled:true") + .run((context) -> assertThat(context).hasSingleBean(MetricPublisher.class)); + } + +} diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/core/CustomRegionProvider.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/core/CustomRegionProvider.java new file mode 100644 index 000000000..1523a9c57 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/core/CustomRegionProvider.java @@ -0,0 +1,28 @@ +/* + * Copyright 2013-2022 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 io.awspring.cloud.autoconfigure.core; + +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.regions.providers.AwsRegionProvider; + +class CustomRegionProvider implements AwsRegionProvider { + + @Override + public Region getRegion() { + return null; + } + +} diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/core/RegionProviderAutoConfigurationTests.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/core/RegionProviderAutoConfigurationTests.java index 4e3056095..f0e722e0a 100644 --- a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/core/RegionProviderAutoConfigurationTests.java +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/core/RegionProviderAutoConfigurationTests.java @@ -143,13 +143,4 @@ public AwsRegionProvider customRegionProvider() { } - static class CustomRegionProvider implements AwsRegionProvider { - - @Override - public Region getRegion() { - return null; - } - - } - } diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/dynamodb/DynamoDbAutoConfigurationTest.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/dynamodb/DynamoDbAutoConfigurationTest.java index ef41420e1..93145e424 100644 --- a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/dynamodb/DynamoDbAutoConfigurationTest.java +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/dynamodb/DynamoDbAutoConfigurationTest.java @@ -20,6 +20,7 @@ import io.awspring.cloud.autoconfigure.ConfiguredAwsClient; import io.awspring.cloud.autoconfigure.core.AwsAutoConfiguration; import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer; +import io.awspring.cloud.autoconfigure.core.CloudWatchMetricsPublisherAutoConfiguration; import io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration; import io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration; import io.awspring.cloud.dynamodb.DynamoDbTableNameResolver; @@ -29,15 +30,20 @@ import java.time.Duration; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.lang.Nullable; +import org.springframework.test.util.ReflectionTestUtils; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; import software.amazon.awssdk.enhanced.dynamodb.TableSchema; import software.amazon.awssdk.http.SdkHttpClient; import software.amazon.awssdk.http.apache.ApacheHttpClient; +import software.amazon.awssdk.metrics.MetricPublisher; +import software.amazon.awssdk.metrics.publishers.cloudwatch.CloudWatchMetricPublisher; +import software.amazon.awssdk.metrics.publishers.cloudwatch.internal.transform.MetricCollectionAggregator; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.DynamoDbClientBuilder; @@ -50,8 +56,8 @@ class DynamoDbAutoConfigurationTest { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.cloud.aws.region.static:eu-west-1") .withConfiguration(AutoConfigurations.of(RegionProviderAutoConfiguration.class, - CredentialsProviderAutoConfiguration.class, DynamoDbAutoConfiguration.class, - AwsAutoConfiguration.class)); + CredentialsProviderAutoConfiguration.class, CloudWatchMetricsPublisherAutoConfiguration.class, + DynamoDbAutoConfiguration.class, AwsAutoConfiguration.class)); @Test void dynamoDBAutoConfigurationIsDisabled() { @@ -105,6 +111,51 @@ void customTableResolverResolverCanBeConfigured() { }); } + @Test + void usesSpecificMetricsClientPropertiesIfSpecified() { + this.contextRunner.withPropertyValues("spring.cloud.aws.dynamodb.metrics.enabled:true", + "spring.cloud.aws.dynamodb.metrics.namespace:custom").run(context -> { + assertThat(context).hasSingleBean(MetricPublisher.class); + assertThat(context).hasSingleBean(DynamoDbClientBuilder.class); + assertThat(context.getBean(DynamoDbClientBuilder.class).overrideConfiguration().metricPublishers() + .size()).isEqualTo(1); + CloudWatchMetricPublisher metricPublisher = (CloudWatchMetricPublisher) context + .getBean(DynamoDbClientBuilder.class).overrideConfiguration().metricPublishers().get(0); + MetricCollectionAggregator metricAggregator = (MetricCollectionAggregator) ReflectionTestUtils + .getField(metricPublisher, "metricAggregator"); + String namespace = (String) ReflectionTestUtils.getField(metricAggregator, "namespace"); + assertThat(namespace).isEqualTo("custom"); + }); + } + + @Test + void doesNotUseMetricsClientIfDisabledForclient() { + this.contextRunner.withPropertyValues("spring.cloud.aws.dynamodb.metrics.enabled:false").run(context -> { + assertThat(context.getBean(DynamoDbClientBuilder.class).overrideConfiguration().metricPublishers().size()) + .isEqualTo(0); + }); + } + + @Test + void usesMetricsPublisherIfAvailable() { + this.contextRunner.run(context -> { + assertThat(context).hasSingleBean(MetricPublisher.class); + assertThat(context).hasSingleBean(DynamoDbClientBuilder.class); + assertThat(context.getBean(DynamoDbClientBuilder.class).overrideConfiguration().metricPublishers().size()) + .isEqualTo(1); + }); + } + + @Test + void doesNotUseMetricsPublisherIfNotAvailable() { + this.contextRunner.withClassLoader(new FilteredClassLoader(CloudWatchMetricPublisher.class)).run(context -> { + assertThat(context).doesNotHaveBean(MetricPublisher.class); + assertThat(context).hasSingleBean(DynamoDbClientBuilder.class); + assertThat(context.getBean(DynamoDbClientBuilder.class).overrideConfiguration().metricPublishers().size()) + .isEqualTo(0); + }); + } + @Test void customDynamoDbClientConfigurer() { this.contextRunner.withUserConfiguration(DynamoDbAutoConfigurationTest.CustomAwsClientConfig.class) diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/s3/S3AutoConfigurationTests.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/s3/S3AutoConfigurationTests.java index 985385b47..3b0ba79b3 100644 --- a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/s3/S3AutoConfigurationTests.java +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/s3/S3AutoConfigurationTests.java @@ -22,6 +22,7 @@ import io.awspring.cloud.autoconfigure.ConfiguredAwsClient; import io.awspring.cloud.autoconfigure.core.AwsAutoConfiguration; import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer; +import io.awspring.cloud.autoconfigure.core.CloudWatchMetricsPublisherAutoConfiguration; import io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration; import io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration; import io.awspring.cloud.autoconfigure.s3.properties.S3Properties; @@ -51,6 +52,9 @@ import software.amazon.awssdk.core.client.config.SdkClientOption; import software.amazon.awssdk.http.SdkHttpClient; import software.amazon.awssdk.http.apache.ApacheHttpClient; +import software.amazon.awssdk.metrics.MetricPublisher; +import software.amazon.awssdk.metrics.publishers.cloudwatch.CloudWatchMetricPublisher; +import software.amazon.awssdk.metrics.publishers.cloudwatch.internal.transform.MetricCollectionAggregator; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.S3ClientBuilder; @@ -64,7 +68,8 @@ class S3AutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.cloud.aws.region.static:eu-west-1") .withConfiguration(AutoConfigurations.of(AwsAutoConfiguration.class, RegionProviderAutoConfiguration.class, - CredentialsProviderAutoConfiguration.class, S3AutoConfiguration.class)); + CloudWatchMetricsPublisherAutoConfiguration.class, CredentialsProviderAutoConfiguration.class, + S3AutoConfiguration.class)); @Test void createsS3ClientBean() { @@ -88,6 +93,51 @@ void s3AutoConfigurationIsDisabled() { }); } + @Test + void usesMetricsPublisherIfAvailable() { + this.contextRunner.run(context -> { + assertThat(context).hasSingleBean(MetricPublisher.class); + assertThat(context).hasSingleBean(S3ClientBuilder.class); + assertThat(context.getBean(S3ClientBuilder.class).overrideConfiguration().metricPublishers().size()) + .isEqualTo(1); + }); + } + + @Test + void usesSpecificMetricsClientPropertiesIfSpecified() { + this.contextRunner.withPropertyValues("spring.cloud.aws.s3.metrics.enabled:true", + "spring.cloud.aws.s3.metrics.namespace:custom").run(context -> { + assertThat(context).hasSingleBean(MetricPublisher.class); + assertThat(context).hasSingleBean(S3ClientBuilder.class); + assertThat(context.getBean(S3ClientBuilder.class).overrideConfiguration().metricPublishers().size()) + .isEqualTo(1); + CloudWatchMetricPublisher metricPublisher = (CloudWatchMetricPublisher) context + .getBean(S3ClientBuilder.class).overrideConfiguration().metricPublishers().get(0); + MetricCollectionAggregator metricAggregator = (MetricCollectionAggregator) ReflectionTestUtils + .getField(metricPublisher, "metricAggregator"); + String namespace = (String) ReflectionTestUtils.getField(metricAggregator, "namespace"); + assertThat(namespace).isEqualTo("custom"); + }); + } + + @Test + void doesNotUseMetricsClientIfDisabledForclient() { + this.contextRunner.withPropertyValues("spring.cloud.aws.s3.metrics.enabled:false").run(context -> { + assertThat(context.getBean(S3ClientBuilder.class).overrideConfiguration().metricPublishers().size()) + .isEqualTo(0); + }); + } + + @Test + void doesNotUseMetricsPublisherIfNotAvailable() { + this.contextRunner.withClassLoader(new FilteredClassLoader(CloudWatchMetricPublisher.class)).run(context -> { + assertThat(context).doesNotHaveBean(MetricPublisher.class); + assertThat(context).hasSingleBean(S3ClientBuilder.class); + assertThat(context.getBean(S3ClientBuilder.class).overrideConfiguration().metricPublishers().size()) + .isEqualTo(0); + }); + } + @Test void autoconfigurationIsNotTriggeredWhenS3ModuleIsNotOnClasspath() { this.contextRunner.withClassLoader(new FilteredClassLoader(S3OutputStreamProvider.class)).run(context -> { diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/ses/SesAutoConfigurationTest.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/ses/SesAutoConfigurationTest.java index a5b1414fb..a0f91b4ae 100644 --- a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/ses/SesAutoConfigurationTest.java +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/ses/SesAutoConfigurationTest.java @@ -20,6 +20,7 @@ import io.awspring.cloud.autoconfigure.ConfiguredAwsClient; import io.awspring.cloud.autoconfigure.core.AwsAutoConfiguration; import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer; +import io.awspring.cloud.autoconfigure.core.CloudWatchMetricsPublisherAutoConfiguration; import io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration; import io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration; import java.net.URI; @@ -32,9 +33,13 @@ import org.springframework.context.annotation.Configuration; import org.springframework.mail.MailSender; import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.test.util.ReflectionTestUtils; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.http.SdkHttpClient; import software.amazon.awssdk.http.apache.ApacheHttpClient; +import software.amazon.awssdk.metrics.MetricPublisher; +import software.amazon.awssdk.metrics.publishers.cloudwatch.CloudWatchMetricPublisher; +import software.amazon.awssdk.metrics.publishers.cloudwatch.internal.transform.MetricCollectionAggregator; import software.amazon.awssdk.services.ses.SesClient; import software.amazon.awssdk.services.ses.SesClientBuilder; @@ -50,7 +55,8 @@ class SesAutoConfigurationTest { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.cloud.aws.region.static:eu-west-1") .withConfiguration(AutoConfigurations.of(AwsAutoConfiguration.class, RegionProviderAutoConfiguration.class, - CredentialsProviderAutoConfiguration.class, SesAutoConfiguration.class)); + CloudWatchMetricsPublisherAutoConfiguration.class, CredentialsProviderAutoConfiguration.class, + SesAutoConfiguration.class)); @Test void mailSenderWithJavaMail() { @@ -86,6 +92,52 @@ void sesAutoConfigurationIsDisabled() { }); } + @Test + void usesSpecificMetricsClientPropertiesIfSpecified() { + this.contextRunner.withPropertyValues("spring.cloud.aws.ses.metrics.enabled:true", + "spring.cloud.aws.ses.metrics.namespace:custom").run(context -> { + assertThat(context).hasSingleBean(MetricPublisher.class); + assertThat(context).hasSingleBean(SesClientBuilder.class); + assertThat( + context.getBean(SesClientBuilder.class).overrideConfiguration().metricPublishers().size()) + .isEqualTo(1); + CloudWatchMetricPublisher metricPublisher = (CloudWatchMetricPublisher) context + .getBean(SesClientBuilder.class).overrideConfiguration().metricPublishers().get(0); + MetricCollectionAggregator metricAggregator = (MetricCollectionAggregator) ReflectionTestUtils + .getField(metricPublisher, "metricAggregator"); + String namespace = (String) ReflectionTestUtils.getField(metricAggregator, "namespace"); + assertThat(namespace).isEqualTo("custom"); + }); + } + + @Test + void doesNotUseMetricsClientIfDisabledForclient() { + this.contextRunner.withPropertyValues("spring.cloud.aws.ses.metrics.enabled:false").run(context -> { + assertThat(context.getBean(SesClientBuilder.class).overrideConfiguration().metricPublishers().size()) + .isEqualTo(0); + }); + } + + @Test + void usesMetricsPublisherIfAvailable() { + this.contextRunner.run(context -> { + assertThat(context).hasSingleBean(MetricPublisher.class); + assertThat(context).hasSingleBean(SesClientBuilder.class); + assertThat(context.getBean(SesClientBuilder.class).overrideConfiguration().metricPublishers().size()) + .isEqualTo(1); + }); + } + + @Test + void doesNotUseMetricsPublisherIfNotAvailable() { + this.contextRunner.withClassLoader(new FilteredClassLoader(CloudWatchMetricPublisher.class)).run(context -> { + assertThat(context).doesNotHaveBean(MetricPublisher.class); + assertThat(context).hasSingleBean(SesClientBuilder.class); + assertThat(context.getBean(SesClientBuilder.class).overrideConfiguration().metricPublishers().size()) + .isEqualTo(0); + }); + } + @Test void withCustomEndpoint() { this.contextRunner.withPropertyValues("spring.cloud.aws.ses.endpoint:http://localhost:8090").run(context -> { diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/sns/SnsAutoConfigurationTest.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/sns/SnsAutoConfigurationTest.java index bb43960b0..edcf29cca 100644 --- a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/sns/SnsAutoConfigurationTest.java +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/sns/SnsAutoConfigurationTest.java @@ -20,6 +20,7 @@ import io.awspring.cloud.autoconfigure.ConfiguredAwsClient; import io.awspring.cloud.autoconfigure.core.AwsAutoConfiguration; import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer; +import io.awspring.cloud.autoconfigure.core.CloudWatchMetricsPublisherAutoConfiguration; import io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration; import io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration; import io.awspring.cloud.sns.core.SnsTemplate; @@ -41,6 +42,9 @@ import software.amazon.awssdk.core.client.config.SdkClientOption; import software.amazon.awssdk.http.SdkHttpClient; import software.amazon.awssdk.http.apache.ApacheHttpClient; +import software.amazon.awssdk.metrics.MetricPublisher; +import software.amazon.awssdk.metrics.publishers.cloudwatch.CloudWatchMetricPublisher; +import software.amazon.awssdk.metrics.publishers.cloudwatch.internal.transform.MetricCollectionAggregator; import software.amazon.awssdk.services.sns.SnsClient; import software.amazon.awssdk.services.sns.SnsClientBuilder; @@ -55,7 +59,7 @@ class SnsAutoConfigurationTest { .withPropertyValues("spring.cloud.aws.region.static:eu-west-1") .withConfiguration(AutoConfigurations.of(RegionProviderAutoConfiguration.class, CredentialsProviderAutoConfiguration.class, SnsAutoConfiguration.class, - AwsAutoConfiguration.class)); + CloudWatchMetricsPublisherAutoConfiguration.class, AwsAutoConfiguration.class)); @Test void snsAutoConfigurationIsDisabled() { @@ -106,6 +110,52 @@ void customSnsClientConfigurer() { }); } + @Test + void usesSpecificMetricsClientPropertiesIfSpecified() { + this.contextRunner.withPropertyValues("spring.cloud.aws.sns.metrics.enabled:true", + "spring.cloud.aws.sns.metrics.namespace:custom").run(context -> { + assertThat(context).hasSingleBean(MetricPublisher.class); + assertThat(context).hasSingleBean(SnsClientBuilder.class); + assertThat( + context.getBean(SnsClientBuilder.class).overrideConfiguration().metricPublishers().size()) + .isEqualTo(1); + CloudWatchMetricPublisher metricPublisher = (CloudWatchMetricPublisher) context + .getBean(SnsClientBuilder.class).overrideConfiguration().metricPublishers().get(0); + MetricCollectionAggregator metricAggregator = (MetricCollectionAggregator) ReflectionTestUtils + .getField(metricPublisher, "metricAggregator"); + String namespace = (String) ReflectionTestUtils.getField(metricAggregator, "namespace"); + assertThat(namespace).isEqualTo("custom"); + }); + } + + @Test + void doesNotUseMetricsClientIfDisabledForclient() { + this.contextRunner.withPropertyValues("spring.cloud.aws.sns.metrics.enabled:false").run(context -> { + assertThat(context.getBean(SnsClientBuilder.class).overrideConfiguration().metricPublishers().size()) + .isEqualTo(0); + }); + } + + @Test + void usesMetricsPublisherIfAvailable() { + this.contextRunner.run(context -> { + assertThat(context).hasSingleBean(MetricPublisher.class); + assertThat(context).hasSingleBean(SnsClientBuilder.class); + assertThat(context.getBean(SnsClientBuilder.class).overrideConfiguration().metricPublishers().size()) + .isEqualTo(1); + }); + } + + @Test + void doesNotUseMetricsPublisherIfNotAvailable() { + this.contextRunner.withClassLoader(new FilteredClassLoader(CloudWatchMetricPublisher.class)).run(context -> { + assertThat(context).doesNotHaveBean(MetricPublisher.class); + assertThat(context).hasSingleBean(SnsClientBuilder.class); + assertThat(context.getBean(SnsClientBuilder.class).overrideConfiguration().metricPublishers().size()) + .isEqualTo(0); + }); + } + @Test void doesNotConfigureArgumentResolversWhenSpringWebNotOnTheClasspath() { this.contextRunner.withClassLoader(new FilteredClassLoader(WebMvcConfigurer.class)).run(context -> {