blob: 1d975f85435fddae916d369a7020f51019996f08 [file] [log] [blame]
Mike Frysingerd03e6b52019-08-03 12:49:01 -04001#!/usr/bin/python2
jamesren7a522042010-06-10 22:53:55 +00002
3"""A script that provides convertion between models.job and a protocol
4buffer object.
5
6This script contains only one class that takes an job instance and
7convert it into a protocol buffer object. The class will also be
8responsible for serializing the job instance via protocol buffers.
9
10"""
11
12# import python libraries
jamesren7a522042010-06-10 22:53:55 +000013import datetime
14import time
Keith Haddow1c58ad72018-05-12 11:23:55 -070015import logging
jamesren7a522042010-06-10 22:53:55 +000016
17# import autotest libraries
18from autotest_lib.tko import models
19from autotest_lib.tko import tko_pb2
jamesrena12b8a02010-06-16 23:28:23 +000020from autotest_lib.tko import utils
jamesren7a522042010-06-10 22:53:55 +000021
22__author__ = 'darrenkuo@google.com (Darren Kuo)'
23
24mktime = time.mktime
25datetime = datetime.datetime
26
27class JobSerializer(object):
28 """A class that takes a job object of the tko module and package
29 it with a protocol buffer.
30
31 This class will take a model.job object as input and create a
32 protocol buffer to include all the content of the job object. This
33 protocol buffer object will be serialized into a binary file.
34 """
35
36 def __init__(self):
37
38 self.job_type_dict = {'dir':str, 'tests':list, 'user':str,
39 'label':str, 'machine':str,
40 'queued_time':datetime,
41 'started_time':datetime,
42 'finished_time':datetime,
43 'machine_owner':str,
44 'machine_group':str, 'aborted_by':str,
45 'aborted_on':datetime,
Michael Tang5f74ffd2016-10-31 10:34:53 -070046 'keyval_dict':dict,
47 'afe_parent_job_id':str,
48 'build_version':str,
49 'suite':str,
50 'board':str}
jamesren7a522042010-06-10 22:53:55 +000051
52 self.test_type_dict = {'subdir':str, 'testname':str,
53 'status':str, 'reason':str,
54 'kernel':models.kernel, 'machine':str,
55 'started_time':datetime,
56 'finished_time':datetime,
57 'iterations':list, 'attributes':dict,
58 'labels':list}
59
jamesren31cd7ab2010-07-20 18:25:32 +000060 self.kernel_type_dict = {'base':str, 'kernel_hash':str}
jamesren7a522042010-06-10 22:53:55 +000061
62 self.iteration_type_dict = {'index':int, 'attr_keyval':dict,
jamesren31cd7ab2010-07-20 18:25:32 +000063 'perf_keyval':dict}
jamesren7a522042010-06-10 22:53:55 +000064
65
66 def deserialize_from_binary(self, infile):
67 """Takes in a binary file name and returns a tko job object.
68
69 The method first deserialize the binary into a protocol buffer
70 job object and then converts the job object into a tko job
71 object.
72
jamesren7a522042010-06-10 22:53:55 +000073
Michael Tang5f74ffd2016-10-31 10:34:53 -070074 @param infile: the name of the binary file that will be deserialized.
75
76 @return: a tko job that is represented by the binary file will
jamesren7a522042010-06-10 22:53:55 +000077 be returned.
78 """
79
jamesrenfe229d42010-06-10 23:55:06 +000080 job_pb = tko_pb2.Job()
jamesren7a522042010-06-10 22:53:55 +000081
82 binary = open(infile, 'r')
83 try:
84 job_pb.ParseFromString(binary.read())
85 finally:
86 binary.close()
87
88 return self.get_tko_job(job_pb)
89
90
jamesrena12b8a02010-06-16 23:28:23 +000091 def serialize_to_binary(self, the_job, tag, binaryfilename):
jamesren7a522042010-06-10 22:53:55 +000092 """Serializes the tko job object into a binary by using a
93 protocol buffer.
94
95 The method takes a tko job object and constructs a protocol
96 buffer job object. Then invokes the native serializing
97 function on the object to get a binary string. The string is
98 then written to outfile.
99
100 Precondition: Assumes that all the information about the job
101 is already in the job object. Any fields that is None will be
102 provided a default value.
103
Michael Tang5f74ffd2016-10-31 10:34:53 -0700104 @param the_job: the tko job object that will be serialized.
jamesrena12b8a02010-06-16 23:28:23 +0000105 tag: contains the job name and the afe_job_id
jamesren7a522042010-06-10 22:53:55 +0000106 binaryfilename: the name of the file that will be written to
Michael Tang5f74ffd2016-10-31 10:34:53 -0700107 @param tag: The job tag string.
108 @param binaryfilename: The output filename.
jamesren7a522042010-06-10 22:53:55 +0000109
Michael Tang5f74ffd2016-10-31 10:34:53 -0700110 @return: the filename of the file that contains the
jamesren7a522042010-06-10 22:53:55 +0000111 binary of the serialized object.
112 """
113
jamesrena12b8a02010-06-16 23:28:23 +0000114 pb_job = tko_pb2.Job()
115 self.set_pb_job(the_job, pb_job, tag)
jamesren7a522042010-06-10 22:53:55 +0000116
117 out = open(binaryfilename, 'wb')
118 try:
jamesrena12b8a02010-06-16 23:28:23 +0000119 out.write(pb_job.SerializeToString())
jamesren7a522042010-06-10 22:53:55 +0000120 finally:
121 out.close()
122
123
jamesrena12b8a02010-06-16 23:28:23 +0000124 def set_afe_job_id_and_tag(self, pb_job, tag):
125 """Sets the pb job's afe_job_id and tag field.
126
127 @param
128 pb_job: the pb job that will have it's fields set.
129 tag: used to set pb_job.tag and pb_job.afe_job_id.
130 """
131 pb_job.tag = tag
132 pb_job.afe_job_id = utils.get_afe_job_id(tag)
133
134
jamesren7a522042010-06-10 22:53:55 +0000135 # getter setter methods
136 def get_tko_job(self, job):
137 """Creates a a new tko job object from the pb job object.
138
139 Uses getter methods on the pb objects to extract all the
140 attributes and finally constructs a tko job object using the
141 models.job constructor.
142
143 @param
144 job: a pb job where data is being extracted from.
145
146 @return a tko job object.
147 """
148
149 fields_dict = self.get_trivial_attr(job, self.job_type_dict)
150
151 fields_dict['tests'] = [self.get_tko_test(test) for test in job.tests]
152
153 fields_dict['keyval_dict'] = dict((keyval.name, keyval.value)
154 for keyval in job.keyval_dict)
155
156 newjob = models.job(fields_dict['dir'], fields_dict['user'],
157 fields_dict['label'],
158 fields_dict['machine'],
159 fields_dict['queued_time'],
160 fields_dict['started_time'],
161 fields_dict['finished_time'],
162 fields_dict['machine_owner'],
163 fields_dict['machine_group'],
164 fields_dict['aborted_by'],
165 fields_dict['aborted_on'],
166 fields_dict['keyval_dict'])
167
168 newjob.tests.extend(fields_dict['tests'])
169
170 return newjob
171
172
jamesrena12b8a02010-06-16 23:28:23 +0000173 def set_pb_job(self, tko_job, pb_job, tag):
jamesren7a522042010-06-10 22:53:55 +0000174 """Set the fields for the new job object.
175
176 Method takes in a tko job and an empty protocol buffer job
177 object. Then safely sets all the appropriate field by first
178 testing if the value in the original object is None.
179
180 @param
181 tko_job: a tko job instance that will have it's values
182 transfered to the new job
183 pb_job: a new instance of the job class provided in the
184 protocol buffer.
jamesrena12b8a02010-06-16 23:28:23 +0000185 tag: used to set pb_job.tag and pb_job.afe_job_id.
jamesren7a522042010-06-10 22:53:55 +0000186 """
187
188 self.set_trivial_attr(tko_job, pb_job, self.job_type_dict)
jamesrena12b8a02010-06-16 23:28:23 +0000189 self.set_afe_job_id_and_tag(pb_job, tag)
Michael Tang5f74ffd2016-10-31 10:34:53 -0700190 if hasattr(tko_job, 'index'):
191 pb_job.job_idx = tko_job.index
jamesren7a522042010-06-10 22:53:55 +0000192
193 for test in tko_job.tests:
194 newtest = pb_job.tests.add()
195 self.set_pb_test(test, newtest)
196
197 for key, val in tko_job.keyval_dict.iteritems():
198 newkeyval = pb_job.keyval_dict.add()
199 newkeyval.name = key
200 newkeyval.value = str(val)
201
202
203 def get_tko_test(self, test):
jamesrena12b8a02010-06-16 23:28:23 +0000204 """Creates a tko test from pb_test.
205
206 Extracts data from pb_test by calling helper methods and
207 creates a tko test using the models.test constructor.
208
209 @param:
210 test: a pb_test where fields will be extracted from.
211
212 @return a new instance of models.test
213 """
jamesren7a522042010-06-10 22:53:55 +0000214 fields_dict = self.get_trivial_attr(test, self.test_type_dict)
215
216 fields_dict['kernel'] = self.get_tko_kernel(test.kernel)
217
218 fields_dict['iterations'] = [self.get_tko_iteration(iteration)
219 for iteration in test.iterations]
220
221 fields_dict['attributes'] = dict((keyval.name, keyval.value)
222 for keyval in test.attributes)
223
224 fields_dict['labels'] = list(test.labels)
225
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700226 # The constructor for models.test accepts a "perf_values" list that
227 # represents performance values of the test. The empty list argument
228 # in the constructor call below represents this value and makes this
229 # code adhere properly to the models.test constructor argument list.
230 # However, the effect of the empty list is that perf values are
231 # ignored in the job_serializer module. This is ok for now because
232 # autotest does not use the current module. If job_serializer is used
233 # in the future, we need to modify the "tko.proto" protobuf file to
234 # understand the notion of perf_values, then modify this file
235 # accordingly to use it.
jamesren7a522042010-06-10 22:53:55 +0000236 return models.test(fields_dict['subdir'],
237 fields_dict['testname'],
238 fields_dict['status'],
239 fields_dict['reason'],
240 fields_dict['kernel'],
241 fields_dict['machine'],
242 fields_dict['started_time'],
243 fields_dict['finished_time'],
244 fields_dict['iterations'],
245 fields_dict['attributes'],
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700246 [],
jamesren7a522042010-06-10 22:53:55 +0000247 fields_dict['labels'])
248
249
250 def set_pb_test(self, tko_test, pb_test):
251 """Sets the various fields of test object of the tko protocol.
252
253 Method takes a tko test and a new test of the protocol buffer and
254 transfers the values in the tko test to the new test.
255
256 @param
257 tko_test: a tko test instance.
258 pb_test: an empty protocol buffer test instance.
259
260 """
261
262 self.set_trivial_attr(tko_test, pb_test, self.test_type_dict)
263
264 self.set_pb_kernel(tko_test.kernel, pb_test.kernel)
Michael Tang5f74ffd2016-10-31 10:34:53 -0700265 if hasattr(tko_test, 'test_idx'):
266 pb_test.test_idx = tko_test.test_idx
jamesren7a522042010-06-10 22:53:55 +0000267
268 for current_iteration in tko_test.iterations:
269 pb_iteration = pb_test.iterations.add()
270 self.set_pb_iteration(current_iteration, pb_iteration)
271
272 for key, val in tko_test.attributes.iteritems():
273 newkeyval = pb_test.attributes.add()
274 newkeyval.name = key
275 newkeyval.value = str(val)
276
277 for current_label in tko_test.labels:
278 pb_test.labels.append(current_label)
279
280
281 def get_tko_kernel(self, kernel):
282 """Constructs a new tko kernel object from a pb kernel object.
283
284 Uses all the getter methods on the pb kernel object to extract
285 the attributes and constructs a new tko kernel object using
286 the model.kernel constructor.
287
288 @param
289 kernel: a pb kernel object where data will be extracted.
290
291 @return a new tko kernel object.
292 """
293
294 fields_dict = self.get_trivial_attr(kernel, self.kernel_type_dict)
295
jamesren31cd7ab2010-07-20 18:25:32 +0000296 return models.kernel(fields_dict['base'], [], fields_dict['kernel_hash'])
jamesren7a522042010-06-10 22:53:55 +0000297
298
299 def set_pb_kernel(self, tko_kernel, pb_kernel):
300 """Set a specific kernel of a test.
301
302 Takes the same form of all the other setting methods. It
303 seperates the string variables from the int variables and set
304 them safely.
305
306 @param
307 tko_kernel: a tko kernel.
308 pb_kernel: an empty protocol buffer kernel.
309
310 """
311
312 self.set_trivial_attr(tko_kernel, pb_kernel, self.kernel_type_dict)
313
jamesren7a522042010-06-10 22:53:55 +0000314
315 def get_tko_iteration(self, iteration):
316 """Creates a new tko iteration with the data in the provided
317 pb iteration.
318
319 Uses the data in the pb iteration and the models.iteration
320 constructor to create a new tko iterations
321
322 @param
323 iteration: a pb iteration instance
324
325 @return a tko iteration instance with the same data.
326 """
327
328 fields_dict = self.get_trivial_attr(iteration,
329 self.iteration_type_dict)
330
331 fields_dict['attr_keyval'] = dict((keyval.name, keyval.value)
332 for keyval in iteration.attr_keyval)
333
334 fields_dict['perf_keyval'] = dict((keyval.name, keyval.value)
335 for keyval in iteration.perf_keyval)
336
337 return models.iteration(fields_dict['index'],
338 fields_dict['attr_keyval'],
339 fields_dict['perf_keyval'])
340
341
342 def set_pb_iteration(self, tko_iteration, pb_iteration):
343 """Sets all fields for a particular iteration.
344
345 Takes same form as all the other setting methods. Sets int,
346 str and datetime variables safely.
347
348 @param
349 tko_iteration: a tko test iteration.
350 pb_iteration: an empty pb test iteration.
351
352 """
353
354 self.set_trivial_attr(tko_iteration, pb_iteration,
355 self.iteration_type_dict)
356
357 for key, val in tko_iteration.attr_keyval.iteritems():
358 newkeyval = pb_iteration.attr_keyval.add()
359 newkeyval.name = key
360 newkeyval.value = str(val)
361
362 for key, val in tko_iteration.perf_keyval.iteritems():
363 newkeyval = pb_iteration.perf_keyval.add()
364 newkeyval.name = key
365 newkeyval.value = str(val)
366
367
368 def get_trivial_attr(self, obj, objdict):
369 """Get all trivial attributes from the object.
370
371 This function is used to extract attributes from a pb job. The
372 dictionary specifies the types of each attribute in each tko
373 class.
374
375 @param
376 obj: the pb object that is being extracted.
377 objdict: the dict that specifies the type.
378
379 @return a dict of each attr name and it's corresponding value.
380 """
381
382 resultdict = {}
383 for field, field_type in objdict.items():
384 value = getattr(obj, field)
385 if field_type in (str, int, long):
386 resultdict[field] = field_type(value)
387 elif field_type == datetime:
388 resultdict[field] = (
389 datetime.fromtimestamp(value/1000.0))
390
391 return resultdict
392
393
394 def set_trivial_attr(self, tko_obj, pb_obj, objdict):
395 """Sets all the easy attributes appropriately according to the
396 type.
397
398 This function is used to set all the trivial attributes
399 provided by objdict, the dictionary that specifies the types
400 of each attribute in each tko class.
401
402 @param
403 tko_obj: the original object that has the data being copied.
404 pb_obj: the new pb object that is being copied into.
405 objdict: specifies the type of each attribute in the class we
406 are working with.
407
408 """
409 for attr, attr_type in objdict.iteritems():
410 if attr_type == datetime:
411 t = getattr(tko_obj, attr)
412 if not t:
413 self.set_attr_safely(pb_obj, attr, t, int)
414 else:
415 t = mktime(t.timetuple()) + 1e-6 * t.microsecond
416 setattr(pb_obj, attr, long(t*1000))
417 else:
418 value = getattr(tko_obj, attr)
419 self.set_attr_safely(pb_obj, attr, value, attr_type)
420
421
422 def set_attr_safely(self, var, attr, value, vartype):
423 """Sets a particular attribute of var if the provided value is
424 not None.
425
426 Checks if value is None. If not, set the attribute of the var
427 to be the default value. This is necessary for the special
428 required fields of the protocol buffer.
429
430 @param
431 var: the variable of which one of the attribute is being set.
432 attr: the attribute that is being set.
433 value: the value that is being checked
434 vartype: the expected type of the attr
435
436 """
437
438 supported_types = [int, long, str]
439 if vartype in supported_types:
440 if value is None:
441 value = vartype()
Keith Haddow1c58ad72018-05-12 11:23:55 -0700442 elif not isinstance(value, vartype):
443 logging.warning('Unexpected type %s for attr %s, should be %s',
Keith Haddowf9ae33f2018-08-01 09:24:18 -0700444 type(value), attr, vartype)
jamesren7a522042010-06-10 22:53:55 +0000445
Keith Haddow1c58ad72018-05-12 11:23:55 -0700446 setattr(var, attr, vartype(value))