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