error.py revision 60aec2423a2c2a14c46dc21757a6fd748931f428
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    """\
278    Indicates that a command failed, is fatal to the test unless caught.
279    """
280    def __init__(self, command, result_obj, additional_text=None):
281        TestError.__init__(self, command, result_obj, additional_text)
282        self.command = command
283        self.result_obj = result_obj
284        self.additional_text = additional_text
285
286    def __str__(self):
287        if self.result_obj.exit_status is None:
288            msg = "Command <%s> failed and is not responding to signals"
289            msg %= self.command
290        else:
291            msg = "Command <%s> failed, rc=%d"
292            msg %= (self.command, self.result_obj.exit_status)
293
294        if self.additional_text:
295            msg += ", " + self.additional_text
296        msg += _context_message(self)
297        msg += '\n' + repr(self.result_obj)
298        return msg
299
300
301class PackageError(TestError):
302    """Indicates an error trying to perform a package operation."""
303    pass
304
305
306class BarrierError(JobError):
307    """Indicates an error happened during a barrier operation."""
308    pass
309
310
311class BarrierAbortError(BarrierError):
312    """Indicate that the barrier was explicitly aborted by a member."""
313    pass
314
315
316class InstallError(JobError):
317    """Indicates an installation error which Terminates and fails the job."""
318    pass
319
320
321class AutotestRunError(AutotestError):
322    """Indicates a problem running server side control files."""
323    pass
324
325
326class AutotestTimeoutError(AutotestError):
327    """This exception is raised when an autotest test exceeds the timeout
328    parameter passed to run_timed_test and is killed.
329    """
330    pass
331
332
333class HostRunErrorMixIn(Exception):
334    """
335    Indicates a problem in the host run() function raised from client code.
336    Should always be constructed with a tuple of two args (error description
337    (str), run result object). This is a common class mixed in to create the
338    client and server side versions of it.
339    """
340    def __init__(self, description, result_obj):
341        self.description = description
342        self.result_obj = result_obj
343        Exception.__init__(self, description, result_obj)
344
345    def __str__(self):
346        return self.description + '\n' + repr(self.result_obj)
347
348
349class HostInstallTimeoutError(JobError):
350    """
351    Indicates the machine failed to be installed after the predetermined
352    timeout.
353    """
354    pass
355
356
357class AutotestHostRunError(HostRunErrorMixIn, AutotestError):
358    pass
359
360
361# server-specific errors
362
363class AutoservError(Exception):
364    pass
365
366
367class AutoservSSHTimeout(AutoservError):
368    """SSH experienced a connection timeout"""
369    pass
370
371
372class AutoservRunError(HostRunErrorMixIn, AutoservError):
373    pass
374
375
376class AutoservSshPermissionDeniedError(AutoservRunError):
377    """Indicates that a SSH permission denied error was encountered."""
378    pass
379
380
381class AutoservVirtError(AutoservError):
382    """Vitualization related error"""
383    pass
384
385
386class AutoservUnsupportedError(AutoservError):
387    """Error raised when you try to use an unsupported optional feature"""
388    pass
389
390
391class AutoservHostError(AutoservError):
392    """Error reaching a host"""
393    pass
394
395
396class AutoservHostIsShuttingDownError(AutoservHostError):
397    """Host is shutting down"""
398    pass
399
400
401class AutoservNotMountedHostError(AutoservHostError):
402    """Found unmounted partitions that should be mounted"""
403    pass
404
405
406class AutoservSshPingHostError(AutoservHostError):
407    """SSH ping failed"""
408    pass
409
410
411class AutoservDiskFullHostError(AutoservHostError):
412    """Not enough free disk space on host"""
413    def __init__(self, path, want_gb, free_space_gb):
414        AutoservHostError.__init__(self,
415            'Not enough free space on %s - %.3fGB free, want %.3fGB' %
416            (path, free_space_gb, want_gb))
417
418        self.path = path
419        self.want_gb = want_gb
420        self.free_space_gb = free_space_gb
421
422
423class AutoservHardwareHostError(AutoservHostError):
424    """Found hardware problems with the host"""
425    pass
426
427
428class AutoservRebootError(AutoservError):
429    """Error occured while rebooting a machine"""
430    pass
431
432
433class AutoservShutdownError(AutoservRebootError):
434    """Error occured during shutdown of machine"""
435    pass
436
437
438class AutoservSubcommandError(AutoservError):
439    """Indicates an error while executing a (forked) subcommand"""
440    def __init__(self, func, exit_code):
441        AutoservError.__init__(self, func, exit_code)
442        self.func = func
443        self.exit_code = exit_code
444
445    def __str__(self):
446        return ("Subcommand %s failed with exit code %d" %
447                (self.func, self.exit_code))
448
449
450class AutoservRepairTotalFailure(AutoservError):
451    """Raised if all attempts to repair the DUT failed."""
452    pass
453
454
455class AutoservRepairFailure(AutoservError):
456    """Raised by a repair method if it is unable to repair a DUT."""
457    pass
458
459
460class AutoservRepairMethodNA(AutoservError):
461    """Raised when for any reason a praticular repair method is NA."""
462    pass
463
464
465class AutoservHardwareRepairRequestedError(AutoservError):
466    """
467    Exception class raised from Host.repair_full() (or overrides) when software
468    repair fails but it successfully managed to request a hardware repair (by
469    notifying the staff, sending mail, etc)
470    """
471    pass
472
473
474class AutoservHardwareRepairRequiredError(AutoservError):
475    """
476    Exception class raised during repairs to indicate that a hardware repair
477    is going to be necessary.
478    """
479    pass
480
481
482class AutoservInstallError(AutoservError):
483    """Error occured while installing autotest on a host"""
484    pass
485
486
487class AutoservPidAlreadyDeadError(AutoservError):
488    """Error occured by trying to kill a nonexistant PID"""
489    pass
490
491
492# packaging system errors
493
494class PackagingError(AutotestError):
495    'Abstract error class for all packaging related errors.'
496
497
498class PackageUploadError(PackagingError):
499    'Raised when there is an error uploading the package'
500
501
502class PackageFetchError(PackagingError):
503    'Raised when there is an error fetching the package'
504
505
506class PackageRemoveError(PackagingError):
507    'Raised when there is an error removing the package'
508
509
510class PackageInstallError(PackagingError):
511    'Raised when there is an error installing the package'
512
513
514class RepoDiskFullError(PackagingError):
515    'Raised when the destination for packages is full'
516
517
518class RepoWriteError(PackagingError):
519    "Raised when packager cannot write to a repo's desitnation"
520
521
522class RepoUnknownError(PackagingError):
523    "Raised when packager cannot write to a repo's desitnation"
524
525
526class RepoError(PackagingError):
527    "Raised when a repo isn't working in some way"
528
529
530class CrosDynamicSuiteException(Exception):
531    """
532    Base class for exceptions coming from dynamic suite code in
533    server/cros/dynamic_suite/*.
534    """
535    pass
536
537
538class StageBuildFailure(CrosDynamicSuiteException):
539    """Raised when the dev server throws 500 while staging a build."""
540    pass
541
542
543class ControlFileEmpty(CrosDynamicSuiteException):
544    """Raised when the control file exists on the server, but can't be read."""
545    pass
546
547
548class ControlFileMalformed(CrosDynamicSuiteException):
549    """Raised when an invalid control file is read."""
550    pass
551
552
553class AsynchronousBuildFailure(CrosDynamicSuiteException):
554    """Raised when the dev server throws 500 while finishing staging of a build.
555    """
556    pass
557
558
559class SuiteArgumentException(CrosDynamicSuiteException):
560    """Raised when improper arguments are used to run a suite."""
561    pass
562
563
564class MalformedDependenciesException(CrosDynamicSuiteException):
565    """Raised when a build has a malformed dependency_info file."""
566    pass
567
568
569class InadequateHostsException(CrosDynamicSuiteException):
570    """Raised when there are too few hosts to run a suite."""
571    pass
572
573
574class NoHostsException(CrosDynamicSuiteException):
575    """Raised when there are no healthy hosts to run a suite."""
576    pass
577
578
579class ControlFileNotFound(CrosDynamicSuiteException):
580    """Raised when a control file cannot be found and/or read."""
581    pass
582
583
584class NoControlFileList(CrosDynamicSuiteException):
585    """Raised to indicate that a listing can't be done."""
586    pass
587
588
589class HostLockManagerReuse(CrosDynamicSuiteException):
590    """Raised when a caller tries to re-use a HostLockManager instance."""
591    pass
592
593
594class ReimageAbortedException(CrosDynamicSuiteException):
595    """Raised when a Reimage job is aborted"""
596    pass
597
598
599class UnknownReimageType(CrosDynamicSuiteException):
600    """Raised when a suite passes in an invalid reimage type"""
601    pass
602
603
604class LabIsDownException(Exception):
605    """Raised when the Lab is Down"""
606    pass
607
608
609class BoardIsDisabledException(Exception):
610    """Raised when a certain board is disabled in the Lab"""
611    pass
612
613
614class NoUniquePackageFound(Exception):
615    """Raised when an executable cannot be mapped back to a single package."""
616    pass
617
618
619# This MUST remain at the end of the file.
620# Limit 'from error import *' to only import the exception instances.
621for _name, _thing in locals().items():
622    try:
623        if issubclass(_thing, Exception):
624            __all__.append(_name)
625    except TypeError:
626        pass  # _thing not a class
627__all__ = tuple(__all__)
628