Universal Extension API

Introduction

Universal Extensions are developed by adhering to a simple API.  This API is provided by the Universal Extension base package. The concise API documentation can be found here: /wiki/spaces/DEV/pages/1310964.

Universal Extension Base Package

The Universal Extension Base Package (universal_extension) is a Python package provided by Stonebranch that contains a collection of classes and functionality required to develop Universal Extensions.  This package is distributed with the Universal Agent.  It is automatically installed with all installation types for all platforms that support Universal Extensions.  The package resides within a zip archive (universal_extension.zip) and is installed to the following locations:

WindowsC:\Program Files\Universal\UAGSrv\uext\universal_extension.zip
Unix/opt/universal/uagsrv/uext/universal_extension.zip

Extension Class

An Extension implementation must provide a Python class named Extension that derives from the Universal Extension base class UniversalExtension and resides in a file named extension.py

At a minimum, the Extension class must:

  1. Reside in a file called extension.py.

  2. Derive from base class UniversalExtension.

  3. Provide a constructor method that, in turn, calls the initializer for the base class.

  4. Implement method extension_start (which is an override of a method defined in the UniversalExtension base class).

  5. Return an instance of class ExtensionResult from method extension_start

 The following code snippet contains the minimum requirements stated above.  It has everything needed to execute as a Universal Extension.  It does not do anything, but it could execute without error.

Minimum Extension implementation requirement (extension.py)
"""Minimum Universal Extension implementation."""
from universal_extension import UniversalExtension
from universal_extension import ExtensionResult
 
class Extension(UniversalExtension):
    """Universal Extension sample module."""
 
    def __init__(self):
        """Init class."""
        super(Extension, self).__init__()
 
    def extension_start(self, fields):
        """Universal Extension primary operation."""
 
        return ExtensionResult()

ExtensionResult Class

The ExtensionResult class is the required return type from all Universal Extension work requests.  It can be seen in the code snippet above returning from extension_start.  Each type of work request supports a different set of parameters that are used to return information from that request type back to the Controller.  The parameters are all optional, and appropriate defaults are set.  However, it would usually not make sense to return without setting some parameters (as in the code snippet above).  The use of ExtensionResult will be discussed further in the context of the specific work request types documented below.

Work Requests

Universal Extensions support three types of work requests from associated Universal Extension tasks in the Controller: Task Execution, Choice Commands, and Dynamic Commands.  These work requests are described below. 

Task Execution (extension_start)

Task Execution is the primary operation of a Universal Extension task.   This is the work request that results from launching a task.  All Extensions must implement extension_start. This is accomplished by adding a method named extension_start with the following signature to the Extension class in file extension.py.

 def extension_start(self, fields):

The extension_start method is the starting point for work that will be performed for a task instance.  The fields parameter passes in a dictionary populated with field values from the associated task instance that was launched in the Controller.  The keys of the dictionary correlate with field names of the task instance that is invoking the extension (defined in the associated Universal Template) and the key values are the values from the associated form fields when the task is launched. The Extension developer can perform any work that is required here: executing binaries on the system, connecting to other systems, etc.

Additional functionality required by the extension can be defined in other classes, files, and or packages.  extension_start is just required as the entry point.

Once the work is done, the extension_start method must return an instance of ExtensionResult ExtensionResult is a class provided in the universal_extension base package for the purpose of returning from work requests.

The following parameters can be passed to the constructor of ExtensionResult for extension_start:

Parameter

Type

Default

Description

rc

int, optional

0

This parameter represents the return code of the task instance that initiated the extension_start operation.  The value returned is implementation-defined and therefore left up to the Extension developer.  The value can be used by the “return code processing” of the task instance in the Controller to determine if the Extension task instance is perceived as completing with Success or Failed

message

str,  optional

None

This parameter specifies a short status string (error message or success message) to be sent to the “Status Description” field on the task instance form.

output_fields

dict, optional

None

Dictionary containing output fields. The parameter  is a dictionary of output fields to be sent back to the controller for the associated Extension instance.  Field names are implementation dependent and correlate with the Universal Template field names in the Controller's Template definition.

unv_output

str, optional

None

This parameter specifies the data that will be returned to the Controller for the command execution. It is interpreted as a UTF-8 encoded text object.

If the output parameter is True, the output_data will be persisted in the Controller as a record under table ops_exec_output, and appearing as Universal Command output type from the task instance Output tab.


The following is an example of a simple extension_start implementation returning a payload of “Hello world!”.

extension_start
    def extension_start(self, fields):
        """Universal Extension primary operation."""
        return ExtensionResult(unv_output = "Hello World!")


Upon execution, the extension_start implemented above would result in the following output in the associated task instance in the Controller:

Choice Commands

Choice Commands support the task definition process in the Controller.  They allow a drop-down "Choice Field" on a task definition form to call down to a Universal Extension on an agent system and retrieve data to populate the drop-down.  This can be used to satisfy any number of scenarios but, typical use cases would be to pull values from a third-party system that may be needed to define the work the task is intended to perform (for example, job names, file names, modes of operation, etc.).

  • Choice Commands are optional and are not required in an Extension implementation. 

  • An Extension may define any number of Choice commands.

Choice commands are defined by adding a method to the Extension class with an appropriate signature and decorating it with the @dynamic_choice_command decorator defined in the universal_extension base package.  Below is an example of a decorated function with the required signature:

Choice Command in extension.py
    @dynamic_choice_command("choice_field_1")
    def choice_command_1(self, fields):
        """Dynamic choice command."""
        return ExtensionResult(
            rc = 0,
            message = "Values for choice field 'choice_field_1'",
            values = ["Value 1", "Value 2", "Value 3"]
            )

In the code snippet above, “choice_field_1” corresponds to the field Name of a field of type choice with “Dynamic Choice” checked in an associated Universal Template in the Controller.  The function name choice_command_1 is arbitrary and could just as well have been my_choice_command_function.  The linkage between a Universal Template choice field and the backing Choice Command handler is made by matching the choice field’s “Name” with the value in the @dynamic_command decorator.  In this case “choice_field_1”.

Once the work is done, the Choice Command method must return an instance of ExtensionResult ExtensionResult is a class provided in the universal_extension base package for the purpose of returning from work requests.

The following parameters can be passed to the constructor of ExtensionResult for returning from a Choice Command:

Parameter

Type

Default

Description

rc

int, optional

0

This parameter represents the return code of the ‘choice’ operation and determines whether the command is perceived as completing with success or failure by the Controller.

A value of 0 indicates success. All other values indicate an error condition and will be ignored by the Controller for form field population. However, the Controller will log the command response. The non-zero value used to represent the error condition is implementation defined and therefore left up to the extension developer.

message

str, optional

Empty string

This parameter specifies a short status string (error message or success message) that will be logged by the Controller. 

values

list, optional

Empty list

This parameter specifies a list which can be populated with a set of string values to be returned to the Controller and used to populate the associated dynamic choice field on an Extension task form.

The default value is an empty list.


The following is an example of a simple Choice Command implementation returning a list of hard coded values that would be used by the Controller to populate an associated Choice field drop-down:

Dynamic Choice Command in extension.py
    @dynamic_choice_command("choice_field_1")
    def choice_command_1(self, fields):
        """Dynamic choice command."""
        return ExtensionResult(
            rc = 0,
            message = "Values for choice field 'choice_field_1'",
            values = ["Value 1", "Value 2", "Value 3"]
            )

Execution, the Choice Command implemented above would result an associated Choice field drop-down in the Controller being populated:

Dynamic Commands

Dynamic Commands are intended to support the functionality of the Primary Operation of the Universal Extension (task execution). 

They can be made available to a task instance in any/all of the following task states:

  • Defined

  • Waiting

  • Time Wait

  • Held

  • Exclusive Requested

  • Exclusive Wait

  • Resource Requested

  • Resource Wait

  • Execution Wait

  • Undeliverable

  • Queued

  • Submitted

  • Action Required

  • Started

  • Running

  • Running/Problems

  • Cancel Pending

  • In Doubt

  • Start Failure

  • Confirmation Required

  • Cancelled

  • Failed

  • Skipped

  • Finished

  • Success

Dynamic Commands can be used to return additional information for a task instance or to carry out some related (or unrelated) action on behalf of the task instance.  The key to Dynamic commands is that they can pass instance specific field data values from the task instance form to the extension process that executes the command on the agent.  This allows them to perform instance specific actions without the user needing to copy/paste values from the task instance into a new task to perform some action.

An example use case for this would be to implement a cancel command for work that was started in a third-party system.  This would be independent of the task instance running (or completed) in the Controller.  Instance specific values (job ID, process ID, etc. could automatically be pulled from the task instance and passed to the Command handler in the Universal Extension on the agent system).

Dynamic Commands are optional and are not required in an Extension implementation.  An Extension may define any number of Dynamic Commands.

The following parameters can be passed to the constructor of ExtensionResult for returning from a Dynamic Command:

Parameter

Type

Default

Description

rc

int, optional

0

This parameter represents the return code of the Dynamic Command operation and determines whether the command is perceived as completing with success or failure by the Controller.

A value of 0 indicates success. All other values indicate an error condition and the command result will not be added to the Controller's Output tab. However, the Controller will log the command response. The non-zero value used to represent the error condition is implementation defined and therefore left up to the extension developer.

message

str, optnial

Empty string

The message parameter specifies a short status string (error message or success message) that will be logged by the Controller. 

output

bool, optional

False

This parameter is a Boolean value that specifies if the command produced output that should be persisted and displayed under the task instance Output tab in the Controller task instance associated with the dynamic command invocation.

The default value is False.

This flag allows distinguishing between a command that does not produce output and a command that produces output but the output returned was empty.

output_data

str, optional

None

This parameter specifies the data that will be returned to the Controller for the command execution.  It is interpreted as a UTF-8 encoded text object.

If the output attribute is True, the output_data will be persisted in the Controller as a record under table ops_exec_output, and appearing as Universal Command output type from the task instance Output tab.

The default value is None.

output_name

str, optional

None

This parameter is used to provide a custom name for the associated output data.  The output_name (if provided) will be used in the presentation of the output_data by the Controller.


The following is an example of a simple Dynamic Command implementation returning a string “Sample output”.

Dynamic Command in extension.py
    @dynamic_command("command_1")
    def dynamic_command_1(self, fields):
        """Dynamic command."""
        return ExtensionResult(
            rc = 0,
            message = "command_1 output",
            output = True,
            output_data = 'Sample output',
            output_name = 'DYNAMIC_OUTPUT')


In the Controller, the Dynamic Command could be invoked from a task instance of the appropriate Universal Extension type.


Selecting the Dynamic Command for the task instance allows the adjustment of any dependent fields.


If the Dynamic Command produces output, a popup window appears to show the result of the Command.


Additionally, the results of Dynamic Commands are persisted with the task instance and can be viewed on the Output tab of the task instance.

Publishing Events

With the 7.2.0.0 release, the ability to publish events was added to the Universal Extension API to extend the Controller's monitoring capabilities. This new addition has a multitude of use cases. For instance, Extensions can now be used to implement a custom Database Monitoring solution; Extensions publish events containing data from a database which Universal Monitors can use to detect a desired event. 

To use this functionality, a Universal Event must be declared globally or locally. Shown below is the definition of a local Universal Event that is part of the sample-1 Universal Template:


The Universal Event is then tied to a Universal Monitor task as shown below:

The sample-monitor-task above will end up in the "Success" status once it receives an event from the Extension attached to the sample-1 Universal template with the "second" attribute set to 28.

On the Extension side, events are published using the publish method implemented in the event module accessible through the universal_extension module. The method signature is as follows: 

def publish(name, attributes, time_to_live=None):

The name parameter specifies the target Event name, attributes is a dictionary specifying the Event's attributes as key-value pairs, and time_to_live specifies the maximum time the published event lives before it expires.

The code snippet below continuously publishes an event with the attributes containing the current second extracted from the time:

Publishing an event
while self.run:
    # get current seconds from the time
    second = datetime.now().second

    # Publish the event
    event.publish(
        'sample_event',
        {'second': second}
    )

	# Wait a second before continuing
	time.sleep(1)

Once the sample-monitor-task is launched, the sample-1-task will automatically be launched as well. The sample-1-task will publish an event every second. Once the condition specified in the sample-monitor-task is met, both tasks will end up in the "Success" state.

Output Only Fields (update_extension_status/update_output_fields)

Universal Extensions provide support for an “Output Only” type field.  This is a field defined in a Universal Template of type Extension that is given a restriction of Output Only.


Output only fields can be updated at any time by a Universal Extension instance running a Task Execution request (extension_start) on an agent system.  This provides a means for the task instance to send back relevant state information.  The Output Only fields can be updated by the task instance as many times as needed.

The updating of Output Only fields is accomplished by calling the update_extension_status method implemented in the UniversalExtension base class or by calling update_output_fields from the ui module accessible through the universal_extension module. The update_output_fields method is more versatile as it can be called in another file/module that is part of the full Extension. Functionally, however, the methods are exactly the same.

Both methods have the following signature (only update_output_fields is shown from now on):

def update_output_fields(fields):

The fields parameter expects a dictionary populated with any/all Output Only fields that should be updated.  The keys of the dictionary directly correlate with the Output Only field names specified in the associated Universal Template.  The values of the dictionary are the values that will be used to update the corresponding Output Only field for the task instance in the Controller.

The following code snippet demonstrates two calls to update the same output only field (“output_1”). To simulate work being done in-between the updates, a delay is created using the sleep function from the time module.

Updating Output Only fields
sleep_value = fields.get('sleep_value', 5)

time.sleep(sleep_value)

# Update output fields.
out_fields = {}
out_fields["output_2"] = "Step One"
ui.update_output_fields(out_fields)

# Do some processing...
time.sleep(sleep_value)

# Update output fields.
out_fields = {}
out_fields["output_2"] = "Step Two"
ui.update_output_fields(out_fields)

The animation below shows the task instance form being updated (note the refresh icon at the top right of the task instance form was clicked continuously for the real-time updates):


< Previous     Next >