• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python
2
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
13import datetime
14import time
15import logging
16
17# import autotest libraries
18from autotest_lib.tko import models
19from autotest_lib.tko import tko_pb2
20from autotest_lib.tko import utils
21
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,
46                              'keyval_dict':dict,
47                              'afe_parent_job_id':str,
48                              'build_version':str,
49                              'suite':str,
50                              'board':str}
51
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
60        self.kernel_type_dict = {'base':str, 'kernel_hash':str}
61
62        self.iteration_type_dict = {'index':int, 'attr_keyval':dict,
63                                    'perf_keyval':dict}
64
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
73
74        @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
77        be returned.
78        """
79
80        job_pb = tko_pb2.Job()
81
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
91    def serialize_to_binary(self, the_job, tag, binaryfilename):
92        """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
104        @param the_job: the tko job object that will be serialized.
105        tag: contains the job name and the afe_job_id
106        binaryfilename: the name of the file that will be written to
107        @param tag: The job tag string.
108        @param binaryfilename: The output filename.
109
110        @return: the filename of the file that contains the
111        binary of the serialized object.
112        """
113
114        pb_job = tko_pb2.Job()
115        self.set_pb_job(the_job, pb_job, tag)
116
117        out = open(binaryfilename, 'wb')
118        try:
119            out.write(pb_job.SerializeToString())
120        finally:
121            out.close()
122
123
124    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
135    # 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
173    def set_pb_job(self, tko_job, pb_job, tag):
174        """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.
185        tag: used to set pb_job.tag and pb_job.afe_job_id.
186        """
187
188        self.set_trivial_attr(tko_job, pb_job, self.job_type_dict)
189        self.set_afe_job_id_and_tag(pb_job, tag)
190        if hasattr(tko_job, 'index'):
191            pb_job.job_idx = tko_job.index
192
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):
204        """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        """
214        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
226        # 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.
236        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'],
246                           [],
247                           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)
265        if hasattr(tko_test, 'test_idx'):
266            pb_test.test_idx = tko_test.test_idx
267
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
296        return models.kernel(fields_dict['base'], [], fields_dict['kernel_hash'])
297
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
314
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()
442            elif not isinstance(value, vartype):
443                logging.warning('Unexpected type %s for attr %s, should be %s',
444                                type(value), attr, vartype)
445
446            setattr(var, attr, vartype(value))
447