package org.zalando.stups.fullstop.jobs.ami; import com.amazonaws.regions.Regions; import com.amazonaws.services.ec2.AmazonEC2Client; import com.amazonaws.services.ec2.model.DescribeImagesRequest; import com.amazonaws.services.ec2.model.DescribeImagesResult; import com.amazonaws.services.ec2.model.DescribeInstancesRequest; import com.amazonaws.services.ec2.model.DescribeInstancesResult; import com.amazonaws.services.ec2.model.Image; import com.amazonaws.services.ec2.model.Instance; import com.amazonaws.services.ec2.model.Reservation; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.zalando.stups.fullstop.aws.ClientProvider; import org.zalando.stups.fullstop.jobs.common.AccountIdSupplier; import org.zalando.stups.fullstop.jobs.common.FetchTaupageYaml; import org.zalando.stups.fullstop.jobs.config.JobsProperties; import org.zalando.stups.fullstop.violation.Violation; import org.zalando.stups.fullstop.violation.ViolationSink; import org.zalando.stups.fullstop.violation.service.ViolationService; import java.time.LocalDate; import java.util.HashSet; import java.util.List; import java.util.Optional; import static com.amazonaws.regions.Region.getRegion; import static com.google.common.collect.Sets.newHashSet; import static java.time.format.DateTimeFormatter.ofPattern; import static java.util.Arrays.asList; import static java.util.Collections.singleton; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.zalando.stups.fullstop.violation.ViolationType.OUTDATED_TAUPAGE; public class FetchAmiJobTest { private static final String ACCOUNT_1 = "111111111111"; private static final String ACCOUNT_2 = "222222222222"; private static final HashSet<String> ACCOUNTS = newHashSet(ACCOUNT_1, ACCOUNT_2); private static final String REGION_1 = "eu-west-1"; private static final String REGION_2 = "us-east-1"; private static final List<String> REGIONS = asList(REGION_1, REGION_2); private static final String INSTANCE_ID = "i-12345"; private static final String IMAGE_ID = "ami-12345"; @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock private ViolationSink mockViolationSink; @Mock private ClientProvider mockClientProvider; @Mock private AccountIdSupplier mockAccountIdSupplier; @Mock private JobsProperties mockJobsProperties; @Mock private ViolationService mockViolationService; @Mock private FetchTaupageYaml mockFetchTaupageYaml; @Mock private AmazonEC2Client mockEC2Client; private FetchAmiJob job; private Instance instance1 = new Instance() .withInstanceId(INSTANCE_ID) .withImageId(IMAGE_ID); @Before public void setUp() throws Exception { job = new FetchAmiJob(mockViolationSink, mockClientProvider, mockAccountIdSupplier, mockJobsProperties, mockViolationService, mockFetchTaupageYaml, "Taupage-AMI-", ACCOUNT_1); when(mockFetchTaupageYaml.getTaupageYaml(anyString(), anyString(), anyString())).thenReturn(Optional.empty()); } @After public void tearDown() throws Exception { verifyNoMoreInteractions(mockViolationSink, mockClientProvider, mockAccountIdSupplier, mockJobsProperties, mockViolationService, mockFetchTaupageYaml, mockEC2Client); } @Test public void testInit() throws Exception { job.init(); } @Test public void testRunWithMultipleAccountsAndRegions() throws Exception { final DescribeInstancesResult describeInstancesResult = new DescribeInstancesResult() .withReservations(new Reservation().withInstances(instance1)); final Image image = new Image() .withImageId(IMAGE_ID) .withName("Taupage-AMI-" + LocalDate.now().format(ofPattern("yyyyMMdd")) + "-123456") .withOwnerId(ACCOUNT_1); when(mockAccountIdSupplier.get()).thenReturn(ACCOUNTS); when(mockJobsProperties.getWhitelistedRegions()).thenReturn(REGIONS); when(mockClientProvider.getClient(eq(AmazonEC2Client.class), anyString(), any())).thenReturn(mockEC2Client); when(mockEC2Client.describeInstances(any(DescribeInstancesRequest.class))).thenReturn(describeInstancesResult); when(mockEC2Client.describeImages(any(DescribeImagesRequest.class))).thenReturn(new DescribeImagesResult().withImages(image)); job.run(); verify(mockAccountIdSupplier).get(); verify(mockJobsProperties).getWhitelistedRegions(); ACCOUNTS.forEach(account -> REGIONS.forEach(regionName -> { verify(mockClientProvider).getClient(eq(AmazonEC2Client.class), eq(account), eq(getRegion(Regions.fromName(regionName)))); verify(mockViolationService).violationExists(eq(account), eq(regionName), eq(FetchAmiJob.EVENT_ID), eq(INSTANCE_ID), eq(OUTDATED_TAUPAGE)); })); final int accountsTimesRegions = ACCOUNTS.size() * REGIONS.size(); final ArgumentCaptor<DescribeInstancesRequest> describeInstances = ArgumentCaptor.forClass(DescribeInstancesRequest.class); verify(mockEC2Client, times(accountsTimesRegions)).describeInstances(describeInstances.capture()); assertThat(describeInstances.getValue().getNextToken()).isNull(); final ArgumentCaptor<DescribeImagesRequest> describeImages = ArgumentCaptor.forClass(DescribeImagesRequest.class); verify(mockEC2Client, times(accountsTimesRegions)).describeImages(describeImages.capture()); assertThat(describeImages.getValue().getImageIds()).containsExactly(IMAGE_ID); } @Test public void testFindOutdatedTaupage() throws Exception { final DescribeInstancesResult describeInstancesResult = new DescribeInstancesResult() .withReservations(new Reservation().withInstances(instance1)); final Image image = new Image() .withImageId(IMAGE_ID) .withName("Taupage-AMI-" + LocalDate.now().minusDays(70).format(ofPattern("yyyyMMdd")) + "-123456") .withOwnerId(ACCOUNT_1); when(mockAccountIdSupplier.get()).thenReturn(singleton(ACCOUNT_1)); when(mockJobsProperties.getWhitelistedRegions()).thenReturn(singletonList(REGION_1)); when(mockClientProvider.getClient(eq(AmazonEC2Client.class), anyString(), any())).thenReturn(mockEC2Client); when(mockEC2Client.describeInstances(any(DescribeInstancesRequest.class))).thenReturn(describeInstancesResult); when(mockEC2Client.describeImages(any(DescribeImagesRequest.class))).thenReturn(new DescribeImagesResult().withImages(image)); job.run(); verify(mockAccountIdSupplier).get(); verify(mockJobsProperties).getWhitelistedRegions(); verify(mockClientProvider).getClient(eq(AmazonEC2Client.class), eq(ACCOUNT_1), eq(getRegion(Regions.fromName(REGION_1)))); verify(mockViolationService).violationExists(eq(ACCOUNT_1), eq(REGION_1), eq(FetchAmiJob.EVENT_ID), eq(INSTANCE_ID), eq(OUTDATED_TAUPAGE)); final ArgumentCaptor<DescribeInstancesRequest> describeInstances = ArgumentCaptor.forClass(DescribeInstancesRequest.class); verify(mockEC2Client).describeInstances(describeInstances.capture()); assertThat(describeInstances.getValue().getNextToken()).isNull(); final ArgumentCaptor<DescribeImagesRequest> describeImages = ArgumentCaptor.forClass(DescribeImagesRequest.class); verify(mockEC2Client).describeImages(describeImages.capture()); assertThat(describeImages.getValue().getImageIds()).containsExactly(IMAGE_ID); verify(mockFetchTaupageYaml).getTaupageYaml(eq(INSTANCE_ID), eq(ACCOUNT_1), eq(REGION_1)); final ArgumentCaptor<Violation> violation = ArgumentCaptor.forClass(Violation.class); verify(mockViolationSink).put(violation.capture()); assertThat(violation.getValue()) .extracting( Violation::getAccountId, Violation::getRegion, Violation::getInstanceId, Violation::getViolationType) .containsExactly( ACCOUNT_1, REGION_1, INSTANCE_ID, OUTDATED_TAUPAGE); } @Test public void testFollowPagination() throws Exception { final DescribeInstancesResult result1 = new DescribeInstancesResult().withNextToken("123"); final DescribeInstancesResult result2 = new DescribeInstancesResult().withNextToken("456"); final DescribeInstancesResult result3 = new DescribeInstancesResult(); when(mockAccountIdSupplier.get()).thenReturn(singleton(ACCOUNT_1)); when(mockJobsProperties.getWhitelistedRegions()).thenReturn(singletonList(REGION_1)); when(mockClientProvider.getClient(eq(AmazonEC2Client.class), anyString(), any())).thenReturn(mockEC2Client); when(mockEC2Client.describeInstances(any(DescribeInstancesRequest.class))).thenReturn(result1, result2, result3); job.run(); verify(mockAccountIdSupplier).get(); verify(mockJobsProperties).getWhitelistedRegions(); verify(mockClientProvider).getClient(eq(AmazonEC2Client.class), eq(ACCOUNT_1), eq(getRegion(Regions.fromName(REGION_1)))); final ArgumentCaptor<DescribeInstancesRequest> describeInstances = ArgumentCaptor.forClass(DescribeInstancesRequest.class); verify(mockEC2Client, times(3)).describeInstances(describeInstances.capture()); assertThat(describeInstances.getAllValues()).extracting(DescribeInstancesRequest::getNextToken).containsExactly(null, "123", "456"); } }