package tw.com.unit; import static org.junit.Assert.*; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import org.easymock.EasyMock; import org.easymock.EasyMockRunner; import org.easymock.EasyMockSupport; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import com.amazonaws.AmazonServiceException; import com.amazonaws.services.cloudformation.model.StackEvent; import com.amazonaws.services.cloudformation.model.StackStatus; import tw.com.PollingStackMonitor; import tw.com.SetsDeltaIndex; import tw.com.StackMonitor; import tw.com.entity.DeletionsPending; import tw.com.entity.StackNameAndId; import tw.com.exceptions.CannotFindVpcException; import tw.com.exceptions.NotReadyException; import tw.com.exceptions.WrongNumberOfStacksException; import tw.com.exceptions.WrongStackStatus; import tw.com.repository.CloudFormRepository; @RunWith(EasyMockRunner.class) public class TestPollingStackMonitor extends EasyMockSupport implements SetsDeltaIndex { private CloudFormRepository cfnRepository; private PollingStackMonitor monitor; private String stackName; private StackNameAndId stackId; private int lastDeltaIndex; @Before public void beforeEachTestRuns() { cfnRepository = createMock(CloudFormRepository.class); monitor = new PollingStackMonitor(cfnRepository); stackName = "stackName"; stackId = new StackNameAndId(stackName, "stackId"); } @Test public void shouldWaitForCreationToComplete() throws WrongNumberOfStacksException, WrongStackStatus, InterruptedException { List<String> aborts = Arrays.asList(StackMonitor.CREATE_ABORTS); StackStatus initialStatus = StackStatus.CREATE_IN_PROGRESS; StackStatus targetStatus = StackStatus.CREATE_COMPLETE; setRepoExcpetationsForSuccess(aborts, initialStatus, targetStatus); replayAll(); String status = monitor.waitForCreateFinished(stackId); assertEquals(targetStatus.toString(), status); verifyAll(); } @Test public void shouldWaitForDeleteToComplete() throws WrongNumberOfStacksException, WrongStackStatus, InterruptedException { List<String> aborts = Arrays.asList(StackMonitor.DELETE_ABORTS); StackStatus initialStatus = StackStatus.DELETE_IN_PROGRESS; StackStatus targetStatus = StackStatus.DELETE_COMPLETE; setRepoExcpetationsForSuccess(aborts, initialStatus, targetStatus); replayAll(); String status = monitor.waitForDeleteFinished(stackId); assertEquals(targetStatus.toString(), status); verifyAll(); } @Test public void shouldWaitForUpdateToComplete() throws WrongNumberOfStacksException, WrongStackStatus, InterruptedException { List<String> aborts = Arrays.asList(StackMonitor.UPDATE_ABORTS); StackStatus initialStatus = StackStatus.UPDATE_IN_PROGRESS; StackStatus targetStatus = StackStatus.UPDATE_COMPLETE; setRepoExcpetationsForSuccess(aborts, initialStatus, targetStatus); replayAll(); String status = monitor.waitForUpdateFinished(stackId); assertEquals(targetStatus.toString(), status); verifyAll(); } @Test public void shouldWaitForUpdateToCompleteWithCleanUp() throws WrongNumberOfStacksException, WrongStackStatus, InterruptedException { List<String> aborts = Arrays.asList(StackMonitor.UPDATE_ABORTS); StackStatus initialStatus = StackStatus.UPDATE_IN_PROGRESS; StackStatus targetStatus = StackStatus.UPDATE_COMPLETE; setRepoExcpetationsForSuccess(aborts, initialStatus, StackStatus.UPDATE_COMPLETE_CLEANUP_IN_PROGRESS); setRepoExcpetationsForSuccess(aborts, StackStatus.UPDATE_COMPLETE_CLEANUP_IN_PROGRESS, targetStatus); replayAll(); String status = monitor.waitForUpdateFinished(stackId); assertEquals(targetStatus.toString(), status); verifyAll(); } @Test public void shouldWaitForRollbackToComplete() throws WrongNumberOfStacksException, WrongStackStatus, InterruptedException, NotReadyException { List<String> aborts = Arrays.asList(StackMonitor.ROLLBACK_ABORTS); StackStatus initialStatus = StackStatus.ROLLBACK_IN_PROGRESS; StackStatus targetStatus = StackStatus.ROLLBACK_COMPLETE; setRepoExcpetationsForSuccess(aborts, initialStatus, targetStatus); replayAll(); String status = monitor.waitForRollbackComplete(stackId); assertEquals(targetStatus.toString(), status); verifyAll(); } @Test public void shouldThrowForCreationFailure() throws WrongNumberOfStacksException, WrongStackStatus, InterruptedException { List<String> aborts = Arrays.asList(StackMonitor.CREATE_ABORTS); setRepoExcpetationsForFailure(aborts, StackStatus.CREATE_IN_PROGRESS, StackStatus.CREATE_FAILED); replayAll(); try { monitor.waitForCreateFinished(stackId); fail("exception expected"); } catch(WrongStackStatus expectedException) { // noop } verifyAll(); } @Test public void shouldThrowForCreationFailureDueToRollback() throws WrongNumberOfStacksException, WrongStackStatus, InterruptedException { List<String> aborts = Arrays.asList(StackMonitor.CREATE_ABORTS); setRepoExcpetationsForFailure(aborts, StackStatus.CREATE_IN_PROGRESS, StackStatus.ROLLBACK_IN_PROGRESS); replayAll(); try { monitor.waitForCreateFinished(stackId); fail("exception expected"); } catch(WrongStackStatus expectedException) { // noop } verifyAll(); } @Test public void shouldThrowForUpdateFailure() throws WrongNumberOfStacksException, WrongStackStatus, InterruptedException { List<String> aborts = Arrays.asList(StackMonitor.UPDATE_ABORTS); setRepoExcpetationsForFailure(aborts, StackStatus.UPDATE_IN_PROGRESS, StackStatus.UPDATE_ROLLBACK_COMPLETE); replayAll(); try { monitor.waitForUpdateFinished(stackId); fail("exception expected"); } catch(WrongStackStatus expectedException) { // noop } verifyAll(); } @Test public void shouldThrowForRollbackFailure() throws WrongNumberOfStacksException, WrongStackStatus, InterruptedException, NotReadyException { List<String> aborts = Arrays.asList(StackMonitor.ROLLBACK_ABORTS); setRepoExcpetationsForFailure(aborts, StackStatus.ROLLBACK_IN_PROGRESS, StackStatus.ROLLBACK_FAILED); replayAll(); try { monitor.waitForRollbackComplete(stackId); fail("exception expected"); } catch(WrongStackStatus expectedException) { // noop } verifyAll(); } @Test public void shouldReturnStatusDeletionFailure() throws WrongNumberOfStacksException, WrongStackStatus, InterruptedException { List<String> aborts = Arrays.asList(StackMonitor.DELETE_ABORTS); setRepoExcpetationsForFailure(aborts, StackStatus.DELETE_IN_PROGRESS, StackStatus.DELETE_FAILED); replayAll(); String result = monitor.waitForDeleteFinished(stackId); assertEquals(StackStatus.DELETE_FAILED.toString(), result); verifyAll(); } @Test public void shouldReturnStatusDeletionOKDueToNoSuchStack() throws WrongNumberOfStacksException, WrongStackStatus, InterruptedException { List<String> aborts = Arrays.asList(StackMonitor.DELETE_ABORTS); AmazonServiceException amazonServiceException = new AmazonServiceException("message"); amazonServiceException.setErrorCode("ValidationError"); EasyMock.expect(cfnRepository.waitForStatusToChangeFrom(stackName, StackStatus.DELETE_IN_PROGRESS, aborts)). andThrow(amazonServiceException); replayAll(); String result = monitor.waitForDeleteFinished(stackId); assertEquals(StackStatus.DELETE_COMPLETE.toString(), result); verifyAll(); } @Test public void shouldReturnStatusDeletionFailDueToOtherException() throws WrongNumberOfStacksException, WrongStackStatus, InterruptedException { List<String> aborts = Arrays.asList(StackMonitor.DELETE_ABORTS); AmazonServiceException amazonServiceException = new AmazonServiceException("message"); amazonServiceException.setErrorCode("someOtherError"); EasyMock.expect(cfnRepository.waitForStatusToChangeFrom(stackName, StackStatus.DELETE_IN_PROGRESS, aborts)). andThrow(amazonServiceException); List<StackEvent> events = new LinkedList<StackEvent>(); EasyMock.expect(cfnRepository.getStackEvents(stackName)).andReturn(events); replayAll(); String result = monitor.waitForDeleteFinished(stackId); assertEquals(StackStatus.DELETE_FAILED.toString(), result); verifyAll(); } @Test public void shouldMonitorMultiplePendingDeletes() throws WrongNumberOfStacksException, InterruptedException { List<String> aborts = Arrays.asList(StackMonitor.DELETE_ABORTS); StackStatus targetStatus = StackStatus.DELETE_COMPLETE; DeletionsPending pending = new DeletionsPending(); pending.add(3, new StackNameAndId("stackC", "id3")); pending.add(2, new StackNameAndId("stackB", "id2")); pending.add(1, new StackNameAndId("stackA", "id1")); EasyMock.expect(cfnRepository.waitForStatusToChangeFrom("stackC", StackStatus.DELETE_IN_PROGRESS, aborts)) .andReturn(targetStatus.toString()); EasyMock.expect(cfnRepository.waitForStatusToChangeFrom("stackB", StackStatus.DELETE_IN_PROGRESS, aborts)) .andReturn(targetStatus.toString()); EasyMock.expect(cfnRepository.waitForStatusToChangeFrom("stackA", StackStatus.DELETE_IN_PROGRESS, aborts)) .andReturn(targetStatus.toString()); replayAll(); monitor.waitForDeleteFinished(pending, this); assertEquals(0, lastDeltaIndex); verifyAll(); } @Test public void shouldMonitorMultiplePendingDeletesAndLeaveIndexCorrectOnFailure() throws WrongNumberOfStacksException, InterruptedException { List<String> aborts = Arrays.asList(StackMonitor.DELETE_ABORTS); StackStatus targetStatus = StackStatus.DELETE_COMPLETE; DeletionsPending pending = new DeletionsPending(); pending.add(3, new StackNameAndId("stackC", "id3")); pending.add(2, new StackNameAndId("stackB", "id2")); pending.add(1, new StackNameAndId("stackA", "id1")); EasyMock.expect(cfnRepository.waitForStatusToChangeFrom("stackC", StackStatus.DELETE_IN_PROGRESS, aborts)) .andReturn(targetStatus.toString()); EasyMock.expect(cfnRepository.waitForStatusToChangeFrom("stackB", StackStatus.DELETE_IN_PROGRESS, aborts)) .andReturn(StackStatus.DELETE_FAILED.toString()); List<StackEvent> events = new LinkedList<StackEvent>(); EasyMock.expect(cfnRepository.getStackEvents("stackB")).andReturn(events); replayAll(); monitor.waitForDeleteFinished(pending, this); assertEquals(2, lastDeltaIndex); verifyAll(); } private void setRepoExcpetationsForSuccess(List<String> aborts, StackStatus initialStatus, StackStatus targetStatus) throws WrongNumberOfStacksException, InterruptedException { EasyMock.expect(cfnRepository.waitForStatusToChangeFrom(stackName, initialStatus, aborts)) .andReturn(targetStatus.toString()); } private void setRepoExcpetationsForFailure(List<String> aborts, StackStatus initialStatus, StackStatus failureStatus) throws WrongNumberOfStacksException, InterruptedException { EasyMock.expect(cfnRepository.waitForStatusToChangeFrom(stackName, initialStatus, aborts)). andReturn(failureStatus.toString()); List<StackEvent> events = new LinkedList<StackEvent>(); EasyMock.expect(cfnRepository.getStackEvents(stackName)).andReturn(events); } @Override public void setDeltaIndex(Integer newDelta) throws CannotFindVpcException { this.lastDeltaIndex = newDelta; } }