Skip to content

Commit 6357e51

Browse files
authored
Extend gather_automation_controller_billing_data (#8)
Gather and ship billing data to console.redhat.com with automatically collecting gap, by storing a last collected timestamp and always collecting from that last succesfully collected timestamp
1 parent 5274bbd commit 6357e51

File tree

6 files changed

+60
-54
lines changed

6 files changed

+60
-54
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,9 @@
77
## 0.0.2
88

99
- gather_automation_controller_billing_data command
10+
11+
## 0.0.3
12+
13+
- gather_automation_controller_billing_data command extension
14+
Adding ability to run without "since specified", collecting any
15+
gap automatically.

README.md

+9
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,13 @@ Gather and ship billing data to console.redhat.com for a dynamic datetime range:
6666
# You need to set 'Red Hat customer username/password' under Automation Controller 'Miscellaneous System' settings
6767
# This will collect and ship data for yesterday, interval <2 days ago, 1 day ago>
6868
metrics-utility gather_automation_controller_billing_data --ship --since=2d --until=1d
69+
```
70+
71+
Gather and ship billing data to console.redhat.com with automatically collecting gap, by storing a last collected
72+
timestamp and always collecting from that last succesfully collected timestamp. To be on the safe side, we can
73+
collect interval <last_collected_timestamp_or_4_weeks_back, 10_minutes_ago> to give all records time to insert.
74+
```
75+
# You need to set 'Red Hat customer username/password' under Automation Controller 'Miscellaneous System' settings
76+
# This will collect and ship data for interval <last_collected_timestamp_or_4_weeks_back, 10_minutes_ago>
77+
metrics-utility gather_automation_controller_billing_data --ship --until=10m
6978
```

metrics_utility/automation_controller_billing/collector.py

+23-34
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66

77
import insights_analytics_collector as base
88

9-
# from django.core.serializers.json import DjangoJSONEncoder
9+
from django.core.serializers.json import DjangoJSONEncoder
1010
# from awx.conf.license import get_license
1111
# from awx.main.models import Job
1212
# from awx.main.access import access_registry
1313
# from rest_framework.exceptions import PermissionDenied
1414
from metrics_utility.automation_controller_billing.package import Package
15-
# from awx.main.utils import datetime_hook
15+
from awx.main.utils import datetime_hook
1616
from awx.main.utils.pglock import advisory_lock
1717

1818
logger = logging.getLogger('awx.main.analytics')
@@ -107,44 +107,33 @@ def _pg_advisory_lock(self, key, wait=False):
107107
yield lock
108108

109109
def _last_gathering(self):
110-
# TODO: fill in later, when integrated with consumption based billing in Controller
111-
112-
# return settings.AUTOMATION_ANALYTICS_LAST_GATHER
113-
return {}
110+
# Not needed in this implementation, but we need to define an abstract method
111+
pass
114112

115113
def _load_last_gathered_entries(self):
116-
# TODO: fill in later, when integrated with consumption based billing in Controller
114+
# We are reusing Settings used by Analytics, so we don't have to backport changes into analytics
115+
# We can safely do this, by making sure we use the same lock as Analytics, before we persist
116+
# these settings.
117+
from awx.conf.models import Setting
117118

118-
# from awx.conf.models import Setting
119+
last_entries = Setting.objects.filter(key='AUTOMATION_ANALYTICS_LAST_ENTRIES').first()
120+
last_gathered_entries = json.loads((last_entries.value if last_entries is not None else '') or '{}', object_hook=datetime_hook)
121+
return last_gathered_entries
119122

120-
# last_entries = Setting.objects.filter(key='AUTOMATION_ANALYTICS_LAST_ENTRIES').first()
121-
# last_gathered_entries = json.loads((last_entries.value if last_entries is not None else '') or '{}', object_hook=datetime_hook)
122-
# return last_gathered_entries
123-
124-
return {}
123+
def _gather_finalize(self):
124+
"""Persisting timestamps (manual/schedule mode only)"""
125+
if self.is_shipping_enabled():
126+
# We need to wait on analytics lock, to update the last collected timestamp settings
127+
# so we don't clash with analytics job collection.
128+
with self._pg_advisory_lock("gather_analytics_lock", wait=True) as acquired:
129+
# We need to load fresh settings again as we're obtaning the lock, since
130+
# Analytics job could have changed this on the background and we'd be resetting
131+
# the Analytics values here.
132+
self._load_last_gathered_entries()
133+
self._update_last_gathered_entries()
125134

126135
def _save_last_gathered_entries(self, last_gathered_entries):
127-
# TODO: fill in later, when integrated with consumption based billing in Controller
128-
129-
# settings.AUTOMATION_ANALYTICS_LAST_ENTRIES = json.dumps(last_gathered_entries, cls=DjangoJSONEncoder)
130-
pass
131-
132-
def _save_last_gather(self):
133-
# TODO: fill in later, when integrated with consumption based billing in Controller
134-
# from awx.main.signals import disable_activity_stream
135-
136-
# with disable_activity_stream():
137-
# if not settings.AUTOMATION_ANALYTICS_LAST_GATHER or self.gather_until > settings.AUTOMATION_ANALYTICS_LAST_GATHER:
138-
# # `AUTOMATION_ANALYTICS_LAST_GATHER` is set whether collection succeeds or fails;
139-
# # if collection fails because of a persistent, underlying issue and we do not set last_gather,
140-
# # we risk the collectors hitting an increasingly greater workload while the underlying issue
141-
# # remains unresolved. Put simply, if collection fails, we just move on.
142-
143-
# # All that said, `AUTOMATION_ANALYTICS_LAST_GATHER` plays a much smaller role in determining
144-
# # what is actually collected than it used to; collectors now mostly rely on their respective entry
145-
# # under `last_entries` to determine what should be collected.
146-
# settings.AUTOMATION_ANALYTICS_LAST_GATHER = self.gather_until
147-
pass
136+
settings.AUTOMATION_ANALYTICS_LAST_ENTRIES = json.dumps(last_gathered_entries, cls=DjangoJSONEncoder)
148137

149138
@staticmethod
150139
def _package_class():

metrics_utility/automation_controller_billing/collectors.py

+13-13
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,21 @@ def something(since):
3333
"""
3434

3535

36-
def trivial_slicing(key, last_gather, **kwargs):
36+
def trivial_slicing(key, _, **kwargs):
3737
since, until = kwargs.get('since', None), kwargs.get('until', now())
38+
if since is not None:
39+
return [(since, until)]
3840

39-
return [(since, until)]
40-
# TODO: load last collected timestamp once we support that path
41-
# if since is not None:
42-
# return [(since, until)]
41+
from awx.conf.models import Setting
4342

44-
# from awx.conf.models import Setting
45-
46-
# horizon = until - timedelta(weeks=4)
47-
# last_entries = Setting.objects.filter(key='AUTOMATION_ANALYTICS_LAST_ENTRIES').first()
48-
# last_entries = json.loads((last_entries.value if last_entries is not None else '') or '{}', object_hook=datetime_hook)
49-
# last_entry = max(last_entries.get(key) or last_gather, horizon)
50-
# return [(last_entry, until)]
43+
horizon = until - timedelta(weeks=4)
44+
last_entries = Setting.objects.filter(key='AUTOMATION_ANALYTICS_LAST_ENTRIES').first()
45+
last_entries = json.loads((last_entries.value if last_entries is not None else '') or '{}', object_hook=datetime_hook)
46+
if last_entries.get(key):
47+
last_entry = max(last_entries.get(key), horizon)
48+
else:
49+
last_entry = horizon
50+
return [(last_entry, until)]
5151

5252
# TODO: implement daily slicing for billing collection?
5353
# def four_hour_slicing(key, last_gather, **kwargs):
@@ -149,7 +149,7 @@ def _copy_table_aap_2_5_and_above(cursor, query, file):
149149

150150

151151
@register('job_host_summary', '1.0', format='csv', description=_('Data for billing'), fnc_slicing=trivial_slicing)
152-
def unified_jobs_table(since, full_path, until, **kwargs):
152+
def job_host_summary_table(since, full_path, until, **kwargs):
153153
# TODO: controler needs to have an index on main_jobhostsummary.modified
154154
query = '''
155155
(SELECT main_jobhostsummary.id,

metrics_utility/management/commands/gather_automation_controller_billing_data.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,12 @@ def handle(self, *args, **options):
5353

5454
# Process since argument
5555
since = None
56-
if opt_since.endswith('d'):
56+
if opt_since and opt_since.endswith('d'):
5757
days_ago = int(opt_since[0:-1])
5858
since = (datetime.datetime.now() - datetime.timedelta(days=days_ago-1)).replace(hour=0, minute=0, second=0, microsecond=0)
59+
elif opt_since and opt_since.endswith('m'):
60+
minutes_ago = int(opt_since[0:-1])
61+
since = (datetime.datetime.now() - datetime.timedelta(minutes=minutes_ago))
5962
else:
6063
since = parser.parse(opt_since) if opt_since else None
6164
# Add default utc timezone
@@ -64,19 +67,18 @@ def handle(self, *args, **options):
6467

6568
# Process until argument
6669
until = None
67-
if opt_until.endswith('d'):
70+
if opt_until and opt_until.endswith('d'):
6871
days_ago = int(opt_until[0:-1])
6972
until = (datetime.datetime.now() - datetime.timedelta(days=days_ago-1)).replace(hour=0, minute=0, second=0, microsecond=0)
73+
elif opt_until and opt_until.endswith('m'):
74+
minutes_ago = int(opt_until[0:-1])
75+
until = (datetime.datetime.now() - datetime.timedelta(minutes=minutes_ago))
7076
else:
7177
until = parser.parse(opt_until) if opt_until else None
7278
# Add default utc timezone
7379
if until and until.tzinfo is None:
7480
until = until.replace(tzinfo=timezone.utc)
7581

76-
if since is None or until is None:
77-
self.logger.error('Both --since and --until arguments must be passed')
78-
return
79-
8082
if opt_ship and opt_dry_run:
8183
self.logger.error('Arguments --ship and --dry-run cannot be processed at the same time, set only one of these.')
8284
return

setup.cfg

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = metrics_utility
33
author = Red Hat
44
author_email = [email protected]
5-
version = 0.0.2
5+
version = 0.0.3
66

77
[options]
88
packages = find:

0 commit comments

Comments
 (0)