Skip to content

Commit d35fefc

Browse files
committed
Report correct durations using periods
Change f082711 introduce a "extract_to" parameter, and an additional check to not report machines that fall outside that interval. However, this prevents long running VMs to be published. The accounting system expects absolute durations (for wall and CPU) from the start of the record to its end or to the reporting date if it has not ended, that is: wall = (end or report time) - start time However, we were doing it simply wrong. Moreover, we were reporting wall time as the CPU time, and this is not true. From an IaaS perspective, CPU time (or vCPU time) should be (wall * cpus) as a CPU allocated is a CPU being used.
1 parent c873858 commit d35fefc

File tree

3 files changed

+91
-110
lines changed

3 files changed

+91
-110
lines changed

caso/extract/manager.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,20 @@
5555

5656
cli_opts = [
5757
cfg.StrOpt('extract_to',
58-
help='Extract records until this date. If it is not set, '
59-
'we use now'),
58+
help='Extract record changes until this date. '
59+
'If it is not set, we use now. If a server has '
60+
'ended after this date, it will be included, but '
61+
'the consuption reported will end on this date. '
62+
'If no time zone is specified, UTC will be used.'),
6063
cfg.StrOpt('extract_from',
61-
help='Extract records from this date. If it is not set, '
62-
'extract records from last run. If none are set, extract '
63-
'records from the beginning of time. If no time zone is '
64-
'specified, UTC will be used.'),
64+
help='Extract records that have changed after this date. This '
65+
'means that if a record has started before this date, and '
66+
'it has changed after this date (i.e. it is still running '
67+
'or it has ended) it will be reported. \n'
68+
'If it is not set, extract records from last run. '
69+
'If it is set to None and last run file is not present, '
70+
'it will extract records from the beginning of time. '
71+
'If no time zone is specified, UTC will be used.'),
6572
cfg.StrOpt('extractor',
6673
choices=SUPPORTED_EXTRACTORS.keys(),
6774
default='nova',

caso/extract/nova.py

Lines changed: 78 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@
1919
import dateutil.parser
2020
import glanceclient.client
2121
import novaclient.client
22+
import novaclient.exceptions
2223
from oslo_config import cfg
2324
from oslo_log import log
2425

2526
from caso.extract import base
26-
from caso.extract import utils
2727
from caso import keystone_client
2828
from caso import record
2929

@@ -48,7 +48,49 @@ def _get_glance_client(self, project):
4848
session = keystone_client.get_session(CONF, project)
4949
return glanceclient.client.Client(2, session=session)
5050

51-
def extract_for_project(self, project, lastrun, extract_to):
51+
def build_record(self, server, vo, images, flavors, users):
52+
status = self.vm_status(server.status)
53+
image_id = None
54+
if server.image:
55+
image = images.get(server.image['id'])
56+
image_id = server.image['id']
57+
if image:
58+
if image.get("vmcatcher_event_ad_mpuri", None) is not None:
59+
image_id = image.get("vmcatcher_event_ad_mpuri", None)
60+
61+
flavor = flavors.get(server.flavor["id"])
62+
if flavor:
63+
bench_name = flavor.get_keys().get(CONF.benchmark_name_key)
64+
bench_value = flavor.get_keys().get(CONF.benchmark_value_key)
65+
else:
66+
bench_name = bench_value = None
67+
68+
if not all([bench_name, bench_value]):
69+
if any([bench_name, bench_value]):
70+
LOG.warning("Benchmark for flavor %s not properly set" %
71+
flavor)
72+
else:
73+
LOG.debug("Benchmark information for flavor %s not set,"
74+
"plase indicate the corret benchmark_name_key "
75+
"and benchmark_value_key in the configuration "
76+
"file or set the correct properties in the "
77+
"flavor." % flavor)
78+
79+
r = record.CloudRecord(server.id,
80+
CONF.site_name,
81+
server.name,
82+
server.user_id,
83+
server.tenant_id,
84+
vo,
85+
compute_service=CONF.service_name,
86+
status=status,
87+
image_id=image_id,
88+
user_dn=users.get(server.user_id, None),
89+
benchmark_type=bench_name,
90+
benchmark_value=bench_value)
91+
return r
92+
93+
def extract_for_project(self, project, extract_from, extract_to):
5294
"""Extract records for a project from given date querying nova.
5395
5496
This method will get information from nova.
@@ -63,7 +105,7 @@ def extract_for_project(self, project, lastrun, extract_to):
63105
# Some API calls do not expect a TZ, so we have to remove the timezone
64106
# from the dates. We assume that all dates coming from upstream are
65107
# in UTC TZ.
66-
lastrun = lastrun.replace(tzinfo=None)
108+
extract_from = extract_from.replace(tzinfo=None)
67109

68110
# Try and except here
69111
nova = self._get_nova_client(project)
@@ -79,9 +121,11 @@ def extract_for_project(self, project, lastrun, extract_to):
79121
marker = None
80122
# Iter over results until we do not have more to get
81123
while True:
82-
aux = nova.servers.list(search_opts={"changes-since": lastrun},
83-
limit=limit,
84-
marker=marker)
124+
aux = nova.servers.list(
125+
search_opts={"changes-since": extract_from},
126+
limit=limit,
127+
marker=marker
128+
)
85129
servers.extend(aux)
86130

87131
if len(aux) < limit:
@@ -94,7 +138,7 @@ def extract_for_project(self, project, lastrun, extract_to):
94138
start = dateutil.parser.parse(servers[0].created)
95139
start = start.replace(tzinfo=None)
96140
else:
97-
start = lastrun
141+
start = extract_from
98142

99143
aux = nova.usage.get(project_id, start, extract_to)
100144
usages = getattr(aux, "server_usages", [])
@@ -105,81 +149,53 @@ def extract_for_project(self, project, lastrun, extract_to):
105149
vo = self.voms_map.get(project)
106150

107151
for server in servers:
108-
status = self.vm_status(server.status)
109-
image_id = None
110-
if server.image:
111-
image = images.get(server.image['id'])
112-
image_id = server.image['id']
113-
if image:
114-
if image.get("vmcatcher_event_ad_mpuri", None) is not None:
115-
image_id = image.get("vmcatcher_event_ad_mpuri", None)
116-
117-
flavor = flavors.get(server.flavor["id"])
118-
if flavor:
119-
bench_name = flavor.get_keys().get(CONF.benchmark_name_key)
120-
bench_value = flavor.get_keys().get(CONF.benchmark_value_key)
121-
else:
122-
bench_name = bench_value = None
123-
124-
if not all([bench_name, bench_value]):
125-
if any([bench_name, bench_value]):
126-
LOG.warning("Benchmark for flavor %s not properly set" %
127-
flavor)
128-
else:
129-
LOG.debug("Benchmark information for flavor %s not set,"
130-
"plase indicate the corret benchmark_name_key "
131-
"and benchmark_value_key in the configuration "
132-
"file or set the correct properties in the "
133-
"flavor." % flavor)
134-
135-
r = record.CloudRecord(server.id,
136-
CONF.site_name,
137-
server.name,
138-
server.user_id,
139-
server.tenant_id,
140-
vo,
141-
compute_service=CONF.service_name,
142-
status=status,
143-
image_id=image_id,
144-
user_dn=users.get(server.user_id, None),
145-
benchmark_type=bench_name,
146-
benchmark_value=bench_value)
147-
records[server.id] = r
152+
records[server.id] = self.build_record(server, vo, images,
153+
flavors, users)
148154

149155
for usage in usages:
150156
if usage["instance_id"] not in records:
151-
continue
157+
try:
158+
server = nova.servers.get(usage["instance_id"])
159+
except novaclient.exceptions.ClientException:
160+
# Maybe the instance is completely missing
161+
continue
162+
records[server.id] = self.build_record(server, vo, images,
163+
flavors, users)
152164
instance_id = usage["instance_id"]
153165
records[instance_id].memory = usage["memory_mb"]
154166
records[instance_id].cpu_count = usage["vcpus"]
155167
records[instance_id].disk = usage["local_gb"]
156168

169+
# Start time must be the time when the machine was created
157170
started = dateutil.parser.parse(usage["started_at"])
171+
records[instance_id].start_time = int(started.strftime("%s"))
172+
173+
# End time must ben the time when the machine was ended, but it may
174+
# be none
158175
if usage.get('ended_at', None) is not None:
159176
ended = dateutil.parser.parse(usage["ended_at"])
177+
records[instance_id].end_time = int(ended.strftime("%s"))
160178
else:
161179
ended = None
162180

163-
# Since the nova API only gives you the "changes-since",
164-
# we need to filter the machines that changed outside
165-
# the interval
166-
if utils.server_outside_interval(lastrun, extract_to, started,
167-
ended):
168-
del records[instance_id]
169-
continue
181+
# Wall and CPU durations are absolute values, not deltas for the
182+
# reporting period. The nova API only gives use the "changes-since"
183+
# usages, therefore we need to calculate the wall duration by
184+
# ourselves, then multiply by the nr of CPUs to get the CPU
185+
# duration.
170186

171-
records[instance_id].start_time = int(started.strftime("%s"))
172-
if ended is not None:
173-
records[instance_id].end_time = int(ended.strftime("%s"))
187+
# If the machine has not ended, report consumption until
188+
# extract_to, otherwise get its consuption by substracting ended -
189+
# started.
190+
if ended is not None and ended < extract_to:
174191
wall = ended - started
175192
else:
176193
wall = extract_to - started
177194

178195
wall = int(wall.total_seconds())
179196
records[instance_id].wall_duration = wall
180197

181-
cput = int(usage["hours"] * 3600)
182-
# NOTE(aloga): in some cases there may be rounding errors and cput
183-
# may be larger than wall.
184-
records[instance_id].cpu_duration = cput if cput < wall else wall
198+
cput = wall * usage["vcpus"]
199+
records[instance_id].cpu_duration = cput
200+
185201
return records

caso/extract/utils.py

Lines changed: 0 additions & 42 deletions
This file was deleted.

0 commit comments

Comments
 (0)