error.py revision 71fe97fe10f6979e7db628e28ef0de087b79c021
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 AutoservSSHError(AutoservError):
371    """Generic SSH Error."""
372    pass
373
374
375class AutoservSSHTimeout(AutoservSSHError):
376    """SSH experienced a connection timeout"""
377    pass
378
379
380class AutoservRunError(HostRunErrorMixIn, AutoservError):
381    pass
382
383
384class AutoservSshPermissionDeniedError(AutoservRunError):
385    """Indicates that a SSH permission denied error was encountered."""
386    pass
387
388
389class AutoservVirtError(AutoservError):
390    """Vitualization related error"""
391    pass
392
393
394class AutoservUnsupportedError(AutoservError):
395    """Error raised when you try to use an unsupported optional feature"""
396    pass
397
398
399class AutoservHostError(AutoservError):
400    """Error reaching a host"""
401    pass
402
403
404class AutoservHostIsShuttingDownError(AutoservHostError):
405    """Host is shutting down"""
406    pass
407
408
409class AutoservNotMountedHostError(AutoservHostError):
410    """Found unmounted partitions that should be mounted"""
411    pass
412
413
414class AutoservSshPingHostError(AutoservHostError):
415    """SSH ping failed"""
416    pass
417
418
419class AutoservDiskFullHostError(AutoservHostError):
420    """Not enough free disk space on host"""
421    def __init__(self, path, want_gb, free_space_gb):
422        AutoservHostError.__init__(self,
423            'Not enough free space on %s - %.3fGB free, want %.3fGB' %
424            (path, free_space_gb, want_gb))
425
426        self.path = path
427        self.want_gb = want_gb
428        self.free_space_gb = free_space_gb
429
430
431class AutoservHardwareHostError(AutoservHostError):
432    """Found hardware problems with the host"""
433    pass
434
435
436class AutoservRebootError(AutoservError):
437    """Error occured while rebooting a machine"""
438    pass
439
440
441class AutoservShutdownError(AutoservRebootError):
442    """Error occured during shutdown of machine"""
443    pass
444
445
446class AutoservSubcommandError(AutoservError):
447    """Indicates an error while executing a (forked) subcommand"""
448    def __init__(self, func, exit_code):
449        AutoservError.__init__(self, func, exit_code)
450        self.func = func
451        self.exit_code = exit_code
452
453    def __str__(self):
454        return ("Subcommand %s failed with exit code %d" %
455                (self.func, self.exit_code))
456
457
458class AutoservRepairTotalFailure(AutoservError):
459    """Raised if all attempts to repair the DUT failed."""
460    pass
461
462
463class AutoservRepairFailure(AutoservError):
464    """Raised by a repair method if it is unable to repair a DUT."""
465    pass
466
467
468class AutoservRepairMethodNA(AutoservError):
469    """Raised when for any reason a praticular repair method is NA."""
470    pass
471
472
473class AutoservHardwareRepairRequestedError(AutoservError):
474    """
475    Exception class raised from Host.repair_full() (or overrides) when software
476    repair fails but it successfully managed to request a hardware repair (by
477    notifying the staff, sending mail, etc)
478    """
479    pass
480
481
482class AutoservHardwareRepairRequiredError(AutoservError):
483    """
484    Exception class raised during repairs to indicate that a hardware repair
485    is going to be necessary.
486    """
487    pass
488
489
490class AutoservInstallError(AutoservError):
491    """Error occured while installing autotest on a host"""
492    pass
493
494
495class AutoservPidAlreadyDeadError(AutoservError):
496    """Error occured by trying to kill a nonexistant PID"""
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 CrosDynamicSuiteException(Exception):
539    """
540    Base class for exceptions coming from dynamic suite code in
541    server/cros/dynamic_suite/*.
542    """
543    pass
544
545
546class StageBuildFailure(CrosDynamicSuiteException):
547    """Raised when the dev server throws 500 while staging a build."""
548    pass
549
550
551class ControlFileEmpty(CrosDynamicSuiteException):
552    """Raised when the control file exists on the server, but can't be read."""
553    pass
554
555
556class ControlFileMalformed(CrosDynamicSuiteException):
557    """Raised when an invalid control file is read."""
558    pass
559
560
561class AsynchronousBuildFailure(CrosDynamicSuiteException):
562    """Raised when the dev server throws 500 while finishing staging of a build.
563    """
564    pass
565
566
567class SuiteArgumentException(CrosDynamicSuiteException):
568    """Raised when improper arguments are used to run a suite."""
569    pass
570
571
572class MalformedDependenciesException(CrosDynamicSuiteException):
573    """Raised when a build has a malformed dependency_info file."""
574    pass
575
576
577class InadequateHostsException(CrosDynamicSuiteException):
578    """Raised when there are too few hosts to run a suite."""
579    pass
580
581
582class NoHostsException(CrosDynamicSuiteException):
583    """Raised when there are no healthy hosts to run a suite."""
584    pass
585
586
587class ControlFileNotFound(CrosDynamicSuiteException):
588    """Raised when a control file cannot be found and/or read."""
589    pass
590
591
592class NoControlFileList(CrosDynamicSuiteException):
593    """Raised to indicate that a listing can't be done."""
594    pass
595
596
597class HostLockManagerReuse(CrosDynamicSuiteException):
598    """Raised when a caller tries to re-use a HostLockManager instance."""
599    pass
600
601
602class ReimageAbortedException(CrosDynamicSuiteException):
603    """Raised when a Reimage job is aborted"""
604    pass
605
606
607class UnknownReimageType(CrosDynamicSuiteException):
608    """Raised when a suite passes in an invalid reimage type"""
609    pass
610
611
612class NoUniquePackageFound(Exception):
613    """Raised when an executable cannot be mapped back to a single package."""
614    pass
615
616
617class RPCException(Exception):
618    """Raised when an RPC encounters an error that a client might wish to
619    handle specially."""
620    pass
621
622
623class NoEligibleHostException(RPCException):
624    """Raised when no host could satisfy the requirements of a job."""
625    pass
626
627
628class InvalidBgJobCall(Exception):
629    """Raised when an invalid call is made to a BgJob object."""
630    pass
631
632
633# This MUST remain at the end of the file.
634# Limit 'from error import *' to only import the exception instances.
635for _name, _thing in locals().items():
636    try:
637        if issubclass(_thing, Exception):
638            __all__.append(_name)
639    except TypeError:
640        pass  # _thing not a class
641__all__ = tuple(__all__)
642