From 8f036a394c162e3a45283db7cef1952247c45e72 Mon Sep 17 00:00:00 2001 From: Oleg Sidorov Date: Mon, 27 Feb 2023 09:16:34 +0100 Subject: [PATCH] [DBParameterGroup] Include parameters in the ReadHandler response (#406) This commit adds DBParameterGroup.parameters in the ReadHandler response. The motivation for this change is to enable drift detector on independent group parameters. At the moment this collection is defined aas WriteOnly, instructing the drift detector to ignore it completely. --- .../common/logging/LoggingProxyClient.java | 69 +++-- .../logging/LoggingProxyClientTest.java | 12 +- .../verification/AccessPermissionAlias.java | 13 + .../AccessPermissionVerificationMode.java | 20 +- .../verification/AccessPermissionTest.java | 15 ++ .../AccessPermissionVerificationModeTest.java | 24 +- .../aws-rds-dbparametergroup.json | 10 +- .../rds/dbparametergroup/BaseHandlerStd.java | 237 ++++++++++++------ .../rds/dbparametergroup/ReadHandler.java | 37 ++- .../rds/dbparametergroup/Translator.java | 42 ++-- .../dbparametergroup/AbstractTestBase.java | 103 ++++++-- .../dbparametergroup/BaseHandlerStdTest.java | 39 +++ .../dbparametergroup/CreateHandlerTest.java | 48 ++-- .../rds/dbparametergroup/ReadHandlerTest.java | 86 ++++++- .../rds/dbparametergroup/TranslatorTest.java | 35 +++ .../dbparametergroup/UpdateHandlerTest.java | 53 +--- 16 files changed, 607 insertions(+), 236 deletions(-) create mode 100644 aws-rds-cfn-test-common/src/main/java/software/amazon/rds/test/common/verification/AccessPermissionAlias.java create mode 100644 aws-rds-cfn-test-common/src/test/java/software/amazon/rds/test/common/verification/AccessPermissionTest.java create mode 100644 aws-rds-dbparametergroup/src/test/java/software/amazon/rds/dbparametergroup/BaseHandlerStdTest.java create mode 100644 aws-rds-dbparametergroup/src/test/java/software/amazon/rds/dbparametergroup/TranslatorTest.java diff --git a/aws-rds-cfn-common/src/main/java/software/amazon/rds/common/logging/LoggingProxyClient.java b/aws-rds-cfn-common/src/main/java/software/amazon/rds/common/logging/LoggingProxyClient.java index cef4f0a5d..5f5c2d1b2 100644 --- a/aws-rds-cfn-common/src/main/java/software/amazon/rds/common/logging/LoggingProxyClient.java +++ b/aws-rds-cfn-common/src/main/java/software/amazon/rds/common/logging/LoggingProxyClient.java @@ -19,41 +19,43 @@ public class LoggingProxyClient implements ProxyClient { final private ProxyClient proxyClient; @Override - public - ResponseT - injectCredentialsAndInvokeV2(RequestT request, Function requestFunction) { - return logAndDelegate(request, requestFunction, proxyClient::injectCredentialsAndInvokeV2); + public ResponseT injectCredentialsAndInvokeV2( + final RequestT request, + final Function requestFunction + ) { + return logRequestResponseAndDelegate(request, requestFunction, proxyClient::injectCredentialsAndInvokeV2); } @Override - public - CompletableFuture - injectCredentialsAndInvokeV2Async(RequestT request, - Function> requestFunction) { - return logAndDelegate(request, requestFunction, proxyClient::injectCredentialsAndInvokeV2Async); + public CompletableFuture injectCredentialsAndInvokeV2Async( + final RequestT request, + final Function> requestFunction + ) { + return logRequestAndDelegate(request, requestFunction, proxyClient::injectCredentialsAndInvokeV2Async); } @Override - public > - IterableT - injectCredentialsAndInvokeIterableV2(RequestT request, Function requestFunction) { - return logAndDelegate(request, requestFunction, proxyClient::injectCredentialsAndInvokeIterableV2); + public > IterableT injectCredentialsAndInvokeIterableV2( + final RequestT request, + final Function requestFunction + ) { + return logRequestAndDelegate(request, requestFunction, proxyClient::injectCredentialsAndInvokeIterableV2); } @Override - public - ResponseInputStream - injectCredentialsAndInvokeV2InputStream(RequestT request, - Function> requestFunction) { - return logAndDelegate(request, requestFunction, proxyClient::injectCredentialsAndInvokeV2InputStream); + public ResponseInputStream injectCredentialsAndInvokeV2InputStream( + final RequestT request, + final Function> requestFunction + ) { + return logRequestAndDelegate(request, requestFunction, proxyClient::injectCredentialsAndInvokeV2InputStream); } @Override - public - ResponseBytes - injectCredentialsAndInvokeV2Bytes(RequestT request, - Function> requestFunction) { - return logAndDelegate(request, requestFunction, proxyClient::injectCredentialsAndInvokeV2Bytes); + public ResponseBytes injectCredentialsAndInvokeV2Bytes( + final RequestT request, + final Function> requestFunction + ) { + return logRequestAndDelegate(request, requestFunction, proxyClient::injectCredentialsAndInvokeV2Bytes); } @Override @@ -61,10 +63,11 @@ public ClientT client() { return proxyClient.client(); } - private ResultT logAndDelegate( + private ResultT logRequestResponseAndDelegate( final RequestT request, final Function requestFunction, - final BiFunction, ResultT> injectCredentials) { + final BiFunction, ResultT> injectCredentials + ) { ResultT result = null; try { requestLogger.log(request); @@ -76,5 +79,19 @@ private ResultT logAndDelegate( return result; } - + private ResultT logRequestAndDelegate( + final RequestT request, + final Function requestFunction, + final BiFunction, ResultT> injectCredentials + ) { + ResultT result = null; + try { + requestLogger.log(request); + result = injectCredentials.apply(request, requestFunction); + } catch (Exception e) { + requestLogger.logAndThrow(e); + } + requestLogger.log("[Result log omitted]"); + return result; + } } diff --git a/aws-rds-cfn-common/src/test/java/software/amazon/rds/common/logging/LoggingProxyClientTest.java b/aws-rds-cfn-common/src/test/java/software/amazon/rds/common/logging/LoggingProxyClientTest.java index 0b607e59c..905f8d7c6 100644 --- a/aws-rds-cfn-common/src/test/java/software/amazon/rds/common/logging/LoggingProxyClientTest.java +++ b/aws-rds-cfn-common/src/test/java/software/amazon/rds/common/logging/LoggingProxyClientTest.java @@ -82,8 +82,8 @@ void test_injectCredentialsAndInvokeV2Bytes() { ResponseBytes response = ResponseBytes.fromByteArray(awsResponse, awsResponse.toString().getBytes()); when(proxy.injectCredentialsAndInvokeV2Bytes(any(), any())).thenReturn(response); proxyRdsClient.injectCredentialsAndInvokeV2Bytes(awsRequest, request -> response); - verify(logger, times(2)).log(captor.capture()); - assertThat(captor.getValue().contains(STACK_ID)).isTrue(); + verify(logger, times(3)).log(captor.capture()); + assertThat(captor.getAllValues().get(0).contains(STACK_ID)).isTrue(); } @Test @@ -91,8 +91,8 @@ void test_injectCredentialsAndInvokeIterableV2() { ProxyClient proxyRdsClient = getProxyRdsClient(logger); when(proxy.injectCredentialsAndInvokeIterableV2(any(), any())).thenReturn(sdkIterable); proxyRdsClient.injectCredentialsAndInvokeIterableV2(awsRequest, request -> sdkIterable); - verify(logger, times(2)).log(captor.capture()); - assertThat(captor.getValue().contains(AWS_ACCOUNT_ID)).isTrue(); + verify(logger, times(3)).log(captor.capture()); + assertThat(captor.getAllValues().get(0).contains(AWS_ACCOUNT_ID)).isTrue(); } @Test @@ -100,7 +100,7 @@ void test_injectCredentialsAndInvokeV2Async() { ProxyClient proxyRdsClient = getProxyRdsClient(logger); when(proxy.injectCredentialsAndInvokeV2Async(any(), any())).thenReturn(awsResponseCompletableFuture); proxyRdsClient.injectCredentialsAndInvokeV2Async(awsRequest, request -> awsResponseCompletableFuture); - verify(logger, times(2)).log(captor.capture()); - assertThat(captor.getValue().contains(STACK_ID)).isTrue(); + verify(logger, times(3)).log(captor.capture()); + assertThat(captor.getAllValues().get(0).contains(STACK_ID)).isTrue(); } } diff --git a/aws-rds-cfn-test-common/src/main/java/software/amazon/rds/test/common/verification/AccessPermissionAlias.java b/aws-rds-cfn-test-common/src/main/java/software/amazon/rds/test/common/verification/AccessPermissionAlias.java new file mode 100644 index 000000000..c292c8e99 --- /dev/null +++ b/aws-rds-cfn-test-common/src/main/java/software/amazon/rds/test/common/verification/AccessPermissionAlias.java @@ -0,0 +1,13 @@ +package software.amazon.rds.test.common.verification; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +public class AccessPermissionAlias { + @Getter + private AccessPermission origin; + + @Getter + private AccessPermission equivalent; +} diff --git a/aws-rds-cfn-test-common/src/main/java/software/amazon/rds/test/common/verification/AccessPermissionVerificationMode.java b/aws-rds-cfn-test-common/src/main/java/software/amazon/rds/test/common/verification/AccessPermissionVerificationMode.java index 4d2100b91..d71ad44bb 100644 --- a/aws-rds-cfn-test-common/src/main/java/software/amazon/rds/test/common/verification/AccessPermissionVerificationMode.java +++ b/aws-rds-cfn-test-common/src/main/java/software/amazon/rds/test/common/verification/AccessPermissionVerificationMode.java @@ -1,7 +1,9 @@ package software.amazon.rds.test.common.verification; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import org.json.JSONArray; @@ -19,9 +21,11 @@ public class AccessPermissionVerificationMode implements VerificationMode { private final Set permissions; + private final Map aliases; public AccessPermissionVerificationMode() { this.permissions = new HashSet<>(); + this.aliases = new HashMap<>(); } public AccessPermissionVerificationMode enablePermission(final AccessPermission permission) { @@ -49,14 +53,24 @@ public AccessPermissionVerificationMode withSchemaPermissions(final JSONObject s return this; } + @ExcludeFromJacocoGeneratedReport + public AccessPermissionVerificationMode withAliases(AccessPermissionAlias... aliases) { + for (final AccessPermissionAlias alias : aliases) { + this.aliases.put(alias.getOrigin(), alias.getEquivalent()); + } + + return this; + } + private MockitoAssertionError missingRequiredPermission(final AccessPermission permission) { return new MockitoAssertionError(String.format("Missing a required access permission: %s", permission)); } private void verifyInvocationPermissions(final Invocation invocation) { - final AccessPermission requiredPermission = AccessPermissionFactory.fromInvocation(invocation); - if (!permissions.contains(requiredPermission)) { - throw missingRequiredPermission(requiredPermission); + AccessPermission requiredPermission = AccessPermissionFactory.fromInvocation(invocation); + AccessPermission permissionAlias = this.aliases.get(requiredPermission); + if (!permissions.contains(requiredPermission) && !permissions.contains(permissionAlias)) { + throw missingRequiredPermission(permissionAlias == null ? requiredPermission : permissionAlias); } } diff --git a/aws-rds-cfn-test-common/src/test/java/software/amazon/rds/test/common/verification/AccessPermissionTest.java b/aws-rds-cfn-test-common/src/test/java/software/amazon/rds/test/common/verification/AccessPermissionTest.java new file mode 100644 index 000000000..76e638d04 --- /dev/null +++ b/aws-rds-cfn-test-common/src/test/java/software/amazon/rds/test/common/verification/AccessPermissionTest.java @@ -0,0 +1,15 @@ +package software.amazon.rds.test.common.verification; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import software.amazon.rds.test.common.core.ServiceProvider; + +class AccessPermissionTest { + + @Test + public void test_toString() { + final AccessPermission permission = new AccessPermission(ServiceProvider.RDS, "TestMethod"); + Assertions.assertThat(permission.toString()).isEqualTo("rds:TestMethod"); + } +} diff --git a/aws-rds-cfn-test-common/src/test/java/software/amazon/rds/test/common/verification/AccessPermissionVerificationModeTest.java b/aws-rds-cfn-test-common/src/test/java/software/amazon/rds/test/common/verification/AccessPermissionVerificationModeTest.java index 54d9ff1fa..6534a6a60 100644 --- a/aws-rds-cfn-test-common/src/test/java/software/amazon/rds/test/common/verification/AccessPermissionVerificationModeTest.java +++ b/aws-rds-cfn-test-common/src/test/java/software/amazon/rds/test/common/verification/AccessPermissionVerificationModeTest.java @@ -15,13 +15,16 @@ class AccessPermissionVerificationModeTest { static class RdsClient { public void testMethod() { } + + public void testMethodAlias() { + } } @Test public void test_unknownPermissionThrowsMockitoAssertionError() { final AccessPermissionVerificationMode verificationMode = new AccessPermissionVerificationMode(); - RdsClient mock = Mockito.mock(RdsClient.class); + RdsClient mock = Mockito.mock(RdsClient.class); mock.testMethod(); Assertions.assertThatThrownBy(() -> { @@ -32,13 +35,30 @@ public void test_unknownPermissionThrowsMockitoAssertionError() { @Test public void test_enabledPermissionVerifiedSuccessfully() { final AccessPermissionVerificationMode verificationMode = new AccessPermissionVerificationMode(); - RdsClient mock = Mockito.mock(RdsClient.class); verificationMode.enablePermission(new AccessPermission(ServiceProvider.RDS, "TestMethod")); + RdsClient mock = Mockito.mock(RdsClient.class); mock.testMethod(); Assertions.assertThatCode(() -> { verificationMode.verify(new VerificationDataImpl(MockUtil.getInvocationContainer(mock), null)); }).doesNotThrowAnyException(); } + + @Test + public void test_enabledPermissionAliasVerifiedSuccessfully() { + final AccessPermission origin = new AccessPermission(ServiceProvider.RDS, "TestMethodAlias"); + final AccessPermission equivalent = new AccessPermission(ServiceProvider.RDS, "TestMethod"); + + final AccessPermissionVerificationMode verificationMode = new AccessPermissionVerificationMode() + .enablePermission(equivalent) + .withAliases(new AccessPermissionAlias(origin, equivalent)); + + RdsClient mock = Mockito.mock(RdsClient.class); + mock.testMethodAlias(); + + Assertions.assertThatCode(() -> { + verificationMode.verify(new VerificationDataImpl(MockUtil.getInvocationContainer(mock), null)); + }).doesNotThrowAnyException(); + } } diff --git a/aws-rds-dbparametergroup/aws-rds-dbparametergroup.json b/aws-rds-dbparametergroup/aws-rds-dbparametergroup.json index d74a87bd1..89001cdf3 100644 --- a/aws-rds-dbparametergroup/aws-rds-dbparametergroup.json +++ b/aws-rds-dbparametergroup/aws-rds-dbparametergroup.json @@ -71,23 +71,23 @@ "/properties/Description", "/properties/Family" ], - "writeOnlyProperties": [ - "/properties/Parameters" - ], "handlers": { "create": { "permissions": [ "rds:AddTagsToResource", "rds:CreateDBParameterGroup", "rds:DescribeDBParameterGroups", + "rds:DescribeDBParameters", "rds:DescribeEngineDefaultParameters", - "rds:ModifyDBParameterGroup", - "rds:ListTagsForResource" + "rds:ListTagsForResource", + "rds:ModifyDBParameterGroup" ] }, "read": { "permissions": [ "rds:DescribeDBParameterGroups", + "rds:DescribeDBParameters", + "rds:DescribeEngineDefaultParameters", "rds:ListTagsForResource" ] }, diff --git a/aws-rds-dbparametergroup/src/main/java/software/amazon/rds/dbparametergroup/BaseHandlerStd.java b/aws-rds-dbparametergroup/src/main/java/software/amazon/rds/dbparametergroup/BaseHandlerStd.java index a0b53fa6b..953ff1d13 100644 --- a/aws-rds-dbparametergroup/src/main/java/software/amazon/rds/dbparametergroup/BaseHandlerStd.java +++ b/aws-rds-dbparametergroup/src/main/java/software/amazon/rds/dbparametergroup/BaseHandlerStd.java @@ -2,23 +2,32 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import lombok.NonNull; import software.amazon.awssdk.services.rds.RdsClient; import software.amazon.awssdk.services.rds.model.DbParameterGroupAlreadyExistsException; import software.amazon.awssdk.services.rds.model.DbParameterGroupNotFoundException; import software.amazon.awssdk.services.rds.model.DbParameterGroupQuotaExceededException; +import software.amazon.awssdk.services.rds.model.DescribeDbParametersRequest; import software.amazon.awssdk.services.rds.model.DescribeDbParametersResponse; +import software.amazon.awssdk.services.rds.model.DescribeEngineDefaultParametersRequest; import software.amazon.awssdk.services.rds.model.DescribeEngineDefaultParametersResponse; +import software.amazon.awssdk.services.rds.model.EngineDefaults; +import software.amazon.awssdk.services.rds.model.Filter; import software.amazon.awssdk.services.rds.model.InvalidDbParameterGroupStateException; import software.amazon.awssdk.services.rds.model.Parameter; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; @@ -71,7 +80,7 @@ public abstract class BaseHandlerStd extends BaseHandler { protected static final int MAX_PARAMETERS_PER_REQUEST = 20; protected static final int MAX_PARAMETER_FILTER_SIZE = 100; - protected static final int MAX_PARAMETER_DESCRIBE_DEPTH = 20; + protected static final int MAX_DESCRIBE_PAGE_DEPTH = 50; protected static final String RESOURCE_IDENTIFIER = "dbparametergroup"; protected static final String STACK_NAME = "rds"; @@ -182,9 +191,9 @@ protected ProgressEvent applyParametersWithReset } return ProgressEvent.progress(model, context) - .then(p -> describeDefaultEngineParameters(proxy, proxyClient, p, new ArrayList<>(paramNames), defaultParams, logger)) + .then(p -> describeEngineDefaultParameters(proxy, proxyClient, p, new ArrayList<>(paramNames), defaultParams, logger)) .then(p -> validateModelParameters(p, defaultParams, logger)) - .then(p -> describeCurrentDBParameters(p, new ArrayList<>(paramNames), currentParams, proxy, proxyClient, logger)) + .then(p -> describeCurrentDBParameters(proxy, proxyClient, p, new ArrayList<>(paramNames), currentParams, logger)) .then(p -> resetParameters(p, defaultParams, currentParams, proxy, proxyClient, logger)) .then(p -> modifyParameters(proxy, proxyClient, p, currentParams, logger)); } @@ -204,14 +213,14 @@ protected ProgressEvent applyParameters( } //Map will be populated in upcoming calls in progress chain. final Map defaultParams = Maps.newHashMap(); - final Set paramNames = new HashSet<>(Optional.ofNullable(desiredParams).orElse(Collections.emptyMap()).keySet()); + final List paramNames = new ArrayList<>(Optional.ofNullable(desiredParams).orElse(Collections.emptyMap()).keySet()); if (paramNames.isEmpty()) { return progress; } return ProgressEvent.progress(model, callbackContext) - .then(p -> describeDefaultEngineParameters(proxy, proxyClient, p, new ArrayList<>(paramNames), defaultParams, logger)) + .then(p -> describeEngineDefaultParameters(proxy, proxyClient, p, paramNames, defaultParams, logger)) .then(p -> validateModelParameters(p, defaultParams, logger)) .then(p -> modifyParameters(proxy, proxyClient, p, defaultParams, logger)); } @@ -383,97 +392,175 @@ private Map getParametersToReset( .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } - private ProgressEvent describeCurrentDBParameters( - final ProgressEvent progress, - final List paramNames, - final Map currentParams, - final AmazonWebServicesClientProxy proxy, + private Iterable fetchDBParametersIterable( final ProxyClient proxyClient, - final RequestLogger logger + final DescribeDbParametersRequest request ) { - for (final List paramNamePartition : Lists.partition(paramNames, MAX_PARAMETER_FILTER_SIZE)) { - String marker = null; - try { - int page = 1; - do { - if (page > MAX_PARAMETER_DESCRIBE_DEPTH) { - return ProgressEvent.failed( - progress.getResourceModel(), - progress.getCallbackContext(), - HandlerErrorCode.InvalidRequest, - "Max DescribeDbParameters response page reached." - ); - } - final DescribeDbParametersResponse response = fetchDbParameters(proxyClient, progress.getResourceModel(), paramNamePartition, marker); - for (final Parameter parameter : response.parameters()) { - currentParams.put(parameter.parameterName(), parameter); - } - marker = response.marker(); - page++; - } while (marker != null); - } catch (Exception e) { - return Commons.handleException(progress, e, DEFAULT_DB_PARAMETER_GROUP_ERROR_RULE_SET); + Iterable result = Collections.emptyList(); + + String marker = null; + int page = 0; + do { + if (page >= MAX_DESCRIBE_PAGE_DEPTH) { + throw new RuntimeException("Max DescribeDBParameters page reached."); + } + final DescribeDbParametersResponse response = proxyClient.injectCredentialsAndInvokeV2( + request.toBuilder().marker(marker).build(), + proxyClient.client()::describeDBParameters + ); + if (response.parameters() != null) { + result = Iterables.concat(result, response.parameters()); + } + marker = response.marker(); + page++; + } while (marker != null); + + return result; + } + + private Iterable fetchDBParametersIterableWithFilters( + final ProxyClient proxyClient, + final String dbParameterGroupName, + final List filterParameterNames + ) { + Iterable iterable = Collections.emptyList(); + + if (filterParameterNames == null) { + final DescribeDbParametersRequest request = DescribeDbParametersRequest.builder() + .dbParameterGroupName(dbParameterGroupName) + .build(); + iterable = fetchDBParametersIterable(proxyClient, request); + } else { + for (final List partition : Lists.partition(filterParameterNames, MAX_PARAMETER_FILTER_SIZE)) { + final Filter[] filters = new Filter[]{Translator.filterByParameterNames(partition)}; + final DescribeDbParametersRequest request = DescribeDbParametersRequest.builder() + .dbParameterGroupName(dbParameterGroupName) + .filters(filters) + .build(); + iterable = Iterables.concat(iterable, fetchDBParametersIterable(proxyClient, request)); } } - return progress; + + return iterable; } - private ProgressEvent describeDefaultEngineParameters( + protected ProgressEvent describeCurrentDBParameters( final AmazonWebServicesClientProxy proxy, final ProxyClient proxyClient, final ProgressEvent progress, - final List paramNames, - final Map defaultParams, + final List filterParameterNames, + final Map accumulator, final RequestLogger logger ) { - for (final List paramNamePartition : Lists.partition(paramNames, MAX_PARAMETER_FILTER_SIZE)) { - String marker = null; - try { - int page = 1; - do { - if (page > MAX_PARAMETER_DESCRIBE_DEPTH) { - return ProgressEvent.failed( - progress.getResourceModel(), - progress.getCallbackContext(), - HandlerErrorCode.InvalidRequest, - "Max DescribeEngineDefaultParameters response page reached." - ); - } - final DescribeEngineDefaultParametersResponse response = fetchEngineDefaultParameters(proxyClient, progress.getResourceModel(), paramNamePartition, marker); - for (final Parameter parameter : response.engineDefaults().parameters()) { - defaultParams.put(parameter.parameterName(), parameter); - } - marker = response.engineDefaults().marker(); - page++; - } while (marker != null); - } catch (Exception e) { - return Commons.handleException(progress, e, DEFAULT_DB_PARAMETER_GROUP_ERROR_RULE_SET); + try { + final Iterable parameters = fetchDBParametersIterableWithFilters( + proxyClient, + progress.getResourceModel().getDBParameterGroupName(), + filterParameterNames + ); + for (final Parameter parameter : parameters) { + accumulator.put(parameter.parameterName(), parameter); } + } catch (Exception e) { + return Commons.handleException(progress, e, DEFAULT_DB_PARAMETER_GROUP_ERROR_RULE_SET); } return progress; } - private DescribeDbParametersResponse fetchDbParameters( + private Iterable fetchEngineDefaultParametersIterable( final ProxyClient proxyClient, - final ResourceModel model, - final List parameterNames, - final String marker + final DescribeEngineDefaultParametersRequest request ) { - return proxyClient.injectCredentialsAndInvokeV2( - Translator.describeDbParametersRequest(model.getDBParameterGroupName(), parameterNames, marker), - proxyClient.client()::describeDBParameters - ); + Iterable result = Collections.emptyList(); + + String marker = null; + int page = 0; + do { + if (page >= MAX_DESCRIBE_PAGE_DEPTH) { + throw new RuntimeException("Max DescribeEngineDefaultParameters page reached."); + } + final DescribeEngineDefaultParametersResponse response = proxyClient.injectCredentialsAndInvokeV2( + request.toBuilder().marker(marker).build(), + proxyClient.client()::describeEngineDefaultParameters + ); + final EngineDefaults engineDefaults = response.engineDefaults(); + if (engineDefaults == null) { + break; + } + if (engineDefaults.parameters() != null) { + result = Iterables.concat(result, engineDefaults.parameters()); + } + marker = response.engineDefaults().marker(); + page++; + } while (marker != null); + + return result; } - private DescribeEngineDefaultParametersResponse fetchEngineDefaultParameters( + private Iterable fetchEngineDefaultParametersIterableWithFilters( final ProxyClient proxyClient, - final ResourceModel model, - final List parameterNames, - final String marker + final String dbParameterGroupFamily, + final List filterParameterNames ) { - return proxyClient.injectCredentialsAndInvokeV2( - Translator.describeEngineDefaultParametersRequest(model.getFamily(), parameterNames, marker), - proxyClient.client()::describeEngineDefaultParameters - ); + Iterable iterable = Collections.emptyList(); + + if (filterParameterNames == null) { + final DescribeEngineDefaultParametersRequest request = DescribeEngineDefaultParametersRequest.builder() + .dbParameterGroupFamily(dbParameterGroupFamily) + .build(); + iterable = fetchEngineDefaultParametersIterable(proxyClient, request); + } else { + for (final List partition : Lists.partition(filterParameterNames, MAX_PARAMETER_FILTER_SIZE)) { + final Filter[] filters = new Filter[]{Translator.filterByParameterNames(partition)}; + final DescribeEngineDefaultParametersRequest request = DescribeEngineDefaultParametersRequest.builder() + .dbParameterGroupFamily(dbParameterGroupFamily) + .filters(filters) + .build(); + iterable = Iterables.concat(iterable, fetchEngineDefaultParametersIterable(proxyClient, request)); + } + } + + return iterable; + } + + protected ProgressEvent describeEngineDefaultParameters( + final AmazonWebServicesClientProxy proxy, + final ProxyClient proxyClient, + final ProgressEvent progress, + final List filterParameterNames, + final Map accumulator, + final RequestLogger logger + ) { + try { + final Iterable parameters = fetchEngineDefaultParametersIterableWithFilters( + proxyClient, + progress.getResourceModel().getFamily(), + filterParameterNames + ); + for (final Parameter parameter : parameters) { + accumulator.put(parameter.parameterName(), parameter); + } + } catch (Exception e) { + return Commons.handleException(progress, e, DEFAULT_DB_PARAMETER_GROUP_ERROR_RULE_SET); + } + + return progress; + } + + @VisibleForTesting + static Map computeModifiedDBParameters( + @NonNull final Map engineDefaultParameters, + @NonNull final Map currentDBParameters + ) { + final Map modifiedParameters = new HashMap<>(); + for (final String paramName : currentDBParameters.keySet()) { + final Parameter currentParam = currentDBParameters.get(paramName); + final Parameter defaultParam = engineDefaultParameters.get(paramName); + if (defaultParam == null || !Objects.equals(defaultParam.parameterValue(), currentParam.parameterValue())) { + modifiedParameters.put(paramName, currentParam); + } + } + + return modifiedParameters; } } diff --git a/aws-rds-dbparametergroup/src/main/java/software/amazon/rds/dbparametergroup/ReadHandler.java b/aws-rds-dbparametergroup/src/main/java/software/amazon/rds/dbparametergroup/ReadHandler.java index f79c6893e..32ec0cfec 100644 --- a/aws-rds-dbparametergroup/src/main/java/software/amazon/rds/dbparametergroup/ReadHandler.java +++ b/aws-rds-dbparametergroup/src/main/java/software/amazon/rds/dbparametergroup/ReadHandler.java @@ -1,9 +1,12 @@ package software.amazon.rds.dbparametergroup; +import java.util.HashMap; import java.util.List; +import java.util.Map; import software.amazon.awssdk.services.rds.RdsClient; import software.amazon.awssdk.services.rds.model.DBParameterGroup; +import software.amazon.awssdk.services.rds.model.Parameter; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; import software.amazon.cloudformation.proxy.ProgressEvent; import software.amazon.cloudformation.proxy.ProxyClient; @@ -44,7 +47,11 @@ protected ProgressEvent handleRequest( try { final DBParameterGroup dBParameterGroup = describeResponse.dbParameterGroups().stream().findFirst().get(); context.setDbParameterGroupArn(dBParameterGroup.dbParameterGroupArn()); - return ProgressEvent.progress(Translator.translateFromDBParameterGroup(dBParameterGroup), context); + final ResourceModel currentModel = Translator.translateFromDBParameterGroup(dBParameterGroup); + if (model.getParameters() != null) { + currentModel.setParameters(model.getParameters()); + } + return ProgressEvent.progress(currentModel, context); } catch (Exception exception) { return Commons.handleException( ProgressEvent.progress(model, context), @@ -53,10 +60,36 @@ protected ProgressEvent handleRequest( ); } }) + .then(progress -> { + if (progress.getResourceModel().getParameters() == null) { + return readParameters(proxy, proxyClient, progress, logger); + } + return progress; + }) .then(progress -> readTags(proxyClient, progress)); } - protected ProgressEvent readTags( + private ProgressEvent readParameters( + final AmazonWebServicesClientProxy proxy, + final ProxyClient proxyClient, + final ProgressEvent progress, + final RequestLogger logger + ) { + final Map engineDefaultParameters = new HashMap<>(); + final Map currentDBParameters = new HashMap<>(); + + return progress + .then(p -> describeEngineDefaultParameters(proxy, proxyClient, p, null, engineDefaultParameters, logger)) + .then(p -> describeCurrentDBParameters(proxy, proxyClient, p, null, currentDBParameters, logger)) + .then(p -> { + p.getResourceModel().setParameters( + Translator.translateParametersFromSdk(computeModifiedDBParameters(engineDefaultParameters, currentDBParameters)) + ); + return p; + }); + } + + private ProgressEvent readTags( final ProxyClient proxyClient, final ProgressEvent progress ) { diff --git a/aws-rds-dbparametergroup/src/main/java/software/amazon/rds/dbparametergroup/Translator.java b/aws-rds-dbparametergroup/src/main/java/software/amazon/rds/dbparametergroup/Translator.java index 1736ac8f3..03a43c71d 100644 --- a/aws-rds-dbparametergroup/src/main/java/software/amazon/rds/dbparametergroup/Translator.java +++ b/aws-rds-dbparametergroup/src/main/java/software/amazon/rds/dbparametergroup/Translator.java @@ -2,12 +2,15 @@ import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import com.amazonaws.arn.Arn; import com.amazonaws.util.CollectionUtils; +import lombok.NonNull; import software.amazon.awssdk.services.rds.model.ApplyMethod; import software.amazon.awssdk.services.rds.model.CreateDbParameterGroupRequest; import software.amazon.awssdk.services.rds.model.DBParameterGroup; @@ -52,34 +55,34 @@ static DescribeDbParameterGroupsRequest describeDbParameterGroupsRequest(final S .build(); } - public static DescribeDbParametersRequest describeDbParametersRequest( + static Filter filterByParameterNames(final Collection paramNames) { + return Filter.builder() + .name(FILTER_PARAMETER_NAME) + .values(paramNames) + .build(); + } + + public static DescribeDbParametersRequest describeDbParametersRequestWithFilters( final String dbParameterGroupName, - final Collection paramNames, + final Filter[] filters, final String marker ) { return DescribeDbParametersRequest.builder() - .marker(marker) .dbParameterGroupName(dbParameterGroupName) - .filters(Filter.builder() - .name(FILTER_PARAMETER_NAME) - .values(paramNames) - .build()) + .filters(filters) + .marker(marker) .build(); } - public static DescribeEngineDefaultParametersRequest describeEngineDefaultParametersRequest( + public static DescribeEngineDefaultParametersRequest describeEngineDefaultParametersRequestWithFilters( final String dbParameterGroupFamily, - final Collection paramNames, + final Filter[] filters, final String marker ) { return DescribeEngineDefaultParametersRequest.builder() - .marker(marker) .dbParameterGroupFamily(dbParameterGroupFamily) - .filters(Filter.builder() - .name(FILTER_PARAMETER_NAME) - .values(paramNames) - .build() - ) + .filters(filters) + .marker(marker) .build(); } @@ -163,6 +166,15 @@ static Arn buildParameterGroupArn(final ResourceHandlerRequest re .build(); } + static Map translateParametersFromSdk(@NonNull final Map parameters) { + final Map result = new HashMap<>(); + for (final Map.Entry kv : parameters.entrySet()) { + result.put(kv.getKey(), kv.getValue().parameterValue()); + } + + return result; + } + private static ApplyMethod getParameterApplyMethod(final Parameter parameter) { if (ParameterType.Dynamic.equalsIgnoreCase(parameter.applyType())) { return ApplyMethod.IMMEDIATE; diff --git a/aws-rds-dbparametergroup/src/test/java/software/amazon/rds/dbparametergroup/AbstractTestBase.java b/aws-rds-dbparametergroup/src/test/java/software/amazon/rds/dbparametergroup/AbstractTestBase.java index a06bbf489..af020b89f 100644 --- a/aws-rds-dbparametergroup/src/test/java/software/amazon/rds/dbparametergroup/AbstractTestBase.java +++ b/aws-rds-dbparametergroup/src/test/java/software/amazon/rds/dbparametergroup/AbstractTestBase.java @@ -5,6 +5,7 @@ import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -14,6 +15,7 @@ import org.json.JSONObject; import org.mockito.internal.util.collections.Sets; +import org.mockito.stubbing.OngoingStubbing; import software.amazon.awssdk.awscore.AwsRequest; import software.amazon.awssdk.awscore.AwsResponse; @@ -36,6 +38,7 @@ import software.amazon.rds.common.logging.RequestLogger; import software.amazon.rds.test.common.core.HandlerName; import software.amazon.rds.test.common.core.TestUtils; +import software.amazon.rds.test.common.verification.AccessPermissionAlias; import software.amazon.rds.test.common.verification.AccessPermissionVerificationMode; public abstract class AbstractTestBase { @@ -103,17 +106,17 @@ public abstract class AbstractTestBase { private static final JSONObject resourceSchema = new Configuration().resourceSchemaJsonObject(); - public void verifyAccessPermissions(final Object mock) { + public void verifyAccessPermissions(final Object mock, final AccessPermissionAlias... aliases) { new AccessPermissionVerificationMode() .withDefaultPermissions() .withSchemaPermissions(resourceSchema, getHandlerName()) + .withAliases(aliases) .verify(TestUtils.getVerificationData(mock)); } static Map translateTagsToMap(final Set tags) { return tags.stream() .collect(Collectors.toMap(Tag::getKey, Tag::getValue)); - } static String getClientRequestToken() { @@ -163,12 +166,33 @@ public RdsClient client() { }; } - void mockDescribeDbParametersResponse(ProxyClient proxyClient, - String firstParamApplyType, - String secondParamApplyType, - boolean isModifiable, - boolean mockDescribeParameters, - boolean isPaginated) { + protected void expectEmptyDescribeParametersResponse(final ProxyClient proxyClient) { + when(proxyClient.client().describeDBParameters(any(DescribeDbParametersRequest.class))) + .thenReturn(DescribeDbParametersResponse.builder().build()); + when(proxyClient.client().describeEngineDefaultParameters(any(DescribeEngineDefaultParametersRequest.class))) + .thenReturn(DescribeEngineDefaultParametersResponse.builder().build()); + } + + void mockDescribeDbParametersResponse( + final ProxyClient proxyClient, + final String firstParamApplyType, + final String secondParamApplyType, + final boolean isModifiable, + final boolean mockDescribeParameters, + final boolean isPaginated + ) { + mockDescribeDbParametersResponse(proxyClient, firstParamApplyType, secondParamApplyType, isModifiable, mockDescribeParameters, isPaginated, 1); + } + + void mockDescribeDbParametersResponse( + final ProxyClient proxyClient, + final String firstParamApplyType, + final String secondParamApplyType, + final boolean isModifiable, + final boolean mockDescribeParameters, + final boolean isPaginated, + final int nTimes + ) { Parameter param1 = Parameter.builder() .parameterName("param1") .parameterValue("system_value") @@ -201,7 +225,6 @@ void mockDescribeDbParametersResponse(ProxyClient proxyClient, .applyType(secondParamApplyType) .build(); - final DescribeEngineDefaultParametersResponse describeEngineDefaultParametersResponse = DescribeEngineDefaultParametersResponse.builder() .engineDefaults(EngineDefaults.builder() .parameters(defaultParam1, param2, param4) @@ -209,35 +232,65 @@ void mockDescribeDbParametersResponse(ProxyClient proxyClient, ).build(); if (!isPaginated) { - when(proxyClient.client().describeEngineDefaultParameters(any(DescribeEngineDefaultParametersRequest.class))).thenReturn(describeEngineDefaultParametersResponse); + repeatedly( + when(proxyClient.client().describeEngineDefaultParameters(any(DescribeEngineDefaultParametersRequest.class))), + (st) -> st.thenReturn(describeEngineDefaultParametersResponse), + nTimes + ); } else { - final DescribeEngineDefaultParametersResponse firstPage = DescribeEngineDefaultParametersResponse.builder() .engineDefaults(EngineDefaults.builder() .parameters(defaultParam1, param2, param4) .marker("marker") .build() ).build(); - - when(proxyClient.client().describeEngineDefaultParameters(any(DescribeEngineDefaultParametersRequest.class))) - .thenReturn(firstPage) - .thenReturn(describeEngineDefaultParametersResponse); - + repeatedly( + when(proxyClient.client().describeEngineDefaultParameters(any(DescribeEngineDefaultParametersRequest.class))), + (st) -> st.thenReturn(firstPage).thenReturn(describeEngineDefaultParametersResponse), + nTimes + ); } - if (!mockDescribeParameters) + if (!mockDescribeParameters) { return; + } + + final DescribeDbParametersResponse describeDbParametersResponse = DescribeDbParametersResponse.builder() + .marker(null) + .parameters(param1, param2, param3) + .build(); - final DescribeDbParametersResponse describeDbParametersResponse = DescribeDbParametersResponse.builder().marker(null) - .parameters(param1, param2, param3).build(); if (!isPaginated) { - when(proxyClient.client().describeDBParameters(any(DescribeDbParametersRequest.class))).thenReturn(describeDbParametersResponse); + repeatedly( + when(proxyClient.client().describeDBParameters(any(DescribeDbParametersRequest.class))), + (st) -> st.thenReturn(describeDbParametersResponse), + nTimes + ); } else { - final DescribeDbParametersResponse firstPage = DescribeDbParametersResponse.builder().marker("marker") - .parameters(param1, param2, param3).build(); + final DescribeDbParametersResponse firstPage = DescribeDbParametersResponse.builder() + .marker("marker") + .parameters(param1, param2, param3) + .build(); + repeatedly( + when(proxyClient.client().describeDBParameters(any(DescribeDbParametersRequest.class))), + (st) -> st.thenReturn(firstPage).thenReturn(describeDbParametersResponse), + nTimes + ); + } + } - when(proxyClient.client().describeDBParameters(any(DescribeDbParametersRequest.class))) - .thenReturn(firstPage) - .thenReturn(describeDbParametersResponse); + private OngoingStubbing repeatedly( + final OngoingStubbing base, + final Function, OngoingStubbing> then, + final int nTimes + ) { + OngoingStubbing cur = base; + for (int it = 0; it < nTimes; it++) { + cur = then.apply(cur); } + return cur; + } + + protected final Iterator responseIterator(final T response) { + return Collections.singletonList(response).iterator(); } } diff --git a/aws-rds-dbparametergroup/src/test/java/software/amazon/rds/dbparametergroup/BaseHandlerStdTest.java b/aws-rds-dbparametergroup/src/test/java/software/amazon/rds/dbparametergroup/BaseHandlerStdTest.java new file mode 100644 index 000000000..9b1d7fd49 --- /dev/null +++ b/aws-rds-dbparametergroup/src/test/java/software/amazon/rds/dbparametergroup/BaseHandlerStdTest.java @@ -0,0 +1,39 @@ +package software.amazon.rds.dbparametergroup; + +import java.util.Map; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.google.common.collect.ImmutableMap; +import software.amazon.awssdk.services.rds.model.Parameter; + +class BaseHandlerStdTest { + + @Test + public void test_computeModifiedDBParameters() { + final Map engineDefaultParameters = ImmutableMap.of( + "param1", Parameter.builder().parameterName("param1").parameterValue("value1").build(), + "param2", Parameter.builder().parameterName("param2").parameterValue("value2").build(), + "param3", Parameter.builder().parameterName("param3").parameterValue("value3").build(), + "param4", Parameter.builder().parameterName("param4").build() + ); + final Map currentDBParameters = ImmutableMap.of( + "param1", Parameter.builder().parameterName("param1").parameterValue("value1").build(), + "param2", Parameter.builder().parameterName("param2").parameterValue("value1-2").build(), + "param4", Parameter.builder().parameterName("param4").build(), + "param5", Parameter.builder().parameterName("param5").parameterValue("value5").build() + ); + + final Map modifiedParameters = BaseHandlerStd.computeModifiedDBParameters( + engineDefaultParameters, + currentDBParameters + ); + + Assertions.assertThat(modifiedParameters) + .isEqualTo(ImmutableMap.of( + "param2", Parameter.builder().parameterName("param2").parameterValue("value1-2").build(), + "param5", Parameter.builder().parameterName("param5").parameterValue("value5").build() + )); + } +} diff --git a/aws-rds-dbparametergroup/src/test/java/software/amazon/rds/dbparametergroup/CreateHandlerTest.java b/aws-rds-dbparametergroup/src/test/java/software/amazon/rds/dbparametergroup/CreateHandlerTest.java index c37d16469..e32349284 100644 --- a/aws-rds-dbparametergroup/src/test/java/software/amazon/rds/dbparametergroup/CreateHandlerTest.java +++ b/aws-rds-dbparametergroup/src/test/java/software/amazon/rds/dbparametergroup/CreateHandlerTest.java @@ -4,7 +4,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -44,6 +43,8 @@ import software.amazon.cloudformation.proxy.ProxyClient; import software.amazon.cloudformation.proxy.ResourceHandlerRequest; import software.amazon.rds.test.common.core.HandlerName; +import software.amazon.rds.test.common.verification.AccessPermissionAlias; +import software.amazon.rds.test.common.verification.AccessPermissionFactory; @ExtendWith(MockitoExtension.class) public class CreateHandlerTest extends AbstractTestBase { @@ -76,7 +77,17 @@ public void setup() { public void tear_down() { verify(rdsClient, atLeastOnce()).serviceName(); verifyNoMoreInteractions(rdsClient); - verifyAccessPermissions(rdsClient); + verifyAccessPermissions( + rdsClient, + new AccessPermissionAlias( + AccessPermissionFactory.fromString("rds:DescribeEngineDefaultParametersPaginator"), + AccessPermissionFactory.fromString("rds:DescribeEngineDefaultParameters") + ), + new AccessPermissionAlias( + AccessPermissionFactory.fromString("rds:DescribeDBParametersPaginator"), + AccessPermissionFactory.fromString("rds:DescribeDBParameters") + ) + ); } @Test @@ -115,7 +126,6 @@ public void handleRequest_SimpleSuccessWithoutApplyParameters() { verify(proxyClient.client()).createDBParameterGroup(any(CreateDbParameterGroupRequest.class)); verify(proxyClient.client()).describeDBParameterGroups(any(DescribeDbParameterGroupsRequest.class)); verify(proxyClient.client()).listTagsForResource(any(ListTagsForResourceRequest.class)); - } @Test @@ -125,7 +135,6 @@ public void handleRequest_SimpleSuccessWithApplyParameters() { mockDescribeDBParameterGroup(); - final ModifyDbParameterGroupResponse modifyDbParameterGroupResponse = ModifyDbParameterGroupResponse.builder().build(); when(proxyClient.client().modifyDBParameterGroup(any(ModifyDbParameterGroupRequest.class))).thenReturn(modifyDbParameterGroupResponse); @@ -146,33 +155,6 @@ public void handleRequest_SimpleSuccessWithApplyParameters() { verify(rdsClient).describeEngineDefaultParameters(any(DescribeEngineDefaultParametersRequest.class)); } - @Test - public void handleRequest_SimpleSuccessWithApplyParametersPagination() { - mockCreateCall(); - mockDescribeDbParametersResponse(proxyClient, "static", "dynamic", true, false, true); - - mockDescribeDBParameterGroup(); - - when(proxyClient.client().modifyDBParameterGroup(any(ModifyDbParameterGroupRequest.class))) - .thenReturn(ModifyDbParameterGroupResponse.builder().build()); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken(getClientRequestToken()) - .desiredResourceState(RESET_RESOURCE_MODEL) - .logicalResourceIdentifier(LOGICAL_RESOURCE_IDENTIFIER).build(); - final ProgressEvent response = handler.handleRequest(proxy, proxyClient, request, new CallbackContext(), EMPTY_REQUEST_LOGGER); - - assertThat(response).isNotNull(); - assertThat(response.getCallbackContext()).isNotNull(); - assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); - assertThat(response.getResourceModels()).isNull(); - assertThat(response.getMessage()).isNull(); - assertThat(response.getErrorCode()).isNull(); - - verify(rdsClient).createDBParameterGroup(any(CreateDbParameterGroupRequest.class)); - verify(rdsClient, times(2)).describeEngineDefaultParameters(any(DescribeEngineDefaultParametersRequest.class)); - } - @Test public void handleRequest_SimpleFailWithAccessDenied() { final String message = "AccessDenied on create request"; @@ -311,8 +293,8 @@ private void mockDescribeDBParameterGroup() { when(proxyClient.client().describeDBParameterGroups(any(DescribeDbParameterGroupsRequest.class))) .thenReturn(DescribeDbParameterGroupsResponse.builder().dbParameterGroups(DB_PARAMETER_GROUP_ACTIVE).build()); - final ListTagsForResourceResponse listTagsForResourceResponse = ListTagsForResourceResponse.builder().build(); - when(proxyClient.client().listTagsForResource(any(ListTagsForResourceRequest.class))).thenReturn(listTagsForResourceResponse); + when(proxyClient.client().listTagsForResource(any(ListTagsForResourceRequest.class))) + .thenReturn(ListTagsForResourceResponse.builder().build()); } private void mockCreateCall() { diff --git a/aws-rds-dbparametergroup/src/test/java/software/amazon/rds/dbparametergroup/ReadHandlerTest.java b/aws-rds-dbparametergroup/src/test/java/software/amazon/rds/dbparametergroup/ReadHandlerTest.java index 9140aa0d7..79b98cef7 100644 --- a/aws-rds-dbparametergroup/src/test/java/software/amazon/rds/dbparametergroup/ReadHandlerTest.java +++ b/aws-rds-dbparametergroup/src/test/java/software/amazon/rds/dbparametergroup/ReadHandlerTest.java @@ -18,14 +18,23 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import com.google.common.collect.ImmutableMap; import software.amazon.awssdk.awscore.exception.AwsErrorDetails; import software.amazon.awssdk.services.rds.RdsClient; import software.amazon.awssdk.services.rds.model.DbParameterGroupNotFoundException; import software.amazon.awssdk.services.rds.model.DescribeDbParameterGroupsRequest; import software.amazon.awssdk.services.rds.model.DescribeDbParameterGroupsResponse; +import software.amazon.awssdk.services.rds.model.DescribeDbParametersRequest; +import software.amazon.awssdk.services.rds.model.DescribeDbParametersResponse; +import software.amazon.awssdk.services.rds.model.DescribeEngineDefaultParametersRequest; +import software.amazon.awssdk.services.rds.model.DescribeEngineDefaultParametersResponse; +import software.amazon.awssdk.services.rds.model.EngineDefaults; import software.amazon.awssdk.services.rds.model.ListTagsForResourceRequest; import software.amazon.awssdk.services.rds.model.ListTagsForResourceResponse; +import software.amazon.awssdk.services.rds.model.Parameter; import software.amazon.awssdk.services.rds.model.Tag; +import software.amazon.awssdk.services.rds.paginators.DescribeDBParametersIterable; +import software.amazon.awssdk.services.rds.paginators.DescribeEngineDefaultParametersIterable; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; import software.amazon.cloudformation.proxy.HandlerErrorCode; import software.amazon.cloudformation.proxy.OperationStatus; @@ -33,6 +42,8 @@ import software.amazon.cloudformation.proxy.ProxyClient; import software.amazon.cloudformation.proxy.ResourceHandlerRequest; import software.amazon.rds.test.common.core.HandlerName; +import software.amazon.rds.test.common.verification.AccessPermissionAlias; +import software.amazon.rds.test.common.verification.AccessPermissionFactory; @ExtendWith(MockitoExtension.class) public class ReadHandlerTest extends AbstractTestBase { @@ -65,7 +76,17 @@ public void setup() { public void tear_down() { verify(rdsClient, atLeastOnce()).serviceName(); verifyNoMoreInteractions(rdsClient); - verifyAccessPermissions(rdsClient); + verifyAccessPermissions( + rdsClient, + new AccessPermissionAlias( + AccessPermissionFactory.fromString("rds:DescribeEngineDefaultParametersPaginator"), + AccessPermissionFactory.fromString("rds:DescribeEngineDefaultParameters") + ), + new AccessPermissionAlias( + AccessPermissionFactory.fromString("rds:DescribeDBParametersPaginator"), + AccessPermissionFactory.fromString("rds:DescribeDBParameters") + ) + ); } @Test @@ -75,8 +96,10 @@ public void handleRequest_SimpleSuccess() { when(proxyClient.client().listTagsForResource(any(ListTagsForResourceRequest.class))) .thenReturn(ListTagsForResourceResponse.builder().tagList(Tag.builder().key("Key").value("Value").build()).build()); + expectEmptyDescribeParametersResponse(proxyClient); + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(RESOURCE_MODEL_WITH_TAGS) + .desiredResourceState(ResourceModel.builder().dBParameterGroupName("testDBParameterGroupName").build()) .build(); final ProgressEvent response = handler.handleRequest(proxy, proxyClient, request, new CallbackContext(), EMPTY_REQUEST_LOGGER); @@ -90,6 +113,8 @@ public void handleRequest_SimpleSuccess() { verify(proxyClient.client()).describeDBParameterGroups(any(DescribeDbParameterGroupsRequest.class)); verify(proxyClient.client()).listTagsForResource(any(ListTagsForResourceRequest.class)); + verify(proxyClient.client()).describeEngineDefaultParameters(any(DescribeEngineDefaultParametersRequest.class)); + verify(proxyClient.client()).describeDBParameters(any(DescribeDbParametersRequest.class)); } @Test @@ -100,7 +125,7 @@ public void handleRequest_SimpleNotFound() { .build()); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(RESOURCE_MODEL) + .desiredResourceState(ResourceModel.builder().dBParameterGroupName("testDBParameterGroupName").build()) .build(); final ProgressEvent response = handler.handleRequest(proxy, proxyClient, request, new CallbackContext(), EMPTY_REQUEST_LOGGER); @@ -120,7 +145,7 @@ public void handleRequest_SimpleException() { .thenThrow(InvalidParameterException.class); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(RESOURCE_MODEL) + .desiredResourceState(ResourceModel.builder().dBParameterGroupName("testDBParameterGroupName").build()) .build(); final ProgressEvent response = handler.handleRequest(proxy, proxyClient, request, new CallbackContext(), EMPTY_REQUEST_LOGGER); @@ -134,4 +159,57 @@ public void handleRequest_SimpleException() { verify(proxyClient.client()).describeDBParameterGroups(any(DescribeDbParameterGroupsRequest.class)); } + + @Test + public void handleRequest_ReadModifiedParameters() { + when(proxyClient.client().describeDBParameterGroups(any(DescribeDbParameterGroupsRequest.class))) + .thenReturn(DescribeDbParameterGroupsResponse.builder().dbParameterGroups(DB_PARAMETER_GROUP_ACTIVE).build()); + when(proxyClient.client().listTagsForResource(any(ListTagsForResourceRequest.class))) + .thenReturn(ListTagsForResourceResponse.builder().tagList(Tag.builder().key("Key").value("Value").build()).build()); + + when(proxyClient.client().describeDBParameters(any(DescribeDbParametersRequest.class))) + .thenReturn(DescribeDbParametersResponse.builder() + .parameters( + Parameter.builder().parameterName("param1").parameterValue("value1").build(), + Parameter.builder().parameterName("param2").parameterValue("value2-modified").build(), + Parameter.builder().parameterName("param3").build() + ) + .marker(null) + .build()); + + when(proxyClient.client().describeEngineDefaultParameters(any(DescribeEngineDefaultParametersRequest.class))) + .thenReturn(DescribeEngineDefaultParametersResponse.builder() + .engineDefaults(EngineDefaults.builder() + .parameters( + Parameter.builder().parameterName("param1").parameterValue("value1").build(), + Parameter.builder().parameterName("param2").parameterValue("value2").build(), + Parameter.builder().parameterName("param3").build() + ) + .marker(null) + .build()) + .build()); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .desiredResourceState(ResourceModel.builder().dBParameterGroupName("testDBParameterGroupName").build()) + .build(); + + final ProgressEvent response = handler.handleRequest(proxy, proxyClient, request, new CallbackContext(), EMPTY_REQUEST_LOGGER); + + assertThat(response).isNotNull(); + assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); + assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); + assertThat(response.getResourceModels()).isNull(); + assertThat(response.getMessage()).isNull(); + assertThat(response.getErrorCode()).isNull(); + + assertThat(response.getResourceModel().getParameters()) + .isEqualTo(ImmutableMap.of( + "param2", "value2-modified" + )); + + verify(proxyClient.client()).describeDBParameterGroups(any(DescribeDbParameterGroupsRequest.class)); + verify(proxyClient.client()).listTagsForResource(any(ListTagsForResourceRequest.class)); + verify(proxyClient.client()).describeEngineDefaultParameters(any(DescribeEngineDefaultParametersRequest.class)); + verify(proxyClient.client()).describeDBParameters(any(DescribeDbParametersRequest.class)); + } } diff --git a/aws-rds-dbparametergroup/src/test/java/software/amazon/rds/dbparametergroup/TranslatorTest.java b/aws-rds-dbparametergroup/src/test/java/software/amazon/rds/dbparametergroup/TranslatorTest.java new file mode 100644 index 000000000..c463e6c00 --- /dev/null +++ b/aws-rds-dbparametergroup/src/test/java/software/amazon/rds/dbparametergroup/TranslatorTest.java @@ -0,0 +1,35 @@ +package software.amazon.rds.dbparametergroup; + +import java.util.Map; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.google.common.collect.ImmutableMap; +import software.amazon.awssdk.services.rds.model.Parameter; + +class TranslatorTest { + + @Test + public void test_translateParametersFromSdk() { + final Parameter p1 = Parameter.builder() + .parameterName("param name 1") + .parameterValue("param value 1") + .build(); + final Parameter p2 = Parameter.builder() + .parameterName("param name 2") + .parameterValue("param value 2") + .build(); + + final Map parameters = ImmutableMap.of( + p1.parameterName(), p1, + p2.parameterName(), p2 + ); + + Assertions.assertThat(Translator.translateParametersFromSdk(parameters)) + .isEqualTo(ImmutableMap.of( + p1.parameterName(), p1.parameterValue(), + p2.parameterName(), p2.parameterValue() + )); + } +} diff --git a/aws-rds-dbparametergroup/src/test/java/software/amazon/rds/dbparametergroup/UpdateHandlerTest.java b/aws-rds-dbparametergroup/src/test/java/software/amazon/rds/dbparametergroup/UpdateHandlerTest.java index ceaa22daa..3347fffc9 100644 --- a/aws-rds-dbparametergroup/src/test/java/software/amazon/rds/dbparametergroup/UpdateHandlerTest.java +++ b/aws-rds-dbparametergroup/src/test/java/software/amazon/rds/dbparametergroup/UpdateHandlerTest.java @@ -26,7 +26,6 @@ import software.amazon.awssdk.services.rds.model.DBParameterGroup; import software.amazon.awssdk.services.rds.model.DescribeDbParameterGroupsRequest; import software.amazon.awssdk.services.rds.model.DescribeDbParameterGroupsResponse; -import software.amazon.awssdk.services.rds.model.DescribeDbParametersRequest; import software.amazon.awssdk.services.rds.model.DescribeEngineDefaultParametersRequest; import software.amazon.awssdk.services.rds.model.ListTagsForResourceRequest; import software.amazon.awssdk.services.rds.model.ListTagsForResourceResponse; @@ -39,6 +38,8 @@ import software.amazon.cloudformation.proxy.ProxyClient; import software.amazon.cloudformation.proxy.ResourceHandlerRequest; import software.amazon.rds.test.common.core.HandlerName; +import software.amazon.rds.test.common.verification.AccessPermissionAlias; +import software.amazon.rds.test.common.verification.AccessPermissionFactory; @ExtendWith(MockitoExtension.class) public class UpdateHandlerTest extends AbstractTestBase { @@ -100,7 +101,17 @@ public void setup() { public void tear_down() { verify(rdsClient, atLeastOnce()).serviceName(); verifyNoMoreInteractions(rdsClient); - verifyAccessPermissions(rdsClient); + verifyAccessPermissions( + rdsClient, + new AccessPermissionAlias( + AccessPermissionFactory.fromString("rds:DescribeEngineDefaultParametersPaginator"), + AccessPermissionFactory.fromString("rds:DescribeEngineDefaultParameters") + ), + new AccessPermissionAlias( + AccessPermissionFactory.fromString("rds:DescribeDBParametersPaginator"), + AccessPermissionFactory.fromString("rds:DescribeDBParameters") + ) + ); } @Test @@ -171,44 +182,6 @@ public void handleRequest_SimpleSuccessWithApplyParameters() { verify(rdsClient).describeEngineDefaultParameters(any(DescribeEngineDefaultParametersRequest.class)); } - @Test - public void handleRequest_SimpleSuccessWithApplyParametersPaginated() { - final UpdateHandler handler = new UpdateHandler(); - - CallbackContext callbackContext = new CallbackContext(); - callbackContext.setParametersApplied(true); - - when(rdsClient.describeDBParameterGroups(any(DescribeDbParameterGroupsRequest.class))) - .thenReturn(DescribeDbParameterGroupsResponse.builder().dbParameterGroups(simpleDbParameterGroup).build()); - - when(rdsClient.listTagsForResource(any(ListTagsForResourceRequest.class))) - .thenReturn(ListTagsForResourceResponse.builder().build()); - - mockDescribeDbParametersResponse(proxyRdsClient, "static", "dynamic", true, true, true); - - when(rdsClient.modifyDBParameterGroup(any(ModifyDbParameterGroupRequest.class))) - .thenReturn(ModifyDbParameterGroupResponse.builder().build()); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken(getClientRequestToken()) - .previousResourceState(previousResourceModel) - .desiredResourceState(RESET_RESOURCE_MODEL) - .logicalResourceIdentifier(LOGICAL_RESOURCE_IDENTIFIER).build(); - final ProgressEvent response = handler.handleRequest(proxy, proxyRdsClient, request, new CallbackContext(), EMPTY_REQUEST_LOGGER); - - assertThat(response).isNotNull(); - assertThat(response.getCallbackContext()).isNotNull(); - assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); - assertThat(response.getResourceModels()).isNull(); - assertThat(response.getMessage()).isNull(); - assertThat(response.getErrorCode()).isNull(); - - verify(rdsClient).resetDBParameterGroup(captor.capture()); - assertThat(captor.getValue().parameters().get(0).applyMethod().toString().equals("pending-reboot")).isTrue(); - verify(rdsClient, times(2)).describeDBParameters(any(DescribeDbParametersRequest.class)); - verify(rdsClient, times(2)).describeEngineDefaultParameters(any(DescribeEngineDefaultParametersRequest.class)); - } - @Test public void handleRequest_SimpleSuccessSameParams() { final UpdateHandler handler = new UpdateHandler();