diff --git a/aws-organizations-account/src/main/java/software/amazon/organizations/account/BaseHandlerStd.java b/aws-organizations-account/src/main/java/software/amazon/organizations/account/BaseHandlerStd.java index 2f6923c..aa8cd0b 100644 --- a/aws-organizations-account/src/main/java/software/amazon/organizations/account/BaseHandlerStd.java +++ b/aws-organizations-account/src/main/java/software/amazon/organizations/account/BaseHandlerStd.java @@ -40,6 +40,15 @@ public abstract class BaseHandlerStd extends BaseHandler { protected static final String CREATE_ACCOUNT_FAILURE_REASON_ACCOUNT_LIMIT_EXCEEDED = "ACCOUNT_LIMIT_EXCEEDED"; protected static final String CREATE_ACCOUNT_FAILURE_REASON_INVALID_ADDRESS = "INVALID_ADDRESS"; protected static final String CREATE_ACCOUNT_FAILURE_REASON_INVALID_EMAIL = "INVALID_EMAIL"; + protected static final String CREATE_ACCOUNT_FAILURE_REASON_CONCURRENT_ACCOUNT_MODIFICATION = "CONCURRENT_ACCOUNT_MODIFICATION"; + protected static final String CREATE_ACCOUNT_FAILURE_REASON_FAILED_BUSINESS_VALIDATION = "FAILED_BUSINESS_VALIDATION"; + protected static final String CREATE_ACCOUNT_FAILURE_REASON_IDENTITY_INVALID_BUSINESS_VALIDATION = "IDENTITY_INVALID_BUSINESS_VALIDATION"; + protected static final String CREATE_ACCOUNT_FAILURE_REASON_INVALID_PAYMENT_INSTRUMENT = "INVALID_PAYMENT_INSTRUMENT"; + protected static final String CREATE_ACCOUNT_FAILURE_REASON_INTERNAL_FAILURE = "INTERNAL_FAILURE"; + protected static final String CREATE_ACCOUNT_FAILURE_REASON_MISSING_BUSINESS_VALIDATION = "MISSING_BUSINESS_VALIDATION"; + protected static final String CREATE_ACCOUNT_FAILURE_REASON_MISSING_PAYMENT_INSTRUMENT = "MISSING_PAYMENT_INSTRUMENT"; + protected static final String CREATE_ACCOUNT_FAILURE_REASON_PENDING_BUSINESS_VALIDATION = "PENDING_BUSINESS_VALIDATION"; + protected static final String CREATE_ACCOUNT_FAILURE_REASON_UNKNOWN_BUSINESS_VALIDATION = "UNKNOWN_BUSINESS_VALIDATION"; protected static final String ACCOUNT_CREATION_STATUS_SUCCEEDED = "SUCCEEDED"; protected static final String ACCOUNT_CREATION_STATUS_FAILED = "FAILED"; // ExponentialBackoffJitter Constants diff --git a/aws-organizations-account/src/main/java/software/amazon/organizations/account/CreateHandler.java b/aws-organizations-account/src/main/java/software/amazon/organizations/account/CreateHandler.java index b2f9b2e..e23cbb0 100644 --- a/aws-organizations-account/src/main/java/software/amazon/organizations/account/CreateHandler.java +++ b/aws-organizations-account/src/main/java/software/amazon/organizations/account/CreateHandler.java @@ -146,17 +146,30 @@ private ProgressEvent handleAccountCreationError String errMsg = String.format("Account creation failed with reason [%s] for request id: %s", failureReason, callbackContext.getCreateAccountRequestId()); logger.log(errMsg); - switch (failureReason) { - case CREATE_ACCOUNT_FAILURE_REASON_EMAIL_ALREADY_EXISTS: - case CREATE_ACCOUNT_FAILURE_REASON_GOVCLOUD_ACCOUNT_ALREADY_EXISTS: - return ProgressEvent.failed(model, callbackContext, HandlerErrorCode.AlreadyExists, errMsg); - case CREATE_ACCOUNT_FAILURE_REASON_ACCOUNT_LIMIT_EXCEEDED: - return ProgressEvent.failed(model, callbackContext, HandlerErrorCode.ServiceLimitExceeded, errMsg); - case CREATE_ACCOUNT_FAILURE_REASON_INVALID_ADDRESS: - case CREATE_ACCOUNT_FAILURE_REASON_INVALID_EMAIL: - return ProgressEvent.failed(model, callbackContext, HandlerErrorCode.InvalidRequest, errMsg); + HandlerErrorCode errorCode = HandlerErrorCode.GeneralServiceException; + if (failureReason.equals(CREATE_ACCOUNT_FAILURE_REASON_EMAIL_ALREADY_EXISTS) || + failureReason.equals(CREATE_ACCOUNT_FAILURE_REASON_GOVCLOUD_ACCOUNT_ALREADY_EXISTS) + ) { + errorCode = HandlerErrorCode.AlreadyExists; + } else if (failureReason.equals(CREATE_ACCOUNT_FAILURE_REASON_ACCOUNT_LIMIT_EXCEEDED)) { + errorCode = HandlerErrorCode.ServiceLimitExceeded; + } else if (failureReason.equals(CREATE_ACCOUNT_FAILURE_REASON_INTERNAL_FAILURE)) { + errorCode = HandlerErrorCode.ServiceInternalError; + } else if (failureReason.equals(CREATE_ACCOUNT_FAILURE_REASON_INVALID_ADDRESS) || + failureReason.equals(CREATE_ACCOUNT_FAILURE_REASON_INVALID_EMAIL) || + failureReason.equals(CREATE_ACCOUNT_FAILURE_REASON_FAILED_BUSINESS_VALIDATION) || + failureReason.equals(CREATE_ACCOUNT_FAILURE_REASON_IDENTITY_INVALID_BUSINESS_VALIDATION) || + failureReason.equals(CREATE_ACCOUNT_FAILURE_REASON_INVALID_PAYMENT_INSTRUMENT) || + failureReason.equals(CREATE_ACCOUNT_FAILURE_REASON_MISSING_BUSINESS_VALIDATION) || + failureReason.equals(CREATE_ACCOUNT_FAILURE_REASON_MISSING_PAYMENT_INSTRUMENT) || + failureReason.equals(CREATE_ACCOUNT_FAILURE_REASON_PENDING_BUSINESS_VALIDATION) || + failureReason.equals(CREATE_ACCOUNT_FAILURE_REASON_UNKNOWN_BUSINESS_VALIDATION) || + failureReason.equals(CREATE_ACCOUNT_FAILURE_REASON_CONCURRENT_ACCOUNT_MODIFICATION) + ) { + errorCode = HandlerErrorCode.InvalidRequest; } - return ProgressEvent.failed(model, callbackContext, HandlerErrorCode.GeneralServiceException, errMsg); + logger.log(String.format("[Exception] ProgressEvent failed in account creation, translated FailureReason: [%s] to CloudFormation error code: [%s].", failureReason, errorCode)); + return ProgressEvent.failed(model, callbackContext, errorCode, errMsg); } protected ProgressEvent moveAccount( diff --git a/aws-organizations-account/src/test/java/software/amazon/organizations/account/AbstractTestBase.java b/aws-organizations-account/src/test/java/software/amazon/organizations/account/AbstractTestBase.java index 8411fee..0d8b286 100644 --- a/aws-organizations-account/src/test/java/software/amazon/organizations/account/AbstractTestBase.java +++ b/aws-organizations-account/src/test/java/software/amazon/organizations/account/AbstractTestBase.java @@ -39,6 +39,7 @@ public class AbstractTestBase { protected static final String ACCOUNT_LIMIT_EXCEEDED = "ACCOUNT_LIMIT_EXCEEDED"; protected static final String INVALID_EMAIL = "INVALID_EMAIL"; protected static final String INTERNAL_FAILURE = "INTERNAL_FAILURE"; + protected static final String UNKNOWN_FAILURE = "UNKNOWN_FAILURE"; protected static final String CREATE_ACCOUNT_STATUS_ID = "car-123456789023"; protected static final Instant REQUESTED_TIMESTAMP = Instant.parse("2017-02-03T10:37:30.00Z"); protected static final Instant COMPLETED_TIMESTAMP = Instant.parse("2017-02-03T10:47:30.00Z"); @@ -86,11 +87,18 @@ public class AbstractTestBase { .build(); protected static final CreateAccountStatus CreateAccountStatusFailedWithInternalFailure = CreateAccountStatus.builder() - .failureReason(INTERNAL_FAILURE) - .id(CREATE_ACCOUNT_STATUS_ID) - .state(FAILED) - .requestedTimestamp(REQUESTED_TIMESTAMP) - .build(); + .failureReason(INTERNAL_FAILURE) + .id(CREATE_ACCOUNT_STATUS_ID) + .state(FAILED) + .requestedTimestamp(REQUESTED_TIMESTAMP) + .build(); + + protected static final CreateAccountStatus CreateAccountStatusFailedWithUnknownFailure = CreateAccountStatus.builder() + .failureReason(UNKNOWN_FAILURE) + .id(CREATE_ACCOUNT_STATUS_ID) + .state(FAILED) + .requestedTimestamp(REQUESTED_TIMESTAMP) + .build(); protected static final CreateAccountStatus CreateAccountStatusSucceeded = CreateAccountStatus.builder() .accountId(TEST_ACCOUNT_ID) diff --git a/aws-organizations-account/src/test/java/software/amazon/organizations/account/CreateHandlerTest.java b/aws-organizations-account/src/test/java/software/amazon/organizations/account/CreateHandlerTest.java index 968980b..1d8fe79 100644 --- a/aws-organizations-account/src/test/java/software/amazon/organizations/account/CreateHandlerTest.java +++ b/aws-organizations-account/src/test/java/software/amazon/organizations/account/CreateHandlerTest.java @@ -323,13 +323,13 @@ public void handleRequest_FailedWithCfnGeneralServiceException() { final ResourceModel model = generateCreateResourceModel(); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); + .desiredResourceState(model) + .build(); final CreateAccountResponse createAccountResponse = getCreateAccountResponse(); final DescribeCreateAccountStatusResponse describeCreateAccountStatusResponse = DescribeCreateAccountStatusResponse.builder() - .createAccountStatus(CreateAccountStatusFailedWithInternalFailure) - .build(); + .createAccountStatus(CreateAccountStatusFailedWithUnknownFailure) + .build(); when(mockProxyClient.client().createAccount(any(CreateAccountRequest.class))).thenReturn(createAccountResponse); when(mockProxyClient.client().describeCreateAccountStatus(any(DescribeCreateAccountStatusRequest.class))).thenReturn(describeCreateAccountStatusResponse); @@ -351,6 +351,39 @@ public void handleRequest_FailedWithCfnGeneralServiceException() { verify(mockProxyClient.client(), times(0)).moveAccount(any(MoveAccountRequest.class)); } + @Test + public void handleRequest_FailedWithCfnServiceInternalError() { + final ResourceModel model = generateCreateResourceModel(); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .desiredResourceState(model) + .build(); + + final CreateAccountResponse createAccountResponse = getCreateAccountResponse(); + final DescribeCreateAccountStatusResponse describeCreateAccountStatusResponse = DescribeCreateAccountStatusResponse.builder() + .createAccountStatus(CreateAccountStatusFailedWithInternalFailure) + .build(); + + when(mockProxyClient.client().createAccount(any(CreateAccountRequest.class))).thenReturn(createAccountResponse); + when(mockProxyClient.client().describeCreateAccountStatus(any(DescribeCreateAccountStatusRequest.class))).thenReturn(describeCreateAccountStatusResponse); + final ProgressEvent response = createHandler.handleRequest(mockAwsClientProxy, request, new CallbackContext(), mockProxyClient, logger); + + assertThat(response).isNotNull(); + assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED); + assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); + assertThat(response.getResourceModel()).isNotNull(); + assertThat(response.getErrorCode()).isEqualTo(HandlerErrorCode.ServiceInternalError); + assertThat(response.getResourceModel().getAccountId()).isNull(); + assertThat(response.getResourceModel().getEmail()).isEqualTo(TEST_ACCOUNT_EMAIL); + assertThat(response.getResourceModel().getAccountName()).isEqualTo(TEST_ACCOUNT_NAME); + assertThat(response.getResourceModel().getParentIds()).isEqualTo(TEST_PARENT_IDS); + assertThat(TagTestResourcesHelper.tagsEqual(response.getResourceModel().getTags(), TagTestResourcesHelper.defaultTags)); + + verify(mockProxyClient.client()).createAccount(any(CreateAccountRequest.class)); + verify(mockProxyClient.client(), atLeast(1)).describeCreateAccountStatus(any(DescribeCreateAccountStatusRequest.class)); + verify(mockProxyClient.client(), times(0)).moveAccount(any(MoveAccountRequest.class)); + } + @Test public void handleRequest_shouldFailWhenNoAccountNameAndEmailAddress() { final ResourceModel model = generateCreateResourceModelNoAccountNameAndEmail();