Пример #1
0
    def test_valid_account_number_and_org_admin_fake_header_defaults(self):
        """
        Test with valid account_number and org_admin returns defaults.

        Test with an internal account header, valid account_number and True
        org_admin headers, expect a normal response for the default start_date
        of "yesterday".
        """
        yesterday = get_today() - datetime.timedelta(days=1)
        today = get_today()
        api_helper.calculate_concurrent(yesterday, today, self.user1.id)
        fake_header = util_helper.get_identity_auth_header(
            account_number=self.user1.username,
            is_org_admin=True,
        )

        client = APIClient()
        client.credentials(
            HTTP_X_RH_IDENTITY=util_helper.get_internal_identity_auth_header(),
            HTTP_X_RH_INTERNAL_FAKE_IDENTITY=fake_header,
        )
        response = client.get(self.concurrent_api_url, data={}, format="json")
        body = response.json()
        self.assertEqual(body["meta"]["count"], 1)
        self.assertEqual(len(body["data"]), 1)
        self.assertEqual(body["data"][0]["date"], str(yesterday))
Пример #2
0
    def enable(self):
        """
        Mark this CloudAccount as enabled and perform operations to make it so.

        This has the side effect of calling the related content_object (e.g.
        AwsCloudAccount) to make any cloud-specific changes. If any that cloud-specific
        function fails, we rollback our state change and re-raise the exception for the
        caller to handle further.
        """
        logger.info(
            _("'is_enabled' is %(is_enabled)s before enabling %(cloudaccount)s"),
            {"is_enabled": self.is_enabled, "cloudaccount": self},
        )
        if not self.is_enabled:
            self.is_enabled = True
            self.enabled_at = get_now()
            self.save()
        try:
            self.content_object.enable()
            # delete stale ConcurrentUsage when an clount is enabled
            ConcurrentUsage.objects.filter(user=self.user, date=get_today()).delete()
        except Exception as e:
            # All failure notifications should happen during the failure
            logger.info(e)
            transaction.set_rollback(True)
            return False

        sources.notify_application_availability(
            self.user.username, self.platform_application_id, "available"
        )
Пример #3
0
    def get_queryset(self):  # noqa: C901
        """Get the queryset of dates filtered to the appropriate inputs."""
        user = self.request.user
        errors = {}
        tomorrow = get_today() + timedelta(days=1)
        try:
            start_date = self.request.query_params.get("start_date", None)
            start_date = parse(
                start_date).date() if start_date else get_today()
            # Start date is inclusive, if start date is tomorrow or after,
            # we do not return anything
            if start_date >= tomorrow:
                errors["start_date"] = [
                    _("start_date cannot be in the future.")
                ]
        except ValueError:
            errors["start_date"] = [
                _("start_date must be a date (YYYY-MM-DD).")
            ]

        try:
            end_date = self.request.query_params.get("end_date", None)
            # End date is noninclusive, set it to tomorrow if one is not provided
            end_date = parse(end_date).date() if end_date else tomorrow
            # If end date is after tomorrow, we do not return anything
            if end_date > tomorrow:
                errors["end_date"] = [_("end_date cannot be in the future.")]
            if end_date <= user.date_joined.date():
                errors["end_date"] = [
                    _("end_date must be after user creation date.")
                ]
        except ValueError:
            errors["end_date"] = [_("end_date must be a date (YYYY-MM-DD).")]

        if errors:
            raise exceptions.ValidationError(errors)

        queryset = DailyConcurrentUsageDummyQueryset(start_date, end_date,
                                                     user.id)
        return queryset
Пример #4
0
 def days(self):
     """Get all days."""
     if self._days is None:
         tomorrow = get_today() + timedelta(days=1)
         days = []
         current = self.start_date
         delta_day = timedelta(days=1)
         end_date = min(self.end_date, tomorrow)
         while current < end_date:
             days.append(current)
             current += delta_day
         self._days = days
     return self._days
    def test_start_date_end_date_defaults(self):
        """
        Test with no start_date and no end_date set.

        Default start_date is "yesterday" and default end_date is "today", and
        since start_date is inclusive and end_date is exclusive, the resulting
        output should be data for one day: yesterday.
        """
        yesterday = get_today() - datetime.timedelta(days=1)
        today = get_today()
        data = {}
        api_helper.calculate_concurrent(yesterday, today, self.user1.id)

        client = APIClient()
        client.force_authenticate(user=self.user1)
        response = client.get(self.concurrent_api_url,
                              data=data,
                              format="json")
        body = response.json()
        self.assertEqual(body["meta"]["count"], 1)
        self.assertEqual(len(body["data"]), 1)
        self.assertEqual(body["data"][0]["date"], str(yesterday))
Пример #6
0
    def test_invalid_account_number(self):
        """
        Test with an invalid account_number fake identity.

        Test with an internal account header, an invalid account_number and True
        org_admin fake identity header, expect an authentication error.
        """
        yesterday = get_today() - datetime.timedelta(days=1)
        today = get_today()
        fake_header = util_helper.get_identity_auth_header(
            account_number=str(_faker.pyint()),
            is_org_admin=True,
        )
        api_helper.calculate_concurrent(yesterday, today, self.user1.id)

        client = APIClient()
        client.credentials(
            HTTP_X_RH_IDENTITY=util_helper.get_internal_identity_auth_header(),
            HTTP_X_RH_INTERNAL_FAKE_IDENTITY=fake_header,
        )
        response = client.get(self.concurrent_api_url, data={}, format="json")
        body = response.json()
        self.assertEqual(response.status_code, 403)
        self.assertEqual(body["detail"], "Incorrect authentication credentials.")
Пример #7
0
    def test_with_false_is_org_admin_parameter(self):
        """
        Test with a False is_org_admin parameter.

        Test with an internal account header, a valid account_number and False
        org_admin headers, expect an authentication error.
        """
        yesterday = get_today() - datetime.timedelta(days=1)
        today = get_today()
        api_helper.calculate_concurrent(yesterday, today, self.user1.id)
        fake_header = util_helper.get_identity_auth_header(
            account_number=self.user1.username,
            is_org_admin=False,
        )

        client = APIClient()
        client.credentials(
            HTTP_X_RH_IDENTITY=util_helper.get_internal_identity_auth_header(),
            HTTP_X_RH_INTERNAL_FAKE_IDENTITY=fake_header,
        )
        response = client.get(self.concurrent_api_url, data={}, format="json")
        body = response.json()
        self.assertEqual(response.status_code, 403)
        self.assertEqual(body["detail"], "User must be an org admin.")
Пример #8
0
    def test_future_start_date_returns_400(self):
        """
        Test with far-future start_date, expecting it to return 400.

        When an start_date is given that is tomorrow or later, we return
        a 400 response, because we cannot predict the future.
        """
        today = get_today()
        future = today + datetime.timedelta(days=1)
        data = {"start_date": str(future)}
        api_helper.calculate_concurrent(today, future, self.user1.id)

        client = APIClient()
        client.force_authenticate(user=self.user1)
        response = client.get(self.concurrent_api_url, data=data, format="json")
        self.assertEqual(response.status_code, 400)

        body = response.json()
        self.assertEqual(body["start_date"], [_("start_date cannot be in the future.")])
Пример #9
0
    def test_future_start_and_end_date_returns_400(self):
        """
        Test with far-future start_date and end_date, expecting no results.

        If the request filters result in dates that are only in the future, we must
        always expect a 400 response because we cannot possibly know
        anything beyond today.
        """
        today = get_today()
        future_start = today + datetime.timedelta(days=50)
        future_end = today + datetime.timedelta(days=100)
        data = {"start_date": str(future_start), "end_date": str(future_end)}
        client = APIClient()
        client.force_authenticate(user=self.user1)
        response = client.get(self.concurrent_api_url, data=data, format="json")
        self.assertEqual(response.status_code, 400)

        body = response.json()
        self.assertEqual(body["start_date"], [_("start_date cannot be in the future.")])
        self.assertEqual(body["end_date"], [_("end_date cannot be in the future.")])
Пример #10
0
    def test_concurrent_usage_deleted_when_clount_enables(
        self,
        mock_initial_aws_describe_instances,
        mock_verify_permissions,
        mock_notify_sources,
    ):
        """Test stale ConcurrentUsage is deleted when clount is enabled."""
        user = util_helper.generate_test_user()

        concurrent_usage = ConcurrentUsage(user=user, date=get_today())
        concurrent_usage.save()

        self.assertEqual(1, ConcurrentUsage.objects.filter(user=user).count())
        aws_account_id = util_helper.generate_dummy_aws_account_id()
        account = api_helper.generate_cloud_account(
            aws_account_id=aws_account_id,
            user=user,
        )
        account.enable()
        self.assertEqual(0, ConcurrentUsage.objects.filter(user=user).count())
Пример #11
0
 def latest_start_date(self):
     """Return the latest allowed start_date."""
     return get_today() - timedelta(days=1)
Пример #12
0
 def latest_end_date(self):
     """Return the latest allowed end_date."""
     return get_today() + timedelta(days=1)
Пример #13
0
 def latest_start_date(self):
     """Return the latest allowed start_date."""
     return get_today()
Пример #14
0
def calculate_max_concurrent_usage_from_runs(runs):
    """
    Calculate maximum concurrent usage of RHEL instances from given Runs.

    We try to find the common intersection of the given runs across the dates,
    users, and cloud accounts referenced in the given Runs.
    """
    date_user_cloud_accounts = set()

    # This nested set of for loops should find the set of all days, user_ids,
    # and cloud_account_ids that may be affected by the given Runs. All of
    # those combinations need to have their max concurrent usage calculated.
    for run in runs:
        start_date = run.start_time.date()
        if run.end_time is not None:
            end_date = run.end_time.date()
        else:
            # No end time on the run? Act like the run ends "today".
            end_date = get_today()
        # But really the end is *tomorrow* since the end is exclusive.
        end_date = end_date + timedelta(days=1)
        user_id = run.instance.cloud_account.user.id
        delta_days = (end_date - start_date).days
        for offset in range(delta_days):
            date = start_date + timedelta(days=offset)
            date_user_cloud_account = (date, user_id)
            date_user_cloud_accounts.add(date_user_cloud_account)
            date_user_no_cloud_account = (date, user_id)
            date_user_cloud_accounts.add(date_user_no_cloud_account)

    for date, user_id in date_user_cloud_accounts:

        # If we already have a concurrent usage calculation task with the same userid
        # same date, and status=SCHEDULED, then we do not run this

        last_calculate_task = get_last_scheduled_concurrent_usage_calculation_task(
            user_id=user_id, date=date)

        # If no such task exists, then schedule one
        if last_calculate_task is None:
            schedule_concurrent_calculation_task(date, user_id)
        # If this task is already complete or is in the process of running,
        # then we schedule a new task.
        elif last_calculate_task.status != ConcurrentUsageCalculationTask.SCHEDULED:
            schedule_concurrent_calculation_task(date, user_id)

        # If the task is scheduled (but has not started running) and it has been
        # in the queue for too long, then we revoke it and schedule a new task.
        elif last_calculate_task.created_at < datetime.now(tz=tz.tzutc(
        )) - timedelta(
                seconds=settings.SCHEDULE_CONCURRENT_USAGE_CALCULATION_DELAY):
            logger.info(
                "Previous scheduled task to calculate concurrent usage for "
                "user_id: %(user_id)s and date: %(date)s has expired. "
                "Scheduling new calculation task." % {
                    "user_id": user_id,
                    "date": date
                })
            last_calculate_task.cancel()
            schedule_concurrent_calculation_task(date, user_id)

        # If the task already exists and is scheduled and it reasonably recent,
        # simply log this message for visibility.
        else:
            logger.info(
                "Task to calculate concurrent usage for user_id: %(user_id)s and "
                "date: %(date)s already exists." % {
                    "user_id": user_id,
                    "date": date
                })
Пример #15
0
def get_max_concurrent_usage(date, user_id):
    """
    Get maximum concurrent usage of RHEL instances in given parameters.

    This may get a pre-calculated version of the results from ConcurrentUsage. We
    attempt to load stored results from the database; we only perform the underlying
    calculation (and save results for future requests) if the result does not yet exist.

    Args:
        date (datetime.date): the day during which we are measuring usage
        user_id (int): required filter on user

    Returns:
        ConcurrentUsage for the give date and user_id.

    """
    logger.debug(
        "Getting Concurrent usage for user_id: %(user_id)s, date: %(date)s" % {
            "user_id": user_id,
            "date": date
        })

    today = get_today()
    if date > today:
        # Early return stub values; we never project future calculations.
        return ConcurrentUsage(date=date, user_id=user_id)

    try:
        logger.info(
            _("Fetching ConcurrentUsage(date=%(date)s, user_id=%(user_id)s)"),
            {
                "date": date,
                "user_id": user_id
            },
        )
        concurrent_usage = ConcurrentUsage.objects.get(date=date,
                                                       user_id=user_id)
        return concurrent_usage
    except ConcurrentUsage.DoesNotExist:
        logger.info(
            _("Could not find ConcurrentUsage(date=%(date)s, user_id=%(user_id)s)"
              ),
            {
                "date": date,
                "user_id": user_id
            },
        )
        # If it does not exist, we will create it after this exception handler.
        pass

    last_calculation_task = get_last_scheduled_concurrent_usage_calculation_task(
        user_id, date)
    logger.info(
        _("last_calculation_task is %(last_calculation_task)s"),
        {"last_calculation_task": last_calculation_task},
    )
    if last_calculation_task is not None:
        if last_calculation_task.status in (
                ConcurrentUsageCalculationTask.SCHEDULED,
                ConcurrentUsageCalculationTask.RUNNING,
        ):
            raise ResultsUnavailable

    schedule_concurrent_calculation_task(date, user_id)
    raise ResultsUnavailable
Пример #16
0
 def default_end_date(self):
     """Return the default end_date."""
     return get_today()
Пример #17
0
 def default_start_date(self):
     """Return the default start_date."""
     return get_today() - timedelta(days=1)
Пример #18
0
 def latest_end_date(self):
     """Return the latest allowed end_date."""
     return get_today()