exception.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. # Copyright 2010 United States Government as represented by the
  2. # Administrator of the National Aeronautics and Space Administration.
  3. # All Rights Reserved.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  6. # not use this file except in compliance with the License. You may obtain
  7. # a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  13. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  14. # License for the specific language governing permissions and limitations
  15. # under the License.
  16. import sys
  17. from oslo_config import cfg
  18. from oslo_log import log as logging
  19. from oslo_versionedobjects import exception as obj_exc
  20. import six
  21. import webob.exc
  22. from webob.util import status_generic_reasons
  23. from webob.util import status_reasons
  24. from coriolis.i18n import _, _LE # noqa
  25. LOG = logging.getLogger(__name__)
  26. CONF = cfg.CONF
  27. TASK_ALREADY_CANCELLING_EXCEPTION_FMT = (
  28. "Task %(task_id)s is in CANCELLING status.")
  29. class ConvertedException(webob.exc.WSGIHTTPException):
  30. def __init__(self, code=500, title="", explanation=""):
  31. self.code = code
  32. # There is a strict rule about constructing status line for HTTP:
  33. # '...Status-Line, consisting of the protocol version followed by a
  34. # numeric status code and its associated textual phrase, with each
  35. # element separated by SP characters'
  36. # (http://www.faqs.org/rfcs/rfc2616.html)
  37. # 'code' and 'title' can not be empty because they correspond
  38. # to numeric status code and its associated text
  39. if title:
  40. self.title = title
  41. else:
  42. try:
  43. self.title = status_reasons[self.code]
  44. except KeyError:
  45. generic_code = self.code // 100
  46. self.title = status_generic_reasons[generic_code]
  47. self.explanation = explanation
  48. super(ConvertedException, self).__init__()
  49. class Error(Exception):
  50. pass
  51. class CoriolisException(Exception):
  52. """Base Coriolis Exception
  53. To correctly use this class, inherit from it and define
  54. a 'message' property. That message will get printf'd
  55. with the keyword arguments provided to the constructor.
  56. """
  57. message = _("An unknown exception occurred.")
  58. code = 500
  59. headers = {}
  60. safe = False
  61. def __init__(self, message=None, **kwargs):
  62. self.kwargs = kwargs
  63. if 'code' not in self.kwargs:
  64. try:
  65. self.kwargs['code'] = self.code
  66. except AttributeError:
  67. pass
  68. for k, v in self.kwargs.items():
  69. if isinstance(v, Exception):
  70. self.kwargs[k] = six.text_type(v)
  71. if self._should_format(message):
  72. try:
  73. message = self.message % kwargs
  74. except Exception:
  75. exc_info = sys.exc_info()
  76. # kwargs doesn't match a variable in the message
  77. # log the issue and the kwargs
  78. LOG.exception(_LE('Exception in string format operation'))
  79. for name, value in kwargs.items():
  80. LOG.error(_LE("%(name)s: %(value)s"),
  81. {'name': name, 'value': value})
  82. if CONF.fatal_exception_format_errors:
  83. six.reraise(*exc_info)
  84. # at least get the core message out if something happened
  85. message = self.message
  86. elif isinstance(message, Exception):
  87. message = six.text_type(message)
  88. # NOTE(luisg): We put the actual message in 'msg' so that we can access
  89. # it, because if we try to access the message via 'message' it will be
  90. # overshadowed by the class' message attribute
  91. self.msg = message
  92. super(CoriolisException, self).__init__(message)
  93. def _should_format(self, message):
  94. return message is None or '%(message)' in self.message
  95. def __unicode__(self):
  96. return six.text_type(self.msg)
  97. class NotAuthorized(CoriolisException):
  98. message = _("Not authorized.")
  99. code = 403
  100. safe = True
  101. class PolicyNotAuthorized(CoriolisException):
  102. message = _("Policy doesn't allow %(action)s to be performed.")
  103. code = 403
  104. safe = True
  105. class Conflict(CoriolisException):
  106. message = _("Conflict")
  107. code = 409
  108. safe = True
  109. class LicensingException(Conflict):
  110. message = _("Licensing exception occurred")
  111. code = 409
  112. safe = True
  113. class AdminRequired(NotAuthorized):
  114. message = _("User does not have admin privileges")
  115. class Invalid(CoriolisException):
  116. message = _("Unacceptable parameters.")
  117. code = 400
  118. safe = True
  119. class InvalidMinionPoolSelection(Invalid):
  120. message = _("The selected minion pool is incompatible.")
  121. class InvalidMinionMachineState(Invalid):
  122. message = _("The selected minion machine is in an invalid state.")
  123. class MinionMachineAllocationFailure(Invalid):
  124. message = _("No minion machines were available for allocation")
  125. class InvalidCustomOSDetectTools(Invalid):
  126. message = _("The provided custom OS detect tools are invalid.")
  127. class InvalidOSMorphingTools(Invalid):
  128. message = _("Invalid OSMorphing tools received: %(tools_class)s")
  129. class InvalidDetectedOSParams(CoriolisException):
  130. message = _("One or more detected OS parameters were invalid.")
  131. safe = True
  132. class InvalidResults(Invalid):
  133. message = _("The results are invalid.")
  134. class InvalidInput(Invalid):
  135. message = _("Invalid input received: %(reason)s")
  136. class InvalidContentType(Invalid):
  137. message = _("Invalid content type %(content_type)s.")
  138. class InvalidHost(Invalid):
  139. message = _("Invalid host: %(reason)s")
  140. class SameDestination(Invalid):
  141. message = _("Origin and destination cannot be the same")
  142. # Cannot be templated as the error syntax varies.
  143. # msg needs to be constructed when raised.
  144. class InvalidParameterValue(Invalid):
  145. message = _("%(err)s")
  146. class InvalidAuthKey(Invalid):
  147. message = _("Invalid auth key: %(reason)s")
  148. class InvalidConfigurationValue(Invalid):
  149. message = _('Value "%(value)s" is not valid for '
  150. 'configuration option "%(option)s"')
  151. class InvalidTaskState(Invalid):
  152. message = _(
  153. 'Task "%(task_id)s" in in an invalid state: %(task_state)s')
  154. class InvalidMinionPoolState(Invalid):
  155. message = _(
  156. 'Minion pool "%(pool_id)s" in in an invalid state: %(pool_state)s')
  157. class TaskIsCancelling(InvalidTaskState):
  158. message = _(TASK_ALREADY_CANCELLING_EXCEPTION_FMT)
  159. class InvalidTaskResult(InvalidTaskState):
  160. message = _('Task returned an invalid result.')
  161. class InvalidActionTasksExecutionState(Invalid):
  162. message = _("Invalid tasks execution state: %(reason)s")
  163. class InvalidMigrationState(Invalid):
  164. message = _("Invalid migration state: %(reason)s")
  165. class InvalidReplicaState(Invalid):
  166. message = _("Invalid replica state: %(reason)s")
  167. class InvalidInstanceState(Invalid):
  168. message = _("Invalid instance state: %(reason)s")
  169. class ExecutionDeadlockException(CoriolisException):
  170. message = _("Execution is bound to be deadlocked.")
  171. class TaskParametersException(CoriolisException):
  172. message = _("Execution task parameters are missing.")
  173. class TaskFieldsConflict(CoriolisException):
  174. message = _("There are fields which will encounter a state conflict.")
  175. class TaskDependencyException(CoriolisException):
  176. message = _(
  177. "Execution task has non-existent tasks referenced as dependencies."
  178. )
  179. class ServiceUnavailable(Invalid):
  180. message = _("Service is unavailable at this time.")
  181. class APIException(CoriolisException):
  182. message = _("Error while requesting %(service)s API.")
  183. safe = True
  184. def __init__(self, message=None, **kwargs):
  185. if 'service' not in kwargs:
  186. kwargs['service'] = 'unknown'
  187. super(APIException, self).__init__(message, **kwargs)
  188. class APITimeout(APIException):
  189. message = _("Timeout while requesting %(service)s API.")
  190. class NotFound(CoriolisException):
  191. message = _("Resource could not be found.")
  192. code = 404
  193. safe = True
  194. class RegionNotFound(NotFound):
  195. message = _("The specified Coriolis region(s) could not be found.")
  196. class OSMorphingToolsNotFound(NotFound):
  197. message = _(
  198. 'No OSMorphing tools were found for OS type "%(os_type)s" for this VM.'
  199. ' This would indicate that it was either not possible to determine the'
  200. ' exact OS release, or this OS release is not supported by Coriolis. '
  201. 'Suggestions include performing any needed OSMorphing steps manually '
  202. 'within the source VM and then re-syncing with the "Skip OS Morphing" '
  203. 'option enabled to bypass this stage, or contacting Cloudbase support '
  204. 'for further assistance.')
  205. class OSDetectToolsNotFound(NotFound):
  206. message = _(
  207. 'No "%(os_type)s" OS detect tools were able to identify the OS for '
  208. ' this VM. '
  209. 'This would indicate that it was either not possible to determine the '
  210. 'exact OS release, or this OS release is not supported by Coriolis. '
  211. 'Suggestions include performing any needed OSMorphing steps manually '
  212. 'within the source VM and then re-syncing with the "Skip OS Morphing" '
  213. 'option enabled to bypass this stage, or contacting Cloudbase support '
  214. 'for further assistance.')
  215. class FileNotFound(NotFound):
  216. message = _("File %(file_path)s could not be found.")
  217. class InstanceNotFound(NotFound):
  218. message = _("Instance \"%(instance_name)s\" could not be found.")
  219. class NetworkNotFound(NotFound):
  220. message = _("Network \"%(network_name)s\" could not be found.")
  221. class DiskStorageMappingNotFound(NotFound):
  222. message = _('No storage mapping for disk with ID "%(id)s" could be found.')
  223. class StorageBackendNotFound(NotFound):
  224. message = _(
  225. 'Storage backend with name "%(storage_name)s" could not be found.')
  226. class ImageNotFound(NotFound):
  227. message = _("Image \"%(image_name)s\" could not be found.")
  228. class FlavorNotFound(NotFound):
  229. message = _("Flavor \"%(flavor_name)s\" could not be found.")
  230. class FloatingIPPoolNotFound(NotFound):
  231. message = _("Floating IP pool \"%(pool_name)s\" could not be found.")
  232. class VolumeNotFound(NotFound):
  233. message = _("Volume \"%(volume_id)s\" could not be found.")
  234. class VolumeSnapshotNotFound(NotFound):
  235. message = _("Volume snapshot \"%(snapshot_id)s\" could not be found.")
  236. class VolumeBackupNotFound(NotFound):
  237. message = _("Volume backup \"%(backup_id)s\" could not be found.")
  238. class Duplicate(CoriolisException):
  239. safe = True
  240. class MalformedRequestBody(CoriolisException):
  241. message = _("Malformed message body: %(reason)s")
  242. code = 400
  243. safe = True
  244. class ConfigNotFound(NotFound):
  245. message = _("Could not find config at %(path)s")
  246. class ParameterNotFound(NotFound):
  247. message = _("Could not find parameter %(param)s")
  248. class PasteAppNotFound(NotFound):
  249. message = _("Could not load paste app '%(name)s' from %(path)s")
  250. class NoValidHost(CoriolisException):
  251. message = _("No valid host was found. %(reason)s")
  252. safe = True
  253. UnsupportedObjectError = obj_exc.UnsupportedObjectError
  254. OrphanedObjectError = obj_exc.OrphanedObjectError
  255. IncompatibleObjectVersion = obj_exc.IncompatibleObjectVersion
  256. ReadOnlyFieldError = obj_exc.ReadOnlyFieldError
  257. ObjectActionError = obj_exc.ObjectActionError
  258. ObjectFieldInvalid = obj_exc.ObjectFieldInvalid
  259. class NotSupportedOperation(Invalid):
  260. message = _("Operation not supported: %(operation)s.")
  261. code = 405
  262. class TaskProcessException(CoriolisException):
  263. safe = True
  264. class TaskProcessCanceledException(TaskProcessException):
  265. pass
  266. class OperatingSystemNotFound(NotFound):
  267. pass
  268. class ConnectionValidationException(CoriolisException):
  269. safe = True
  270. class SchemaValidationException(CoriolisException):
  271. safe = True
  272. class QEMUException(Exception):
  273. pass
  274. if six.PY2:
  275. class ConnectionRefusedError(OSError):
  276. pass
  277. else:
  278. ConnectionRefusedError = six.moves.builtins.ConnectionRefusedError
  279. class UnrecognizedWorkerInitSystem(CoriolisException):
  280. message = _(
  281. "Could not determine init system for temporary worker VM. The image "
  282. "used for the worker VM must use systemd as an init system for "
  283. "Coriolis to be able to use it for data Replication.")
  284. class NoRegionError(CoriolisException):
  285. safe = True
  286. code = 503
  287. message = _(
  288. "No Coriolis region is avaialable to process this request at this "
  289. "time.")
  290. class NoSuitableRegionError(NoRegionError):
  291. message = _(
  292. "No Coriolis Region(s) fitting the criteria of the required operation "
  293. "could be found.")
  294. class NoServiceError(CoriolisException):
  295. safe = True
  296. code = 503
  297. message = _(
  298. "No service is avaialable to process this request at this time.")
  299. class NoWorkerServiceError(NoServiceError):
  300. message = _(
  301. "No Coriolis Worker Service(s) were found. Please ensure that "
  302. "at least one or Coriolis Worker Service(s) are registered "
  303. "within the Coriolis installation.")
  304. class NoSuitableWorkerServiceError(NoServiceError):
  305. message = _(
  306. "No suitable Coriolis Worker service was found which fits the "
  307. "criteria for the required operation.")
  308. class OSMorphingException(CoriolisException):
  309. pass
  310. class PackageManagerOperationException(OSMorphingException):
  311. pass
  312. class FailedPackageInstallationException(PackageManagerOperationException):
  313. message = (
  314. "Failed to install required packages %(package_names)s through "
  315. "%(package_manager)s. Please ensure that the required packages are "
  316. "available within the %(package_manager)s repositories configured "
  317. "within the source machine. If not, please either add or enable "
  318. "additional repositories within the source machine which contain the "
  319. "packages Coriolis requires, or attempt to manually install the "
  320. "packages on the source machine and then migrate the VM using Coriolis"
  321. " with the OSMorphing process disabled. Error was: %(error)s")
  322. class FailedPackageUninstallationException(PackageManagerOperationException):
  323. message = (
  324. "Failed to remove unwanted packages (%(package_names)s) through "
  325. "%(package_manager)s. Error was: %(error)s")
  326. class MinionMachineCommandTimeout(CoriolisException):
  327. pass
  328. class OSMorphingOperationTimeout(MinionMachineCommandTimeout):
  329. pass
  330. class OSMorphingSSHOperationTimeout(OSMorphingOperationTimeout):
  331. message = (
  332. "Pending SSH command %(cmd)s timed out after %(timeout)s seconds. "
  333. "Coriolis may have encountered connection issues to the minion machine"
  334. " or the command execution time exceeds the timeout set. Try extending"
  335. " the timeout by editing the 'default_osmorphing_operation_timeout' "
  336. "in Coriolis' static configuration file.")
  337. class OSMorphingWinRMOperationTimeout(OSMorphingOperationTimeout):
  338. message = (
  339. "Pending WinRM command %(cmd)s timed out after %(timeout)s seconds. "
  340. "Coriolis may have encountered connection issues to the minion machine"
  341. " or the command execution time exceeds the timeout set. Try extending"
  342. " the timeout by editing the 'default_osmorphing_operation_timeout' "
  343. "in Coriolis' static configuration file.")
  344. class MigrationLicenceFulfilledException(Invalid):
  345. message = (
  346. "The Live Migration operation with ID '%(action_id)s' (licensing "
  347. "reservation '%(reservation_id)s') has already been fulfilled on "
  348. "%(fulfilled_at)s. Please create a new Live Migration operation to "
  349. "create a new licensing reservation.")