• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#pylint: disable-msg=C0111
2
3"""
4Internal global error types
5"""
6
7import sys, traceback, threading
8from traceback import format_exception
9
10# Add names you want to be imported by 'from errors import *' to this list.
11# This must be list not a tuple as we modify it to include all of our
12# the Exception classes we define below at the end of this file.
13__all__ = ['format_error', 'context_aware', 'context', 'get_context',
14           'exception_context']
15
16
17def format_error():
18    t, o, tb = sys.exc_info()
19    trace = format_exception(t, o, tb)
20    # Clear the backtrace to prevent a circular reference
21    # in the heap -- as per tutorial
22    tb = ''
23
24    return ''.join(trace)
25
26
27# Exception context information:
28# ------------------------------
29# Every function can have some context string associated with it.
30# The context string can be changed by calling context(str) and cleared by
31# calling context() with no parameters.
32# get_context() joins the current context strings of all functions in the
33# provided traceback.  The result is a brief description of what the test was
34# doing in the provided traceback (which should be the traceback of a caught
35# exception).
36#
37# For example: assume a() calls b() and b() calls c().
38#
39# @error.context_aware
40# def a():
41#     error.context("hello")
42#     b()
43#     error.context("world")
44#     error.get_context() ----> 'world'
45#
46# @error.context_aware
47# def b():
48#     error.context("foo")
49#     c()
50#
51# @error.context_aware
52# def c():
53#     error.context("bar")
54#     error.get_context() ----> 'hello --> foo --> bar'
55#
56# The current context is automatically inserted into exceptions raised in
57# context_aware functions, so usually test code doesn't need to call
58# error.get_context().
59
60ctx = threading.local()
61
62
63def _new_context(s=""):
64    if not hasattr(ctx, "contexts"):
65        ctx.contexts = []
66    ctx.contexts.append(s)
67
68
69def _pop_context():
70    ctx.contexts.pop()
71
72
73def context(s="", log=None):
74    """
75    Set the context for the currently executing function and optionally log it.
76
77    @param s: A string.  If not provided, the context for the current function
78            will be cleared.
79    @param log: A logging function to pass the context message to.  If None, no
80            function will be called.
81    """
82    ctx.contexts[-1] = s
83    if s and log:
84        log("Context: %s" % get_context())
85
86
87def base_context(s="", log=None):
88    """
89    Set the base context for the currently executing function and optionally
90    log it.  The base context is just another context level that is hidden by
91    default.  Functions that require a single context level should not use
92    base_context().
93
94    @param s: A string.  If not provided, the base context for the current
95            function will be cleared.
96    @param log: A logging function to pass the context message to.  If None, no
97            function will be called.
98    """
99    ctx.contexts[-1] = ""
100    ctx.contexts[-2] = s
101    if s and log:
102        log("Context: %s" % get_context())
103
104
105def get_context():
106    """Return the current context (or None if none is defined)."""
107    if hasattr(ctx, "contexts"):
108        return " --> ".join([s for s in ctx.contexts if s])
109
110
111def exception_context(e):
112    """Return the context of a given exception (or None if none is defined)."""
113    if hasattr(e, "_context"):
114        return e._context  # pylint: disable=W0212
115
116
117def set_exception_context(e, s):
118    """Set the context of a given exception."""
119    e._context = s
120
121
122def join_contexts(s1, s2):
123    """Join two context strings."""
124    if s1:
125        if s2:
126            return "%s --> %s" % (s1, s2)
127        else:
128            return s1
129    else:
130        return s2
131
132
133def context_aware(fn):
134    """A decorator that must be applied to functions that call context()."""
135    def new_fn(*args, **kwargs):
136        _new_context()
137        _new_context("(%s)" % fn.__name__)
138        try:
139            try:
140                return fn(*args, **kwargs)
141            except Exception, e:
142                if not exception_context(e):
143                    set_exception_context(e, get_context())
144                raise
145        finally:
146            _pop_context()
147            _pop_context()
148    new_fn.__name__ = fn.__name__
149    new_fn.__doc__ = fn.__doc__
150    new_fn.__dict__.update(fn.__dict__)
151    return new_fn
152
153
154def _context_message(e):
155    s = exception_context(e)
156    if s:
157        return "    [context: %s]" % s
158    else:
159        return ""
160
161
162
163class TimeoutException(Exception):
164    """
165    Generic exception raised on retry timeouts.
166    """
167    pass
168
169
170class JobContinue(SystemExit):
171    """Allow us to bail out requesting continuance."""
172    pass
173
174
175class JobComplete(SystemExit):
176    """Allow us to bail out indicating continuation not required."""
177    pass
178
179
180class AutotestError(Exception):
181    """The parent of all errors deliberatly thrown within the client code."""
182    def __str__(self):
183        return Exception.__str__(self) + _context_message(self)
184
185
186class JobError(AutotestError):
187    """Indicates an error which terminates and fails the whole job (ABORT)."""
188    pass
189
190
191class UnhandledJobError(JobError):
192    """Indicates an unhandled error in a job."""
193    def __init__(self, unhandled_exception):
194        if isinstance(unhandled_exception, JobError):
195            JobError.__init__(self, *unhandled_exception.args)
196        elif isinstance(unhandled_exception, str):
197            JobError.__init__(self, unhandled_exception)
198        else:
199            msg = "Unhandled %s: %s"
200            msg %= (unhandled_exception.__class__.__name__,
201                    unhandled_exception)
202            if not isinstance(unhandled_exception, AutotestError):
203                msg += _context_message(unhandled_exception)
204            msg += "\n" + traceback.format_exc()
205            JobError.__init__(self, msg)
206
207
208class TestBaseException(AutotestError):
209    """The parent of all test exceptions."""
210    # Children are required to override this.  Never instantiate directly.
211    exit_status = "NEVER_RAISE_THIS"
212
213
214class TestError(TestBaseException):
215    """Indicates that something went wrong with the test harness itself."""
216    exit_status = "ERROR"
217
218
219class TestNAError(TestBaseException):
220    """Indictates that the test is Not Applicable.  Should be thrown
221    when various conditions are such that the test is inappropriate."""
222    exit_status = "TEST_NA"
223
224
225class TestFail(TestBaseException):
226    """Indicates that the test failed, but the job will not continue."""
227    exit_status = "FAIL"
228
229
230class TestWarn(TestBaseException):
231    """Indicates that bad things (may) have happened, but not an explicit
232    failure."""
233    exit_status = "WARN"
234
235
236class TestFailRetry(TestFail):
237    """Indicates that the test failed, but in a manner that may be retried
238    if test retries are enabled for this test."""
239    exit_status = "FAIL"
240
241
242class UnhandledTestError(TestError):
243    """Indicates an unhandled error in a test."""
244    def __init__(self, unhandled_exception):
245        if isinstance(unhandled_exception, TestError):
246            TestError.__init__(self, *unhandled_exception.args)
247        elif isinstance(unhandled_exception, str):
248            TestError.__init__(self, unhandled_exception)
249        else:
250            msg = "Unhandled %s: %s"
251            msg %= (unhandled_exception.__class__.__name__,
252                    unhandled_exception)
253            if not isinstance(unhandled_exception, AutotestError):
254                msg += _context_message(unhandled_exception)
255            msg += "\n" + traceback.format_exc()
256            TestError.__init__(self, msg)
257
258
259class UnhandledTestFail(TestFail):
260    """Indicates an unhandled fail in a test."""
261    def __init__(self, unhandled_exception):
262        if isinstance(unhandled_exception, TestFail):
263            TestFail.__init__(self, *unhandled_exception.args)
264        elif isinstance(unhandled_exception, str):
265            TestFail.__init__(self, unhandled_exception)
266        else:
267            msg = "Unhandled %s: %s"
268            msg %= (unhandled_exception.__class__.__name__,
269                    unhandled_exception)
270            if not isinstance(unhandled_exception, AutotestError):
271                msg += _context_message(unhandled_exception)
272            msg += "\n" + traceback.format_exc()
273            TestFail.__init__(self, msg)
274
275
276class CmdError(TestError):
277    """Indicates that a command failed, is fatal to the test unless caught."""
278    def __init__(self, command, result_obj, additional_text=None):
279        TestError.__init__(self, command, result_obj, additional_text)
280        self.command = command
281        self.result_obj = result_obj
282        self.additional_text = additional_text
283
284    def __str__(self):
285        if self.result_obj.exit_status is None:
286            msg = "Command <%s> failed and is not responding to signals"
287            msg %= self.command
288        else:
289            msg = "Command <%s> failed, rc=%d"
290            msg %= (self.command, self.result_obj.exit_status)
291
292        if self.additional_text:
293            msg += ", " + self.additional_text
294        msg += _context_message(self)
295        msg += '\n' + repr(self.result_obj)
296        return msg
297
298
299class CmdTimeoutError(CmdError):
300    """Indicates that a command timed out."""
301    pass
302
303
304class PackageError(TestError):
305    """Indicates an error trying to perform a package operation."""
306    pass
307
308
309class BarrierError(JobError):
310    """Indicates an error happened during a barrier operation."""
311    pass
312
313
314class BarrierAbortError(BarrierError):
315    """Indicate that the barrier was explicitly aborted by a member."""
316    pass
317
318
319class InstallError(JobError):
320    """Indicates an installation error which Terminates and fails the job."""
321    pass
322
323
324class AutotestRunError(AutotestError):
325    """Indicates a problem running server side control files."""
326    pass
327
328
329class AutotestTimeoutError(AutotestError):
330    """This exception is raised when an autotest test exceeds the timeout
331    parameter passed to run_timed_test and is killed.
332    """
333    pass
334
335
336class HostRunErrorMixIn(Exception):
337    """
338    Indicates a problem in the host run() function raised from client code.
339    Should always be constructed with a tuple of two args (error description
340    (str), run result object). This is a common class mixed in to create the
341    client and server side versions of it.
342    """
343    def __init__(self, description, result_obj):
344        self.description = description
345        self.result_obj = result_obj
346        Exception.__init__(self, description, result_obj)
347
348    def __str__(self):
349        return self.description + '\n' + repr(self.result_obj)
350
351
352class HostInstallTimeoutError(JobError):
353    """
354    Indicates the machine failed to be installed after the predetermined
355    timeout.
356    """
357    pass
358
359
360class AutotestHostRunError(HostRunErrorMixIn, AutotestError):
361    pass
362
363
364# server-specific errors
365
366class AutoservError(Exception):
367    pass
368
369
370class AutoservSSHTimeout(AutoservError):
371    """SSH experienced a connection timeout"""
372    pass
373
374
375class AutoservRunError(HostRunErrorMixIn, AutoservError):
376    pass
377
378
379class AutoservSshPermissionDeniedError(AutoservRunError):
380    """Indicates that a SSH permission denied error was encountered."""
381    pass
382
383
384class AutoservVirtError(AutoservError):
385    """Vitualization related error"""
386    pass
387
388
389class AutoservUnsupportedError(AutoservError):
390    """Error raised when you try to use an unsupported optional feature"""
391    pass
392
393
394class AutoservHostError(AutoservError):
395    """Error reaching a host"""
396    pass
397
398
399class AutoservHostIsShuttingDownError(AutoservHostError):
400    """Host is shutting down"""
401    pass
402
403
404class AutoservNotMountedHostError(AutoservHostError):
405    """Found unmounted partitions that should be mounted"""
406    pass
407
408
409class AutoservSshPingHostError(AutoservHostError):
410    """SSH ping failed"""
411    pass
412
413
414class AutoservDiskFullHostError(AutoservHostError):
415    """Not enough free disk space on host"""
416
417    def __init__(self, path, want_gb, free_space_gb):
418        super(AutoservDiskFullHostError, self).__init__(
419            'Not enough free space on %s - %.3fGB free, want %.3fGB' %
420                    (path, free_space_gb, want_gb))
421        self.path = path
422        self.want_gb = want_gb
423        self.free_space_gb = free_space_gb
424
425
426class AutoservNoFreeInodesError(AutoservHostError):
427    """Not enough free i-nodes on host"""
428
429    def __init__(self, path, want_inodes, free_inodes):
430        super(AutoservNoFreeInodesError, self).__init__(
431            'Not enough free inodes on %s - %d free, want %d' %
432                    (path, free_inodes, want_inodes))
433        self.path = path
434        self.want_inodes = want_inodes
435        self.free_inodes = free_inodes
436
437
438class AutoservHardwareHostError(AutoservHostError):
439    """Found hardware problems with the host"""
440    pass
441
442
443class AutoservRebootError(AutoservError):
444    """Error occured while rebooting a machine"""
445    pass
446
447
448class AutoservShutdownError(AutoservRebootError):
449    """Error occured during shutdown of machine"""
450    pass
451
452
453class AutoservSuspendError(AutoservRebootError):
454    """Error occured while suspending a machine"""
455    pass
456
457
458class AutoservSubcommandError(AutoservError):
459    """Indicates an error while executing a (forked) subcommand"""
460    def __init__(self, func, exit_code):
461        AutoservError.__init__(self, func, exit_code)
462        self.func = func
463        self.exit_code = exit_code
464
465    def __str__(self):
466        return ("Subcommand %s failed with exit code %d" %
467                (self.func, self.exit_code))
468
469
470class AutoservRepairTotalFailure(AutoservError):
471    """Raised if all attempts to repair the DUT failed."""
472    pass
473
474
475class AutoservRepairFailure(AutoservError):
476    """Raised by a repair method if it is unable to repair a DUT."""
477    pass
478
479
480class AutoservRepairMethodNA(AutoservError):
481    """Raised when for any reason a praticular repair method is NA."""
482    pass
483
484
485class AutoservInstallError(AutoservError):
486    """Error occured while installing autotest on a host"""
487    pass
488
489
490class AutoservPidAlreadyDeadError(AutoservError):
491    """Error occured by trying to kill a nonexistant PID"""
492    pass
493
494
495class AutoservCrashLogCollectRequired(AutoservError):
496    """Need to collect crash-logs first"""
497    pass
498
499
500# packaging system errors
501
502class PackagingError(AutotestError):
503    'Abstract error class for all packaging related errors.'
504
505
506class PackageUploadError(PackagingError):
507    'Raised when there is an error uploading the package'
508
509
510class PackageFetchError(PackagingError):
511    'Raised when there is an error fetching the package'
512
513
514class PackageRemoveError(PackagingError):
515    'Raised when there is an error removing the package'
516
517
518class PackageInstallError(PackagingError):
519    'Raised when there is an error installing the package'
520
521
522class RepoDiskFullError(PackagingError):
523    'Raised when the destination for packages is full'
524
525
526class RepoWriteError(PackagingError):
527    "Raised when packager cannot write to a repo's desitnation"
528
529
530class RepoUnknownError(PackagingError):
531    "Raised when packager cannot write to a repo's desitnation"
532
533
534class RepoError(PackagingError):
535    "Raised when a repo isn't working in some way"
536
537
538class StageControlFileFailure(Exception):
539    """Exceptions encountered staging control files."""
540    pass
541
542
543class CrosDynamicSuiteException(Exception):
544    """
545    Base class for exceptions coming from dynamic suite code in
546    server/cros/dynamic_suite/*.
547    """
548    pass
549
550
551class StageBuildFailure(CrosDynamicSuiteException):
552    """Raised when the dev server throws 500 while staging a build."""
553    pass
554
555
556class ControlFileEmpty(CrosDynamicSuiteException):
557    """Raised when the control file exists on the server, but can't be read."""
558    pass
559
560
561class ControlFileMalformed(CrosDynamicSuiteException):
562    """Raised when an invalid control file is read."""
563    pass
564
565
566class AsynchronousBuildFailure(CrosDynamicSuiteException):
567    """Raised when the dev server throws 500 while finishing staging of a build.
568    """
569    pass
570
571
572class SuiteArgumentException(CrosDynamicSuiteException):
573    """Raised when improper arguments are used to run a suite."""
574    pass
575
576
577class MalformedDependenciesException(CrosDynamicSuiteException):
578    """Raised when a build has a malformed dependency_info file."""
579    pass
580
581
582class InadequateHostsException(CrosDynamicSuiteException):
583    """Raised when there are too few hosts to run a suite."""
584    pass
585
586
587class NoHostsException(CrosDynamicSuiteException):
588    """Raised when there are no healthy hosts to run a suite."""
589    pass
590
591
592class ControlFileNotFound(CrosDynamicSuiteException):
593    """Raised when a control file cannot be found and/or read."""
594    pass
595
596
597class NoControlFileList(CrosDynamicSuiteException):
598    """Raised to indicate that a listing can't be done."""
599    pass
600
601
602class HostLockManagerReuse(CrosDynamicSuiteException):
603    """Raised when a caller tries to re-use a HostLockManager instance."""
604    pass
605
606
607class ReimageAbortedException(CrosDynamicSuiteException):
608    """Raised when a Reimage job is aborted"""
609    pass
610
611
612class UnknownReimageType(CrosDynamicSuiteException):
613    """Raised when a suite passes in an invalid reimage type"""
614    pass
615
616
617class NoUniquePackageFound(Exception):
618    """Raised when an executable cannot be mapped back to a single package."""
619    pass
620
621
622class RPCException(Exception):
623    """Raised when an RPC encounters an error that a client might wish to
624    handle specially."""
625    pass
626
627
628class NoEligibleHostException(RPCException):
629    """Raised when no host could satisfy the requirements of a job."""
630    pass
631
632
633class InvalidBgJobCall(Exception):
634    """Raised when an invalid call is made to a BgJob object."""
635    pass
636
637
638class HeartbeatOnlyAllowedInShardModeException(Exception):
639    """Raised when a heartbeat is attempted but not allowed."""
640    pass
641
642
643class UnallowedRecordsSentToMaster(Exception):
644    pass
645
646
647class InvalidDataError(Exception):
648    """Exception raised when invalid data provided for database operation.
649    """
650    pass
651
652
653class ContainerError(Exception):
654    """Exception raised when program runs into error using container.
655    """
656
657
658class IllegalUser(Exception):
659    """Exception raise when a program runs as an illegal user."""
660
661
662# This MUST remain at the end of the file.
663# Limit 'from error import *' to only import the exception instances.
664for _name, _thing in locals().items():
665    try:
666        if issubclass(_thing, Exception):
667            __all__.append(_name)
668    except TypeError:
669        pass  # _thing not a class
670__all__ = tuple(__all__)
671