blob: 0c5641cd8b8cef51696abe655892bf2b22bebef3 [file] [log] [blame]
Internal global error types
import sys, traceback, threading, logging
from traceback import format_exception
# Add names you want to be imported by 'from errors import *' to this list.
# This must be list not a tuple as we modify it to include all of our
# the Exception classes we define below at the end of this file.
__all__ = ['format_error', 'context_aware', 'context', 'get_context',
def format_error():
t, o, tb = sys.exc_info()
trace = format_exception(t, o, tb)
# Clear the backtrace to prevent a circular reference
# in the heap -- as per tutorial
tb = ''
return ''.join(trace)
# Exception context information:
# ------------------------------
# Every function can have some context string associated with it.
# The context string can be changed by calling context(str) and cleared by
# calling context() with no parameters.
# get_context() joins the current context strings of all functions in the
# provided traceback. The result is a brief description of what the test was
# doing in the provided traceback (which should be the traceback of a caught
# exception).
# For example: assume a() calls b() and b() calls c().
# @error.context_aware
# def a():
# error.context("hello")
# b()
# error.context("world")
# error.get_context() ----> 'world'
# @error.context_aware
# def b():
# error.context("foo")
# c()
# @error.context_aware
# def c():
# error.context("bar")
# error.get_context() ----> 'hello --> foo --> bar'
# The current context is automatically inserted into exceptions raised in
# context_aware functions, so usually test code doesn't need to call
# error.get_context().
ctx = threading.local()
def _new_context(s=""):
if not hasattr(ctx, "contexts"):
ctx.contexts = []
def _pop_context():
def context(s="", log=None):
Set the context for the currently executing function and optionally log it.
@param s: A string. If not provided, the context for the current function
will be cleared.
@param log: A logging function to pass the context message to. If None, no
function will be called.
ctx.contexts[-1] = s
if s and log:
log("Context: %s" % get_context())
def base_context(s="", log=None):
Set the base context for the currently executing function and optionally
log it. The base context is just another context level that is hidden by
default. Functions that require a single context level should not use
@param s: A string. If not provided, the base context for the current
function will be cleared.
@param log: A logging function to pass the context message to. If None, no
function will be called.
ctx.contexts[-1] = ""
ctx.contexts[-2] = s
if s and log:
log("Context: %s" % get_context())
def get_context():
"""Return the current context (or None if none is defined)."""
if hasattr(ctx, "contexts"):
return " --> ".join([s for s in ctx.contexts if s])
def exception_context(e):
"""Return the context of a given exception (or None if none is defined)."""
if hasattr(e, "_context"):
return e._context
def set_exception_context(e, s):
"""Set the context of a given exception."""
e._context = s
def join_contexts(s1, s2):
"""Join two context strings."""
if s1:
if s2:
return "%s --> %s" % (s1, s2)
return s1
return s2
def context_aware(fn):
"""A decorator that must be applied to functions that call context()."""
def new_fn(*args, **kwargs):
_new_context("(%s)" % fn.__name__)
return fn(*args, **kwargs)
except Exception, e:
if not exception_context(e):
set_exception_context(e, get_context())
new_fn.__name__ = fn.__name__
new_fn.__doc__ = fn.__doc__
return new_fn
def _context_message(e):
s = exception_context(e)
if s:
return " [context: %s]" % s
return ""
class JobContinue(SystemExit):
"""Allow us to bail out requesting continuance."""
class JobComplete(SystemExit):
"""Allow us to bail out indicating continuation not required."""
class AutotestError(Exception):
"""The parent of all errors deliberatly thrown within the client code."""
def __str__(self):
return Exception.__str__(self) + _context_message(self)
class JobError(AutotestError):
"""Indicates an error which terminates and fails the whole job (ABORT)."""
class UnhandledJobError(JobError):
"""Indicates an unhandled error in a job."""
def __init__(self, unhandled_exception):
if isinstance(unhandled_exception, JobError):
JobError.__init__(self, *unhandled_exception.args)
elif isinstance(unhandled_exception, str):
JobError.__init__(self, unhandled_exception)
msg = "Unhandled %s: %s"
msg %= (unhandled_exception.__class__.__name__,
if not isinstance(unhandled_exception, AutotestError):
msg += _context_message(unhandled_exception)
msg += "\n" + traceback.format_exc()
JobError.__init__(self, msg)
class TestBaseException(AutotestError):
"""The parent of all test exceptions."""
# Children are required to override this. Never instantiate directly.
class TestError(TestBaseException):
"""Indicates that something went wrong with the test harness itself."""
class TestNAError(TestBaseException):
"""Indictates that the test is Not Applicable. Should be thrown
when various conditions are such that the test is inappropriate."""
class TestFail(TestBaseException):
"""Indicates that the test failed, but the job will not continue."""
class TestWarn(TestBaseException):
"""Indicates that bad things (may) have happened, but not an explicit
class UnhandledTestError(TestError):
"""Indicates an unhandled error in a test."""
def __init__(self, unhandled_exception):
if isinstance(unhandled_exception, TestError):
TestError.__init__(self, *unhandled_exception.args)
elif isinstance(unhandled_exception, str):
TestError.__init__(self, unhandled_exception)
msg = "Unhandled %s: %s"
msg %= (unhandled_exception.__class__.__name__,
if not isinstance(unhandled_exception, AutotestError):
msg += _context_message(unhandled_exception)
msg += "\n" + traceback.format_exc()
TestError.__init__(self, msg)
class UnhandledTestFail(TestFail):
"""Indicates an unhandled fail in a test."""
def __init__(self, unhandled_exception):
if isinstance(unhandled_exception, TestFail):
TestFail.__init__(self, *unhandled_exception.args)
elif isinstance(unhandled_exception, str):
TestFail.__init__(self, unhandled_exception)
msg = "Unhandled %s: %s"
msg %= (unhandled_exception.__class__.__name__,
if not isinstance(unhandled_exception, AutotestError):
msg += _context_message(unhandled_exception)
msg += "\n" + traceback.format_exc()
TestFail.__init__(self, msg)
class CmdError(TestError):
Indicates that a command failed, is fatal to the test unless caught.
def __init__(self, command, result_obj, additional_text=None):
TestError.__init__(self, command, result_obj, additional_text)
self.command = command
self.result_obj = result_obj
self.additional_text = additional_text
def __str__(self):
if self.result_obj.exit_status is None:
msg = "Command <%s> failed and is not responding to signals"
msg %= self.command
msg = "Command <%s> failed, rc=%d"
msg %= (self.command, self.result_obj.exit_status)
if self.additional_text:
msg += ", " + self.additional_text
msg += _context_message(self)
msg += '\n' + repr(self.result_obj)
return msg
class PackageError(TestError):
"""Indicates an error trying to perform a package operation."""
class BarrierError(JobError):
"""Indicates an error happened during a barrier operation."""
class BarrierAbortError(BarrierError):
"""Indicate that the barrier was explicitly aborted by a member."""
class InstallError(JobError):
"""Indicates an installation error which Terminates and fails the job."""
class AutotestRunError(AutotestError):
"""Indicates a problem running server side control files."""
class AutotestTimeoutError(AutotestError):
"""This exception is raised when an autotest test exceeds the timeout
parameter passed to run_timed_test and is killed.
class HostRunErrorMixIn(Exception):
Indicates a problem in the host run() function raised from client code.
Should always be constructed with a tuple of two args (error description
(str), run result object). This is a common class mixed in to create the
client and server side versions of it.
def __init__(self, description, result_obj):
self.description = description
self.result_obj = result_obj
Exception.__init__(self, description, result_obj)
def __str__(self):
return self.description + '\n' + repr(self.result_obj)
class AutotestHostRunError(HostRunErrorMixIn, AutotestError):
# server-specific errors
class AutoservError(Exception):
class AutoservSSHTimeout(AutoservError):
"""SSH experienced a connection timeout"""
class AutoservRunError(HostRunErrorMixIn, AutoservError):
class AutoservSshPermissionDeniedError(AutoservRunError):
"""Indicates that a SSH permission denied error was encountered."""
class AutoservVirtError(AutoservError):
"""Vitualization related error"""
class AutoservUnsupportedError(AutoservError):
"""Error raised when you try to use an unsupported optional feature"""
class AutoservHostError(AutoservError):
"""Error reaching a host"""
class AutoservHostIsShuttingDownError(AutoservHostError):
"""Host is shutting down"""
class AutoservNotMountedHostError(AutoservHostError):
"""Found unmounted partitions that should be mounted"""
class AutoservSshPingHostError(AutoservHostError):
"""SSH ping failed"""
class AutoservDiskFullHostError(AutoservHostError):
"""Not enough free disk space on host"""
def __init__(self, path, want_gb, free_space_gb):
'Not enough free space on %s - %.3fGB free, want %.3fGB' %
(path, free_space_gb, want_gb))
self.path = path
self.want_gb = want_gb
self.free_space_gb = free_space_gb
class AutoservHardwareHostError(AutoservHostError):
"""Found hardware problems with the host"""
class AutoservRebootError(AutoservError):
"""Error occured while rebooting a machine"""
class AutoservShutdownError(AutoservRebootError):
"""Error occured during shutdown of machine"""
class AutoservSubcommandError(AutoservError):
"""Indicates an error while executing a (forked) subcommand"""
def __init__(self, func, exit_code):
AutoservError.__init__(self, func, exit_code)
self.func = func
self.exit_code = exit_code
def __str__(self):
return ("Subcommand %s failed with exit code %d" %
(self.func, self.exit_code))
class AutoservHardwareRepairRequestedError(AutoservError):
Exception class raised from Host.repair_full() (or overrides) when software
repair fails but it successfully managed to request a hardware repair (by
notifying the staff, sending mail, etc)
class AutoservHardwareRepairRequiredError(AutoservError):
Exception class raised during repairs to indicate that a hardware repair
is going to be necessary.
class AutoservInstallError(AutoservError):
"""Error occured while installing autotest on a host"""
# packaging system errors
class PackagingError(AutotestError):
'Abstract error class for all packaging related errors.'
class PackageUploadError(PackagingError):
'Raised when there is an error uploading the package'
class PackageFetchError(PackagingError):
'Raised when there is an error fetching the package'
class PackageRemoveError(PackagingError):
'Raised when there is an error removing the package'
class PackageInstallError(PackagingError):
'Raised when there is an error installing the package'
class RepoDiskFullError(PackagingError):
'Raised when the destination for packages is full'
class RepoWriteError(PackagingError):
"Raised when packager cannot write to a repo's desitnation"
class RepoUnknownError(PackagingError):
"Raised when packager cannot write to a repo's desitnation"
class RepoError(PackagingError):
"Raised when a repo isn't working in some way"
# This MUST remain at the end of the file.
# Limit 'from error import *' to only import the exception instances.
for _name, _thing in locals().items():
if issubclass(_thing, Exception):
except TypeError:
pass # _thing not a class
__all__ = tuple(__all__)