package tw.com.unit;
import com.amazonaws.services.cloudformation.model.Parameter;
import com.amazonaws.services.cloudformation.model.Stack;
import com.amazonaws.services.cloudformation.model.StackStatus;
import com.amazonaws.services.cloudformation.model.TemplateParameter;
import com.amazonaws.services.ec2.model.Vpc;
import com.amazonaws.services.identitymanagement.model.User;
import org.apache.commons.io.FileUtils;
import org.easymock.EasyMock;
import org.easymock.EasyMockRunner;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import tw.com.*;
import tw.com.entity.*;
import tw.com.exceptions.CfnAssistException;
import tw.com.providers.IdentityProvider;
import tw.com.providers.NotificationSender;
import tw.com.repository.*;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import static org.junit.Assert.*;
@RunWith(EasyMockRunner.class)
public class TestAwsFacadeDeltaApplicationAndRollbacks extends UpdateStackExpectations {
private AwsFacade aws;
private NotificationSender notificationSender;
private IdentityProvider identityProvider;
private User user;
private static final String THIRD_FILE = "03createRoutes.json";
@Before
public void beforeEachTestRuns() throws IOException {
monitor = createMock(MonitorStackEvents.class);
cfnRepository = createMock(CloudFormRepository.class);
vpcRepository = createStrictMock(VpcRepository.class);
ELBRepository elbRepository = createMock(ELBRepository.class);
cloudRepository = createStrictMock(CloudRepository.class);
notificationSender = createStrictMock(NotificationSender.class);
identityProvider = createStrictMock(IdentityProvider.class);
user = new User("path", "userName", "userId", "arn", new Date());
String regionName = EnvironmentSetupForTests.getRegion().getName();
aws = new AwsFacade(monitor, cfnRepository, vpcRepository, elbRepository, cloudRepository, notificationSender, identityProvider, regionName);
deleteFile(THIRD_FILE);
}
@Test
public void shouldApplySimpleTemplateNoParameters() throws IOException, InterruptedException, CfnAssistException {
List<File> files = loadFiles(new File(FilesForTesting.ORDERED_SCRIPTS_FOLDER));
EasyMock.expect(cloudRepository.getZones("eu-west-1")).andStubReturn(new HashMap<>());
EasyMock.expect(vpcRepository.getVpcIndexTag(projectAndEnv)).andReturn("0");
setExpectationsForValidationPass(files);
// processing pass
Integer count = 1;
for(File file : files) {
setExpectationsForFile(count, file, new LinkedList<>());
count++;
}
replayAll();
ArrayList<StackNameAndId> result = aws.applyTemplatesFromFolder(FilesForTesting.ORDERED_SCRIPTS_FOLDER,
projectAndEnv, new LinkedList<>());
assertEquals(files.size(), result.size());
validateStacksCreated(files, 1, result);
verifyAll();
}
@Test
public void shouldApplySimpleTemplateNoParametersOnlyNeeded() throws IOException, InterruptedException, CfnAssistException {
List<File> allFiles = loadFiles(new File(FilesForTesting.ORDERED_SCRIPTS_FOLDER));
List<File> files = allFiles.subList(1, 2);
EasyMock.expect(cloudRepository.getZones("eu-west-1")).andReturn(new HashMap<>());
EasyMock.expect(vpcRepository.getVpcIndexTag(projectAndEnv)).andReturn("1");
setExpectationsForValidationPass(allFiles);
// processing pass
Integer count = 2;
for(File file : files) {
setExpectationsForFile(count, file, new LinkedList<>());
count++;
}
replayAll();
ArrayList<StackNameAndId> result = aws.applyTemplatesFromFolder(FilesForTesting.ORDERED_SCRIPTS_FOLDER, projectAndEnv, new LinkedList<>());
assertEquals(files.size(), result.size());
validateStacksCreated(files, 2, result);
verifyAll();
}
@Test
public void shouldApplySimpleTemplateNoParametersNoneNeeded() throws IOException, InterruptedException, CfnAssistException {
List<File> allFiles = loadFiles(new File(FilesForTesting.ORDERED_SCRIPTS_FOLDER));
EasyMock.expect(vpcRepository.getVpcIndexTag(projectAndEnv)).andReturn("2");
setExpectationsForValidationPass(allFiles);
replayAll();
ArrayList<StackNameAndId> result = aws.applyTemplatesFromFolder(FilesForTesting.ORDERED_SCRIPTS_FOLDER, projectAndEnv, new LinkedList<>());
assertEquals(0, result.size());
verifyAll();
}
@Test
public void shouldApplyNewFileAsNeeded() throws IOException, InterruptedException, CfnAssistException {
List<File> originalFiles = loadFiles(new File(FilesForTesting.ORDERED_SCRIPTS_FOLDER));
EasyMock.expect(cloudRepository.getZones("eu-west-1")).andReturn(new HashMap<>());
EasyMock.expect(vpcRepository.getVpcIndexTag(projectAndEnv)).andReturn("2");
// validation pass does all files
setExpectationsForValidationPass(originalFiles);
// second run set up
Path currentPath = FileSystems.getDefault().getPath(FilesForTesting.ORDERED_SCRIPTS_FOLDER, "holding", THIRD_FILE);
File newFile = new File(currentPath.toString());
LinkedList<File> newFiles = new LinkedList<>();
newFiles.addAll(originalFiles);
newFiles.add(newFile);
// second run expectations
EasyMock.expect(vpcRepository.getVpcIndexTag(projectAndEnv)).andReturn("2");
setExpectationsForValidationPass(newFiles);
setExpectationsForFile(3, newFile, new LinkedList<>());
replayAll();
ArrayList<StackNameAndId> result = aws.applyTemplatesFromFolder(FilesForTesting.ORDERED_SCRIPTS_FOLDER, projectAndEnv, new LinkedList<>());
assertEquals(0, result.size());
copyInFile(THIRD_FILE);
result = aws.applyTemplatesFromFolder(FilesForTesting.ORDERED_SCRIPTS_FOLDER, projectAndEnv, new LinkedList<>());
assertEquals(1, result.size());
verifyAll();
}
@Test
public void shouldApplyFilesInAFolderWithUpdate() throws CfnAssistException, IOException, InterruptedException {
List<File> allFiles = loadFiles(FilesForTesting.ORDERED_SCRIPTS_WITH_UPDATES_FOLDER.toFile());
EasyMock.expect(cloudRepository.getZones("eu-west-1")).andReturn(new HashMap<>());
EasyMock.expect(vpcRepository.getVpcIndexTag(projectAndEnv)).andReturn("0");
// first file - no parameters needed
setExpectationsForValidationPass(allFiles);
LinkedList<Parameter> cfnParams = new LinkedList<>();
List<TemplateParameter> templateParameters = Collections.singletonList(new TemplateParameter().
withParameterKey(AwsFacade.PARAMETER_STACKNAME).withDefaultValue("01createSubnet"));
// processing pass
setExpectationsForFile(1, allFiles.get(0), new LinkedList<>());
setUpdateExpectations("CfnAssistTest01createSubnet", allFiles.get(1).getAbsolutePath(), templateParameters,
cfnParams);
vpcRepository.setVpcIndexTag(projectAndEnv, "2");
EasyMock.expectLastCall();
replayAll();
ArrayList<StackNameAndId> result = aws.applyTemplatesFromFolder(FilesForTesting.ORDERED_SCRIPTS_WITH_UPDATES_FOLDER.toString(),
projectAndEnv, cfnParams);
verifyAll();
assertEquals(2, result.size());
}
@Test
public void shouldRollbackTemplatesWithNoUpdateBasedOnIndex() throws CfnAssistException {
String stackA = "CfnAssistTest01createSubnet";
StackEntry stackEntryA = new StackEntry(projectAndEnv.getProject(), projectAndEnv.getEnvTag(),
new Stack().withStackName(stackA));
StackNameAndId stackANameAndId = new StackNameAndId(stackA, "id1");
String stackB = "CfnAssistTest02createAcls";
StackNameAndId stackBNameAndId = new StackNameAndId(stackB, "id2");
StackEntry stackEntryB = new StackEntry(projectAndEnv.getProject(), projectAndEnv.getEnvTag(),
new Stack().withStackName(stackB));
SetDeltaIndexForProjectAndEnv setDeltaIndexForProjectAndEnv = new SetDeltaIndexForProjectAndEnv(projectAndEnv,vpcRepository);
EasyMock.expect(vpcRepository.getVpcIndexTag(projectAndEnv)).andReturn("2");
EasyMock.expect(cfnRepository.getStacknameByIndex(projectAndEnv.getEnvTag(), 2)).andReturn(stackEntryB);
EasyMock.expect(cfnRepository.getStackNameAndId(stackB)).andReturn(stackBNameAndId);
cfnRepository.deleteStack(stackB);
EasyMock.expectLastCall();
EasyMock.expect(cfnRepository.getStacknameByIndex(projectAndEnv.getEnvTag(), 1)).andReturn(stackEntryA);
EasyMock.expect(cfnRepository.getStackNameAndId(stackA)).andReturn(stackANameAndId);
cfnRepository.deleteStack(stackA);
EasyMock.expectLastCall();
EasyMock.expect(vpcRepository.getSetsDeltaIndexFor(projectAndEnv)).
andReturn(setDeltaIndexForProjectAndEnv);
DeletionsPending pending = new DeletionsPending();
pending.add(2, stackBNameAndId);
pending.add(1, stackANameAndId);
List<String> deletedStacks = new LinkedList<>();
deletedStacks.add(stackB);
deletedStacks.add(stackA);
EasyMock.expect(monitor.waitForDeleteFinished(pending, setDeltaIndexForProjectAndEnv)).andReturn(deletedStacks);
replayAll();
List<String> result = aws.rollbackTemplatesByIndexTag(projectAndEnv);
verifyAll();
assertEquals(2, result.size());
assertTrue(result.contains(stackA));
assertTrue(result.contains(stackB));
}
@Ignore
@Test
public void shouldRollbackWithDeltas() throws CfnAssistException {
fail("no way to do this until we can unpdate tags on a stack");
}
@Test
public void shouldStepBackLastChangeOnAVpc() throws CfnAssistException {
String stackB = "CfnAssistTest02createAcls";
StackNameAndId stackBNameAndId = new StackNameAndId(stackB, "id2");
SetDeltaIndexForProjectAndEnv setDeltaIndexForProjectAndEnv = new SetDeltaIndexForProjectAndEnv(projectAndEnv,vpcRepository);
EasyMock.expect(vpcRepository.getVpcIndexTag(projectAndEnv)).andReturn("2");
StackEntry stackEntry = new StackEntry(projectAndEnv.getProject(), projectAndEnv.getEnvTag(),
new Stack().withStackName(stackB));
EasyMock.expect(cfnRepository.getStacknameByIndex(projectAndEnv.getEnvTag(), 2)).andReturn(stackEntry);
EasyMock.expect(cfnRepository.getStackNameAndId(stackB)).andReturn(stackBNameAndId);
cfnRepository.deleteStack(stackB);
EasyMock.expectLastCall();
EasyMock.expect(vpcRepository.getSetsDeltaIndexFor(projectAndEnv)).andReturn(setDeltaIndexForProjectAndEnv);
DeletionsPending pending = new DeletionsPending();
pending.add(2, stackBNameAndId);
List<String> deletedStacks = new LinkedList<>();
deletedStacks.add(stackB);
EasyMock.expect(monitor.waitForDeleteFinished(pending, setDeltaIndexForProjectAndEnv)).andReturn(deletedStacks);
replayAll();
List<String> result = aws.stepbackLastChange(projectAndEnv);
verifyAll();
assertEquals(1, result.size());
assertTrue(result.contains(stackB));
}
@Ignore
@Test
public void shouldStepBackLastChangeOnAVpcWhenUpdate() {
fail("Cannot support this until way to update tag on an existing stack");
}
private void setExpectationsForValidationPass(List<File> allFiles)
throws IOException {
for(File file : allFiles) {
String templateContents = EnvironmentSetupForTests.loadFile(file.getAbsolutePath());
List<TemplateParameter> templateParameters = new LinkedList<>();
EasyMock.expect(cfnRepository.validateStackTemplate(templateContents)).andReturn(templateParameters);
}
}
private void validateStacksCreated(List<File> files, Integer count,
ArrayList<StackNameAndId> result) {
for(File file : files) {
String expectedName = aws.createStackName(file, projectAndEnv);
StackNameAndId expected = new StackNameAndId(expectedName, count.toString());
assertTrue(result.contains(expected));
count++;
}
}
private void setExpectationsForFile(Integer count, File file, List<TemplateParameter> templateParameters)
throws IOException, CfnAssistException, InterruptedException {
String templateContents = EnvironmentSetupForTests.loadFile(file.getAbsolutePath());
Collection<Parameter> creationParameters = new LinkedList<>();
String stackName = aws.createStackName(file, projectAndEnv);
StackNameAndId stackNameAndId = new StackNameAndId(stackName, count.toString());
EasyMock.expect(vpcRepository.getCopyOfVpc(projectAndEnv)).andReturn(new Vpc());
EasyMock.expect(cfnRepository.validateStackTemplate(templateContents)).andReturn(templateParameters);
EasyMock.expect(cfnRepository.getStackStatus(stackName)).andReturn("");
// tagging
Tagging tagging = new Tagging();
tagging.setIndexTag(count);
// create stack
EasyMock.expect(cfnRepository.createStack(projectAndEnv, templateContents, stackName, creationParameters, monitor, tagging))
.andReturn(stackNameAndId);
// monitor
EasyMock.expect(monitor.waitForCreateFinished(stackNameAndId)).andReturn(StackStatus.CREATE_COMPLETE.toString());
// notification
EasyMock.expect(identityProvider.getUserId()).andReturn(user);
CFNAssistNotification notification = new CFNAssistNotification(stackName, StackStatus.CREATE_COMPLETE.toString(), user);
EasyMock.expect(notificationSender.sendNotification(notification)).andReturn("sentMessageId");
// success
EasyMock.expect(cfnRepository.createSuccess(stackNameAndId)).andReturn(
new Stack().withStackId(count.toString()).withStackName(stackName));
// index update
vpcRepository.setVpcIndexTag(projectAndEnv, count.toString());
}
private List<File> loadFiles(File folder) {
FilenameFilter jsonFilter = new JsonExtensionFilter();
File[] files = folder.listFiles(jsonFilter);
Arrays.sort(files); // place in lexigraphical order
return Arrays.asList(files);
}
private Path copyInFile(String filename) throws IOException {
Path srcFile = FileSystems.getDefault().getPath(FilesForTesting.ORDERED_SCRIPTS_FOLDER, "holding", filename);
Path destFile = FileSystems.getDefault().getPath(FilesForTesting.ORDERED_SCRIPTS_FOLDER, filename);
Files.deleteIfExists(destFile);
FileUtils.copyFile(srcFile.toFile(), destFile.toFile());
return destFile;
}
private void deleteFile(String filename) throws IOException {
Path destFile = FileSystems.getDefault().getPath(FilesForTesting.ORDERED_SCRIPTS_FOLDER, filename);
Files.deleteIfExists(destFile);
}
}