Skip to content

Commit 2161a33

Browse files
committed
Fix record extraction and do not request only deleted records
Commit 0058b6c introduced a bug in how we retrieve the records, since we introduced the "deleted" filter and this was filtering out all the instances that are not deleted. Moreover, the way we were retrieving the results was wrong as only servers with a changed status within the requested period were considered for the record building. We cannot use 'changes-since' in the servers.list() API query, as it will only include changes that have chanted its status after that date. Also, we cannot just get all the usages and then query the servers.get() server by server, as deleted servers are not returned by ths servers.get() call. What we do now is the following. 1.- List all the deleted servers that changed after the start date 2.- Build the records for the period [start, end] 3.- Get all the usages 4.- Iter over the usages and: 4.1.- get information for non deleted servers 4.2.- do nothing with deleted servers, as we collected in in step (2) However, usage was eventually correctly reported, as sooner or later one server may have a changed status and we will generate the record. In order to get accurate results, accounting records must be regenerated at sites, possibly month by month, like: caso-extract --extract-from 2017-01-01 --extract-to 2017-01-31 caso-extract --extract-from 2017-02-01 --extract-to 2017-02-28 (...) Closes #47
1 parent ae6eb97 commit 2161a33

File tree

2 files changed

+78
-25
lines changed

2 files changed

+78
-25
lines changed

caso/extract/nova.py

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,29 @@ def extract_for_project(self, project, extract_from, extract_to):
119119
flavors[flavor.id] = flavor.to_dict()
120120
flavors[flavor.id]["extra"] = flavor.get_keys()
121121

122+
images = {image.id: image for image in glance.images.list()}
123+
records = {}
124+
125+
vo = self.voms_map.get(project)
126+
127+
# We cannot use 'changes-since' in the servers.list() API query, as it
128+
# will only include changes that have changed its status after that
129+
# date. However we cannot just get all the usages and then query
130+
# server by server, as deleted servers are not returned by this
131+
# servers.get() call. What we do is the following:
132+
#
133+
# 1.- List all the deleted servers that changed after the start date
134+
# 2.- Build the records for the period [start, end]
135+
# 3.- Get all the usages
136+
# 4.- Iter over the usages and:
137+
# 4.1.- get information for non deleted servers
138+
# 4.2.- do nothing with deleted servers, as we collected in in step (2)
139+
140+
# 1.- List all the deleted servers from that period.
122141
servers = []
123142
limit = 200
124143
marker = None
125-
# Iter over results until we do not have more to get
144+
# Use a marker and iter over results until we do not have more to get
126145
while True:
127146
aux = nova.servers.list(
128147
search_opts={"changes-since": extract_from,
@@ -138,35 +157,41 @@ def extract_for_project(self, project, extract_from, extract_to):
138157

139158
servers = sorted(servers, key=operator.attrgetter("created"))
140159

141-
# We are going to query for the sever usages below. It will return the
142-
# usages for the specified period. However, usages should be absolute
143-
# values, not deltas. Therefore we need to change the start time for
144-
# the query with that of the oldest server.
145-
if servers:
146-
start = dateutil.parser.parse(servers[0].created)
147-
start = start.replace(tzinfo=None)
148-
else:
149-
start = extract_from
150-
151-
aux = nova.usage.get(project_id, start, extract_to)
152-
usages = getattr(aux, "server_usages", [])
153-
154-
images = {image.id: image for image in glance.images.list()}
155-
records = {}
156-
157-
vo = self.voms_map.get(project)
158-
160+
# 2.- Build the records for the period. Drop servers outside the period
161+
# (we do this manually as we cannot limit the query to a period, only
162+
# changes after start date).
159163
for server in servers:
160164
server_start = dateutil.parser.parse(server.created)
161165
server_start = server_start.replace(tzinfo=None)
162-
if server_start > extract_to:
166+
# Some servers may be deleted before 'extract_from' but updated
167+
# afterwards
168+
server_end = server.__getattr__('OS-SRV-USG:terminated_at')
169+
if server_end:
170+
server_end = dateutil.parser.parse(server_end)
171+
server_end = server_end.replace(tzinfo=None)
172+
if (server_start > extract_to or
173+
(server_end and server_end < extract_from)):
163174
continue
164175
records[server.id] = self.build_record(server, vo, images,
165176
flavors, users)
166177

178+
# 3.- Get all the usages for the period
179+
start = extract_from
180+
aux = nova.usage.get(project_id, start, extract_to)
181+
usages = getattr(aux, "server_usages", [])
182+
183+
# 4.- Iter over the results and
167184
for usage in usages:
185+
# 4.1 and 4.2 Get the server if it is not yet there
168186
if usage["instance_id"] not in records:
169-
continue
187+
server = nova.servers.get(usage["instance_id"])
188+
189+
server_start = dateutil.parser.parse(server.created)
190+
server_start = server_start.replace(tzinfo=None)
191+
if server_start > extract_to:
192+
continue
193+
records[server.id] = self.build_record(server, vo, images,
194+
flavors, users)
170195
instance_id = usage["instance_id"]
171196
records[instance_id].memory = usage["memory_mb"]
172197
records[instance_id].cpu_count = usage["vcpus"]
@@ -185,10 +210,10 @@ def extract_for_project(self, project, extract_from, extract_to):
185210
ended = None
186211

187212
# Wall and CPU durations are absolute values, not deltas for the
188-
# reporting period. The nova API only gives use the "changes-since"
189-
# usages, therefore we need to calculate the wall duration by
190-
# ourselves, then multiply by the nr of CPUs to get the CPU
191-
# duration.
213+
# reporting period. The nova API only gives use the usages for the
214+
# requested period, therefore we need to calculate the wall
215+
# duration by ourselves, then multiply by the nr of CPUs to get the
216+
# CPU duration.
192217

193218
# If the machine has not ended, report consumption until
194219
# extract_to, otherwise get its consuption by substracting ended -

caso/tests/extract/test_nova.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
# not use this file except in compliance with the License. You may obtain
5+
# a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
15+
from caso.extract import nova
16+
from caso.tests import base
17+
18+
19+
class TestCasoManager(base.TestCase):
20+
def setUp(self):
21+
super(TestCasoManager, self).setUp()
22+
self.flags(mapping_file="etc/caso/voms.json.sample")
23+
self.extractor = nova.OpenStackExtractor()
24+
25+
def tearDown(self):
26+
self.reset_flags()
27+
28+
super(TestCasoManager, self).tearDown()

0 commit comments

Comments
 (0)