Exceptions
Cloudomation raises exceptions in executions of flows when errors occur. Flows can catch exceptions and implement logic to handle errors.
Use Cases
Cloudomation uses standard Python exceptions to signal errors. Exceptions can be raised
- by Python. See Built-in Exceptions
- by third-party modules used in a flow. See the documentation of the module you are using.
- by Cloudomation
This article describes the exceptions raised by Cloudomation.
Exception Hierarchy
CloudomationExceptionParent of all Cloudomation specific exceptions.
InternalErrorAn internal error occured. If the error persists, please report a bug to info@cloudomation.com.
PermissionDeniedThe execution does not have sufficient permissions to perform the action.
LifecycleSignalA signal to end an execution. Do not use this class directly, but one of the subclasses.
It is not possible to catch any of the LifecycleSignal exceptions.ExecutionCancelA signal to end an exception with status
ENDED_CANCELLED.ExecutionErrorA signal to end an exception with status
ENDED_ERROR.ExecutionSuccessA signal to end an exception with status
ENDED_SUCCESS.
ResourceNotFoundErrorA requested resource does not exist.
DependencyFailedErrorA dependency failed.
TimeoutExceededErrorA timeout occured.
LockTimeoutErrorA timeout occured while waiting for a lock.
DependencyTimeoutErrorA timeout occured while waiting for a dependency.
MessageResponseTimeoutErrorA timeout occured while waiting for a message response.
MissingInputErrorAn input was required by a task but not provided.
InvalidInputErrorAn input was not of an accepted type.
ExtraInputErrorAn input was provided to a task but not needed.
Unhandled errors
When an exception is raised in an execution and it is not handled Cloudomation will end the execution with the status ENDED_ERROR and provide a traceback in the status_message field.
This example shows how an unhandled exception is presented to the user.
import flow_apidef handler(system: flow_api.System, this: flow_api.Execution):system.file('this-file-does-not-exist').get_text_content()return this.success('all done')
The execution of this flow will end with the status ENDED_ERROR and an error message:
Traceback (most recent call last):File "my-flow (c379ba18-51ea-4a49-a84b-80c2987b177e)", line 4, in handlerflow_api.exceptions.ResourceNotFoundError: file name this-file-does-not-exist
Handling errors
Flows can catch exceptions and implement logic to deal with errors.
This example shows how a flow can handle an exception.
import flow_apidef handler(system: flow_api.System, this: flow_api.Execution):try:content = system.file('report.txt').get_text_content()except flow_api.ResourceNotFoundError:# the file was not found. Let's create it!content = 'initial content'system.file('report.txt').save_text_content(content)# at this point we can be sure that the file exists.return this.success('all done')
Cleaning up
Flows might want to clean up resources they created even when an error occurs. The finally block can be used to do just that.
Consider an execution which launches a cloud-vm instance, then runs some script on the instance and subsequently deletes the cloud-vm instance.
Without a finally block. In case of an error, no cleanup is done.
import flow_apidef handler(system: flow_api.System, this: flow_api.Execution):# execute a child execution which launches a cloud-vmthis.flow('create-cloud-vm')# use the cloud-vmthis.connect('cloud-vm',script='sleep 10; exit 42' # fail after 10 seconds)# execute a child execution which removes the cloud-vmthis.flow('remove-cloud-vm')return this.success('all done')
What will happen:
our execution will start a child execution of the flow
create-cloud-vmwhen the child execution
create-cloud-vmends successfully our execution will continueour execution will start a child execution of the connection
cloud-vmwhen the child execution
cloud-vmfails a DependencyFailedError will be thrown in our executionsince our execution does not handle the exception, it will end with ENDED_ERROR
the status message of our execution will contain the traceback of the error:
Traceback (most recent call last):File "my-flow (a3d9d997-f355-47dd-8ba4-9b702b6dc1ba)", line 10, in handlerflow_api.exceptions.DependencyFailedError: dependency SSH (00891f5b-2fb2-4950-a3fa-4a887ea0279c) did not succeedcaused by:return code: 42
The flow remove-cloud-vm was never started. The cloud-vm will continue running and incur costs.
A better approach places the cleanup in a finally block.
Cleanup in a finally block. Even in case of an error or cancellation of the execution the cleanup will run.
import flow_apidef handler(system: flow_api.System, this: flow_api.Execution):# execute a child execution which launches a cloud-vmthis.flow('create-cloud-vm')try:# use the cloud-vmthis.connect('cloud-vm',script='sleep 10; exit 42' # fail after 10 seconds)finally:# execute a child execution which removes the cloud-vmthis.flow('remove-cloud-vm')return this.success('all done')
What will happen:
our execution will start a child execution of the flow
create-cloud-vmwhen the child execution
create-cloud-vmends successfully our execution will continueour execution will start a child execution of the connection
cloud-vmwhen the child execution
cloud-vmfails a DependencyFailedError will be thrown in our executionthe code of the
finallyblock is executedour execution will start a child execution of the flow
remove-cloud-vmwhen the child execution
remove-cloud-vmends successfilly our execution will continuesince the
DependencyFailedErrorwas not caught by anexceptblock, it will be re-thrown and our execution will end withENDED_ERRORthe status message of our execution will contain the traceback of the error:
Traceback (most recent call last):File "my-flow (2f1b49c2-29a7-4b17-a51d-c31862d33c47)", line 9, in handlerflow_api.exceptions.DependencyFailedError: dependency SSH (d878bbaf-f1a7-439b-b504-dbe78ec0f96b) did not succeedcaused by:return code: 42
Although the execution ends with the same error message, it started the flow remove-cloud-vm which can clean up the resources which were allocated.
It is also possible to do both, handle the error and ensure a cleanup.
Handle an exception and cleanup in a finally block.
import flow_apidef handler(system: flow_api.System, this: flow_api.Execution):# execute a child execution which launches a cloud-vmthis.flow('create-cloud-vm')try:# use the cloud-vmthis.connect('cloud-vm',script='sleep 10; exit 42' # fail after 10 seconds)except flow_api.DependencyFailedError as ex:# an error occured. let's send a notificationsystem.user('operator').send_mail(subject='error notification',html=(f'''<h1>cloud-vm script failed</h1><pre>{str(ex)}</pre>'''),)finally:# execute a child execution which removes the cloud-vmthis.flow('remove-cloud-vm')return this.success('all done')
What will happen:
- our execution will start a child execution of the flow
create-cloud-vm - when the child execution
create-cloud-vmends successfully our execution will continue - our execution will start a child execution of the connection
cloud-vm - when the child execution
cloud-vmfails a DependencyFailedError will be thrown in our execution - the code in the
except flow_api.DependencyFailedErroris executed - our execution sends a notification to a Cloudomation user called
operator - the code of the
finallyblock is executed - our execution will start a child execution of the flow
remove-cloud-vm - when the child execution
remove-cloud-vmends successfilly our execution will continue - the exception was handled, so our execution will continue normnally and execute the code after the
try-except-finallyblock - our execution will end with ENDED_SUCCESS and the status message "all done"
When a execution catches an exception it sometimes is better to let it end with the status ENDED_ERROR. This is especially important when the execution is used as a dependency of other executions. This way the parent execution knows that there was an error and can react accordingly.