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_power_off_not_present(self, mock_aws,
                                   mock_calculate_max_concurrent):
        """Create power_off event for running instance not present in the describe."""
        described_instances = {}  # empty means no instances found.
        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_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()
Esempio n. 4
0
    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)
Esempio n. 5
0
    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)
Esempio n. 6
0
 def test_initial_aws_describe_instances_account_disabled(self, mock_aws):
     """Test early return when account exists but is disabled."""
     account = account_helper.generate_cloud_account(is_enabled=False)
     tasks.initial_aws_describe_instances(account.id)
     mock_aws.get_session.assert_not_called()
Esempio n. 7
0
 def test_initial_aws_describe_instances_missing_account(self, mock_aws):
     """Test early return when account does not exist."""
     account_id = -1  # negative number account ID should never exist.
     tasks.initial_aws_describe_instances(account_id)
     mock_aws.get_session.assert_not_called()
    def test_initial_aws_describe_instances_twice(
        self,
        mock_util_aws,
        mock_aws,
        mock_start,
        mock_notify_sources,
        mock_schedule_concurrent_calculation_task,
    ):
        """
        Test calling initial_aws_describe_instances twice with no data changes.

        This test asserts appropriate behavior if we call account.enable after
        performing the initial AWS account describe once but without generating any new
        activity after the initial describe. This is likely to happen for AWS accounts
        with any long-running instances that have not changed state since the last time
        account.enable was called. Since our account availability checks routinely call
        account.enable to verify permissions, we need to test handling this use case.

        Why is this a concern? We used to naively *always* insert a new InstanceEvent
        for a running instance seen within initial_aws_describe_instances. That's not a
        problem in isolation; the new event would result in the runs being recreated and
        concurrent usage being recalculated. However, this becomes a problem if the
        instance's run spans many days (meaning many more recalculations) or if the
        account.enable function (which also calls initial_aws_describe_instances) is
        being called frequently, which may be the case since we cannot control when
        external callers hit an account's availability_check endpoint.

        A resolution to this concern is to perform an existence check before allowing
        initial_aws_describe_instances to insert a new InstanceEvent. If the most recent
        event is power_on, then we don't need to store another power_on, and we don't
        need to rebuild the run and recalculate concurrent usages. The updated expected
        behavior of calling account.enable shortly after initial_aws_describe_instances
        is that no new InstantEvent objects will be created by account.enable if a
        running described instance's most recent event is power_on.
        """
        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()
        ec2_ami_id = described_ami["ImageId"]
        described_instance = util_helper.generate_dummy_describe_instance(
            image_id=ec2_ami_id, state=aws.InstanceState.running)
        ec2_instance_id = described_instance["InstanceId"]
        described_instances = {region: [described_instance]}
        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, []

        date_of_initial_describe = util_helper.utc_dt(2020, 3, 1, 0, 0, 0)
        date_of_redundant_enable = util_helper.utc_dt(2020, 3, 2, 0, 0, 0)

        with util_helper.clouditardis(date_of_initial_describe):
            tasks.initial_aws_describe_instances(account.id)
            mock_start.assert_called_with(account.content_object.account_arn,
                                          ec2_ami_id, region)
            mock_schedule_concurrent_calculation_task.assert_called()

        # Reset because we need to check these mocks again later.
        mock_schedule_concurrent_calculation_task.reset_mock()
        mock_start.reset_mock()

        with patch.object(
                tasks, "initial_aws_describe_instances"
        ) as mock_initial, util_helper.clouditardis(date_of_redundant_enable):
            # 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()
            mock_notify_sources.delay.assert_called()

        with util_helper.clouditardis(date_of_redundant_enable):
            tasks.initial_aws_describe_instances(account.id)
            # start_image_inspection should not be called because we already know
            # about the image from the earlier initial_aws_describe_instances call.
            mock_start.assert_not_called()
            # schedule_concurrent_calculation_task should not be called because we
            # should not have generated any new events here that would require it.
            mock_schedule_concurrent_calculation_task.assert_not_called()

        # The relevant describe and account.enable processing is now done.
        # Now we just need to assert that we did not create redundant power_on events.
        instance_events = (AwsInstance.objects.get(
            ec2_instance_id=ec2_instance_id).instance.get().instanceevent_set.
                           order_by("occurred_at"))
        self.assertEqual(instance_events.count(), 1)
        instance_event = instance_events.first()
        self.assertEqual(instance_event.occurred_at, date_of_initial_describe)
        self.assertEqual(instance_event.event_type,
                         InstanceEvent.TYPE.power_on)