Skip to content

Commit f6bd04e

Browse files
cgoodfredmaskarb
andauthored
[COST-4807] Make provider_uuid not a query param and parameterize bigquery queries (#5005)
* update monthly_sql query to be parameterized * remove custom SQL * make source uuid part of the URL instead of a param --------- Co-authored-by: Michael Skarbek <[email protected]>
1 parent 1af090c commit f6bd04e

File tree

4 files changed

+59
-111
lines changed

4 files changed

+59
-111
lines changed

koku/masu/api/bigquery_cost.py

+35-36
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,19 @@ def daily_sql(self, invoice_month, results):
4848
SELECT DATE(usage_start_time) as usage_date,
4949
sum(cost) as cost,
5050
FROM {self.table_name}
51-
WHERE invoice.month = '{invoice_month}'
52-
AND DATE(_PARTITIONTIME) BETWEEN "{str(self.start_date)}" AND "{str(self.end_date)}"
51+
WHERE invoice.month = @invoice_month
52+
AND DATE(_PARTITIONTIME) BETWEEN @start_date AND @end_date
5353
GROUP BY usage_date ORDER BY usage_date;
5454
"""
55-
55+
job_config = bigquery.QueryJobConfig(
56+
query_parameters=[
57+
bigquery.ScalarQueryParameter("invoice_month", "STRING", invoice_month),
58+
bigquery.ScalarQueryParameter("start_date", "DATE", self.start_date),
59+
bigquery.ScalarQueryParameter("end_date", "DATE", self.end_date),
60+
]
61+
)
5662
LOG.info(daily_query)
57-
rows = self.client.query(daily_query).result()
63+
rows = self.client.query(daily_query, job_config=job_config).result()
5864
daily_dict = {}
5965
for row in rows:
6066
daily_dict[str(row["usage_date"])] = {"cost": row.get("cost")}
@@ -66,10 +72,17 @@ def monthly_sql(self, invoice_month, results, key):
6672
monthly_query = f"""
6773
SELECT sum(cost) as cost,
6874
FROM {self.table_name}
69-
WHERE DATE(_PARTITIONTIME) BETWEEN "{str(self.start_date)}" AND "{str(self.end_date)}"
70-
AND invoice.month = '{invoice_month}'
75+
WHERE DATE(_PARTITIONTIME) BETWEEN @start_date AND @end_date
76+
AND invoice.month = @invoice_month
7177
"""
72-
rows = self.client.query(monthly_query).result()
78+
job_config = bigquery.QueryJobConfig(
79+
query_parameters=[
80+
bigquery.ScalarQueryParameter("invoice_month", "STRING", invoice_month),
81+
bigquery.ScalarQueryParameter("start_date", "DATE", self.start_date),
82+
bigquery.ScalarQueryParameter("end_date", "DATE", self.end_date),
83+
]
84+
)
85+
rows = self.client.query(monthly_query, job_config=job_config).result()
7386
for row in rows:
7487
results[key] = row[0] # TODO: Remove once bigquery is updated.
7588
metadata = {
@@ -79,30 +92,22 @@ def monthly_sql(self, invoice_month, results, key):
7992
results[key + "_metadata"] = metadata
8093
return results
8194

82-
def custom_sql(self, query):
83-
"""Takes a custom query and replaces the table_name."""
84-
query = query.replace("table_name", self.table_name)
85-
rows = self.client.query(query).result()
86-
return rows
87-
8895

8996
@never_cache
90-
@api_view(http_method_names=["GET", "POST"])
97+
@api_view(http_method_names=["GET"])
9198
@permission_classes((AllowAny,))
9299
@renderer_classes(tuple(api_settings.DEFAULT_RENDERER_CLASSES))
93-
def bigquery_cost(request): # noqa: C901
100+
def bigquery_cost(request, *args, **kwargs): # noqa: C901
94101
"""Returns the invoice monthly cost."""
95102
params = request.query_params
96-
provider_uuid = params.get("provider_uuid")
97-
return_daily = True if "daily" in params.keys() else False
98-
99-
if provider_uuid is None:
100-
errmsg = "provider_uuid is a required parameter."
101-
return Response({"Error": errmsg}, status=status.HTTP_400_BAD_REQUEST)
103+
provider_uuid = kwargs.get("source_uuid")
104+
return_daily = "daily" in params.keys()
102105

103-
provider = Provider.objects.filter(uuid=provider_uuid).first()
106+
provider = Provider.objects.filter(
107+
uuid=provider_uuid, type__in=[Provider.PROVIDER_GCP, Provider.PROVIDER_GCP_LOCAL]
108+
).first()
104109
if not provider:
105-
errmsg = f"The provider_uuid {provider_uuid} does not exist."
110+
errmsg = f"The *GCP* provider_uuid {provider_uuid} does not exist."
106111
return Response({"Error": errmsg}, status=status.HTTP_400_BAD_REQUEST)
107112
credentials = provider.authentication.credentials
108113
data_source = provider.billing_source.data_source
@@ -122,19 +127,13 @@ def bigquery_cost(request): # noqa: C901
122127
results = {}
123128
try:
124129
bq_helper = BigQueryHelper(table_name)
125-
if request.method == "POST":
126-
data = request.data
127-
query = data.get("query")
128-
results = bq_helper.custom_sql(query)
129-
resp_key = "custom_query_results"
130-
else:
131-
for key, invoice_month in mapping.items():
132-
if return_daily:
133-
results = bq_helper.daily_sql(invoice_month, results)
134-
resp_key = "daily_invoice_cost_mapping"
135-
else:
136-
results = bq_helper.monthly_sql(invoice_month, results, key)
137-
resp_key = "monthly_invoice_cost_mapping"
130+
for key, invoice_month in mapping.items():
131+
if return_daily:
132+
results = bq_helper.daily_sql(invoice_month, results)
133+
resp_key = "daily_invoice_cost_mapping"
134+
else:
135+
results = bq_helper.monthly_sql(invoice_month, results, key)
136+
resp_key = "monthly_invoice_cost_mapping"
138137
except GoogleCloudError as err:
139138
return Response({"Error": err.message}, status=status.HTTP_400_BAD_REQUEST)
140139

koku/masu/api/urls.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,7 @@
7373
path("celery_queue_tasks/", celery_queue_tasks, name="celery_queue_tasks"),
7474
path("celery_queue_lengths/", celery_queue_lengths, name="celery_queue_lengths"),
7575
path("clear_celery_queues/", clear_celery_queues, name="clear_celery_queues"),
76-
path(
77-
"gcp_invoice_monthly_cost/", bigquery_cost, name="gcp_invoice_monthly_cost"
78-
), # TODO: Remove once iqe is updated
79-
path("bigquery_cost/", bigquery_cost, name="bigquery_cost"),
76+
path("bigquery_cost/<uuid:source_uuid>/", bigquery_cost, name="bigquery_cost"),
8077
path("purge_trino_files/", purge_trino_files, name="purge_trino_files"),
8178
path("db-performance", db_performance_redirect, name="db_perf_no_slash_redirect"),
8279
path("db-performance/", db_performance_redirect, name="db_perf_slash_redirect"),

koku/masu/openapi.json

+2-37
Original file line numberDiff line numberDiff line change
@@ -1593,50 +1593,15 @@
15931593
]
15941594
}
15951595
},
1596-
"/gcp_invoice_monthly_cost/": {
1597-
"get": {
1598-
"summary": "Returns dictionary with cost mappings for continuity checks",
1599-
"operationId": "gcpInvoiceMonthlyCost",
1600-
"description": "Returns a dictionary with cost mappings for continuity checks",
1601-
"parameters": [
1602-
{
1603-
"name": "provider_uuid",
1604-
"in": "query",
1605-
"description": "The provider UUID",
1606-
"required": true,
1607-
"schema": {
1608-
"type": "string",
1609-
"format": "uuid",
1610-
"example": "83ee048e-3c1d-43ef-b945-108225ae52f4"
1611-
}
1612-
}
1613-
],
1614-
"responses": {
1615-
"200": {
1616-
"description": "Returns a dictionary with cost mappings for continuity checks",
1617-
"content": {
1618-
"application/json": {
1619-
"schema": {
1620-
"$ref": "#/components/schemas/gcpInvoiceMonthlyCost"
1621-
}
1622-
}
1623-
}
1624-
}
1625-
},
1626-
"tags": [
1627-
"GCP Invoice Monthly Cost"
1628-
]
1629-
}
1630-
},
1631-
"/bigquery_cost/": {
1596+
"/bigquery_cost/{provider_uuid}/": {
16321597
"get": {
16331598
"summary": "Returns dictionary with cost mappings for continuity checks",
16341599
"operationId": "BigQueryCost",
16351600
"description": "Returns a dictionary with cost mappings for continuity checks",
16361601
"parameters": [
16371602
{
16381603
"name": "provider_uuid",
1639-
"in": "query",
1604+
"in": "path",
16401605
"description": "The provider UUID",
16411606
"required": true,
16421607
"schema": {

koku/masu/test/api/test_bigquery_cost.py

+21-34
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
"""Test the update_cost_model_costs endpoint view."""
66
from collections import OrderedDict
77
from unittest.mock import patch
8-
from urllib.parse import urlencode
98
from uuid import uuid4
109

1110
from django.test.utils import override_settings
1211
from django.urls import reverse
1312

13+
from api.models import Provider
1414
from masu.api.bigquery_cost import get_total
1515
from masu.test import MasuTestCase
1616

@@ -23,13 +23,12 @@ class BigQueryCostTest(MasuTestCase):
2323
def test_success_gcp_monthly_return(self, _):
2424
"""Test successful endpoint return"""
2525
expected_value = 308.45
26-
dict = OrderedDict()
27-
dict["cost"] = expected_value
28-
dict[0] = expected_value
29-
mocked_value = [dict]
30-
params = {"provider_uuid": self.gcp_provider_uuid}
31-
query_string = urlencode(params)
32-
url = reverse("bigquery_cost") + "?" + query_string
26+
expected_dict = OrderedDict(
27+
cost=expected_value,
28+
)
29+
expected_dict[0] = expected_value
30+
mocked_value = [expected_dict]
31+
url = reverse("bigquery_cost", kwargs={"source_uuid": self.gcp_provider_uuid})
3332
with patch("masu.api.bigquery_cost.bigquery") as bigquery:
3433
bigquery.Client.return_value.query.return_value.result.return_value = mocked_value
3534
response = self.client.get(url)
@@ -42,51 +41,39 @@ def test_success_gcp_daily_return(self, _):
4241
"""Test successful endpoint return"""
4342
expected_cost = 308.45
4443
expected_credit = 10
45-
dict = OrderedDict()
46-
dict["usage_date"] = "2022-08-01"
47-
dict["cost"] = expected_cost
48-
dict["credit_amount"] = expected_credit
49-
dict[0] = expected_cost + expected_credit
50-
mocked_value = [dict]
51-
params = {"provider_uuid": self.gcp_provider_uuid}
52-
query_string = urlencode(params)
53-
url = reverse("bigquery_cost") + "?" + query_string + "&daily"
44+
expected_dict = OrderedDict(
45+
usage_date="2022-08-01",
46+
cost=expected_cost,
47+
credit_amount=expected_credit,
48+
)
49+
expected_dict[0] = expected_cost + expected_credit
50+
mocked_value = [expected_dict]
51+
url = reverse("bigquery_cost", kwargs={"source_uuid": self.gcp_provider_uuid}) + "?daily"
5452
with patch("masu.api.bigquery_cost.bigquery") as bigquery:
5553
bigquery.Client.return_value.query.return_value.result.return_value = mocked_value
5654
response = self.client.get(url)
5755
body = response.json()
5856
self.assertIsNotNone(body)
5957

60-
@patch("koku.middleware.MASU", return_value=True)
61-
def test_require_provider_uuid(self, _):
62-
"""Test the GET bigquery_cost endpoint with no provider uuid."""
63-
response = self.client.get(reverse("bigquery_cost"))
64-
body = response.json()
65-
errmsg = body.get("Error")
66-
expected_errmsg = "provider_uuid is a required parameter."
67-
self.assertEqual(response.status_code, 400)
68-
self.assertEqual(errmsg, expected_errmsg)
69-
7058
@patch("koku.middleware.MASU", return_value=True)
7159
def test_bigquery_request_with_bad_provider_uuid(self, _):
7260
"""Test the GET bigquery_cost endpoint with incorrect provider uuid."""
7361
provider_uuid = uuid4()
74-
params = {"provider_uuid": provider_uuid}
75-
query_string = urlencode(params)
76-
url = reverse("bigquery_cost") + "?" + query_string
62+
url = reverse("bigquery_cost", kwargs={"source_uuid": provider_uuid})
7763
response = self.client.get(url)
7864
body = response.json()
7965
errmsg = body.get("Error")
80-
expected_errmsg = f"The provider_uuid {provider_uuid} does not exist."
66+
expected_errmsg = f"The *GCP* provider_uuid {provider_uuid} does not exist."
8167
self.assertEqual(response.status_code, 400)
8268
self.assertEqual(errmsg, expected_errmsg)
8369

8470
@patch("koku.middleware.MASU", return_value=True)
8571
def test_unable_to_build_gcp_table_name(self, _):
8672
"""Test the GET bigquery_cost endpoint with no provider uuid."""
87-
params = {"provider_uuid": self.aws_provider_uuid}
88-
query_string = urlencode(params)
89-
url = reverse("bigquery_cost") + "?" + query_string
73+
p = Provider.objects.get(uuid=self.gcp_provider_uuid)
74+
p.authentication.credentials = {}
75+
p.authentication.save()
76+
url = reverse("bigquery_cost", kwargs={"source_uuid": self.gcp_provider_uuid})
9077
response = self.client.get(url)
9178
body = response.json()
9279
errmsg = body.get("Error")

0 commit comments

Comments
 (0)