Skip to content

Commit 393f43d

Browse files
committed
chore(volumes): usage-based billing
1 parent 1948569 commit 393f43d

6 files changed

Lines changed: 71 additions & 21 deletions

File tree

charts/controller/templates/_helpers.tpl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,9 @@ oss:
416416
{{/* Generate controller config default volume claim template */}}
417417
{{ define "controller.config.defaultVolumeClaimTemplate" }}
418418
csi:
419+
metadata:
420+
annotations:
421+
billing.drycc.cc/type: usage
419422
spec:
420423
accessModes:
421424
- ReadWriteMany
@@ -425,6 +428,9 @@ csi:
425428
storage: $size
426429
volumeMode: Filesystem
427430
nfs:
431+
metadata:
432+
annotations:
433+
billing.drycc.cc/type: basic
428434
spec:
429435
accessModes:
430436
- ReadWriteMany
@@ -434,6 +440,9 @@ nfs:
434440
storage: $size
435441
volumeName: $volume_name
436442
oss:
443+
metadata:
444+
annotations:
445+
billing.drycc.cc/type: basic
437446
spec:
438447
accessModes:
439448
- ReadWriteMany

rootfs/api/management/commands/upload_network_usage.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def _upload_network_usage(self, start_time, app_map, timestamp):
5858
def handle(self, *args, **options):
5959
if settings.WORKFLOW_MANAGER_URL:
6060
start_time, timestamp, task_id = timezone.now(), int(time.time()), uuid.uuid4().hex
61-
logger.info(f"pushing {task_id} limits to workflow_manager when {timezone.now()}")
61+
logger.info(f"pushing {task_id} networks to workflow_manager when {timezone.now()}")
6262
app_map = {}
6363
for app in App.objects.all():
6464
app_map[app.id] = app
@@ -67,5 +67,5 @@ def handle(self, *args, **options):
6767
app_map = {}
6868
if len(app_map) > 0:
6969
self._upload_network_usage(start_time, app_map, timestamp)
70-
logger.info(f"pushed {task_id} limits to workflow_manager when {timezone.now()}")
70+
logger.info(f"pushed {task_id} networks to workflow_manager when {timezone.now()}")
7171
self.stdout.write("done")

rootfs/api/management/commands/upload_volume_usage.py

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
import logging
44
import random
55
from datetime import timedelta
6+
from asgiref.sync import async_to_sync
67
from django.utils import timezone
78
from django.core.management.base import BaseCommand
89
from django.conf import settings
9-
from api.models.volume import Volume
10+
from api import monitor
11+
from api.models.app import App
1012
from api.tasks import send_usage
1113

1214
logger = logging.getLogger(__name__)
@@ -15,23 +17,40 @@
1517
class Command(BaseCommand):
1618
"""Management command for push data to manager"""
1719

20+
def _upload_volume_usage(self, start_time, app_map, timestamp):
21+
stop = timestamp - (timestamp % 3600)
22+
start = stop - 3600
23+
volumes = []
24+
for item in async_to_sync(monitor.query_volume_size)(app_map.keys(), start, stop): # noqa
25+
metric = item["metric"]
26+
_, value = item["value"]
27+
volumes.append({
28+
"app_id": str(app_map[metric['namespace']].uuid),
29+
"owner": app_map[metric['namespace']].owner_id,
30+
"type": "volume",
31+
"unit": "bytes",
32+
"name": metric["storageclass"],
33+
"usage": value,
34+
"kwargs": {
35+
"persistentvolumeclaim": metric['persistentvolumeclaim'],
36+
},
37+
"timestamp": start
38+
})
39+
send_usage.apply_async(
40+
args=(volumes,), eta=start_time + timedelta(seconds=random.randint(1, 1800))
41+
)
42+
1843
def handle(self, *args, **options):
1944
if settings.WORKFLOW_MANAGER_URL:
20-
start_time, timestamp, task_id = timezone.now(), time.time(), uuid.uuid4().hex
21-
logger.info(f"pushing {task_id} volumes to workflow_manager when {start_time}")
22-
volume_list = []
23-
for volume in Volume.objects.all():
24-
volume_list.extend(volume.to_usages(timestamp))
25-
if len(volume_list) % 1000 == 0:
26-
send_usage.apply_async(
27-
args=(volume_list,),
28-
eta=start_time + timedelta(seconds=random.randint(1, 1800))
29-
)
30-
volume_list = []
31-
if len(volume_list) > 0:
32-
send_usage.apply_async(
33-
args=(volume_list,),
34-
eta=start_time + timedelta(seconds=random.randint(1, 1800))
35-
)
45+
start_time, timestamp, task_id = timezone.now(), int(time.time()), uuid.uuid4().hex
46+
logger.info(f"pushing {task_id} volumes to workflow_manager when {timezone.now()}")
47+
app_map = {}
48+
for app in App.objects.all():
49+
app_map[app.id] = app
50+
if len(app_map) % 1000 == 0:
51+
self._upload_volume_usage(start_time, app_map, timestamp)
52+
app_map = {}
53+
if len(app_map) > 0:
54+
self._upload_volume_usage(start_time, app_map, timestamp)
3655
logger.info(f"pushed {task_id} volumes to workflow_manager when {timezone.now()}")
3756
self.stdout.write("done")

rootfs/api/monitor.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,18 @@
2828
by (pod)
2929
"""
3030

31+
query_volume_size_promql_tpl = """
32+
max by(namespace, persistentvolumeclaim, storageclass) (
33+
kube_persistentvolumeclaim_resource_requests_storage_bytes{namespace="%s"}
34+
* on(namespace, persistentvolumeclaim) group_left(storageclass)
35+
kube_persistentvolumeclaim_info{namespace="%s"}
36+
* on(namespace, persistentvolumeclaim) group_left()
37+
kube_persistentvolumeclaim_annotations{
38+
namespace="%s",
39+
annotation_billing_drycc_cc_type="usage"
40+
}
41+
)
42+
"""
3143

3244
query_network_receive_usage_promql_tpl = """
3345
sum (rate (container_network_receive_bytes_total{pod=~"^%s-.*$",namespace="%s"}[%s]))
@@ -70,6 +82,14 @@ async def last_metrics(namespace) -> AsyncGenerator[Iterator, str]:
7082
)
7183

7284

85+
async def query_volume_size(namespaces: Iterator[str], start: int, stop: int
86+
) -> list[tuple[dict[str, str], int]]:
87+
url = urljoin(settings.DRYCC_VICTORIAMETRICS_URL, "/select/0/prometheus/api/v1/query")
88+
promql = query_volume_size_promql_tpl % (
89+
"|".join(namespaces), "|".join(namespaces), "|".join(namespaces))
90+
return await query_prom(url, {"query": promql, "start": start, "end": stop})
91+
92+
7393
async def query_network_receive_flow(namespaces: Iterator[str], start: int, stop: int
7494
) -> list[tuple[dict[str, str], int]]:
7595
url = urljoin(settings.DRYCC_VICTORIAMETRICS_URL, "/select/0/prometheus/api/v1/query")

rootfs/scheduler/resources/pv.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
import logging
3+
from api import utils
34
from scheduler.resources import Resource
45
from scheduler.exceptions import KubeHTTPException
56

@@ -19,7 +20,7 @@ def manifest(self, name, version=None, **kwargs):
1920
'labels': labels
2021
},
2122
}
22-
data.update(kwargs)
23+
data = utils.dict_merge(data, kwargs)
2324
if version:
2425
data["metadata"]["resourceVersion"] = version
2526
return data

rootfs/scheduler/resources/pvc.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
import logging
3+
from api import utils
34
from scheduler.resources import Resource
45
from scheduler.exceptions import KubeHTTPException
56

@@ -20,7 +21,7 @@ def manifest(self, namespace, name, version=None, **kwargs):
2021
'labels': labels
2122
}
2223
}
23-
data.update(kwargs)
24+
data = utils.dict_merge(data, kwargs)
2425
if version:
2526
data["metadata"]["resourceVersion"] = version
2627
return data

0 commit comments

Comments
 (0)