def test_generate_aws_ami_messages_deduplicate(self): """Test that messages are also deduplicated.""" region = util_helper.get_random_region() image_id_1 = util_helper.generate_dummy_image_id() image_id_2 = util_helper.generate_dummy_image_id() instance_1 = util_helper.generate_dummy_describe_instance( image_id=image_id_1) instance_2 = util_helper.generate_dummy_describe_instance( image_id=image_id_1) instance_3 = util_helper.generate_dummy_describe_instance( image_id=image_id_1) instance_4 = util_helper.generate_dummy_describe_instance( image_id=image_id_2) instances_data = { region: [instance_1, instance_2, instance_3, instance_4] } ami_list = [image_id_1, image_id_2] expected = [ { "cloud_provider": AWS_PROVIDER_STRING, "region": region, "image_id": image_id_1, }, { "cloud_provider": AWS_PROVIDER_STRING, "region": region, "image_id": image_id_2, }, ] result = generate_aws_ami_messages(instances_data, ami_list) self.assertEqual(len(result), len(expected)) for message in expected: self.assertIn(message, result)
def test_get_running_instances(self): """Assert we get expected instances in a dict keyed by regions.""" mock_arn = helper.generate_dummy_arn() mock_regions = [f'region-{uuid.uuid4()}'] mock_role = helper.generate_dummy_role() mock_running_instance = helper.generate_dummy_describe_instance( state=aws.InstanceState.running) mock_stopped_instance = helper.generate_dummy_describe_instance( state=aws.InstanceState.stopped) mock_described = { 'Reservations': [ { 'Instances': [ mock_running_instance, mock_stopped_instance, ], }, ], } expected_found = {mock_regions[0]: [mock_running_instance]} with patch.object(aws, 'get_regions') as mock_get_regions, \ patch.object(aws, 'boto3') as mock_boto3: mock_assume_role = mock_boto3.client.return_value.assume_role mock_assume_role.return_value = mock_role mock_get_regions.return_value = mock_regions mock_client = mock_boto3.Session.return_value.client.return_value mock_client.describe_instances.return_value = mock_described actual_found = aws.get_running_instances(aws.get_session(mock_arn)) self.assertDictEqual(expected_found, actual_found)
def test_create_succeeds_when_account_verified(self): """Test saving and processing of a test ARN.""" aws_account_id = util_helper.generate_dummy_aws_account_id() arn = util_helper.generate_dummy_arn(aws_account_id) role = util_helper.generate_dummy_role() region = random.choice(util_helper.SOME_AWS_REGIONS) running_instances = { region: [ util_helper.generate_dummy_describe_instance( state=aws.InstanceState.running), util_helper.generate_dummy_describe_instance( state=aws.InstanceState.stopping) ] } validated_data = { 'account_arn': arn, } mock_request = Mock() mock_request.user = util_helper.generate_test_user() context = {'request': mock_request} with patch.object(aws, 'verify_account_access') as mock_verify, \ patch.object(aws.sts, 'boto3') as mock_boto3, \ patch.object(aws, 'get_running_instances') as mock_get_run, \ patch.object(account_serializers, 'copy_ami_snapshot') as mock_copy_snapshot: mock_assume_role = mock_boto3.client.return_value.assume_role mock_assume_role.return_value = role mock_verify.return_value = True, [] mock_get_run.return_value = running_instances mock_copy_snapshot.return_value = None serializer = AwsAccountSerializer(context=context) result = serializer.create(validated_data) self.assertIsInstance(result, AwsAccount) account = AwsAccount.objects.get(aws_account_id=aws_account_id) self.assertEqual(aws_account_id, account.aws_account_id) self.assertEqual(arn, account.account_arn) instances = AwsInstance.objects.filter(account=account).all() self.assertEqual(len(running_instances[region]), len(instances)) for region, mock_instances_list in running_instances.items(): for mock_instance in mock_instances_list: instance_id = mock_instance['InstanceId'] instance = AwsInstance.objects.get(ec2_instance_id=instance_id) self.assertIsInstance(instance, AwsInstance) self.assertEqual(region, instance.region) event = InstanceEvent.objects.get(instance=instance) self.assertIsInstance(event, InstanceEvent) self.assertEqual(InstanceEvent.TYPE.power_on, event.event_type) amis = AwsMachineImage.objects.filter(account=account).all() self.assertEqual(len(running_instances[region]), len(amis))
def test_check_cluster_instances_under_limit(self, mock_aws, mock_boto3): """Assert logs info and debug if launch times are under the configured limit.""" instance_id_1 = helper.generate_dummy_instance_id() instance_id_2 = helper.generate_dummy_instance_id() instance_ids = [instance_id_1, instance_id_2] launch_time = TEST_TIME - timedelta( seconds=10) # less than 600 seconds older mock_aws.describe_instances.return_value = dict(( instance_id, helper.generate_dummy_describe_instance(instance_id, launch_time=launch_time), ) for instance_id in instance_ids) with self.assertLogs("api.clouds.aws.tasks.inspection", level="DEBUG") as logging_watcher: inspection.check_cluster_instances_age(instance_ids) self.assertEqual(len(logging_watcher.output), 4) self.assertInLog(instance_id_1, logging_watcher.output[0]) self.assertInLog(instance_id_2, logging_watcher.output[1]) self.assertInLog(instance_id_1, logging_watcher.output[2], "DEBUG") self.assertInLog(instance_id_2, logging_watcher.output[3], "DEBUG") self.assertInLog("fits within", logging_watcher.output[2], "DEBUG") self.assertInLog("10.0 seconds", logging_watcher.output[2], "DEBUG") self.assertInLog("fits within", logging_watcher.output[3], "DEBUG") self.assertInLog("10.0 seconds", logging_watcher.output[3], "DEBUG") mock_boto3.Session.assert_called_once() mock_aws.describe_instances.assert_called_once_with( mock_boto3.Session.return_value, instance_ids, mock_aws.ECS_CLUSTER_REGION, )
def test_check_cluster_instances_age_no_launch_time( self, mock_aws, mock_boto3): """Assert logs info and errors if no launch times from AWS.""" instance_id_1 = helper.generate_dummy_instance_id() instance_id_2 = helper.generate_dummy_instance_id() instance_ids = [instance_id_1, instance_id_2] mock_aws.describe_instances.return_value = dict(( instance_id, helper.generate_dummy_describe_instance(instance_id, launch_time=None), ) for instance_id in instance_ids) with self.assertLogs("api.clouds.aws.tasks.inspection", level="INFO") as logging_watcher: inspection.check_cluster_instances_age(instance_ids) self.assertEqual(len(logging_watcher.output), 4) self.assertInLog(instance_id_1, logging_watcher.output[0]) self.assertInLog(instance_id_2, logging_watcher.output[1]) self.assertInLog(instance_id_1, logging_watcher.output[2], "ERROR") self.assertInLog(instance_id_2, logging_watcher.output[3], "ERROR") self.assertInLog("no launch time", logging_watcher.output[2], "ERROR") self.assertInLog("no launch time", logging_watcher.output[3], "ERROR") mock_boto3.Session.assert_called_once() mock_aws.describe_instances.assert_called_once_with( mock_boto3.Session.return_value, instance_ids, mock_aws.ECS_CLUSTER_REGION, )
def test_save_instance_with_available_image(self): """Test that save instance events also writes image on instance.""" aws_account_id = util_helper.generate_dummy_aws_account_id() arn = util_helper.generate_dummy_arn(aws_account_id) account = api_helper.generate_cloud_account( arn=arn, aws_account_id=aws_account_id) region = util_helper.get_random_region() instances_data = { region: [ util_helper.generate_dummy_describe_instance( state=aws.InstanceState.running) ] } ami_id = instances_data[region][0]["ImageId"] mock_session = Mock() described_amis = util_helper.generate_dummy_describe_image( image_id=ami_id, owner_id=aws_account_id) with patch.object(util.aws, "describe_images") as mock_describe_images: mock_describe_images.return_value = [described_amis] util.create_new_machine_images(mock_session, instances_data) mock_describe_images.assert_called_with(mock_session, {ami_id}, region) awsinstance = util.save_instance(account, instances_data[region][0], region) instance = awsinstance.instance.get() self.assertEqual(instance.machine_image.content_object.ec2_ami_id, ami_id) self.assertEqual(instance.machine_image.status, MachineImage.PENDING)
def test_describe_instances(self): """Assert that describe_instances returns a dict of instances data.""" instance_ids = [ helper.generate_dummy_instance_id(), helper.generate_dummy_instance_id(), helper.generate_dummy_instance_id(), helper.generate_dummy_instance_id(), ] individual_described_instances = [ helper.generate_dummy_describe_instance(instance_id) for instance_id in instance_ids ] response = { "Reservations": [ { "Instances": individual_described_instances[:2], }, { "Instances": individual_described_instances[2:], }, ], } mock_session = Mock() mock_client = mock_session.client.return_value mock_client.describe_instances.return_value = response region = helper.get_random_region() described_instances = ec2.describe_instances(mock_session, instance_ids, region) self.assertEqual(set(described_instances.keys()), set(instance_ids)) for described_instance in individual_described_instances: self.assertIn(described_instance, described_instances.values())
def test_create_new_machine_images_with_windows_image(self): """Test that new windows machine images are marked appropriately.""" aws_account_id = util_helper.generate_dummy_aws_account_id() arn = util_helper.generate_dummy_arn(aws_account_id) account = AwsAccount( account_arn=arn, aws_account_id=aws_account_id, user=util_helper.generate_test_user(), ) account.save() region = random.choice(util_helper.SOME_AWS_REGIONS) running_instances = { region: [ util_helper.generate_dummy_describe_instance( state=aws.InstanceState.running ) ] } running_instances[region][0]['Platform'] = 'Windows' ami_id = running_instances[region][0]['ImageId'] result = util.create_new_machine_images(account, running_instances) amis = AwsMachineImage.objects.filter(account=account).all() self.assertEqual(result, [ami_id]) for ami in amis: self.assertEqual(ami.ec2_ami_id, ami_id) self.assertEqual(ImageTag.objects.filter( description='windows').first(), ami.tags.filter(description='windows').first())
def test_create_new_machine_images_with_windows_image(self): """Test that new windows machine images are marked appropriately.""" aws_account_id = util_helper.generate_dummy_aws_account_id() arn = util_helper.generate_dummy_arn(aws_account_id) api_helper.generate_cloud_account(arn=arn, aws_account_id=aws_account_id) region = util_helper.get_random_region() instances_data = { region: [ util_helper.generate_dummy_describe_instance( state=aws.InstanceState.running) ] } instances_data[region][0]["Platform"] = "Windows" ami_id = instances_data[region][0]["ImageId"] mock_session = Mock() described_amis = util_helper.generate_dummy_describe_image( image_id=ami_id, owner_id=aws_account_id) with patch.object(util.aws, "describe_images") as mock_describe_images: mock_describe_images.return_value = [described_amis] result = util.create_new_machine_images(mock_session, instances_data) mock_describe_images.assert_called_with(mock_session, {ami_id}, region) self.assertEqual(result, [ami_id]) images = list(AwsMachineImage.objects.all()) self.assertEqual(len(images), 1) self.assertEqual(images[0].ec2_ami_id, ami_id) self.assertEqual(images[0].platform, AwsMachineImage.WINDOWS)
def test_check_cluster_volume_mounts_not_ready( self, mock_describe_cluster_instances): """Assert checking the volume mounts when they are not ready.""" device_mappings = [ util_helper.generate_dummy_block_device_mapping( device_name=self.ami_mountpoints[0][1], status="attaching"), util_helper.generate_dummy_block_device_mapping( device_name=self.ami_mountpoints[1][1], status="suspicious"), ] described_instances = { self.ec2_instance_id: util_helper.generate_dummy_describe_instance( instance_id=self.ec2_instance_id, device_mappings=device_mappings) } mock_describe_cluster_instances.return_value = described_instances with self.assertLogs("api.clouds.aws.tasks.inspection", level="INFO") as logging_watcher: volumes_mounted = tasks.inspection._check_cluster_volume_mounts( self.ec2_instance_id, self.ami_mountpoints) self.assertFalse(volumes_mounted) self.assertEqual(len(logging_watcher.output), 3) self.assertIn("INFO", logging_watcher.output[0]) self.assertIn("is still attaching", logging_watcher.output[0]) self.assertIn("ERROR", logging_watcher.output[1]) self.assertIn("has unexpected status", logging_watcher.output[1]) self.assertIn("WARNING", logging_watcher.output[2]) self.assertIn("not found in", logging_watcher.output[2])
def test_add_messages_to_queue(self, mock_kombu): """Test that messages get added to a message queue.""" queue_name = 'Test Queue' region = random.choice(util_helper.SOME_AWS_REGIONS) instance = util_helper.generate_dummy_describe_instance() instances_data = {region: [instance]} ami_list = [instance['ImageId']] messages = util.generate_aws_ami_messages(instances_data, ami_list) mock_routing_key = queue_name mock_body = messages[0] mock_exchange = mock_kombu.Exchange.return_value mock_queue = mock_kombu.Queue.return_value mock_conn = mock_kombu.Connection.return_value mock_with_conn = mock_conn.__enter__.return_value mock_producer = mock_with_conn.Producer.return_value mock_pub = mock_producer.publish util.add_messages_to_queue(queue_name, messages) mock_pub.assert_called_with(mock_body, retry=True, exchange=mock_exchange, routing_key=mock_routing_key, declare=[mock_queue])
def test_instance_already_power_off(self, mock_aws, mock_calculate_max_concurrent): """Don't create power_off for instance that already has recent power_off.""" event = account_helper.generate_single_instance_event( self.instance, self.power_off_time, event_type=InstanceEvent.TYPE.power_off) self.process_event(event) mock_calculate_max_concurrent.reset_mock() described_instances = { self.region: [ util_helper.generate_dummy_describe_instance( instance_id=self.aws_instance.ec2_instance_id, state=aws.InstanceState.stopped, ) ] } mock_aws.describe_instances_everywhere.return_value = described_instances describe_time = self.power_off_time + datetime.timedelta(days=1) with util_helper.clouditardis(describe_time): tasks.initial_aws_describe_instances(self.account.id) mock_calculate_max_concurrent.assert_not_called() self.assertExpectedEventsAndRun()
def test_create_new_machine_images(self): """Test that new machine images are saved to the DB.""" aws_account_id = util_helper.generate_dummy_aws_account_id() arn = util_helper.generate_dummy_arn(aws_account_id) account = AwsAccount( account_arn=arn, aws_account_id=aws_account_id, user=util_helper.generate_test_user(), ) account.save() region = random.choice(util_helper.SOME_AWS_REGIONS) running_instances = { region: [ util_helper.generate_dummy_describe_instance( state=aws.InstanceState.running ) ] } ami_id = running_instances[region][0]['ImageId'] result = util.create_new_machine_images(account, running_instances) amis = AwsMachineImage.objects.filter(account=account).all() self.assertEqual(result, [ami_id]) for ami in amis: self.assertEqual(ami.ec2_ami_id, ami_id)
def test_create_succeeds_when_account_verified(self): """Test saving and processing of a test ARN.""" aws_account_id = util_helper.generate_dummy_aws_account_id() arn = util_helper.generate_dummy_arn(aws_account_id) role = util_helper.generate_dummy_role() region = f'region-{uuid.uuid4()}' running_instances = { region: [ util_helper.generate_dummy_describe_instance( state=aws.InstanceState.running), util_helper.generate_dummy_describe_instance( state=aws.InstanceState.stopping) ] } validated_data = { 'account_arn': arn, } with patch.object(aws, 'verify_account_access') as mock_verify, \ patch.object(aws, 'boto3') as mock_boto3, \ patch.object(aws, 'get_running_instances') as mock_get_running: mock_assume_role = mock_boto3.client.return_value.assume_role mock_assume_role.return_value = role mock_verify.return_value = True mock_get_running.return_value = running_instances serializer = AwsAccountSerializer() result = serializer.create(validated_data) self.assertIsInstance(result, AwsAccount) account = AwsAccount.objects.get(aws_account_id=aws_account_id) self.assertEqual(aws_account_id, account.aws_account_id) self.assertEqual(arn, account.account_arn) instances = AwsInstance.objects.filter(account=account).all() self.assertEqual(len(running_instances[region]), len(instances)) for region, mock_instances_list in running_instances.items(): for mock_instance in mock_instances_list: instance_id = mock_instance['InstanceId'] instance = AwsInstance.objects.get(ec2_instance_id=instance_id) self.assertIsInstance(instance, AwsInstance) self.assertEqual(region, instance.region) event = InstanceEvent.objects.get(instance=instance) self.assertIsInstance(event, InstanceEvent) self.assertEqual(InstanceEvent.TYPE.power_on, event.event_type)
def test_create_fails_when_another_arn_has_same_aws_account_id(self): """Test that an account is not saved if ARN reuses an AWS account.""" user = util_helper.generate_test_user() aws_account_id = util_helper.generate_dummy_aws_account_id() arn = util_helper.generate_dummy_arn(aws_account_id) role = util_helper.generate_dummy_role() region = random.choice(util_helper.SOME_AWS_REGIONS) running_instances = { region: [ util_helper.generate_dummy_describe_instance( state=aws.InstanceState.running), util_helper.generate_dummy_describe_instance( state=aws.InstanceState.stopping) ] } validated_data = { 'account_arn': arn, } # Create one with the same AWS account ID but a different ARN. account_helper.generate_aws_account( aws_account_id=aws_account_id, user=user, ) mock_request = Mock() mock_request.user = util_helper.generate_test_user() context = {'request': mock_request} with patch.object(aws, 'verify_account_access') as mock_verify, \ patch.object(aws.sts, 'boto3') as mock_boto3, \ patch.object(aws, 'get_running_instances') as mock_get_run: mock_assume_role = mock_boto3.client.return_value.assume_role mock_assume_role.return_value = role mock_verify.return_value = True, [] mock_get_run.return_value = running_instances serializer = AwsAccountSerializer(context=context) with self.assertRaises(ValidationError) as cm: serializer.create(validated_data) raised_exception = cm.exception self.assertIn('account_arn', raised_exception.detail) self.assertIn(aws_account_id, raised_exception.detail['account_arn'][0])
def test_generate_dummy_describe_instance_default(self): """Assert generated instance has values where expected.""" instance = helper.generate_dummy_describe_instance() self.assertIsNotNone(instance['ImageId']) self.assertIsNotNone(instance['InstanceId']) self.assertIsNotNone(instance['InstanceType']) self.assertIsNotNone(instance['SubnetId']) self.assertIsNotNone(instance['State']) self.assertIsNotNone(instance['State']['Code']) self.assertIsNotNone(instance['State']['Name'])
def test_generate_dummy_describe_instance_default(self): """Assert generated instance has values where expected.""" instance = helper.generate_dummy_describe_instance() self.assertIsNotNone(instance["ImageId"]) self.assertIsNotNone(instance["InstanceId"]) self.assertIsNotNone(instance["InstanceType"]) self.assertIsNotNone(instance["SubnetId"]) self.assertIsNotNone(instance["State"]) self.assertIsNotNone(instance["State"]["Code"]) self.assertIsNotNone(instance["State"]["Name"]) self.assertEqual(len(instance["BlockDeviceMappings"]), 2)
def test_generate_aws_ami_messages(self): """Test that messages are formatted correctly.""" region = random.choice(util_helper.SOME_AWS_REGIONS) instance = util_helper.generate_dummy_describe_instance() instances_data = {region: [instance]} ami_list = [instance['ImageId']] expected = [{'cloud_provider': AWS_PROVIDER_STRING, 'region': region, 'image_id': instance['ImageId']}] result = util.generate_aws_ami_messages(instances_data, ami_list) self.assertEqual(result, expected)
def test_generate_dummy_describe_instance_with_values(self): """Assert generated instance contains given values.""" image_id = helper.generate_dummy_image_id() instance_id = helper.generate_dummy_instance_id() subnet_id = helper.generate_dummy_subnet_id() state = aws.InstanceState.shutting_down instance_type = random.choice(helper.SOME_EC2_INSTANCE_TYPES) instance = helper.generate_dummy_describe_instance( instance_id, image_id, subnet_id, state, instance_type) self.assertEqual(instance['ImageId'], image_id) self.assertEqual(instance['InstanceId'], instance_id) self.assertEqual(instance['InstanceType'], instance_type) self.assertEqual(instance['SubnetId'], subnet_id) self.assertEqual(instance['State']['Code'], state.value) self.assertEqual(instance['State']['Name'], state.name)
def test_generate_aws_ami_messages(self): """Test that messages are formatted correctly.""" region = util_helper.get_random_region() instance = util_helper.generate_dummy_describe_instance() instances_data = {region: [instance]} ami_list = [instance["ImageId"]] expected = [{ "cloud_provider": AWS_PROVIDER_STRING, "region": region, "image_id": instance["ImageId"], }] result = generate_aws_ami_messages(instances_data, ami_list) self.assertEqual(result, expected)
def test_instance_found_not_running(self, mock_aws, mock_calculate_max_concurrent): """Create power_off event for instance found stopped in the describe.""" described_instances = { self.region: [ util_helper.generate_dummy_describe_instance( instance_id=self.aws_instance.ec2_instance_id, state=aws.InstanceState.stopped, ) ] } mock_aws.describe_instances_everywhere.return_value = described_instances with util_helper.clouditardis(self.power_off_time): tasks.initial_aws_describe_instances(self.account.id) mock_calculate_max_concurrent.assert_called() self.assertExpectedEventsAndRun()
def test_save_instance_with_missing_machineimage(self): """ Test that save_instance works around a missing MachineImage. This is a use case that we *shouldn't* need, but until we identify how exactly AwsMachineImage objects are being saved without their matching MachineImage objects, we have this workaround to create the MachineImage at the time it is needed. """ aws_account_id = util_helper.generate_dummy_aws_account_id() arn = util_helper.generate_dummy_arn(aws_account_id) account = api_helper.generate_cloud_account( arn=arn, aws_account_id=aws_account_id) region = util_helper.get_random_region() instances_data = { region: [ util_helper.generate_dummy_describe_instance( state=aws.InstanceState.running) ] } ami_id = instances_data[region][0]["ImageId"] # Create the AwsMachineImage without its paired MachineImage. aws_machine_image = AwsMachineImage.objects.create( owner_aws_account_id=util_helper.generate_dummy_aws_account_id(), ec2_ami_id=ami_id, platform="none", ) # Verify that the MachineImage was *not* created before proceeding. with self.assertRaises(MachineImage.DoesNotExist): aws_machine_image.machine_image.get() awsinstance = util.save_instance(account, instances_data[region][0], region) instance = awsinstance.instance.get() self.assertEqual(instance.machine_image.content_object.ec2_ami_id, ami_id) self.assertEqual(instance.machine_image.status, MachineImage.UNAVAILABLE)
def test_check_cluster_volume_mounts_success( self, mock_describe_cluster_instances): """Assert successfully checking the volume mounts are ready.""" device_mappings = [ util_helper.generate_dummy_block_device_mapping( device_name=self.ami_mountpoints[0][1]), util_helper.generate_dummy_block_device_mapping( device_name=self.ami_mountpoints[1][1]), util_helper.generate_dummy_block_device_mapping( device_name=self.ami_mountpoints[2][1]), ] described_instances = { self.ec2_instance_id: util_helper.generate_dummy_describe_instance( instance_id=self.ec2_instance_id, device_mappings=device_mappings) } mock_describe_cluster_instances.return_value = described_instances volumes_mounted = tasks.inspection._check_cluster_volume_mounts( self.ec2_instance_id, self.ami_mountpoints) self.assertTrue(volumes_mounted)
def test_save_instance_with_unavailable_image(self): """Test that save instance events also writes image on instance.""" aws_account_id = util_helper.generate_dummy_aws_account_id() arn = util_helper.generate_dummy_arn(aws_account_id) account = api_helper.generate_cloud_account( arn=arn, aws_account_id=aws_account_id) region = util_helper.get_random_region() instances_data = { region: [ util_helper.generate_dummy_describe_instance( state=aws.InstanceState.running) ] } ami_id = instances_data[region][0]["ImageId"] awsinstance = util.save_instance(account, instances_data[region][0], region) instance = awsinstance.instance.get() self.assertEqual(instance.machine_image.content_object.ec2_ami_id, ami_id) self.assertEqual(instance.machine_image.status, MachineImage.UNAVAILABLE)
def test_generate_dummy_describe_instance_with_values(self): """Assert generated instance contains given values.""" image_id = helper.generate_dummy_image_id() instance_id = helper.generate_dummy_instance_id() subnet_id = helper.generate_dummy_subnet_id() state = aws.InstanceState.shutting_down instance_type = helper.get_random_instance_type() device_mapping = helper.generate_dummy_block_device_mapping() instance = helper.generate_dummy_describe_instance( instance_id=instance_id, image_id=image_id, subnet_id=subnet_id, state=state, instance_type=instance_type, device_mappings=[device_mapping], ) self.assertEqual(instance["ImageId"], image_id) self.assertEqual(instance["InstanceId"], instance_id) self.assertEqual(instance["InstanceType"], instance_type) self.assertEqual(instance["SubnetId"], subnet_id) self.assertEqual(instance["State"]["Code"], state.value) self.assertEqual(instance["State"]["Name"], state.name) self.assertEqual(instance["State"]["Name"], state.name) self.assertEqual(instance["BlockDeviceMappings"], [device_mapping])
def test_initial_aws_describe_instances( self, mock_util_aws, mock_aws, mock_start, mock_calculate_concurrent_usage_task): """ Test happy-path behaviors of initial_aws_describe_instances. This test simulates a situation in which three running instances are found. One instance has the Windows platform, another instance has its image tagged for OpenShift, and a third instance has neither of those. The end result is that the three running instances should be saved, three power-on events should be saved (one for each instance), three images should be saved, and two new tasks should be spawned for inspecting the two not-windows images. """ account = account_helper.generate_cloud_account() # Set up mocked data in AWS API responses. region = util_helper.get_random_region() described_ami_unknown = util_helper.generate_dummy_describe_image() described_ami_openshift = util_helper.generate_dummy_describe_image( openshift=True) described_ami_windows = util_helper.generate_dummy_describe_image() ami_id_unknown = described_ami_unknown["ImageId"] ami_id_openshift = described_ami_openshift["ImageId"] ami_id_windows = described_ami_windows["ImageId"] ami_id_unavailable = util_helper.generate_dummy_image_id() ami_id_gone = util_helper.generate_dummy_image_id() all_instances = [ util_helper.generate_dummy_describe_instance( image_id=ami_id_unknown, state=aws.InstanceState.running), util_helper.generate_dummy_describe_instance( image_id=ami_id_openshift, state=aws.InstanceState.running), util_helper.generate_dummy_describe_instance( image_id=ami_id_windows, state=aws.InstanceState.running, platform=AwsMachineImage.WINDOWS, ), util_helper.generate_dummy_describe_instance( image_id=ami_id_unavailable, state=aws.InstanceState.running), util_helper.generate_dummy_describe_instance( image_id=ami_id_gone, state=aws.InstanceState.terminated), ] described_instances = {region: all_instances} mock_aws.describe_instances_everywhere.return_value = described_instances mock_util_aws.describe_images.return_value = [ described_ami_unknown, described_ami_openshift, described_ami_windows, ] mock_util_aws.is_windows.side_effect = aws.is_windows mock_util_aws.OPENSHIFT_TAG = aws.OPENSHIFT_TAG mock_util_aws.InstanceState.is_running = aws.InstanceState.is_running start_inspection_calls = [ call( account.content_object.account_arn, described_ami_unknown["ImageId"], region, ) ] tasks.initial_aws_describe_instances(account.id) mock_start.assert_has_calls(start_inspection_calls) # Verify that we created all five instances. instances_count = Instance.objects.filter( cloud_account=account).count() self.assertEqual(instances_count, 5) # Verify that the running instances exist with power-on events. for described_instance in all_instances[:4]: instance_id = described_instance["InstanceId"] aws_instance = AwsInstance.objects.get(ec2_instance_id=instance_id) self.assertIsInstance(aws_instance, AwsInstance) self.assertEqual(region, aws_instance.region) event = InstanceEvent.objects.get( instance=aws_instance.instance.get()) self.assertIsInstance(event, InstanceEvent) self.assertEqual(InstanceEvent.TYPE.power_on, event.event_type) # Verify that the not-running instances exist with no events. for described_instance in all_instances[4:]: instance_id = described_instance["InstanceId"] aws_instance = AwsInstance.objects.get(ec2_instance_id=instance_id) self.assertIsInstance(aws_instance, AwsInstance) self.assertEqual(region, aws_instance.region) self.assertFalse( InstanceEvent.objects.filter( instance=aws_instance.instance.get()).exists()) # Verify that we saved images for all instances, even if not running. images_count = AwsMachineImage.objects.count() self.assertEqual(images_count, 5) aws_image = AwsMachineImage.objects.get(ec2_ami_id=ami_id_unknown) image = aws_image.machine_image.get() self.assertFalse(image.rhel_detected) self.assertFalse(image.openshift_detected) self.assertEqual(image.name, described_ami_unknown["Name"]) aws_image = AwsMachineImage.objects.get(ec2_ami_id=ami_id_openshift) image = aws_image.machine_image.get() self.assertFalse(image.rhel_detected) self.assertTrue(image.openshift_detected) self.assertEqual(image.name, described_ami_openshift["Name"]) aws_image = AwsMachineImage.objects.get(ec2_ami_id=ami_id_windows) image = aws_image.machine_image.get() self.assertFalse(image.rhel_detected) self.assertFalse(image.openshift_detected) self.assertEqual(image.name, described_ami_windows["Name"]) self.assertEqual(aws_image.platform, AwsMachineImage.WINDOWS) aws_image = AwsMachineImage.objects.get(ec2_ami_id=ami_id_unavailable) image = aws_image.machine_image.get() self.assertFalse(image.rhel_detected) self.assertFalse(image.openshift_detected) self.assertEqual(image.status, MachineImage.UNAVAILABLE)
def test_initial_aws_describe_instances_after_disable_enable( self, mock_util_aws, mock_aws, mock_start, mock_sources_notify, mock_schedule_concurrent_calculation_task, ): """ Test calling initial_aws_describe_instances multiple times. Historically (and in the simplified ideal happy-path), we would only ever call initial_aws_describe_instances exactly once when an AwsCloudAccount was first created. However, since we added support to disable accounts, it's now possible (and likely common) for an account to be created (and enabled), call describe, be disabled, be enabled again, and call describe again during the enable. We need to ensure that running described instances that generated "power_off" InstanceEvents as a result of AwsCloudAccount.disable would later correctly get new "power_on" InstanceEvents as a result of AwsCloudAccount.enable. """ account = account_helper.generate_cloud_account() # Set up mocked data in AWS API responses. region = util_helper.get_random_region() described_ami = util_helper.generate_dummy_describe_image() ami_id = described_ami["ImageId"] all_instances = [ util_helper.generate_dummy_describe_instance( image_id=ami_id, state=aws.InstanceState.running), ] described_instances = {region: all_instances} mock_aws.describe_instances_everywhere.return_value = described_instances mock_util_aws.describe_images.return_value = [described_ami] mock_util_aws.is_windows.side_effect = aws.is_windows mock_util_aws.InstanceState.is_running = aws.InstanceState.is_running mock_util_aws.AwsArn = aws.AwsArn mock_util_aws.verify_account_access.return_value = True, [] inspection_call = call( account.content_object.account_arn, described_ami["ImageId"], region, ) date_of_initial_describe = util_helper.utc_dt(2020, 3, 1, 0, 0, 0) date_of_disable = util_helper.utc_dt(2020, 3, 2, 0, 0, 0) date_of_reenable = util_helper.utc_dt(2020, 3, 3, 0, 0, 0) with util_helper.clouditardis(date_of_initial_describe): tasks.initial_aws_describe_instances(account.id) mock_start.assert_has_calls([inspection_call]) with util_helper.clouditardis(date_of_disable): account.disable() mock_util_aws.delete_cloudtrail.assert_called() # Before calling "start_image_inspection" again, let's change the mocked return # values from AWS to include another instance and image. We should ultimately # expect the original instance + ami to be found but *not* describe its image. # Only the new instance + ami should be fully described. described_ami_2 = util_helper.generate_dummy_describe_image() ami_id_2 = described_ami_2["ImageId"] all_instances.append( util_helper.generate_dummy_describe_instance( image_id=ami_id_2, state=aws.InstanceState.running)) described_instances = {region: all_instances} mock_aws.describe_instances_everywhere.return_value = described_instances mock_util_aws.describe_images.return_value = [ described_ami, described_ami_2 ] inspection_call_2 = call( account.content_object.account_arn, described_ami_2["ImageId"], region, ) with patch.object( tasks, "initial_aws_describe_instances" ) as mock_initial, util_helper.clouditardis(date_of_reenable): # Even though we want to test initial_aws_describe_instances, we need to # mock this particular call because we need to short-circuit Celery. account.enable() mock_initial.delay.assert_called() with util_helper.clouditardis(date_of_reenable): tasks.initial_aws_describe_instances(account.id) mock_start.assert_has_calls([inspection_call_2]) # Now that the dust has settled, let's check that the two instances have events # in the right configuration. The first instance is on, off, and on; the second # instance is on. aws_instance_1_events = (AwsInstance.objects.get( ec2_instance_id=all_instances[0]["InstanceId"]).instance.get( ).instanceevent_set.order_by("occurred_at").all()) self.assertEqual(len(aws_instance_1_events), 3) instance_1_event_1 = aws_instance_1_events[0] self.assertEqual(instance_1_event_1.occurred_at, date_of_initial_describe) self.assertEqual(instance_1_event_1.event_type, InstanceEvent.TYPE.power_on) instance_1_event_2 = aws_instance_1_events[1] self.assertEqual(instance_1_event_2.occurred_at, date_of_disable) self.assertEqual(instance_1_event_2.event_type, InstanceEvent.TYPE.power_off) instance_1_event_3 = aws_instance_1_events[2] self.assertEqual(instance_1_event_3.occurred_at, date_of_reenable) self.assertEqual(instance_1_event_3.event_type, InstanceEvent.TYPE.power_on) aws_instance_2_events = (AwsInstance.objects.get( ec2_instance_id=all_instances[1]["InstanceId"]).instance.get( ).instanceevent_set.order_by("occurred_at").all()) self.assertEqual(len(aws_instance_2_events), 1) instance_2_event_1 = aws_instance_2_events[0] self.assertEqual(instance_2_event_1.occurred_at, date_of_reenable) self.assertEqual(instance_2_event_1.event_type, InstanceEvent.TYPE.power_on)
def test_is_instance_windows_with_other_platform(self): """Test that an instance with Platform 'other' is not windows.""" dummy_instance = helper.generate_dummy_describe_instance( platform='other') self.assertFalse(ec2.is_instance_windows(dummy_instance))
def test_is_instance_windows_with_empty_platform(self): """Test that an instance with no Platform is not windows.""" dummy_instance = helper.generate_dummy_describe_instance() self.assertFalse(ec2.is_instance_windows(dummy_instance))
def test_is_instance_windows_with_unexpected_case(self): """Test that an instance with Platform 'WiNdOwS' is windows.""" dummy_instance = helper.generate_dummy_describe_instance( platform='WiNdOwS') self.assertTrue(ec2.is_instance_windows(dummy_instance))