Cancel Command
Introduction
In versions prior to 7.1.0.0, Universal Extension task instances could be cancelled via the Controller just like any other task type, but the instances do not participate in the Cancellation process. As a result, there is no chance to do any sort of cleanup before Cancellation. Starting with 7.1.0.0, the Universal Extension API was enhanced with a new method called extension_cancel() which allows for any cleanup work before the process is terminated.
On this page, we will cover the following:
- Add a new cancel_cleanup_time field to the "UE Task" Universal Template
- Add a backing implementation for Cancel Command to the extension.py file.
- Build the modified Extension.
- Upload the modified Extension.
- Demonstrate the Cancel command in three different scenarios
- Graceful Cancellation
- Timeout
- Double Cancel
Step 1 - Add a new "cancel_cleanup_time" field to the "UE Task" Universal Template
Navigate to the "UE Task" Universal Template.
In the "Fields" tab, add a new field called "cancel_cleanup_time" as shown below:
The value of this field will be used to simulate the time spent doing the cleanup work in extension_cancel().
Save the field.
Step 2 - Add a backing implementation for Cancel Command to the extension.py file
Open file ~/dev/extensions/sample-task/src/extension.py
in your editor of choice.
Add the implementation of the extension_cancel() method:
from __future__ import (print_function) import time from universal_extension import UniversalExtension from universal_extension import ExtensionResult from universal_extension import logger from universal_extension import ui from universal_extension.deco import dynamic_choice_command from universal_extension.deco import dynamic_command class Extension(UniversalExtension): """Required class that serves as the entry point for the extension """ def __init__(self): """Initializes an instance of the 'Extension' class """ # Call the base class initializer super(Extension, self).__init__() @dynamic_choice_command("primary_choice_field") def primary_choice_command(self, fields): """Dynamic choice command implementation for primary_choice_field. Parameters ---------- fields : dict populated with the values of the dependent fields """ return ExtensionResult( rc=0, message="Values for choice field: 'primary_choice_field'", values=["Start", "Pause", "Stop", "Build", "Destroy"] ) @dynamic_choice_command("secondary_choice_field") def secondary_choice_command(self, fields): """Dynamic choice command implementation for secondary_choice_field. Parameters ---------- fields : dict populated with the values of the dependent fields """ return ExtensionResult( rc=0, message="Values for choice field: 'secondary_choice_field'", values=["System", "Command", "Application", "Transfer", "Evidence"] ) @dynamic_command("reset_environment") def reset_environment(self, fields): """Dynamic command implementation for reset_environment command. Parameters ---------- fields : dict populated with the values of the dependent fields """ # Reset the state of the Output Only 'step' fields. out_fields = {} out_fields["step_1"] = "Initial" out_fields["step_2"] = "Initial" ui.update_output_fields(out_fields) return ExtensionResult( message="Message: Hello from dynamic command 'reset_environment'!", output=True, output_data='The environment has been reset.', output_name='DYNAMIC_OUTPUT') @dynamic_command("async_print_word") def async_print_word(self, fields): """ Adds each letter of self.WORD to self.async_queue If curr_index is odd, then the function will sleep for 5 seconds before adding self.WORD to self.async_queue """ curr_index = self.async_index if curr_index % 2 != 0: self.async_index += 1 time.sleep(5) else: self.async_index += 1 self.async_queue.append(self.WORD[curr_index]) return ExtensionResult( message="", output=True, output_data="async_print_word()", output_name='DYNAMIC_ASYNC_OUTPUT') @dynamic_command("sync_print_word") def sync_print_word(self, fields): """ Adds each letter of self.WORD to self.sync_queue If curr_index is odd, then the function will sleep for 5 seconds before adding self.WORD to self.sync_queue """ curr_index = self.sync_index if curr_index % 2 != 0: self.sync_index += 1 time.sleep(5) else: self.sync_index += 1 self.sync_queue.append(self.WORD[curr_index]) return ExtensionResult( message="", output=True, output_data="sync_print_word()", output_name='DYNAMIC_SYNC_OUTPUT') def extension_start(self, fields): """Required method that serves as the starting point for work performed for a task instance. Parameters ---------- fields : dict populated with field values from the associated task instance launched in the Controller Returns ------- ExtensionResult once the work is done, an instance of ExtensionResult must be returned. See the documentation for a full list of parameters that can be passed to the ExtensionResult class constructor """ # Extract the cancel_cleanup_time self.cancel_cleanup_time = fields.get('cancel_cleanup_time', 0) # Sleep for 45 seconds time.sleep(45) # Get the value of the 'action' field action = fields.get('action', [""])[0] if action.lower() == 'print': # Print to standard output... print("Hello STDOUT!") else: # Log to standard error... logger.info('Hello STDERR!') # Return the result with a payload containing a Hello message... return ExtensionResult( unv_output='Hello Extension!' ) def extension_cancel(self): """Optional method that allows the Extension instance to perform any cleanup work. """ logger.info('About to sleep for %d seconds' % self.cancel_cleanup_time) time.sleep(self.cancel_cleanup_time) logger.info('Done with extension_cancel()')
Line 144 | The cancel_cleanup_time field is extracted from fields and stored in self.cancel_cleanup_time so that it can be accessed in extension_cancel() |
Lines 147 | The extension_start() method is modified to sleep for 45 seconds. |
Lines 164-170 | The extension_cancel() method sleeps for self.cancel_cleanup_time number of seconds. It also logs two statements; one before and one after the sleep time period. |
Step 2 - Build and Upload the Extension Zip Archive Using the UIP VS Code Extension
Save all changes to extension.py.
From the VS Code command pallet, execute the UIP: Push command as shown below:
Recall that the UIP: Push command builds and uploads the Extension zip archive
Step 3 - Demonstrate Cancel Command
Before working with the Cancel command, we will need to make sure the cancel timeout value (this is NOT the cancel_cleanup_time field above) is set to 10 seconds. Unless you have explicitly modified the value, it will be 10 seconds by default. Open uags.conf, and if there is an entry for extension_cancel_timeout, make sure it is 10 seconds.
a. Graceful Cancellation
Graceful Cancellation is when the extension_cancel() method finishes before the 10 second Cancel timeout period is up.
- Navigate to the "ue-task-test" task instance form, and ensure the Cancel Cleanup Time field is set to 0 as shown:
- Launch the task using the VS Code "UIP: Task Launch" command
- After about 2-3 seconds, right-click the task instance on the Controller and click "Cancel"
- Wait until the task status is "Cancelled"
- The entire process should look similar to:
Notice that both the log statements were printed since the timeout period of 10 seconds did not expire before the extension_cancel() cleanup finished.
b. Timeout
Timeout is when the extension_cancel() method has not finished before the 10 second Cancel timeout period is up.
- Navigate to the "ue-task-test" task instance form, and ensure the Cancel Cleanup Time field is set to 15 as shown:
- Launch the task using the VS Code "UIP: Task Launch" command
- After about 2-3 seconds, right-click the task instance on the Controller and click "Cancel"
- Wait until the task status is "Cancelled"
- The entire process should look similar to:
Notice that only the first log statement was printed. Since the cancel cleanup time value was set to 15 seconds, the Cancel command timed out after 10 seconds, and the Extension process was forcefully terminated.
c. Double Cancel
Double Cancel is when the Extension instance is "Cancelled" twice from the Controller. When the agent receives the second Cancel, it immediately terminates the Extension process regardless of whether the timeout has occurred or not.
- Navigate to the "ue-task-test" task instance form, and ensure the Cancel Cleanup Time field is set to 15 as shown:
- Launch the task using the VS Code "UIP: Task Launch" command
- After about 2-3 seconds, right-click the task instance on the Controller and click "Cancel"
- Immediately after, right-click the task instance and click "Cancel" once again.
- The status should immediately transition to "Cancelled"
- The entire process should look similar to:
Notice that only the first log statement was printed. Since the Extension process was terminated by the Double Cancel before the 15 second sleep period was up, the second log statement did not get printed.
Step 4 - Update the Local template.json
In step 1, we modified the Universal Template by adding the new cancel_cleanup_time field. Recall that inside ~/dev/extensions/sample-task/src/templates, there is a template.json file. This file should correspond to the Universal Template in the Controller.
Right now, they are not both the same. The Controller's version of template.json has the new field. To grab those changes, use the pull command as shown below:
UIP VS Code Extension
Now, both the local and Controller's version of the Universal Template are the same.
/wiki/spaces/DEV/pages/1313202 /wiki/spaces/DEV/pages/1313206