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();