Publishing Events

Introduction

As part of 7.2.0.0, Universal Extensions can now be used to extend the Controller's monitoring capabilities through Universal Events and Universal Monitors/Universal Monitor Triggers.

Specifically, the Universal Extension API has been enhanced to supported publishing events using the publish method from the event module. 

Throughout this document, the event publishing functionality will be demonstrated using a contrived file monitor example. 

On this page, we will cover the following:

  1. Set up the new UE Publisher template.
  2. Add a directory field to the UE Publisher template.
  3. Enhance the UE Publisher's local event template.
  4. Modify the ue-publisher Extension.
  5. Create a task for the UE Publisher template.
  6. Create a Universal Monitor task and trigger.
  7. Run the Universal Monitor Trigger.
  8. Update the local template.json.

Step 1 - Set up the new "UE Publisher" template

Up until now, we have been working with the "UE Task" Universal Template. For this tutorial, we will need to use the "UE Publisher" template that was added to the VS Code Extension and UIP-CLI.

Create a new folder ~/dev/extensions/sample-publisher where the new template will be stored.

Navigate to the activity bar on the left hand size and click the Stonebranch logo.

Go ahead and click the icon shown below to initialize the ue-publisher template:

Setting template parameter values for selected starter template

In the following prompts that are used to configure the template, everything can be kept as is. Note the

"UE Publisher" template has an additional option called "Local Universal Event name" shown below.


Open the ~/dev/extensions/sample-publisher/extension.py if currently not open. Let's examine the file:

extension.py
from __future__ import (print_function)
from time import sleep
from universal_extension import UniversalExtension
from universal_extension import ExtensionResult
from universal_extension import event


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__()

        # Flag to control the event loop below
        self.run = True

    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
        """

        # Get the value of the 'sleep_value' field
        sleep_value = fields.get('sleep_value', 3)

        # loop that publishes events continuously as long as self.run is True
        while self.run:
            # Publish an event
            event.publish(
                'publisher_event',
                {}
            )

            # sleep before publishing next event
            sleep(sleep_value)

        # Return the result with a payload marking the end of extension_start()
        return ExtensionResult(
            unv_output='extension_start() finished'
        )

    def extension_cancel(self):
        """Optional method that allows the Extension to do any cleanup work
        before finishing
        """
        # Set self.run to False which will end the event loop above
        self.run = False

Line 2Imports the sleep method from the time module which will be used to add a delay between successive events. 
Line 5Imports the event module which contains the publish method used later on.
Line 19A flag called self.run with initial value of True is added to control the event loop.
Line 40Extract the sleep_value field from the task instance passed down by the Controller.
Line 43While loop that runs as long as the self.run field value is True
Line 45-48Used to publish an empty event, as shown by the empty attributes dictionary. 
Line 51Used to add a tiny delay before the next event is published
Line 58-63Upon receiving the Cancel command from the Controller, the self.run flag is set to False


Now, let's push the extension out to the Controller. Since this is the first time, use the UIP: Push All command:

If successful, you should see the "UE Publisher" template in the "Universal Templates" list:

Step 2 - Add a directory field to the "UE Publisher" template

Navigate to the Fields tab of the UE Publisher template, and add a new field as shown below:

This required field will be used to specify the directory to get the file list of. 

Save the template.

Step 3 - Enhance the "UE Publisher's" local event template

Navigate to the Event Templates tab of the UE Publisher template:

Double click the publisher_event entry, and modify it as shown below:

A new attribute of type Text called filelist is added which will contain the list of files (formatted as a string) in the directory specified by the directory field added in the last step.

Save the Event Template. 

Step 4 - Modify the ue-publisher extension

Now, we need to enhance ~/dev/extensions/sample-publisher/extension.py to publish the filelist event. Update the file as shown below:

extension.py
from __future__ import (print_function)
from time import sleep
import os
from universal_extension import UniversalExtension
from universal_extension import ExtensionResult
from universal_extension import event


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__()

        # Flag to control the event loop below
        self.run = True

    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
        """

        # Get the value of the 'sleep_value' field
        sleep_value = fields.get('sleep_value', 3)

        # Get the value of the 'directory' field
        directory = fields.get('directory', '')

        # Verify the directory exists
        if not directory:
            return ExtensionResult(
                rc=-1,
                unv_output="specified directory is empty"
            )
        elif not os.path.exists(directory):
            return ExtensionResult(
                rc=-1,
                unv_output="specified directory does not exist"
            )

        prev_filelist = set()
        # loop that publishes events continuously as long as self.run is True
        while self.run:
            curr_filelist = set(os.listdir(directory))

            # Subtracting 'prev_filelist' from 'curr_filelist' will ensure the
            # 'filelist' only contains the newly detected files.
            filelist = curr_filelist - prev_filelist
            filelist = ','.join(filelist)

            prev_filelist = curr_filelist.copy()

            if filelist:
                print(filelist)

            # Publish the event
            event.publish(
                'publisher_event',
                {"filelist": filelist}
            )

            # sleep before publishing next event
            sleep(sleep_value)

        # Return the result with a payload marking the end of extension_start()
        return ExtensionResult(
            unv_output='extension_start() finished'
        )

    def extension_cancel(self):
        """Optional method that allows the Extension to do any cleanup work
        before finishing
        """
        # Set self.run to False which will end the event loop above
        self.run = False

Line 3Imports the os module used to list files in an directory.
Line 44Extract the directory field from the task instance passed down by the Controller.
Line 47-56Verify the directory exists
Line 58-80Get the current directory listing and remove the files from the previous iteration, only leaving new files discovered in the current iteration. Then, publish the filtered file listing as a comma-separated string to the publisher_event event.

Save the changes to extension.py and execute the UIP: Push command as shown below:

Recall that the UIP: Push command builds and uploads the Extension zip archive

Step 5 - Create a task for the "UE Publisher" template

Right-click on the navigation tree pane, and click Refresh as shown below:

Then under the Services menu, click UE Publisher Tasks under the Integrations section:

Create a new task called sample-publisher-task:

  • For the directory field, type in a valid path on the system where the selected agent is running

Save the task.

Step 6 - Create a Universal Monitor task and trigger

Now that we have the event publishing logic added, we need to attach the event itself to a Universal Monitor task.

Under the Services menu, click Universal Monitors under the Monitors section as shown below:

Create a new Universal Monitor task as shown below:

  • Make sure the Event Type is set to Local since the ue_publisher Event Template is (locally) attached to the UE Publisher Universal Template 
  • Make sure the Universal Template is set to UE Publisher
  • Make sure the Event Template is set to Publisher Event (this is the user-friendly name of ue_publisher Event Template)
  • Make sure the Universal Task Publisher is set to sample-publisher-task
  • Make sure there is an entry for Filelist as shown above. As you may have guessed, this Universal Monitor task will check if Filelist contains test.txt

Save the task.

We can either run the sample-monitor-task as a standalone task which finishes upon detecting the target file (test.txt), or we can attach the task to a Universal Monitor trigger. To cover the complete functionality, we will attach it to a Universal Monitor Trigger.

Under the Services menu, click Universal Monitor Triggers under the Triggers section. Create a new trigger as shown below:

The trigger above will launch the Sleep 0 task when the sample-monitor-task's specified criteria is matched.

Save the trigger.

Step 7 - Run the Universal Monitor Trigger

We are finally ready to see the event functionality in action. Navigate to the sample-monitor-trigger, right-click on it, and select Enable:

Clicking Enable will launch the sample-monitor-task and sample-publisher-task. To see this, go to the Instances tab of the sample-monitor-trigger. You should see the following:

The sample-publisher-task is sending an event every 5 seconds with the filelisting. Since we haven't created the test.txt file in the specified directory, the Sleep 0 task isn't triggered yet.

In your specified directory, create the test.txt file. After about 5 seconds, click the Refresh icon in the Instances tab of the sample-monitor-trigger, and you should see the following:

If you delete the test.txt file and create it again, the Sleep 0 Timer task should be launched again by the trigger.

Step 8 - Update the local template.json

Throughout the tutorial, we modified the Universal Template several times. Recall that inside ~/dev/extensions/sample-publisher/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. To grab those changes, use the pull command as shown below:

UIP VS Code Extension

 Click here to expand uip-cli details...

Step Supplemental - CLI


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