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
CloudomationException
Parent of all Cloudomation specific exceptions.
InternalError
An internal error occured. If the error persists, please report a bug to info@cloudomation.com.
PermissionDenied
The execution does not have sufficient permissions to perform the action.
LifecycleSignal
A 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.ExecutionCancel
A signal to end an exception with status
ENDED_CANCELLED
.ExecutionError
A signal to end an exception with status
ENDED_ERROR
.ExecutionSuccess
A signal to end an exception with status
ENDED_SUCCESS
.
ResourceNotFoundError
A requested resource does not exist.
DependencyFailedError
A dependency failed.
TimeoutExceededError
A timeout occured.
LockTimeoutError
A timeout occured while waiting for a lock.
DependencyTimeoutError
A timeout occured while waiting for a dependency.
MessageResponseTimeoutError
A timeout occured while waiting for a message response.
MissingInputError
An input was required by a task but not provided.
InvalidInputError
An input was not of an accepted type.
ExtraInputError
An 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-vm
when the child execution
create-cloud-vm
ends successfully our execution will continueour execution will start a child execution of the connection
cloud-vm
when the child execution
cloud-vm
fails 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-vm
when the child execution
create-cloud-vm
ends successfully our execution will continueour execution will start a child execution of the connection
cloud-vm
when the child execution
cloud-vm
fails a DependencyFailedError will be thrown in our executionthe code of the
finally
block is executedour execution will start a child execution of the flow
remove-cloud-vm
when the child execution
remove-cloud-vm
ends successfilly our execution will continuesince the
DependencyFailedError
was not caught by anexcept
block, it will be re-thrown and our execution will end withENDED_ERROR
the 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-vm
ends successfully our execution will continue - our execution will start a child execution of the connection
cloud-vm
- when the child execution
cloud-vm
fails a DependencyFailedError will be thrown in our execution - the code in the
except flow_api.DependencyFailedError
is executed - our execution sends a notification to a Cloudomation user called
operator
- the code of the
finally
block is executed - our execution will start a child execution of the flow
remove-cloud-vm
- when the child execution
remove-cloud-vm
ends successfilly our execution will continue - the exception was handled, so our execution will continue normnally and execute the code after the
try-except-finally
block - 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.