PageRenderTime 57ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/client/shared/error.py

https://github.com/manul7/autotest
Python | 519 lines | 503 code | 7 blank | 9 comment | 2 complexity | 9f6c88c43c015c30c4fc98ac20762a5f MD5 | raw file
Possible License(s): LGPL-3.0, GPL-2.0
  1. """
  2. Internal global error types
  3. """
  4. import sys, traceback, threading, logging
  5. from traceback import format_exception
  6. # Add names you want to be imported by 'from errors import *' to this list.
  7. # This must be list not a tuple as we modify it to include all of our
  8. # the Exception classes we define below at the end of this file.
  9. __all__ = ['format_error', 'context_aware', 'context', 'get_context',
  10. 'exception_context']
  11. def format_error():
  12. t, o, tb = sys.exc_info()
  13. trace = format_exception(t, o, tb)
  14. # Clear the backtrace to prevent a circular reference
  15. # in the heap -- as per tutorial
  16. tb = ''
  17. return ''.join(trace)
  18. # Exception context information:
  19. # ------------------------------
  20. # Every function can have some context string associated with it.
  21. # The context string can be changed by calling context(str) and cleared by
  22. # calling context() with no parameters.
  23. # get_context() joins the current context strings of all functions in the
  24. # provided traceback. The result is a brief description of what the test was
  25. # doing in the provided traceback (which should be the traceback of a caught
  26. # exception).
  27. #
  28. # For example: assume a() calls b() and b() calls c().
  29. #
  30. # @error.context_aware
  31. # def a():
  32. # error.context("hello")
  33. # b()
  34. # error.context("world")
  35. # error.get_context() ----> 'world'
  36. #
  37. # @error.context_aware
  38. # def b():
  39. # error.context("foo")
  40. # c()
  41. #
  42. # @error.context_aware
  43. # def c():
  44. # error.context("bar")
  45. # error.get_context() ----> 'hello --> foo --> bar'
  46. #
  47. # The current context is automatically inserted into exceptions raised in
  48. # context_aware functions, so usually test code doesn't need to call
  49. # error.get_context().
  50. ctx = threading.local()
  51. def _new_context(s=""):
  52. if not hasattr(ctx, "contexts"):
  53. ctx.contexts = []
  54. ctx.contexts.append(s)
  55. def _pop_context():
  56. ctx.contexts.pop()
  57. def context(s="", log=None):
  58. """
  59. Set the context for the currently executing function and optionally log it.
  60. @param s: A string. If not provided, the context for the current function
  61. will be cleared.
  62. @param log: A logging function to pass the context message to. If None, no
  63. function will be called.
  64. """
  65. ctx.contexts[-1] = s
  66. if s and log:
  67. log("Context: %s" % get_context())
  68. def base_context(s="", log=None):
  69. """
  70. Set the base context for the currently executing function and optionally
  71. log it. The base context is just another context level that is hidden by
  72. default. Functions that require a single context level should not use
  73. base_context().
  74. @param s: A string. If not provided, the base context for the current
  75. function will be cleared.
  76. @param log: A logging function to pass the context message to. If None, no
  77. function will be called.
  78. """
  79. ctx.contexts[-1] = ""
  80. ctx.contexts[-2] = s
  81. if s and log:
  82. log("Context: %s" % get_context())
  83. def get_context():
  84. """Return the current context (or None if none is defined)."""
  85. if hasattr(ctx, "contexts"):
  86. return " --> ".join([s for s in ctx.contexts if s])
  87. def exception_context(e):
  88. """Return the context of a given exception (or None if none is defined)."""
  89. if hasattr(e, "_context"):
  90. return e._context
  91. def set_exception_context(e, s):
  92. """Set the context of a given exception."""
  93. e._context = s
  94. def join_contexts(s1, s2):
  95. """Join two context strings."""
  96. if s1:
  97. if s2:
  98. return "%s --> %s" % (s1, s2)
  99. else:
  100. return s1
  101. else:
  102. return s2
  103. def context_aware(fn):
  104. """A decorator that must be applied to functions that call context()."""
  105. def new_fn(*args, **kwargs):
  106. _new_context()
  107. _new_context("(%s)" % fn.__name__)
  108. try:
  109. try:
  110. return fn(*args, **kwargs)
  111. except Exception, e:
  112. if not exception_context(e):
  113. set_exception_context(e, get_context())
  114. raise
  115. finally:
  116. _pop_context()
  117. _pop_context()
  118. new_fn.__name__ = fn.__name__
  119. new_fn.__doc__ = fn.__doc__
  120. new_fn.__dict__.update(fn.__dict__)
  121. return new_fn
  122. def _context_message(e):
  123. s = exception_context(e)
  124. if s:
  125. return " [context: %s]" % s
  126. else:
  127. return ""
  128. class JobContinue(SystemExit):
  129. """Allow us to bail out requesting continuance."""
  130. pass
  131. class JobComplete(SystemExit):
  132. """Allow us to bail out indicating continuation not required."""
  133. pass
  134. class AutotestError(Exception):
  135. """The parent of all errors deliberatly thrown within the client code."""
  136. def __str__(self):
  137. return Exception.__str__(self) + _context_message(self)
  138. class JobError(AutotestError):
  139. """Indicates an error which terminates and fails the whole job (ABORT)."""
  140. pass
  141. class UnhandledJobError(JobError):
  142. """Indicates an unhandled error in a job."""
  143. def __init__(self, unhandled_exception):
  144. if isinstance(unhandled_exception, JobError):
  145. JobError.__init__(self, *unhandled_exception.args)
  146. elif isinstance(unhandled_exception, str):
  147. JobError.__init__(self, unhandled_exception)
  148. else:
  149. msg = "Unhandled %s: %s"
  150. msg %= (unhandled_exception.__class__.__name__,
  151. unhandled_exception)
  152. if not isinstance(unhandled_exception, AutotestError):
  153. msg += _context_message(unhandled_exception)
  154. msg += "\n" + traceback.format_exc()
  155. JobError.__init__(self, msg)
  156. class TestBaseException(AutotestError):
  157. """The parent of all test exceptions."""
  158. # Children are required to override this. Never instantiate directly.
  159. exit_status="NEVER_RAISE_THIS"
  160. class TestError(TestBaseException):
  161. """Indicates that something went wrong with the test harness itself."""
  162. exit_status="ERROR"
  163. class TestNAError(TestBaseException):
  164. """Indictates that the test is Not Applicable. Should be thrown
  165. when various conditions are such that the test is inappropriate."""
  166. exit_status="TEST_NA"
  167. class TestFail(TestBaseException):
  168. """Indicates that the test failed, but the job will not continue."""
  169. exit_status="FAIL"
  170. class TestWarn(TestBaseException):
  171. """Indicates that bad things (may) have happened, but not an explicit
  172. failure."""
  173. exit_status="WARN"
  174. class UnhandledTestError(TestError):
  175. """Indicates an unhandled error in a test."""
  176. def __init__(self, unhandled_exception):
  177. if isinstance(unhandled_exception, TestError):
  178. TestError.__init__(self, *unhandled_exception.args)
  179. elif isinstance(unhandled_exception, str):
  180. TestError.__init__(self, unhandled_exception)
  181. else:
  182. msg = "Unhandled %s: %s"
  183. msg %= (unhandled_exception.__class__.__name__,
  184. unhandled_exception)
  185. if not isinstance(unhandled_exception, AutotestError):
  186. msg += _context_message(unhandled_exception)
  187. msg += "\n" + traceback.format_exc()
  188. TestError.__init__(self, msg)
  189. class UnhandledTestFail(TestFail):
  190. """Indicates an unhandled fail in a test."""
  191. def __init__(self, unhandled_exception):
  192. if isinstance(unhandled_exception, TestFail):
  193. TestFail.__init__(self, *unhandled_exception.args)
  194. elif isinstance(unhandled_exception, str):
  195. TestFail.__init__(self, unhandled_exception)
  196. else:
  197. msg = "Unhandled %s: %s"
  198. msg %= (unhandled_exception.__class__.__name__,
  199. unhandled_exception)
  200. if not isinstance(unhandled_exception, AutotestError):
  201. msg += _context_message(unhandled_exception)
  202. msg += "\n" + traceback.format_exc()
  203. TestFail.__init__(self, msg)
  204. class CmdError(TestError):
  205. """\
  206. Indicates that a command failed, is fatal to the test unless caught.
  207. """
  208. def __init__(self, command, result_obj, additional_text=None):
  209. TestError.__init__(self, command, result_obj, additional_text)
  210. self.command = command
  211. self.result_obj = result_obj
  212. self.additional_text = additional_text
  213. def __str__(self):
  214. if self.result_obj.exit_status is None:
  215. msg = "Command <%s> failed and is not responding to signals"
  216. msg %= self.command
  217. else:
  218. msg = "Command <%s> failed, rc=%d"
  219. msg %= (self.command, self.result_obj.exit_status)
  220. if self.additional_text:
  221. msg += ", " + self.additional_text
  222. msg += _context_message(self)
  223. msg += '\n' + repr(self.result_obj)
  224. return msg
  225. class PackageError(TestError):
  226. """Indicates an error trying to perform a package operation."""
  227. pass
  228. class BarrierError(JobError):
  229. """Indicates an error happened during a barrier operation."""
  230. pass
  231. class BarrierAbortError(BarrierError):
  232. """Indicate that the barrier was explicitly aborted by a member."""
  233. pass
  234. class NetCommunicationError(JobError):
  235. """Indicate that network communication was broken."""
  236. pass
  237. class DataSyncError(NetCommunicationError):
  238. """Indicates problem during synchronization data over network."""
  239. pass
  240. class InstallError(JobError):
  241. """Indicates an installation error which Terminates and fails the job."""
  242. pass
  243. class AutotestRunError(AutotestError):
  244. """Indicates a problem running server side control files."""
  245. pass
  246. class AutotestTimeoutError(AutotestError):
  247. """This exception is raised when an autotest test exceeds the timeout
  248. parameter passed to run_timed_test and is killed.
  249. """
  250. pass
  251. class HostRunErrorMixIn(Exception):
  252. """
  253. Indicates a problem in the host run() function raised from client code.
  254. Should always be constructed with a tuple of two args (error description
  255. (str), run result object). This is a common class mixed in to create the
  256. client and server side versions of it.
  257. """
  258. def __init__(self, description, result_obj):
  259. self.description = description
  260. self.result_obj = result_obj
  261. Exception.__init__(self, description, result_obj)
  262. def __str__(self):
  263. return self.description + '\n' + repr(self.result_obj)
  264. class HostInstallTimeoutError(JobError):
  265. """
  266. Indicates the machine failed to be installed after the predetermined
  267. timeout.
  268. """
  269. pass
  270. class HostInstallProfileError(JobError):
  271. """
  272. Indicates the machine failed to have a profile assigned.
  273. """
  274. pass
  275. class AutotestHostRunError(HostRunErrorMixIn, AutotestError):
  276. pass
  277. # server-specific errors
  278. class AutoservError(Exception):
  279. pass
  280. class AutoservSSHTimeout(AutoservError):
  281. """SSH experienced a connection timeout"""
  282. pass
  283. class AutoservRunError(HostRunErrorMixIn, AutoservError):
  284. pass
  285. class AutoservSshPermissionDeniedError(AutoservRunError):
  286. """Indicates that a SSH permission denied error was encountered."""
  287. pass
  288. class AutoservVirtError(AutoservError):
  289. """Vitualization related error"""
  290. pass
  291. class AutoservUnsupportedError(AutoservError):
  292. """Error raised when you try to use an unsupported optional feature"""
  293. pass
  294. class AutoservHostError(AutoservError):
  295. """Error reaching a host"""
  296. pass
  297. class AutoservHostIsShuttingDownError(AutoservHostError):
  298. """Host is shutting down"""
  299. pass
  300. class AutoservNotMountedHostError(AutoservHostError):
  301. """Found unmounted partitions that should be mounted"""
  302. pass
  303. class AutoservSshPingHostError(AutoservHostError):
  304. """SSH ping failed"""
  305. pass
  306. class AutoservDiskFullHostError(AutoservHostError):
  307. """Not enough free disk space on host"""
  308. def __init__(self, path, want_gb, free_space_gb):
  309. AutoservHostError.__init__(self,
  310. 'Not enough free space on %s - %.3fGB free, want %.3fGB' %
  311. (path, free_space_gb, want_gb))
  312. self.path = path
  313. self.want_gb = want_gb
  314. self.free_space_gb = free_space_gb
  315. class AutoservHardwareHostError(AutoservHostError):
  316. """Found hardware problems with the host"""
  317. pass
  318. class AutoservRebootError(AutoservError):
  319. """Error occured while rebooting a machine"""
  320. pass
  321. class AutoservShutdownError(AutoservRebootError):
  322. """Error occured during shutdown of machine"""
  323. pass
  324. class AutoservSubcommandError(AutoservError):
  325. """Indicates an error while executing a (forked) subcommand"""
  326. def __init__(self, func, exit_code):
  327. AutoservError.__init__(self, func, exit_code)
  328. self.func = func
  329. self.exit_code = exit_code
  330. def __str__(self):
  331. return ("Subcommand %s failed with exit code %d" %
  332. (self.func, self.exit_code))
  333. class AutoservHardwareRepairRequestedError(AutoservError):
  334. """
  335. Exception class raised from Host.repair_full() (or overrides) when software
  336. repair fails but it successfully managed to request a hardware repair (by
  337. notifying the staff, sending mail, etc)
  338. """
  339. pass
  340. class AutoservHardwareRepairRequiredError(AutoservError):
  341. """
  342. Exception class raised during repairs to indicate that a hardware repair
  343. is going to be necessary.
  344. """
  345. pass
  346. class AutoservInstallError(AutoservError):
  347. """Error occured while installing autotest on a host"""
  348. pass
  349. # packaging system errors
  350. class PackagingError(AutotestError):
  351. 'Abstract error class for all packaging related errors.'
  352. class PackageUploadError(PackagingError):
  353. 'Raised when there is an error uploading the package'
  354. class PackageFetchError(PackagingError):
  355. 'Raised when there is an error fetching the package'
  356. class PackageRemoveError(PackagingError):
  357. 'Raised when there is an error removing the package'
  358. class PackageInstallError(PackagingError):
  359. 'Raised when there is an error installing the package'
  360. class RepoDiskFullError(PackagingError):
  361. 'Raised when the destination for packages is full'
  362. class RepoWriteError(PackagingError):
  363. "Raised when packager cannot write to a repo's desitnation"
  364. class RepoUnknownError(PackagingError):
  365. "Raised when packager cannot write to a repo's desitnation"
  366. class RepoError(PackagingError):
  367. "Raised when a repo isn't working in some way"
  368. # This MUST remain at the end of the file.
  369. # Limit 'from error import *' to only import the exception instances.
  370. for _name, _thing in locals().items():
  371. try:
  372. if issubclass(_thing, Exception):
  373. __all__.append(_name)
  374. except TypeError:
  375. pass # _thing not a class
  376. __all__ = tuple(__all__)