Skip to content

Commit

Permalink
Implement archive record function
Browse files Browse the repository at this point in the history
And integrate archive into the adapter event loop. Each billing
cycle is archived with metering and usage info included. With a
default archive length of 6 months.
  • Loading branch information
smarlowucf committed Jan 9, 2024
1 parent f342b84 commit 7718fa6
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 1 deletion.
2 changes: 2 additions & 0 deletions csp_billing_adapter/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
csp_hookspecs,
hookspecs,
storage_hookspecs,
archive_hookspecs,
hookimpls
)

Expand All @@ -71,6 +72,7 @@ def get_plugin_manager() -> pluggy.PluginManager:
pm.add_hookspecs(hookspecs)
pm.add_hookspecs(csp_hookspecs)
pm.add_hookspecs(storage_hookspecs)
pm.add_hookspecs(archive_hookspecs)
pm.register(hookimpls)
pm.load_setuptools_entrypoints('csp_billing_adapter')
return pm
Expand Down
55 changes: 55 additions & 0 deletions csp_billing_adapter/archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@

"""Utility functions for handling a rolling dictionary archive."""

import functools
import logging

from csp_billing_adapter.config import Config
from csp_billing_adapter.utils import retry_on_exception

log = logging.getLogger('CSPBillingAdapter')

DEFAULT_RETENTION_PERIOD = 6 # in months


def append_metering_records(
archive: list,
Expand Down Expand Up @@ -45,3 +55,48 @@ def append_metering_records(
return archive[1:]
else:
return archive


def archive_record(
hook,
config: Config,
billing_record: dict
) -> None:
"""
:param hook:
The Pluggy plugin manager hook that will be
used to call the meter_billing operation.
:param config:
The configuration specifying the metrics that
need to be processed in the usage records list.
:param billing_record:
The dictionary containing the most recent
metering and usage records to be archived.
"""
archive = retry_on_exception(
functools.partial(
hook.get_metering_archive,
config=config,
),
logger=log,
func_name="hook.get_metering_archive"
)

if archive is None:
archive = []

archive = append_metering_records(
archive,
billing_record,
config.archive_retention_period or DEFAULT_RETENTION_PERIOD
)

retry_on_exception(
functools.partial(
hook.save_metering_archive,
config=config,
archive_data=archive
),
logger=log,
func_name="hook.save_metering_archive"
)
15 changes: 15 additions & 0 deletions csp_billing_adapter/bill_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
string_to_date
)
from csp_billing_adapter.config import Config
from csp_billing_adapter.archive import archive_record

log = logging.getLogger('CSPBillingAdapter')

Expand Down Expand Up @@ -655,3 +656,17 @@ def process_metering(
)
csp_config['usage'] = billable_usage
csp_config['last_billed'] = metering_time

# Save last usage and metering records to archive
billing_record = {
'billing_time': metering_time,
'billing_status': billing_status,
'billed_usage': billed_dimensions,
'usage_records': billable_records
}

archive_record(
hook,
config,
billing_record
)
16 changes: 16 additions & 0 deletions csp_billing_adapter/memory_archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,26 @@

import csp_billing_adapter

from csp_billing_adapter.config import Config

memory_archive = []

log = logging.getLogger('CSPBillingAdapter')


@csp_billing_adapter.hookimpl(trylast=True)
def get_archive_location():
"""Retrieve archive location."""
return '/tmp/fake_archive.json'


@csp_billing_adapter.hookimpl(trylast=True)
def get_metering_archive(config: Config):
return memory_archive.copy()


@csp_billing_adapter.hookimpl(trylast=True)
def save_metering_archive(config: Config, archive_data: list):
global memory_archive

memory_archive = archive_data
3 changes: 3 additions & 0 deletions tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ def cba_pm(cba_config):
# reset the in-memory csp_config to empty
pm.hook.save_csp_config(config=cba_config, csp_config=dict())

# reset the in-memory archive to empty
pm.hook.save_metering_archive(config=cba_config, archive_data=list())

return pm


Expand Down
45 changes: 44 additions & 1 deletion tests/unit/test_archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
for the archive util functions.
"""

from csp_billing_adapter.archive import append_metering_records
from csp_billing_adapter.archive import (
append_metering_records,
archive_record
)


def test_append_metering_records():
Expand All @@ -40,3 +43,43 @@ def test_append_metering_records():

assert len(archive) == 6
assert archive[4] == records


def test_archive_record(cba_pm, cba_config):
record = {
'billing_time': '2024-02-09T18:11:59.527064+00:00',
'billing_status': {
'tier_1': {
'record_id': 'd92c6e6556b14770994f5b64ebe3d339',
'status': 'succeeded'
}
},
'billed_usage': {
'tier_1': 10
},
'usage_records': [
{
'managed_node_count': 9,
'reporting_time': '2024-01-09T18:11:59.527673+00:00',
'base_product': 'cpe:/o:suse:product:v1.2.3'
},
{
'managed_node_count': 9,
'reporting_time': '2024-01-09T18:11:59.529096+00:00',
'base_product': 'cpe:/o:suse:product:v1.2.3'
},
{
'managed_node_count': 10,
'reporting_time': '2024-01-09T18:11:59.531424+00:00',
'base_product': 'cpe:/o:suse:product:v1.2.3'
}
]
}
archive_record(
cba_pm.hook,
cba_config,
record
)
archive = cba_pm.hook.get_metering_archive(config=cba_config)
assert len(archive) == 1
assert archive[0] == record

0 comments on commit 7718fa6

Please sign in to comment.