Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[COST-3761] Generate example API data for several days #5085

Merged
merged 5 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
190 changes: 190 additions & 0 deletions koku/api/report/ocp/network/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import dataclasses
import secrets
from datetime import datetime
from datetime import timedelta


def get_random_usage_amount(min: int = 1, length: int = 10) -> float:
return secrets.choice(range(min, 10**length - 1)) / (10 ** (length - 1))


def zero_usd() -> "Value":
return Value(0.0, units="USD")


def zero_gb() -> "Value":
return Value(0.0, units="GB")

Check warning on line 16 in koku/api/report/ocp/network/example.py

View check run for this annotation

Codecov / codecov/patch

koku/api/report/ocp/network/example.py#L16

Added line #L16 was not covered by tests


@dataclasses.dataclass(frozen=True)
class Value:
value: float = dataclasses.field(default_factory=get_random_usage_amount)
units: str = "GB"

def __add__(self, other):
if isinstance(other, self.__class__):
return self.__class__(self.value + other.value, self.units)

return self.__class__(self.value + other, self.units)

Check warning on line 28 in koku/api/report/ocp/network/example.py

View check run for this annotation

Codecov / codecov/patch

koku/api/report/ocp/network/example.py#L28

Added line #L28 was not covered by tests

def __sub__(self, other):
if isinstance(other, self.__class__):
return self.__class__(self.value - other.value, self.units)

Check warning on line 32 in koku/api/report/ocp/network/example.py

View check run for this annotation

Codecov / codecov/patch

koku/api/report/ocp/network/example.py#L32

Added line #L32 was not covered by tests

return self.__class__(self.value - other, self.units)

Check warning on line 34 in koku/api/report/ocp/network/example.py

View check run for this annotation

Codecov / codecov/patch

koku/api/report/ocp/network/example.py#L34

Added line #L34 was not covered by tests


@dataclasses.dataclass
class Cost:
raw: Value = dataclasses.field(default_factory=zero_usd)
markup: Value = dataclasses.field(default_factory=zero_usd)
usage: Value = dataclasses.field(default_factory=zero_usd)
total: Value = dataclasses.field(init=False)

def __post_init__(self):
self.total = Value(sum((self.raw.value, self.markup.value, self.usage.value)), units="USD")


@dataclasses.dataclass
class NetworkUsage:
date: str = datetime.now().strftime("%Y-%m-%d")
data_transfer_in: Value = dataclasses.field(default_factory=Value)
data_transfer_out: Value = dataclasses.field(default_factory=Value)
resource_id: str = "i-727f8dc9c567f1552"
clusters: list[str] = dataclasses.field(default_factory=list)
source_uuid: str = "6795d5e6-951c-4382-a8d3-390515d7ec3d"
region: str = "us-east-2"
infrastructure: Cost = dataclasses.field(init=False)
supplementary: Cost = dataclasses.field(init=False, default_factory=Cost)
markup: Cost = dataclasses.field(init=False)

def __post_init__(self):
amount = get_random_usage_amount()
self.infrastructure = Cost(Value(amount, units="USD"))
self.markup = self.infrastructure

if not self.clusters:
self.clusters = ["Test AWS Cluster"]


@dataclasses.dataclass
class DailyNetworkUsage:
date: str
values: list[NetworkUsage]


@dataclasses.dataclass
class Total:
usage: Value = Value()
data_transfer_in: Value = Value()
data_transfer_out: Value = Value()
infrastructure: Cost = dataclasses.field(default_factory=Cost)
supplementary: Cost = dataclasses.field(default_factory=Cost)
cost: Cost = dataclasses.field(default_factory=Cost)

@classmethod
def generate(cls, data: list[DailyNetworkUsage]) -> "Total":
data_transfer_in = cls.total_value("data_transfer_in", data)
data_transfer_out = cls.total_value("data_transfer_out", data)

return cls(
usage=Value(data_transfer_in + data_transfer_out),
data_transfer_in=Value(data_transfer_in),
data_transfer_out=Value(data_transfer_out),
infrastructure=cls.total_cost("infrastructure", data),
supplementary=cls.total_cost("supplementary", data),
cost=cls.total_cost("infrastructure", data),
)

@staticmethod
def total_value(field: str, data: list[DailyNetworkUsage]) -> float:
return sum(getattr(network_usage, field).value for daily_usage in data for network_usage in daily_usage.values)

@staticmethod
def total_cost(field: str, data: list[DailyNetworkUsage]) -> Cost:
cost_fields = (item.name for item in dataclasses.fields(Cost))
total = Cost()
for cost_field in cost_fields:
for daily_usage in data:
for network_usage in daily_usage.values:
running_total_value = getattr(total, cost_field)
value_to_add = getattr(getattr(network_usage, field), cost_field)
setattr(total, cost_field, running_total_value + value_to_add)

return total


@dataclasses.dataclass
class ExampleResponseBody:
total: Total
data: list[DailyNetworkUsage]

@classmethod
def generate(cls, count=10, *args, **kwargs):
daily_usage = []
date_counter = datetime.today()
for _ in range(count):
date = date_counter.strftime("%Y-%m-%d")
daily_usage.append(DailyNetworkUsage(date, [NetworkUsage(date)]))
date_counter -= timedelta(days=1)

return cls(Total.generate(daily_usage), daily_usage)


@dataclasses.dataclass
class TotalGroupBy(Total):
@staticmethod
def total_value(field: str, data: list["GroupByDailyNetworkUsage"]) -> float:
return sum(
getattr(network_usage, field).value
for daily_usage in data
for cluster in daily_usage.clusters
for network_usage in cluster.values
)

@staticmethod
def total_cost(field: str, data: list["GroupByDailyNetworkUsage"]) -> Cost:
cost_fields = (item.name for item in dataclasses.fields(Cost))
total = Cost()
for cost_field in cost_fields:
for daily_usage in data:
for cluster in daily_usage.clusters:
for network_usage in cluster.values:
running_total_value = getattr(total, cost_field)
value_to_add = getattr(getattr(network_usage, field), cost_field)
setattr(total, cost_field, running_total_value + value_to_add)

return total


@dataclasses.dataclass
class GroupByNetworkUsage:
cluster: str = "test-ocp-cluster"
values: list[NetworkUsage] = dataclasses.field(default_factory=list)

def __post_init__(self):
for value in self.values:
value.cluster = self.cluster


@dataclasses.dataclass
class GroupByDailyNetworkUsage:
date: str
clusters: list[GroupByNetworkUsage]


@dataclasses.dataclass
class ExampleGroupByResponseBody:
total: Total
data: list[GroupByDailyNetworkUsage]

@classmethod
def generate(cls, count=10, *args, **kwargs):
daily_usage = []
date_counter = datetime.today()
for _ in range(count):
date = date_counter.strftime("%Y-%m-%d")
daily_usage.append(GroupByDailyNetworkUsage(date, [GroupByNetworkUsage(values=[NetworkUsage(date)])]))
date_counter -= timedelta(days=1)

return cls(TotalGroupBy.generate(daily_usage), daily_usage)
123 changes: 10 additions & 113 deletions koku/api/report/ocp/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
# SPDX-License-Identifier: Apache-2.0
#
"""View for OpenShift Usage Reports."""
import dataclasses

from rest_framework.pagination import Response
from rest_framework.views import status

from api.common.pagination import ReportPagination
from api.common.permissions.openshift_access import OpenShiftAccessPermission
from api.models import Provider
from api.report.ocp.network.example import ExampleGroupByResponseBody
from api.report.ocp.network.example import ExampleResponseBody
from api.report.ocp.query_handler import OCPReportQueryHandler
from api.report.ocp.serializers import OCPCostQueryParamSerializer
from api.report.ocp.serializers import OCPInventoryQueryParamSerializer
Expand Down Expand Up @@ -63,118 +67,11 @@ def get(self, request, **kwargs):
):
return Response(data="Under development", status=status.HTTP_400_BAD_REQUEST)

response_fixture = {
"total": {
"usage": {
"value": 3.6801746441,
"units": "GB",
},
"data_transfer_in": {
"value": 12.82510775,
"units": "GB",
},
"data_transfer_out": {
"value": 8.080656137,
"units": "GB",
},
"infrastructure": {
"raw": {"value": 1945.51192915, "units": "USD"},
"markup": {"value": 0.0, "units": "USD"},
"usage": {"value": 0.0, "units": "USD"},
"total": {"value": 1945.51192915, "units": "USD"},
},
"supplementary": {
"raw": {"value": 0.0, "units": "USD"},
"markup": {"value": 0.0, "units": "USD"},
"usage": {"value": 0.0, "units": "USD"},
"total": {"value": 0.0, "units": "USD"},
},
"cost": {
"raw": {"value": 4863.77982288, "units": "USD"},
"markup": {"value": 0.0, "units": "USD"},
"usage": {"value": 0.0, "units": "USD"},
"total": {"value": 4863.77982288, "units": "USD"},
},
},
"data": [
{
"date": "2024-03-31",
"values": [
{
"date": "2024-03-31",
"data_transfer_in": {
"value": 3.6801746441,
"units": "GB",
},
"data_transfer_out": {
"value": 2.6866746441,
"units": "GB",
},
"resource_id": "i-727f8dc9c567f1552",
"clusters": ["Test OCP on AWS"],
"source_uuid": "6795d5e6-951c-4382-a8d3-390515d7ec3d",
"region": "us-east-1",
"infrastructure": {
"raw": {"value": 1945.51192915, "units": "USD"},
"markup": {"value": 0.0, "units": "USD"},
"usage": {"value": 0.0, "units": "USD"},
"total": {"value": 1945.5119291598, "units": "USD"},
},
"supplementary": {
"raw": {"value": 0.0, "units": "USD"},
"markup": {"value": 0.0, "units": "USD"},
"usage": {"value": 0.0, "units": "USD"},
"total": {"value": 0.0, "units": "USD"},
},
"cost": {
"raw": {"value": 1945.51192915, "units": "USD"},
"markup": {"value": 0.0, "units": "USD"},
"usage": {"value": 0.0, "units": "USD"},
"total": {"value": 1945.51192915, "units": "USD"},
},
},
],
},
{
"date": "2024-04-01",
"values": [
{
"date": "2024-04-01",
"data_transfer_in": {
"value": 9.1449331092,
"units": "GB",
},
"data_transfer_out": {
"value": 5.3939814929,
"units": "GB",
},
"resource_id": "i-727f8dc9c567f1552",
"clusters": ["Test OCP on AWS"],
"source_uuid": "6795d5e6-951c-4382-a8d3-390515d7ec3d",
"region": "us-east-1",
"infrastructure": {
"raw": {"value": 2918.26789372, "units": "USD"},
"markup": {"value": 0.0, "units": "USD"},
"usage": {"value": 0.0, "units": "USD"},
"total": {"value": 2918.26789372, "units": "USD"},
},
"supplementary": {
"raw": {"value": 0.0, "units": "USD"},
"markup": {"value": 0.0, "units": "USD"},
"usage": {"value": 0.0, "units": "USD"},
"total": {"value": 0.0, "units": "USD"},
},
"cost": {
"raw": {"value": 2918.267893725, "units": "USD"},
"markup": {"value": 0.0, "units": "USD"},
"usage": {"value": 0.0, "units": "USD"},
"total": {"value": 2918.267893725, "units": "USD"},
},
}
],
},
],
}
if any(param.startswith("group_by") for param in request.query_params):
data = ExampleGroupByResponseBody.generate()
else:
data = ExampleResponseBody.generate()

paginator = ReportPagination()
paginated_result = paginator.paginate_queryset(response_fixture, request)
paginated_result = paginator.paginate_queryset(dataclasses.asdict(data), request)
return paginator.get_paginated_response(paginated_result)
15 changes: 15 additions & 0 deletions koku/api/report/test/ocp/view/test_ocp_network_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,18 @@ def test_network_api_access_dev(self):
response = client.get(url, **self.headers)

self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_network_api_group_by(self):
url = reverse("reports-openshift-network")
client = APIClient()
with (
schema_context(self.schema_name),
patch(
"api.report.ocp.view.UNLEASH_CLIENT.is_enabled",
return_value=True,
),
):
response = client.get(url, {"group_by[cluster]": "*"}, **self.headers)

self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIsNotNone(response.data["data"][0].get("clusters"))
Loading