1919import dateutil .parser
2020import glanceclient .client
2121import novaclient .client
22+ import novaclient .exceptions
2223from oslo_config import cfg
2324from oslo_log import log
2425
2526from caso .extract import base
26- from caso .extract import utils
2727from caso import keystone_client
2828from 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
0 commit comments