~xnox/ubuntu-archive-tools/sru-report-autopkgtest-vomit

665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
1
#!/usr/bin/python3
2
# -*- coding: utf-8 -*-
3
4
# Copyright (C) 2011, 2012  Canonical Ltd.
5
# Author: Stéphane Graber <stgraber@ubuntu.com>
6
7
# This library is free software; you can redistribute it and/or
8
# modify it under the terms of the GNU Lesser General Public
9
# License as published by the Free Software Foundation; either
10
# version 2.1 of the License, or (at your option) any later version.
11
12
# This library is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
# Lesser General Public License for more details.
16
17
# You should have received a copy of the GNU Lesser General Public
18
# License along with this library; if not, write to the Free Software
19
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
20
# USA
21
22
try:
23
    import xmlrpc.client as xmlrpclib
24
except ImportError:
25
    import xmlrpclib
26
27
import base64
28
from datetime import datetime
29
30
# Taken from qatracker/qatracker.modules (PHP code)
31
# cat qatracker.module | grep " = array" | sed -e 's/^\$//g' \
32
#   -e 's/array(/[/g' -e 's/);/]/g' -e "s/t('/\"/g" -e "s/')/\"/g"
33
### AUTO-GENERATED ->
34
qatracker_build_milestone_status = ["Active", "Re-building", "Disabled",
35
                                    "Superseded", "Ready"]
36
qatracker_milestone_notify = ["No", "Yes"]
37
qatracker_milestone_autofill = ["No", "Yes"]
38
qatracker_milestone_status = ["Testing", "Released", "Archived"]
39
qatracker_milestone_series_status = ["Active", "Disabled"]
714 by Stéphane Graber
Update qatracker module to latest upstream version
40
qatracker_milestone_series_manifest_status = ["Active", "Disabled"]
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
41
qatracker_product_status = ["Active", "Disabled"]
42
qatracker_product_type = ["iso", "package", "hardware"]
43
qatracker_product_download_type = ["HTTP", "RSYNC", "ZSYNC",
677 by Colin Watson
make all scripts pass current stricter pep8(1) in raring
44
                                   "GPG signature", "MD5 checksum", "Comment",
45
                                   "Torrent"]
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
46
qatracker_testsuite_testcase_status = ["Mandatory", "Disabled", "Run-once",
677 by Colin Watson
make all scripts pass current stricter pep8(1) in raring
47
                                       "Optional"]
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
48
qatracker_result_result = ["Failed", "Passed", "In progress"]
49
qatracker_result_status = ["Active", "Disabled"]
740 by Stéphane Graber
Update qatracker.py
50
qatracker_rebuild_status = ["Requested", "Queued", "Building", "Built",
51
                            "Published", "Canceled"]
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
52
### <- AUTO-GENERATED
53
54
55
class QATrackerRPCObject():
56
    """Base class for objects received over XML-RPC"""
57
58
    CONVERT_BOOL = []
59
    CONVERT_DATE = []
60
    CONVERT_INT = []
61
62
    def __init__(self, tracker, rpc_dict):
63
        # Convert the dict we get from the API into an object
64
65
        for key in rpc_dict:
66
            if key in self.CONVERT_INT:
67
                try:
68
                    setattr(self, key, int(rpc_dict[key]))
69
                except ValueError:
70
                    setattr(self, key, None)
71
            elif key in self.CONVERT_BOOL:
72
                setattr(self, key, rpc_dict[key] == "true")
73
            elif key in self.CONVERT_DATE:
74
                try:
75
                    setattr(self, key, datetime.strptime(rpc_dict[key],
677 by Colin Watson
make all scripts pass current stricter pep8(1) in raring
76
                                                         '%Y-%m-%d %H:%M:%S'))
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
77
                except ValueError:
78
                    setattr(self, key, None)
79
            else:
80
                setattr(self, key, str(rpc_dict[key]))
81
82
        self.tracker = tracker
83
84
    def __repr__(self):
85
        return "%s: %s" % (self.__class__.__name__, self.title)
86
87
88
class QATrackerBug(QATrackerRPCObject):
89
    """A bug entry"""
90
91
    CONVERT_INT = ['bugnumber', 'count']
92
    CONVERT_DATE = ['earliest_report', 'latest_report']
93
94
    def __repr__(self):
95
        return "%s: %s" % (self.__class__.__name__, self.bugnumber)
96
97
98
class QATrackerBuild(QATrackerRPCObject):
99
    """A build entry"""
100
101
    CONVERT_INT = ['id', 'productid', 'userid', 'status']
102
    CONVERT_DATE = ['date']
103
104
    def __repr__(self):
105
        return "%s: %s" % (self.__class__.__name__, self.id)
106
107
    def add_result(self, testcase, result, comment='', hardware='', bugs={}):
108
        """Add a result to the build"""
109
677 by Colin Watson
make all scripts pass current stricter pep8(1) in raring
110
        if (self.tracker.access not in ("user", "admin") and
111
                self.tracker.access is not None):
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
112
            raise Exception("Access denied, you need 'user' but are '%s'" %
677 by Colin Watson
make all scripts pass current stricter pep8(1) in raring
113
                            self.tracker.access)
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
114
115
        build_testcase = None
116
117
        # FIXME: Supporting 'str' containing the testcase name would be nice
118
        if isinstance(testcase, QATrackerTestcase):
119
            build_testcase = testcase.id
120
        elif isinstance(testcase, int):
121
            build_testcase = testcase
122
123
        if not build_testcase:
124
            raise IndexError("Couldn't find testcase: %s" % (testcase,))
125
126
        if isinstance(result, list):
127
            raise TypeError("result must be a string or an integer")
128
129
        build_result = self.tracker._get_valid_id_list(qatracker_result_result,
677 by Colin Watson
make all scripts pass current stricter pep8(1) in raring
130
                                                       result)
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
131
132
        if not isinstance(bugs, dict):
133
            raise TypeError("bugs must be a dict")
134
135
        for bug in bugs:
136
            if not isinstance(bug, int) or bug <= 0:
137
                raise ValueError("A bugnumber must be a number >= 0")
138
139
            if not isinstance(bugs[bug], int) or bugs[bug] not in (0, 1):
140
                raise ValueError("A bugimportance must be in (0,1)")
141
142
        resultid = int(self.tracker.tracker.results.add(self.id,
143
                                                        build_testcase,
144
                                                        build_result[0],
145
                                                        str(comment),
146
                                                        str(hardware),
147
                                                        bugs))
148
        if resultid == -1:
149
            raise Exception("Couldn't post your result.")
150
151
        new_result = None
152
        for entry in self.get_results(build_testcase, 0):
153
            if entry.id == resultid:
154
                new_result = entry
155
                break
156
157
        return new_result
158
159
    def get_results(self, testcase, status=qatracker_result_status):
160
        """Get a list of results for the given build and testcase"""
161
162
        build_testcase = None
163
164
        # FIXME: Supporting 'str' containing the testcase name would be nice
165
        if isinstance(testcase, QATrackerTestcase):
166
            build_testcase = testcase.id
167
        elif isinstance(testcase, int):
168
            build_testcase = testcase
169
170
        if not build_testcase:
171
            raise IndexError("Couldn't find testcase: %s" % (testcase,))
172
173
        record_filter = self.tracker._get_valid_id_list(
714 by Stéphane Graber
Update qatracker module to latest upstream version
174
            qatracker_result_status,
175
            status)
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
176
177
        if len(record_filter) == 0:
178
            return []
179
180
        results = []
677 by Colin Watson
make all scripts pass current stricter pep8(1) in raring
181
        for entry in self.tracker.tracker.results.get_list(
182
                self.id, build_testcase, list(record_filter)):
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
183
            results.append(QATrackerResult(self.tracker, entry))
184
185
        return results
186
187
188
class QATrackerMilestone(QATrackerRPCObject):
189
    """A milestone entry"""
190
191
    CONVERT_INT = ['id', 'status', 'series']
192
    CONVERT_BOOL = ['notify']
193
194
    def get_bugs(self):
195
        """Returns a list of all bugs linked to this milestone"""
196
197
        bugs = []
198
        for entry in self.tracker.tracker.bugs.get_list(self.id):
199
            bugs.append(QATrackerBug(self.tracker, entry))
200
201
        return bugs
202
203
    def add_build(self, product, version, note="", notify=True):
204
        """Add a build to the milestone"""
205
206
        if self.status != 0:
207
            raise TypeError("Only active milestones are accepted")
208
209
        if self.tracker.access != "admin" and self.tracker.access is not None:
210
            raise Exception("Access denied, you need 'admin' but are '%s'" %
677 by Colin Watson
make all scripts pass current stricter pep8(1) in raring
211
                            self.tracker.access)
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
212
213
        if not isinstance(notify, bool):
214
            raise TypeError("notify must be a boolean")
215
216
        build_product = None
217
218
        if isinstance(product, QATrackerProduct):
219
            build_product = product
220
        else:
221
            valid_products = self.tracker.get_products(0)
222
223
            for entry in valid_products:
677 by Colin Watson
make all scripts pass current stricter pep8(1) in raring
224
                if (entry.title.lower() == str(product).lower() or
225
                        entry.id == product):
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
226
                    build_product = entry
227
                    break
228
229
        if not build_product:
230
            raise IndexError("Couldn't find product: %s" % product)
231
232
        if build_product.status != 0:
233
            raise TypeError("Only active products are accepted")
234
235
        self.tracker.tracker.builds.add(build_product.id, self.id,
677 by Colin Watson
make all scripts pass current stricter pep8(1) in raring
236
                                        str(version), str(note), notify)
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
237
238
        new_build = None
239
        for entry in self.get_builds(0):
677 by Colin Watson
make all scripts pass current stricter pep8(1) in raring
240
            if (entry.productid == build_product.id
241
                    and entry.version == str(version)):
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
242
                new_build = entry
243
                break
244
245
        return new_build
246
247
    def get_builds(self, status=qatracker_build_milestone_status):
248
        """Get a list of builds for the milestone"""
249
250
        record_filter = self.tracker._get_valid_id_list(
677 by Colin Watson
make all scripts pass current stricter pep8(1) in raring
251
            qatracker_build_milestone_status, status)
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
252
253
        if len(record_filter) == 0:
254
            return []
255
256
        builds = []
257
        for entry in self.tracker.tracker.builds.get_list(self.id,
677 by Colin Watson
make all scripts pass current stricter pep8(1) in raring
258
                                                          list(record_filter)):
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
259
            builds.append(QATrackerBuild(self.tracker, entry))
260
261
        return builds
262
263
264
class QATrackerProduct(QATrackerRPCObject):
265
    CONVERT_INT = ['id', 'type', 'status']
266
267
    def get_testcases(self, series,
677 by Colin Watson
make all scripts pass current stricter pep8(1) in raring
268
                      status=qatracker_testsuite_testcase_status):
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
269
        """Get a list of testcases associated with the product"""
270
271
        record_filter = self.tracker._get_valid_id_list(
677 by Colin Watson
make all scripts pass current stricter pep8(1) in raring
272
            qatracker_testsuite_testcase_status, status)
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
273
274
        if len(record_filter) == 0:
275
            return []
276
277
        if isinstance(series, QATrackerMilestone):
278
            seriesid = series.series
279
        elif isinstance(series, int):
280
            seriesid = series
281
        else:
282
            raise TypeError("series needs to be a valid QATrackerMilestone"
283
                            " instance or an integer")
284
285
        testcases = []
677 by Colin Watson
make all scripts pass current stricter pep8(1) in raring
286
        for entry in self.tracker.tracker.testcases.get_list(
287
                self.id, seriesid, list(record_filter)):
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
288
            testcases.append(QATrackerTestcase(self.tracker, entry))
289
290
        return testcases
291
292
714 by Stéphane Graber
Update qatracker module to latest upstream version
293
class QATrackerRebuild(QATrackerRPCObject):
294
    CONVERT_INT = ['id', 'seriesid', 'productid', 'milestoneid', 'requestedby',
295
                   'changedby', 'status']
296
    CONVERT_DATE = ['requestedat', 'changedat']
297
298
    def __repr__(self):
299
        return "%s: %s" % (self.__class__.__name__, self.id)
300
301
    def save(self):
302
        """Save any change that happened on this entry.
303
           NOTE: At the moment only supports the status field."""
304
305
        if (self.tracker.access != "admin" and
306
                self.tracker.access is not None):
307
            raise Exception("Access denied, you need 'admin' but are '%s'" %
308
                            self.tracker.access)
309
310
        retval = self.tracker.tracker.rebuilds.update_status(self.id,
311
                                                             self.status)
312
        if retval is not True:
313
            raise Exception("Failed to update rebuild")
314
315
        return retval
316
723 by Stéphane Graber
Update qatracker.py from upstream.
317
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
318
class QATrackerResult(QATrackerRPCObject):
319
    CONVERT_INT = ['id', 'reporterid', 'revisionid', 'result', 'changedby',
677 by Colin Watson
make all scripts pass current stricter pep8(1) in raring
320
                   'status']
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
321
    CONVERT_DATE = ['date', 'lastchange']
322
    __deleted = False
323
324
    def __repr__(self):
325
        return "%s: %s" % (self.__class__.__name__, self.id)
326
327
    def delete(self):
328
        """Remove the result from the tracker"""
329
677 by Colin Watson
make all scripts pass current stricter pep8(1) in raring
330
        if (self.tracker.access not in ("user", "admin") and
331
                self.tracker.access is not None):
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
332
            raise Exception("Access denied, you need 'user' but are '%s'" %
677 by Colin Watson
make all scripts pass current stricter pep8(1) in raring
333
                            self.tracker.access)
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
334
335
        if self.__deleted:
336
            raise IndexError("Result has already been removed")
337
338
        retval = self.tracker.tracker.results.delete(self.id)
339
        if retval is not True:
340
            raise Exception("Failed to remove result")
341
342
        self.status = 1
343
        self.__deleted = True
344
345
    def save(self):
346
        """Save any change that happened on this entry"""
347
677 by Colin Watson
make all scripts pass current stricter pep8(1) in raring
348
        if (self.tracker.access not in ("user", "admin") and
349
                self.tracker.access is not None):
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
350
            raise Exception("Access denied, you need 'user' but are '%s'" %
677 by Colin Watson
make all scripts pass current stricter pep8(1) in raring
351
                            self.tracker.access)
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
352
353
        if self.__deleted:
354
            raise IndexError("Result no longer exists")
355
356
        retval = self.tracker.tracker.results.update(self.id, self.result,
677 by Colin Watson
make all scripts pass current stricter pep8(1) in raring
357
                                                     self.comment,
358
                                                     self.hardware,
359
                                                     self.bugs)
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
360
        if retval is not True:
361
            raise Exception("Failed to update result")
362
714 by Stéphane Graber
Update qatracker module to latest upstream version
363
        return retval
364
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
365
366
class QATrackerSeries(QATrackerRPCObject):
367
    CONVERT_INT = ['id', 'status']
368
714 by Stéphane Graber
Update qatracker module to latest upstream version
369
    def get_manifest(self, status=qatracker_milestone_series_manifest_status):
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
370
        """Get a list of products in the series' manifest"""
371
714 by Stéphane Graber
Update qatracker module to latest upstream version
372
        record_filter = self.tracker._get_valid_id_list(
373
            qatracker_milestone_series_manifest_status, status)
374
375
        if len(record_filter) == 0:
376
            return []
377
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
378
        manifest_entries = []
714 by Stéphane Graber
Update qatracker module to latest upstream version
379
        for entry in self.tracker.tracker.series.get_manifest(
380
                self.id, list(record_filter)):
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
381
            manifest_entries.append(QATrackerSeriesManifest(
714 by Stéphane Graber
Update qatracker module to latest upstream version
382
                                    self.tracker, entry))
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
383
384
        return manifest_entries
385
386
387
class QATrackerSeriesManifest(QATrackerRPCObject):
714 by Stéphane Graber
Update qatracker module to latest upstream version
388
    CONVERT_INT = ['id', 'productid', 'status']
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
389
390
    def __repr__(self):
391
        return "%s: %s" % (self.__class__.__name__, self.product_title)
392
393
394
class QATrackerTestcase(QATrackerRPCObject):
395
    CONVERT_INT = ['id', 'status', 'weight', 'suite']
396
397
398
class QATracker():
399
    def __init__(self, url, username=None, password=None):
400
        class AuthTransport(xmlrpclib.Transport):
401
            def set_auth(self, auth):
402
                self.auth = auth
403
404
            def get_host_info(self, host):
405
                host, extra_headers, x509 = \
406
                    xmlrpclib.Transport.get_host_info(self, host)
407
                if extra_headers is None:
408
                    extra_headers = []
409
                extra_headers.append(('Authorization', 'Basic %s' % auth))
410
                return host, extra_headers, x509
411
412
        if username and password:
413
            try:
414
                auth = str(base64.b64encode(
714 by Stéphane Graber
Update qatracker module to latest upstream version
415
                           bytes('%s:%s' % (username, password), 'utf-8')),
416
                           'utf-8')
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
417
            except TypeError:
418
                auth = base64.b64encode('%s:%s' % (username, password))
419
420
            transport = AuthTransport()
421
            transport.set_auth(auth)
422
            drupal = xmlrpclib.ServerProxy(url, transport=transport)
423
        else:
424
            drupal = xmlrpclib.ServerProxy(url)
425
426
        # Call listMethods() so if something is wrong we know it immediately
427
        drupal.system.listMethods()
428
429
        # Get our current access
430
        self.access = drupal.qatracker.get_access()
431
432
        self.tracker = drupal.qatracker
433
434
    def _get_valid_id_list(self, status_list, status):
435
        """ Get a list of valid keys and a list or just a single
436
            entry of input to check against the list of valid keys.
437
            The function looks for valid indexes and content, doing
438
            case insensitive checking for strings and returns a list
439
            of indexes for the list of valid keys. """
440
441
        def process(status_list, status):
442
            valid_status = [entry.lower() for entry in status_list]
443
444
            if isinstance(status, int):
445
                if status < 0 or status >= len(valid_status):
446
                    raise IndexError("Invalid status: %s" % status)
447
                return int(status)
448
449
            if isinstance(status, str):
450
                status = status.lower()
451
                if status not in valid_status:
452
                    raise IndexError("Invalid status: %s" % status)
453
                return valid_status.index(status)
454
455
            raise TypeError("Invalid status type: %s (expected str or int)" %
456
                            type(status))
457
458
        record_filter = set()
459
460
        if isinstance(status, list):
461
            for entry in status:
462
                record_filter.add(process(status_list, entry))
463
        else:
464
            record_filter.add(process(status_list, status))
465
466
        return list(record_filter)
467
468
    def get_bugs(self):
469
        """Get a list of all bugs reported on the site"""
470
471
        bugs = []
472
        for entry in self.tracker.bugs.get_list(0):
473
            bugs.append(QATrackerBug(self, entry))
474
475
        return bugs
476
477
    def get_milestones(self, status=qatracker_milestone_status):
478
        """Get a list of all milestones"""
479
480
        record_filter = self._get_valid_id_list(qatracker_milestone_status,
481
                                                status)
482
483
        if len(record_filter) == 0:
484
            return []
485
486
        milestones = []
487
        for entry in self.tracker.milestones.get_list(list(record_filter)):
488
            milestones.append(QATrackerMilestone(self, entry))
489
490
        return milestones
491
492
    def get_products(self, status=qatracker_product_status):
493
        """Get a list of all products"""
494
495
        record_filter = self._get_valid_id_list(qatracker_product_status,
496
                                                status)
497
498
        if len(record_filter) == 0:
499
            return []
500
501
        products = []
502
        for entry in self.tracker.products.get_list(list(record_filter)):
503
            products.append(QATrackerProduct(self, entry))
504
505
        return products
506
714 by Stéphane Graber
Update qatracker module to latest upstream version
507
    def get_rebuilds(self, status=qatracker_rebuild_status):
508
        """Get a list of all rebuilds"""
509
510
        record_filter = self._get_valid_id_list(
511
            qatracker_rebuild_status, status)
512
513
        if len(record_filter) == 0:
514
            return []
515
516
        rebuilds = []
517
        for entry in self.tracker.rebuilds.get_list(list(record_filter)):
518
            rebuilds.append(QATrackerRebuild(self, entry))
519
520
        return rebuilds
521
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
522
    def get_series(self, status=qatracker_milestone_series_status):
523
        """Get a list of all series"""
524
525
        record_filter = self._get_valid_id_list(
677 by Colin Watson
make all scripts pass current stricter pep8(1) in raring
526
            qatracker_milestone_series_status, status)
665.2.2 by Stéphane Graber
Add qatracker.py from python-qatracker branch.
527
528
        if len(record_filter) == 0:
529
            return []
530
531
        series = []
532
        for entry in self.tracker.series.get_list(list(record_filter)):
533
            series.append(QATrackerSeries(self, entry))
534
535
        return series