예제 #1
0
async def test_regular_run(app_with_first_write, perun_test_group,
                           MeasurementClass, usage_delta, start_date):
    """Tests the complete workflow of the application without any expected errors.

    Incoming data of the InfluxDB are simulated two times to trigger different
    scenarios (first measurement vs second measurement)"""
    app, http_client, influx_client, measurement1 = app_with_first_write
    assert (perun_test_group.credits_timestamps.value[
        MeasurementClass.metric.name] == start_date
            ), "Timestamp from measurement was not stored correctly in group"
    # let's send the second measurement
    measurement2 = replace(
        measurement1,
        timestamp=start_date + timedelta(days=7),
        value=measurement1.value + usage_delta,
    )
    billing_point = BillingHistory(
        measurement=perun_test_group.name,
        timestamp=measurement2.timestamp,
        credits_left=perun_test_group.credits_granted.value - usage_delta,
        metric_name=measurement2.metric.name,
        metric_friendly_name=measurement2.metric.friendly_name,
    )
    await write_and_mirror(app, http_client, influx_client, measurement2)
    await perun_test_group.connect()
    billing_points = await get_billing_history(perun_test_group, influx_client)
    assert perun_test_group.credits_timestamps.value[
        MeasurementClass.metric.name] == start_date + timedelta(
            days=7
        ), "Timestamp from measurement was not stored correctly in group"
    # since our CREDITS_PER_VIRTUAL_HOUR are 1
    assert perun_test_group.credits_used.value == usage_delta
    assert [billing_point] == billing_points
예제 #2
0
async def test_api_endpoint(influx_client: InfluxDBClient, aiohttp_client):
    now = datetime.now()
    yesterday = now - timedelta(days=1)
    tomorrow = now + timedelta(days=1)

    credits_left = Credits(Decimal(300))
    metric_name = metric_friendly_name = "test_history_metric"
    project_name = "test_history_measurement"
    point = BillingHistory(
        measurement=project_name,
        timestamp=now,
        credits_left=credits_left,
        metric_name=metric_name,
        metric_friendly_name=metric_friendly_name,
    )
    await influx_client.write_billing_history(point)
    app = await create_app(_existing_influxdb_client=influx_client)
    http_client = await aiohttp_client(app)
    resp1 = await http_client.get(
        app.router["api_credits_history"].url_for(project_name=project_name)
    )
    resp2 = await http_client.get(
        app.router["api_credits_history"]
        .url_for(project_name=project_name)
        .with_query(
            {
                "start_date": yesterday.strftime(datetime_format),
                "end_date": tomorrow.strftime(datetime_format),
            }
        )
    )
    expected_resp = dict(
        credits=["credits", point.credits_left],
        metrics=["metrics", point.metric_friendly_name],
        timestamps=["timestamps", point.timestamp.strftime(datetime_format)],
    )
    assert resp1.status == resp2.status == HTTPStatus.OK
    assert await resp1.json() == await resp2.json() == expected_resp
    resp1 = await http_client.get(
        app.router["api_credits_history"]
        .url_for(project_name=project_name)
        .with_query({"start_date": tomorrow.strftime(datetime_format)})
    )
    resp2 = await http_client.get(
        app.router["api_credits_history"]
        .url_for(project_name=project_name)
        .with_query({"end_date": yesterday.strftime(datetime_format)})
    )
    assert resp1.status == resp2.status == HTTPStatus.NO_CONTENT
예제 #3
0
async def test_history_exists(influx_client):

    now = datetime.now()

    credits_left = 300
    metric_name = metric_friendly_name = "test_history_metric"
    project_name = "test_history_measurement"
    point = BillingHistory(
        measurement=project_name,
        timestamp=now,
        credits_left=credits_left,
        metric_name=metric_name,
        metric_friendly_name=metric_friendly_name,
    )
    await influx_client.write_billing_history(point)
    assert await influx_client.project_has_history(project_name)
    assert not await influx_client.project_has_history(f"not{project_name}")
예제 #4
0
async def test_50_percent_notification(
    app_with_first_write,
    perun_test_group,
    smtpserver,
    usage_delta,
    start_date,
    MeasurementClass,
):
    """Tests the complete workflow of the application without any expected errors but
    let the group fall under 50% of its granted credits.
    """
    app, http_client, influx_client, measurement1 = app_with_first_write
    # let's send the second measurement
    measurement2 = replace(
        measurement1,
        timestamp=start_date + timedelta(days=7),
        value=measurement1.value + usage_delta,
    )
    half_of_granted_credits = Decimal(perun_test_group.credits_granted.value /
                                      2)
    perun_test_group.credits_used.value = half_of_granted_credits
    await perun_test_group.save()

    await write_and_mirror(app, http_client, influx_client, measurement2)
    await perun_test_group.connect()
    billing_point = BillingHistory(
        measurement=perun_test_group.name,
        timestamp=measurement2.timestamp,
        credits_left=half_of_granted_credits - usage_delta,
        metric_name=measurement2.metric.name,
        metric_friendly_name=measurement2.metric.friendly_name,
    )
    billing_points = await get_billing_history(perun_test_group, influx_client)
    assert perun_test_group.credits_timestamps.value[
        MeasurementClass.metric.name] == start_date + timedelta(
            days=7
        ), "Timestamp from measurement was not stored correctly in group"
    # since our CREDITS_PER_VIRTUAL_HOUR are 1
    assert perun_test_group.credits_used.value == half_of_granted_credits + usage_delta
    assert [billing_point
            ] == billing_points, "Billing history has been stored incorrectly"
    assert len(smtpserver.outbox) == 1, "No notification has been send"
예제 #5
0
async def test_no_billing_due_to_rounding(aiohttp_client, os_credits_offline,
                                          influx_client, perun_test_group,
                                          start_date):
    """The measurements are all valid but no credits are billed and no timestamps
    updated when the second measurement is processed since the costs of the metric are
    so low they get lost when rounding according to given precision.
    
    Depending on the chosen rounding strategy multiple measurements have to processed
    before their accumulated usage delta leads to a billing."""
    from os_credits.main import create_app
    from os_credits.settings import config
    from os_credits.credits.base_models import (
        Metric,
        TotalUsageMetric,
        UsageMeasurement,
    )

    test_metric_name = "whole_run_test_cheap_1"

    class _TestMetricCheap(TotalUsageMetric,
                           name=test_metric_name,
                           friendly_name=test_metric_name):
        # by setting the costs per hour this way we can be sure that the first billings
        # will be rounded to zero
        CREDITS_PER_VIRTUAL_HOUR = config["OS_CREDITS_PRECISION"] * Decimal(
            "10")**-1
        description = "Test Metric 1 for whole run test"

    @dataclass(frozen=True)
    class _TestMeasurementCheap(UsageMeasurement):
        metric: Type[Metric] = _TestMetricCheap

    measurement = _TestMeasurementCheap(
        measurement=test_metric_name,
        timestamp=start_date,
        location_id=perun_test_group.resource_id,
        project_name=perun_test_group.name,
        value=100,
    )
    usage_delta = 1

    app = await create_app(_existing_influxdb_client=influx_client)
    http_client = await aiohttp_client(app)

    await write_and_mirror(app,
                           http_client,
                           influx_client,
                           measurement=measurement)
    # with default rounding Strategy ROUND_TO_HALF_EVEN
    # https://en.wikipedia.org/wiki/Rounding#Round_half_to_even
    # the following measurements will not cause any bills
    # choosing the ranges according to the amount of the credits to bill they accumulate
    for i in range(1, 6):
        measurement = replace(
            measurement,
            timestamp=measurement.timestamp + timedelta(days=7),
            value=measurement.value + usage_delta,
        )

        await write_and_mirror(app, http_client, influx_client, measurement)
        await perun_test_group.connect()
        billing_points = await get_billing_history(perun_test_group,
                                                   influx_client)
        assert (
            perun_test_group.credits_timestamps.value[test_metric_name] ==
            start_date
        ), "Timestamp from measurement was updated although no credits were billed"
        assert (
            perun_test_group.credits_used.value == 0
        ), """Credits were billed although this should not have happened given the required
        precision"""
        assert billing_points == []
    # this measurement should lead to a bill
    measurement = replace(
        measurement,
        timestamp=measurement.timestamp + timedelta(days=7),
        value=measurement.value + usage_delta,
    )
    await write_and_mirror(app, http_client, influx_client, measurement)
    await perun_test_group.connect()
    billing_points = await get_billing_history(perun_test_group, influx_client)
    expected_credits = config["OS_CREDITS_PRECISION"]
    billing_point = BillingHistory(
        measurement=perun_test_group.name,
        timestamp=measurement.timestamp,
        credits_left=(Decimal(perun_test_group.credits_granted.value) -
                      config["OS_CREDITS_PRECISION"]),
        metric_name=measurement.metric.name,
        metric_friendly_name=measurement.metric.friendly_name,
    )
    assert (
        perun_test_group.credits_timestamps.value[test_metric_name] ==
        measurement.timestamp
    ), "Timestamp from measurement was updated although no credits were billed"
    assert (
        perun_test_group.credits_used.value == expected_credits
    ), """Credits were billed although this should not have happened given the required
    precision"""
    assert billing_points == [billing_point]
예제 #6
0
async def main() -> int:
    parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter,
                            description=__doc__)
    parser.add_argument(
        "-c",
        "--initial-credits",
        type=int,
        help="Amount of initial credits",
        default=INITIAL_CREDITS,
    )
    parser.add_argument("-p",
                        "--project",
                        default=PROJECT,
                        help="Name of the project")
    parser.add_argument(
        "-e",
        "--entries",
        default=ENTRIES,
        type=int,
        help="How many entries should be created",
    )
    parser.add_argument(
        "-i",
        "--credits-interval",
        type=int,
        nargs=2,
        default=RANDOM_INTERVAL,
        help="Interval used for random credits amount",
    )
    parser.add_argument(
        "-t",
        "--time-interval",
        type=int,
        default=BILLING_INTERVAL_SECONDS,
        help="How many seconds should pass between each bill",
    )
    parser.add_argument(
        "-m",
        "--metric",
        default=METRIC,
        help=
        "Name of the metric to bill for (will be used as friendly name as well)",
    )

    args = parser.parse_args()

    client = InfluxDBClient()

    if not await client.ensure_history_db_exists():
        print("Credits history db does not exist. Aborting")
        return 1

    billing_interval = timedelta(seconds=args.time_interval)
    billing_point = BillingHistory(
        measurement=args.project,
        time=datetime.now() - billing_interval * args.entries,
        credits_left=args.initial_credits - randint(*args.credits_interval),
        metric_name=args.metric,
        metric_friendly_name=args.metric,
    )
    points = [billing_point]

    for _ in range(args.entries - 1):
        billing_point = replace(
            billing_point,
            credits_left=billing_point.credits_left -
            randint(*args.credits_interval),
            time=billing_point.time + billing_interval,
        )
        points.append(billing_point)
    print("Generated points. Sending to InfluxDB now")
    await client.write_billing_history(points)

    return 0