PageRenderTime 75ms CodeModel.GetById 42ms RepoModel.GetById 1ms app.codeStats 0ms

/hostmanager/tomato/lib/error.py

https://github.com/GLab/ToMaTo
Python | 288 lines | 245 code | 21 blank | 22 comment | 34 complexity | 665b65397cc892ea41a0f2d51f623dd3 MD5 | raw file
  1. import os, hashlib, re, sys
  2. import httplib
  3. import inspect, traceback
  4. import anyjson as json
  5. from .exceptionhandling import writedown_current_exception
  6. MODULE = os.environ.get("TOMATO_MODULE", "unknown")
  7. TYPES = {}
  8. def generate_inspect_trace(frame=None):
  9. res = []
  10. if frame is None:
  11. frame = inspect.currentframe()
  12. # there may be recursive errors if repr(obj) fails for any local variable at any point.
  13. # thus, we need to figure out whether we have such a recursion before any repr function is called.
  14. # we detect a recursion by testing whether this function appears multiple times in the stack.
  15. # the try: outside the repr() or str() function below does not mean that this function is not called in error handling.
  16. self_count = 0 # count how often this function is in the trace. abort if recursion may be happening
  17. for frame_ in inspect.getouterframes(frame):
  18. if frame_[3] == generate_inspect_trace.__name__:
  19. self_count += 1
  20. if self_count > 1: # assume recursion if this function is in the stack 2 or more times
  21. return []
  22. for frame_ in inspect.getouterframes(frame):
  23. obj = {
  24. 'locals': {},
  25. 'file': frame_[1],
  26. 'line_no': frame_[2],
  27. 'function': frame_[3],
  28. 'code': frame_[4]
  29. }
  30. for k, v in frame_[0].f_locals.iteritems():
  31. try:
  32. v_ = repr(v)
  33. except:
  34. try:
  35. v_ = str(v)
  36. except:
  37. v_ = "<unreadable object>"
  38. if len(v_) > 1000:
  39. v_ = v_[:1000]
  40. obj['locals'][k] = v_
  41. res.append(obj)
  42. res.reverse()
  43. return res
  44. class Error(Exception):
  45. TYPE = "general"
  46. UNKNOWN = None
  47. def __init__(self, code=None, message=None, data=None, type=None, todump=None, module=MODULE, httpcode=None, onscreenmessage=None, frame=None, frame_trace=None, trace=None, wasdumped=False):
  48. self.type = type or self.TYPE
  49. self.code = code
  50. self.message = message
  51. self.data = data or {}
  52. self.module = module
  53. self.wasdumped = wasdumped
  54. if trace is None:
  55. self.trace = traceback.extract_stack()
  56. else:
  57. self.trace = trace
  58. if httpcode is None:
  59. self.httpcode = getCodeHTTPErrorCode(code)
  60. else:
  61. self.httpcode = httpcode
  62. if onscreenmessage is None:
  63. self.onscreenmessage = message
  64. else:
  65. self.onscreenmessage = onscreenmessage
  66. if todump is not None:
  67. self.todump = todump # will be set to False by dump.py after dumping
  68. else:
  69. self.todump = not isinstance(self, UserError) or self.module != MODULE
  70. if frame_trace is None: # this must be last because it may call repr(self)
  71. if frame is None:
  72. self.frame_trace = generate_inspect_trace(inspect.currentframe())
  73. else:
  74. self.frame_trace = generate_inspect_trace(frame)
  75. else:
  76. self.frame_trace = frame_trace
  77. def group_id(self):
  78. """
  79. Returns a group ID. This should be the same for errors thrown due to the same reason.
  80. """
  81. return hashlib.md5(
  82. json.dumps({
  83. 'code':self.code,
  84. 'type':self.type,
  85. 'message':re.sub('[a-fA-F0-9]+', 'x', str(self.message)),
  86. 'module':self.module})
  87. ).hexdigest()
  88. def dump(self):
  89. """
  90. dump this error through the dump manager.
  91. """
  92. import dump
  93. dump.dumpError(self)
  94. def handle(self):
  95. """
  96. pass this error to the exceptionhandling library
  97. """
  98. writedown_current_exception()
  99. @property
  100. def raw(self):
  101. """
  102. creates a dict representation of this error.
  103. """
  104. res = dict(self.__dict__)
  105. if 'frame_trace' in res:
  106. del res['frame_trace']
  107. return res
  108. @property
  109. def rawstr(self):
  110. """
  111. creates a string representation of this error.
  112. """
  113. return json.dumps(self.raw)
  114. @staticmethod
  115. def parse(raw):
  116. return TYPES.get(raw["type"], Error)(**raw)
  117. @staticmethod
  118. def parsestr(rawstr):
  119. """
  120. creates an Error object from the string representation
  121. """
  122. raw = json.loads(rawstr)
  123. return Error.parse(raw)
  124. @classmethod
  125. def check(cls, condition, code, message, todump=None, *args, **kwargs):
  126. if condition: return
  127. exception = cls(code=code, message=message, todump=todump,
  128. frame=inspect.currentframe(), trace=traceback.extract_stack(), *args, **kwargs)
  129. raise exception
  130. @classmethod
  131. def wrap(cls, error, code=UNKNOWN, todump=None, message=None, trace=None, frame_trace=None, frame=None, *args, **kwargs):
  132. if trace is None:
  133. try:
  134. trace = traceback.extract_tb(sys.exc_traceback)
  135. except:
  136. pass
  137. return cls(code=code, message=message or repr(error), todump=todump, trace=trace, frame_trace=frame_trace, frame=frame, *args, **kwargs)
  138. def __str__(self):
  139. lines = []
  140. for k, v in self.data.items():
  141. if k == "trace":
  142. lines.append("\ttrace=")
  143. for l in v.splitlines():
  144. lines.append("\t\t" + l)
  145. else:
  146. lines.append("\t%s=%r" % (k, v))
  147. return "%s %s error [%s]: %s\n%s" % (self.module, self.type, self.code, self.message or "", "\n".join(lines))
  148. def __repr__(self):
  149. return "Error(module=%r, type=%r, code=%r, message=%r, data=%r, onscreenmessage=%r)" % (self.module, self.type, self.code, self.message, self.data,self.onscreenmessage)
  150. def ErrorType(Type):
  151. TYPES[Type.TYPE]=Type
  152. return Type
  153. @ErrorType
  154. class InternalError(Error):
  155. TYPE = "internal"
  156. HOST_ERROR = "host_error"
  157. INVALID_NEXT_STATE = "invalid_next_state"
  158. UPCAST = "upcast"
  159. MUST_OVERRIDE = "must_override"
  160. WRONG_DATA = "wrong_data"
  161. INVALID_STATE = "invalid_state"
  162. COMMAND_FAILED = "command_failed"
  163. INVALID_PARAMETER = "invalid_parameter"
  164. CONFIGURATION_ERROR = "configuration_error"
  165. RESOURCE_ERROR = "resource_error"
  166. ASSERTION = "assertion"
  167. DEPRECATED_CODE = "deprecated_code"
  168. @ErrorType
  169. class UserError(Error):
  170. TYPE = "user"
  171. ENTITY_DOES_NOT_EXIST = "entity_does_not_exist"
  172. UNSUPPORTED_ACTION = "unsupported_action"
  173. UNSUPPORTED_ATTRIBUTE = "unsupported_attribute"
  174. INVALID_STATE = "invalid_state" # The request is valid but the subject is in a wrong state
  175. ENTITY_BUSY = "entity_busy"
  176. UNABLE_TO_CONNECT = "unable_to_connect"
  177. ALREADY_CONNECTED = "already_connected"
  178. DIFFERENT_USER = "different_user" # The entity belongs to a different user
  179. UNSUPPORTED_TYPE = "unsupported_type"
  180. INVALID_CONFIGURATION = "invalid_configuration" # All of the values are valid in general but the combination is invalid
  181. INVALID_VALUE = "invalid_value" # One of the values is invalid
  182. NO_DATA_AVAILABLE = "no_data_available"
  183. COMMAND_FAILED = "command_failed" # A command executed by the user failed (OpenVZ)
  184. DENIED = "denied" # This action is denied because of permissions or by policy
  185. NOT_LOGGED_IN = "not_logged_in" # Request to log in to continue
  186. NOT_EMPTY = "not_empty" # Container can not be deleted because it is not empty
  187. TIMED_OUT = "timed_out" # The subject timed out
  188. AMBIGUOUS = "ambiguous" # The request was ambiguous
  189. ALREADY_EXISTS = "already_exists" # The object can not be created because it already exists
  190. NO_RESOURCES = "no_resources" # No resources to satisfy this request
  191. INVALID_RESOURCE_TYPE = "invalid_resource_type" # user requested an existing resource, but it is of the wrong type
  192. INVALID_DATA = "invalid_value"
  193. @ErrorType
  194. class TransportError(Error):
  195. TYPE = "transport"
  196. INVALID_URL = "invalid_url"
  197. UNAUTHORIZED = "unauthorized"
  198. SSL = "ssl"
  199. CONNECT = "connect"
  200. RPC = "rpc"
  201. @ErrorType
  202. class NetworkError(Error):
  203. TYPE = "network"
  204. NOT_REACHABLE = "not reachable"
  205. # This is used by other functions. It maps error types to onscreen error types (i.e., better readable), and HTTP response codes.
  206. type_translator = {
  207. # The details here are not really interesting to the user since they cannot do something about it anyway.
  208. # Thus, just show an internal error with code 500 in this case.
  209. InternalError.HOST_ERROR: ("Internal Error",httplib.INTERNAL_SERVER_ERROR),
  210. InternalError.INVALID_NEXT_STATE: ("Internal Error",httplib.INTERNAL_SERVER_ERROR),
  211. InternalError.UPCAST: ("Internal Error",httplib.INTERNAL_SERVER_ERROR),
  212. InternalError.MUST_OVERRIDE: ("Internal Error",httplib.INTERNAL_SERVER_ERROR),
  213. InternalError.WRONG_DATA: ("Internal Error",httplib.INTERNAL_SERVER_ERROR),
  214. InternalError.INVALID_STATE: ("Internal Error",httplib.INTERNAL_SERVER_ERROR),
  215. InternalError.COMMAND_FAILED: ("Internal Error",httplib.INTERNAL_SERVER_ERROR),
  216. InternalError.INVALID_PARAMETER: ("Internal Error",httplib.INTERNAL_SERVER_ERROR),
  217. InternalError.CONFIGURATION_ERROR: ("Internal Error",httplib.INTERNAL_SERVER_ERROR),
  218. InternalError.RESOURCE_ERROR: ("Internal Error",httplib.INTERNAL_SERVER_ERROR),
  219. InternalError.ASSERTION: ("Internal Error",httplib.INTERNAL_SERVER_ERROR),
  220. # These Errors are more important to the user
  221. UserError.ENTITY_DOES_NOT_EXIST: ("%(entity)s not found",httplib.NOT_FOUND),
  222. UserError.UNSUPPORTED_ACTION: ("Unsupported Action",httplib.METHOD_NOT_ALLOWED),
  223. UserError.UNSUPPORTED_ATTRIBUTE: ("Unsupported Attribute",httplib.BAD_REQUEST),
  224. UserError.INVALID_STATE: ("Invalid State",httplib.CONFLICT),
  225. UserError.ENTITY_BUSY: ("%(entity)s Busy",httplib.CONFLICT),
  226. UserError.UNABLE_TO_CONNECT: ("Unable to Connect",httplib.CONFLICT),
  227. UserError.ALREADY_CONNECTED: ("Already Connected",httplib.CONFLICT),
  228. UserError.DIFFERENT_USER: ("Different User",httplib.UNAUTHORIZED),
  229. UserError.UNSUPPORTED_TYPE: ("Unsupported Type",httplib.BAD_REQUEST),
  230. UserError.INVALID_CONFIGURATION: ("Invalid Configuration",httplib.BAD_REQUEST),
  231. UserError.INVALID_VALUE: ("Invalid Value",httplib.BAD_REQUEST),
  232. UserError.NO_DATA_AVAILABLE: ("No Data Available",httplib.CONFLICT),
  233. UserError.COMMAND_FAILED: ("Command Failed",httplib.INTERNAL_SERVER_ERROR),
  234. UserError.DENIED: ("Denied",httplib.UNAUTHORIZED),
  235. UserError.NOT_LOGGED_IN: ("Not Logged in",httplib.UNAUTHORIZED),
  236. UserError.NOT_EMPTY: ("Not Empty",httplib.CONFLICT),
  237. UserError.TIMED_OUT: ("Timed Out",httplib.INTERNAL_SERVER_ERROR),
  238. UserError.AMBIGUOUS: ("Ambiguous",httplib.BAD_REQUEST),
  239. UserError.ALREADY_EXISTS: ("%s Already Exists",httplib.CONFLICT),
  240. UserError.NO_RESOURCES: ("No Resources",httplib.SERVICE_UNAVAILABLE),
  241. UserError.INVALID_RESOURCE_TYPE: ("Invalid Resource Type", httplib.BAD_REQUEST)
  242. }
  243. def _translate(code):
  244. return type_translator.get(code, ("Unexpected Error", httplib.INTERNAL_SERVER_ERROR))
  245. def getCodeMsg(code, entity="Entity"):
  246. return _translate(code)[0] % {'entity':entity}
  247. def getCodeHTTPErrorCode(code):
  248. return _translate(code)[1]
  249. def assert_(condition, message, code=InternalError.ASSERTION, *args, **kwargs):
  250. if condition: return
  251. raise InternalError(message=message, code=code, *args, **kwargs)