Skip navigation
Share:|

As requested by a few people, here's an index of the articles. I'll update this as we go.

 

Writing BAO Adapters - Part 1: Calculus Not Required

Writing BAO Adapters - Part 2: Lock and Load

Writing BAO Adapters - Part 3: Acting Up (again)

Writing BAO Adapters - Part 4: Adding Flesh to the Actor's Bones

Writing BAO Adapters - Part 5: Big Brother is Monitoring You

Writing BAO Adapters - Part 6: Put Some Meat on Your Monitor – coming soon!

Writing BAO Adapters - Part 7: The Sincerest Form of Flattery – coming slightly less soon!

Writing BAO Adapters - Part 8: Sprinkle Some Awesome on Your Adapter – this one might be a while...

Writing BAO Adapters - Part 9: Improving the Development Process – it's going to be ages before this one is written.

Share:|

Writing BAO Adapters - Index

 

Introduction

In Part 4 we put together a working, though simplistic, actor adapter. Now we move on to the monitor adapter. This adapter type is effectively the reverse of the actor; where the actor allows us to effect change in other systems, the monitor allows change in other systems to affect us.

 

Special thanks to Ranganath Samudrala for the simple monitor adapter example he sent me some time back, which really helped my understanding of how monitor adapters are constructed.

 

Our example monitor adapter

As we did with the actor adapter before, we’ll first implement a very basic skeleton that loads on the grid and generates a simple event. It turns out this is slightly trickier with the monitor adapter since it must have an active execution loop running in order for the adapter to stay running on the grid.

 

In the next article the adapter will be extended to send events based on a real external change, but for now we'll simply send an event to the grid based on a timer; in other words, generate an event every x milliseconds.

 

Configuration Parameters

As with the actor adapters, each named monitor adapter on the grid has an XML configuration document that allows the user to set global parameters for that adapter. Unlike actors, however, there are no adapter requests in which these parameters can be overridden.

 

We’ll include a single configuration parameter in our basic adaper: sleep-delay. This will be the number of milliseconds between events and will default to 5000, which will fire off an event every five seconds.

 

WARNING: There is no checking for ‘sensible’ values here, so if you set this to a value of 1 millisecond your adapter is going to flood the grid with events. This is not likely to make your grid peers very happy...

 

The only required method for the monitor adapter configuration (in addition to the constructor) is validate(). This method is called when BAO wishes to check that the configuration is valid; if you find any configuration parameters that are not suitable, you throw an InvalidConfigurationException.

 

The Monitor Adapter Life-cycle

The life-cycle of the monitor adapter is very much the same as that of the actor adapter if you look at it in terms of its operational states (stopped, started, and so on). There is a significant implementation difference, though; where an actor adapter only executes when it is passed an adapter request, an enabled monitor adapter is running all the time. It may spend much of this time in an idle loop, or waiting for an inbound socket connection, or polling a file.

 

Here's an extended version of the adapter life-cycle diagram seen in an earlier article:

 

Adapter States - extended.png

 

The official development documentation for AO doesn't make clear the purpose of all of the adapter states, so a little bit of interpretation and invention is needed to establish how best to use them. The two that you must use are RUNNING and STOPPED. The others aren't critical but let's call it good practice to use them. I would propose:

 

INITIALIZING: Set this as early as possible in your initialize() method.

STARTING: Set this at the end of your initialize() method.

RUNNING: Set this at the start of your run() method.

STOPPING: Set this at the start of your shutdown() method.

STOPPED: Set this at the end of your shutdown() method.

FAULT: Generally it seems better to generate an appropriate exception to indicate a fault (e.g. InvalidConfigurationException), but there are certainly cases where we might wish to move the adapter to a fault state that aren't covered by the standard adapter exception. An example might be that the target system has gone offline.

PAUSED: Ignore this one for now; at the moment I can't see that pause functionality even exists as part of the adapter definition, so this is one to come back to in a future article .

 

Event De-duplication

The grid performs a very important function in relation to monitor adapters, and that is de-duplication of events. This is mainly useful when you have multiple peers running the same adapter for resilience, and those adapters are likely to generate the same event in response to some single external change. The grid peers generate a hash of each received event and store this in a queue. After a short time (2 minutes by default), each hash expires from the queue and is removed. Every incoming event is checked against this queue, and if its hash matches an existing entry then the event is discarded.

 

This is why our “dummy” event includes a counter, otherwise the majority of our events would be discarded as they’d be identical to the first one recorded on the queue. After two minutes, that first entry expires and another identical event would be allowed through.

 

So be aware of de-duplication and how it can work both for you and against you. If you are missing events, chances are you’re sending multiple identical events within two minutes of each other. If you are getting more events than expected, you may have introduced some unintended “uniqueness” to your events that is preventing them from being de-duplicated.

 

Gotta Have Rules

All events your adapter sends to the adapter manager are then pushed to the Activity Processor for rules processing. All rules present in active modules are evaluated and matching rules result in the execution of the associated workflow, with the event passed in the context item “inputevent”.

 

Note that it is entirely possible for multiple rules to match the event, and hence multiple workflows will fire based on that event.

 

Required Methods

There are a few mandatory methods for monitor adapters:

public void run()

public void shutdown()

 

The run() method is the “main loop” of the adapter that listens, polls, or performs whatever our monitoring action happens to be. If you need an adapter that monitors a tiger by poking it with a stick, this is the method in which to implement that.

 

As might be expected, shutdown() should contain any logic required to disconnect from external systems or otherwise clean up as the adapter is being stopped.

 

The Configuration Class

The basic configuration class is not so different from the one used with the actor adapter:

 

package com.example.bao.adapter.timedmonitor.Configuration;

import com.example.bao.adapter.timedmonitor.TimedMonitorAdapter;
import com.realops.common.configuration.InvalidConfigurationException;
import com.realops.foundation.adapterframework.configuration.AdapterConfigurationException;
import com.realops.foundation.adapterframework.configuration.BaseAdapterConfiguration;

import java.util.Hashtable;
import java.util.Set;

/**
* This is the configuration class for the Timed Monitor adapter (see {@link TimedMonitorAdapter}).
*
 * @author      Gordon Mckeown <gordon_mckeown@bmc.com>
* @version     1.0
*
*/
public class TimedMonitorAdapterConfig extends BaseAdapterConfiguration {
    private static final String CONFIG_SLEEP_DELAY = "sleep-delay";
    private static final long DEFAULT_SLEEP_DELAY = 5000;

    /**
     * Class constructor taking the ID of the adapter being instantiated.
     *
     * @param adapterId     String uniquely identifying this instance of the adapter
     */
    public TimedMonitorAdapterConfig(String adapterId) {
        super(adapterId);
        addRequiredKeys();
        addValidKeys();
    }

    /**
     * Class constructor taking the ID of the adapter and a hashtable of default options
     *
     * @param id            String uniquely identifying this instance of the adapter
     * @param defaults
     */
    public TimedMonitorAdapterConfig(String id, Hashtable defaults) {
        super(id, defaults);
        addRequiredKeys();
        addValidKeys();
    }

    /**
     * Class constructor taking the ID of the adapter, a hash-table of default options, and sets defining the valid and required keys.
     *
     * @param id            String uniquely identifying this instance of the adapter
     * @param defaults
     * @param validKeys
     * @param requiredKeys
     * @throws AdapterConfigurationException
     */
    public TimedMonitorAdapterConfig(String id, Hashtable defaults, Set validKeys, Set requiredKeys)
            throws AdapterConfigurationException {
        super(id, defaults, validKeys, requiredKeys);
        addRequiredKeys();
        addValidKeys();
    }

    /**
     * Method to advise the adapter manager which configuration entries are mandatory
     */
    private void addRequiredKeys() {
        // Do nothing
    }

    /**
     * Method to advise the adapter manager which configuration entries are permitted.
     */
    private void addValidKeys() {
        this.addValidKey(CONFIG_SLEEP_DELAY);
    }

    /**
     * Method to perform validation checks on the supplied adapter configuration.
     *
     * @throws InvalidConfigurationException
     */
    public void validate() throws InvalidConfigurationException {
        super.validate();
    }

    /**
     * Getter method for the configured delay between events
     * @return  the chosen sleep delay in milliseconds
     */
    public long getSleepDelay() {
        Long sleepDelay = super.getLongProperty(CONFIG_SLEEP_DELAY);
        if (sleepDelay == null || sleepDelay.longValue() == 0) {
            sleepDelay = DEFAULT_SLEEP_DELAY;
        }
        return sleepDelay;
    }
}






 

The Monitor Adapter Class

Here’s the bit you've been waiting for; our skeleton monitor adapter:

 

package com.example.bao.adapter.timedmonitor;

import com.example.bao.adapter.timedmonitor.Configuration.TimedMonitorAdapterConfig;
import com.realops.common.enumeration.StateEnum;
import com.realops.common.xml.XML;
import com.realops.foundation.adapterframework.AbstractMonitorAdapter;
import com.realops.foundation.adapterframework.AdapterException;
import com.realops.foundation.adapterframework.AdapterManager;

/**
* This class defines a minimal framework to get a monitor adapter running on an AO grid.
*
 * @author      Gordon Mckeown <gordon_mckeown@bmc.com>
* @version     1.0
*
*/
public class TimedMonitorAdapter extends AbstractMonitorAdapter {
    private Long sleepDelay;
    private TimedMonitorAdapterConfig adapterConfig;
    private int executionCounter = 0;

    /**
     * Default constructor
     */
    public TimedMonitorAdapter() {
        super();
    }

    /**
     * Initialisation method called when adapter is started by Adapter Manager
     *
     * @param adapterManager
     */
    @Override
    @SuppressWarnings("deprecation")
    public void initialize(AdapterManager adapterManager) {
        super.initialize(adapterManager);
        setState(StateEnum.INITIALIZING);
        adapterConfig = (TimedMonitorAdapterConfig) getConfiguration();
        sleepDelay = adapterConfig.getSleepDelay();
        setState(StateEnum.STARTING);
    }

    /**
     * Returns the class name of our configuration class.
     *
     * @return  Name of configuration class
     */
    @Override
    public String getAdapterConfigurationClassName() {
        return TimedMonitorAdapterConfig.class.getName();
    }

    /**
     * Shutdown method called when adapter is stopped by Adapter Manager
     *
     * @throws AdapterException
     */
    @Override
    @SuppressWarnings("deprecation")
    public void shutdown() throws AdapterException {
        setState(StateEnum.STOPPING);
        try {
            Thread.sleep(sleepDelay);   // Allow main loop time to finish
            setState(StateEnum.STOPPED);
        } catch (InterruptedException e) {
            setState(StateEnum.FAULT);
        }
    }

    /**
     * Main adapter logic loop that runs whilst adapter is in "running" state.
     */
    @Override
    @SuppressWarnings("deprecation")
    public void run() {
        setState(StateEnum.RUNNING);

        while(getState() == StateEnum.RUNNING) {
            executionCounter++;
            XML xmlEvent = new XML("dummy-event");
            xmlEvent.setText(getName());
            this.sendEvent("Dummy event, loop number: " + executionCounter, xmlEvent);
            try {
                Thread.sleep(sleepDelay);
            } catch (InterruptedException e) {
                setState(StateEnum.FAULT);
            }
        }
    }
}





 

A Test Class

Because of the way monitor adapters work – their ‘always running’ nature – our test class needs to be constructed slightly differently. Also, we need a way to receive the event being sent by the adapter, and this involves creating a “mock” adapter manager. Creating an adapter manager object requires an additional library from the tomcat/lib folder on your CDP: servlet-api.jar.

 

Without this library in your class path, you will see “class not found” errors relating to javax/servlet/filter when trying to test. I’ve simply added it to the library folder containing the other hundred-or-so BAO libs.

 

Our example test class isn't massively functional; it simply executes the monitor adapter in a thread for 20 seconds and then shuts it down. No particular checks are performed to ensure the correct operation of the adapter itself. All that said, here’s the test class:

 

package com.example.bao.adapter.timedmonitor;

import com.example.bao.adapter.timedmonitor.Configuration.TimedMonitorAdapterConfig;
import com.realops.common.configuration.InvalidConfigurationException;
import com.realops.common.enumeration.StateEnum;
import com.realops.common.util.ResourceLocator;
import com.realops.common.xml.InvalidXMLFormatException;
import com.realops.common.xml.XML;
import com.realops.foundation.adapterframework.AdapterManager;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
import static org.junit.Assert.*;

/**
* Test class for the Timed Monitor Adapter
*
 * @author  Gordon Mckeown <gordon_mckeown@bmc.com>
* @version 1.0
*/
public class TimedMonitorAdapterTest {
    private TimedMonitorAdapterConfig config;
    private TimedMonitorAdapter adapter;
    private AdapterManager am;

    private static final String CONFIG_FILE_ADAPTER = "adapter-config.xml";
    private static final String ADAPTER_ID = "TimedMonitorID";
    private static final String ADAPTER_NAME = "TimedMonitorAdapter";
    private static final String PEER_NAME = "DevStudio";

    @Before
    public void setUp() throws Exception {
        try {
            config = new TimedMonitorAdapterConfig("TestTimedMonitorAdapter");
            XML configXml = loadResourceXml(CONFIG_FILE_ADAPTER);
            config.fromXML(configXml);

            adapter = new TimedMonitorAdapter();
            adapter.setPeerName(PEER_NAME);
            adapter.createConfiguration(ADAPTER_ID);
            adapter.updateConfiguration(config);

            adapter.setName(ADAPTER_NAME);
            am = new MockAdapterManager();
            adapter.initialize(am);

        } catch (InvalidXMLFormatException e) {
            fail("Configuration file XML format error");
        } catch (InvalidConfigurationException e) {
            fail("Invalid entries in configuration file");
        }
    }

    @After
    public void tearDown() throws Exception {
        adapter = null;
    }

    @Test
    @SuppressWarnings("deprecation")
    public void testTimedMonitorAdapter() {
        Thread t = new Thread(adapter, "TestTimedMonitorAdapter");
        t.start();

        try {
            Thread.sleep(20000);
        } catch (Exception e) {
            fail("An exception occurred during the thread sleep.");
        }

        try {
            adapter.shutdown();
            t.join(6000);
        } catch (Exception e) {
            fail("An exception occurred during the thread join");
        } finally {
            assertTrue("Failed to stop adapter!", adapter.getState() == StateEnum.STOPPED);
        }
    }

    /**
     * Method that loads XML document from a resource file
     *
     * @param fileName  File from which to load XML document
     * @return          XML object representing the loaded file
     */
    private XML loadResourceXml(String fileName) {
        XML xmlData = null;
        try {
            InputStream stream = ResourceLocator.getSystemResourceAsInputStream(fileName);
            assertNotNull("Unable to load file resource " + fileName, stream);
            xmlData = XML.read(stream);
        } catch (Exception e) {
            e.printStackTrace();
            fail(e.getMessage());
        }
        return xmlData;
    }
}





 

And the MockAdapterManager class definition (which can live in the same directory as your test class):

 

package com.example.bao.adapter.timedmonitor;

import com.realops.foundation.adapterframework.AdapterEvent;
import com.realops.foundation.adapterframework.AdapterManager;

/**
* A mock adapter manager that simply outputs the event details as a string
*
 * @author      Gordon Mckeown <gordon_mckeown@bmc.com>
* @version     1.0
*
*/
public class MockAdapterManager extends AdapterManager {
    /**
     * Default constructor - performs no action
     */
    public MockAdapterManager() {
        // Do nothing
    }

    /**
     * Handle event sent from monitor adapter
     *
     * @param event AdapterEvent containing event information from adapter
     */

    @Override
    public void sendEvent(AdapterEvent event) {
        System.out.println(event.toXML().toCompactString());
    }


}






 

Finally, the very important adapter configuration XML file (adapter-config.xml) that must live in your test resources folder:

 

<config>
    <grid>
        <grid>
            <adapters>
                <adapter id="TimedMonitorID">
                    <name>TimedMonitorAdapter</name>
                    <class-name>com.example.bao.adapter.timedmonitor.TimedMonitorAdapter</class-name>
                    <config>
                        <sleep-delay>5000</sleep-delay>
                    </config>
                </adapter>
            </adapters>
        </grid>
    </grid>
</config>



 

Supporting Module

Although not essential at this stage of our adapter development, let’s put together a simple workflow to do something with the events being generated. This workflow will just dump the input event into the processes.log file; we could achieve pretty much the same thing by playing with the log levels on our peers (though that would log into grid.log – take a look at "Seeing the Events" below).

 

So create your workflow (I've called mine HandleTimedEvents), set an input parameter of “inputevent” and a single Assign activity that logs the content of inputevent like so:

 

2014-12-03 13_23_20-192.168.0.158 - Remote Desktop Connection.png

 

Ensuring you left the "Expose Process: In Rules" option ticked in your process properties, create a rule within the same module and set it up like this:

 

2014-12-03 13_24_43-192.168.0.158 - Remote Desktop Connection.png

 

Note that Core_Timed_Monitor is the name of my adapter on the grid, so change this to suit if you’re going to call yours something else.

 

Uploading to the Grid

This was detailed in Part 3, so I won’t go through it in full detail again. Just ensure you generate a JAR file, and use the following class name: com.example.bao.adapter.timedmonitor.TimedMonitorAdapter


Use a type of “timed monitor adapter” if your imagination fails you, and don’t forget to upload your supporting module and activate it!

 

Seeing the Events

Increase the log level of the Activity Manager on your peers to DEBUG and you will start to see entries like this one:

 

03 Dec 2014 13:06:44,258 DEBUG DefaultAttributeManager : Thread[AMP - Activity Processor - Process Execution - 3994,5,main] set attribute with name=inputevent, value=<adapter-event><source-adapter>Core_Timed_Monitor</source-adapter><event>Dummy event, loop number: 102</event><data><dummy-event>Core_Timed_Monitor</dummy-event></data></adapter-event>, isGlobal=false, for com.realops.foundation.activityprocessor.executionstate.DefaultAttributeManager@70e7bc6b







 

One quick XML pretty print later and we can see our event in all of its glory:

 

<adapter-event>
    <source-adapter>Core_Timed_Monitor</source-adapter>
    <event>Dummy event, loop number: 102</event>
    <data>
        <dummy-event>Core_Timed_Monitor</dummy-event>
    </data>
</adapter-event>







 

This should help you in defining the rules used to trigger workflows. If your rule is working, you’ll also be seeing the logging entries from your workflow being placed into the processes.log:

 

The process started. It is triggered by a rule.
03 Dec 2014 13:06:44,316 [Current Time=Wed Dec 03 13:06:44 GMT 2014] [Process Name=:BMC_Testing_GMK:SimpleMonitor:HandleTimedEvents] [Root Job Id=5b55545cf05bfc30:-4862ff7a:149a97323ba:-7fc41-1417613209310] [Job Id=5b55545cf05bfc30:-4862ff7a:149a97323ba:-7fc41-1417613209310]
!!!! Timed monitor adapter event received !!!!
[inputevent=
<adapter-event>
  <source-adapter>Core_Timed_Monitor</source-adapter>
  <event>Dummy event, loop number: 102</event>
  <data>
    <dummy-event>Core_Timed_Monitor</dummy-event>
  </data>
</adapter-event>]








What's Next?

At this point you should have this obnoxious little monitor adapter pumping events onto your grid, and a workflow that gets called for each event. In the next part we’ll look at extending the adapter to respond to “real” external events rather than an internal timer.

Share:|

Overview

We’re going to briefly change things up a little bit for this blog post and switch from Java to Jython. Normal Java service will be resumed shortly! I'm somewhat new to Jython myself, so this article shares some of the basics needed to get scripts to work. I plan to create a follow-up article to covered some more advanced topics once I've figured out how things fit together.

 

A capability of Atrium Orchestrator that has been sadly overlooked for many years is its ability to execute Jython scripts from within workflows. AO’s workflow activities and its use of XPath and XSLT allow a reasonable amount of flexibility, and I'm often impressed by what’s possible with some clever manipulation of XML.

 

Unfortunately this does mean that sometimes tasks that ought to be simple become an exercise in futility; I think anyone who has wanted a loop that repeats infinitely until a condition is met will relate to this!

 

One of the great strengths of AO is that you don’t need a programming background to create simple workflows, but there are going to be times when some good old-fashioned code is just the best way to get something done. You have a number of options if you go down this route:

 

  • Call an out-of-process script using the command-line adapter. This means just about anything you can run from the command-line is available to you. Running out-of-process has its disadvantages, though, as there is an overhead in memory and process setup time.
  • Call a Perl script using the Script adapter. This also uses an external out-of-process Perl instance, with the disadvantages as above.
  • Call a Jython script using the Script adapter. This executes in-process and so is very quick, but Jython is an interesting beast.

 

Prior to the 20.14.01 content release, Jython scripts were somewhat crippled by our inclusion of the out-of-date Jython 2.1 library (from 2001!). We've now moved to version 2.5.3, which is of 2012 vintage and the most recent stable release as at this writing.

 

Ignoring the somewhat out-of-date version of Jython used previously, why aren't more people using these scripts in their workflows? There are, of course, those people who just are not familiar with Python/Jython syntax, and I'm not intending to teach anyone the basics of the language here. I think for those who do know Jython, the issue has been one of documentation: the script adapter documentation is fairly high-level and code examples in the community are few and far between. So that’s what I aim to fix here with some practical examples of things you can achieve with Jython in AO.


Basic Setup

Firstly, let’s do some basic grid configuration. Initially we’re going to use the script activity, and this means you must have an instance of the ro-adapter-script adapter active on your grid and it MUST be called ScriptAdapter. This name is a restriction of the script activity itself; if we call the script adapter directly we can give the adapter a different name, which is essential if you want to use both Jython and Perl via this adapter.

 

The configuration of the script adapter is a little, well… let’s call it idiosyncratic. It asks for the path to a Perl executable, which of course is not relevant to Jython. You do, however, have to enter the path to a valid file here and it must include the string “jython”. I created an empty file called /opt/bmc/jythonDummyFile on my CDP and used this in the configuration. You DON’T need to point to a valid Python or Jython interpreter as this is provided with the script adapter. The adapter configuration will look something like this:

 

Script Adapter Configuration.png

 

So now let’s start with something simple to demonstrate passing variables into and out of a Jython script. We will convert a string to upper-case; even though there is a basic transform in AO that can do this already, it’s a useful exercise.

 

We need an assign activity to set up the initial string, a script activity to run the Jython code, and we’ll have another assign activity that allows us to do some logging and other post-execution trickery. It looks like this:



The Set Variables activity is as follows:

 

Setting Context Items.png

 

It is very important to note the quotes around the string. Imagine this string will be placed directly into a statement in this way:


var1 = This is a simple string

 

This will generate an error in Jython. So by quoting the string, the actual assignment is:

 

var1 = "This is a simple string"

 

Of course, when passing numeric values, these don’t need quotes.


The Script activity is setup thusly:

 

Script Activity Settings.png

 

And last, but by no means least, we have the awesomeness that is our Jython script. We’re using an embedded script here, so click the “View/Edit Script” button and paste this in, ensuring that the scripting language drop-down is set to Jython:


var2 = var1.upper()


Yes, that’s it. You can see that we take the context item inputString and assign it to the Jython variable var1. The script performs an upper() operation on this string, assigning the result to var2. And upon completing execution, AO will stick var2 into our outputString context item which we can dump out using the logging tab and see:

 

 

[outputString=THIS IS A SIMPLE STRING]

 

 

Numerics and Multiple Values

This time we’ll make things a little more complex. Set three context items:

 

valueA = 7
valueB = 3
myString = "Multiplying the supplied values gives: "







The Jython script is this:

 

 

returnValue = var3 + str(var1 * var2)

 

 

And you configure the Script activity in the following way:


Second Script config.png

 

Running this will get you the output:

 

 

[outputString=Multiplying the supplied values gives: 21]

 

 

Hopefully this is enough for you to understand the basic method used for passing variables into and out of a Jython script. So far we have only covered simple string and numeric values; how do we pass out an array or list, for example?

 

Stick in this script code, leaving everything else the same as it was:

 

returnValue = [ "Tomato", "Orange", "Lychee" ]
returnValue.append("Watermelon")
returnValue.append("Kumquat")







Any idea what we’ll get in our outputString context item?

 

[outputString=['Tomato', 'Orange', 'Lychee', 'Watermelon', 'Kumquat']]

 

Unfortunately not the most useful output in AO terms as we need to tokenize it. Far more useful would be an XML document that we could process; however, so far I've been unable to get AO to recognize a returned XML document as XML.

 

Importing Modules

Well here’s the really bad news. At the moment, the Script Adapter doesn't include the standard libraries that are available for Jython, so standard Python imports don't work. You can import standard Java classes, so we could use the Java Random class to generate a pseudo-random password:

 

from java.util import Random

charList = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!#."

rgen = Random()
generatedPassword = ""

for _ in range(int(passLength)):
    num = rgen.nextInt(len(charList))
    generatedPassword = generatedPassword + charList[num:num+1]







I'm hoping at this stage you can set up your input and output context items without too much assistance. If you need a hint, you need to pass a number to "passLength" as the input, and "generatedPassword" is your output variable.


Wouldn't it be nice if you could use the standard Python libraries from your Jython scripts? Well, you can. Unofficially and in a non-supported fashion for the moment. Grab the Jython 2.5.3 installer and run it (you'll need a JRE installed). Select the Standalone install and this will generate a jython.jar file. Upload this to the adapter folder containing the script adapter on each peer for which it's enabled (under server/grids/<gridname>/library/adapters/implementations/ro-adapter-script_20.14.01.00_1 most likely). Rename the existing jython-2.5.3.jar to jython-2.5.3.old.jar and rename the new file from jython.jar to jython-2.5.3.jar. Restart the script adapter.


Magically, scripts like this one should now work:


import random

charList = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!#."
generatedPassword = ""

for _ in range(int(passLength)):
    num = random.randint(0, len(charList))
    generatedPassword = generatedPassword + charList[num:num+1]





Did I mention that doing this is currently totally unsupported by BMC? If you'd like to see this implemented in the product, please go vote for the idea on this subject!


Debugging Your Jython Script

Debugging a script by running it in Dev Studio is something you only want to do if you’re a masochist and really enjoy digging for syntax errors in the grid.log. You are far better off writing some code against a Jython interpreter and simulating the inputs you expect from AO. Once you’ve squashed your bugs, then paste it into the script window (or link to it as a file if you prefer). At the moment, using this method means you can't use any Python imports, hence either using Java libs or sticking to some pretty basic scripting.


If you have a convenient Linux machine, your distro should have a Jython package that you can install with "apt-get install jython" or "yum install jython" depending on your Debian or Redhat leanings. Crazy Gentoo users can figure it out for themselves . On Windows, you should be able to download the installer from the Jython.org downloads page.

 

This will allow you to test your scripts from the command-line first and move them to AO when you have them functionally complete.


If you choose to upload the standalone Jython jar that includes the standard libs, you can also do your testing using Python (just bear in mind you can't import Java libs in Python!).

Share:|

Writing BAO Adapters - Index

 

Introduction

If you've been following the series so far, you'll know that in Part 3 we created a skeleton actor adapter. This contained the core components required to register it on the grid, and little else. It didn't provide any actual actions we could trigger from our workflows, which is what we'll remedy this time around.


Adapter Configuration

When the adapter is activated on the grid, we can specify some parameters for global or generic options; this saves us entering or changing them on a per-adapter-call basis. You might run all SSH services on a single non-standard port, so it makes sense for that to be set at the adapter level. We will offer the following adapter configuration items:


  • Whether to check the target is alive by performing an ICMP ping first (defaults to “true”)
  • The port to use for SSH scans (defaults to “22”)
  • The port to use for SMB scans (defaults to “445”)


These are translated into an adapter configuration XML document, which gives us:

 

<config>
  <check-alive>true</check-alive>
  <ssh-port>22</ssh-port>
  <smb-port>445</smb-port>
</config>










It is possible to include the target hostname in here as well; however, for this use case I'm going to suggest that it doesn't make sense. For those who think it should be included, consider this an exercise for the reader


Defining Behaviour

Actor adapters are provided two key pieces of information when called: an action and a request.


The action is a (typically short) text string defining some relevant high level action type; for example, in a file-based adapter this might indicate “read” or “write”. We're free to use this for whatever we choose, and the AO adapter manager will simply pass this string over for us to interpret as we see fit.


The request is an XML document containing well-formed elements and values in a structure that you can determine. The document root element can be any valid element name and in this case we’ll go with “host-adapter-request”.


Let's assume that our adapter will build up over time into a suite of networking tools, so our choice of host identification is just one action among many; I know the previous choice of adapter name doesn't suggest this, but we’ll just throw logic out of the window for now. As such, we can either use the action parameter to dictate this choice – similar to the File adapter's choices of read, write, append, and delete – or we could structure our request XML so it includes an element which does this instead, as the VMware Infrastructure adapter does with its operation type element.


In this case we're going with an action and will call it “Identify Host”. If you don't supply this action when calling our adapter, it's going to refuse to do anything at all and will return an error.The adapter request for our adapter can contain four pieces of information:


  • The hostname or IP address to scan (mandatory)

  • Whether to check the target is alive by performing an ICMP ping first (optional, will over-ride the adapter configuration value)

  • The port to use for the SSH scan (optional, will over-ride the adapter configuration value)

  • The port to use for the SMB scan (optional, will over-ride the adapter configuration value)


A complete request might be structured like this (though note that when passing it to the adapter call in your workflow you must wrap it in “request-data” tags):


<host-adapter-request>
  <target>192.168.1.1</target>
  <check-alive>true</check-alive>
  <ssh-port>22</ssh-port>
  <smb-port>445</smb-port>
</host-adapter-request>










What's Our Status?

In the final paper-based step we need to decide what the output from the adapter call is going to look like. Typically an actor adapter will report back status information in two sections: one for the adapter (the metadata) and one for the action the adapter was asked to perform.


Keep in mind that whilst our adapter might complete successfully, meaning it performed the requested action, the action itself might be considered a failure; for example, if the target host doesn't respond. We should distinguish between these two types of error condition.


So we will return a status code that indicates what happened when trying to scan the remote machine, and I'm going to go with a multi-part approach. At the simplest level we will return a “status” element containing either of:


  • success: We were able to determine the remote host type
  • error: We weren't able to determine the remote host type

 

If we provide the error status we will include two additional fields: “error” containing a short error code and “description” providing some more detailed error information. These will be as follows:


  • DNSFAIL – We weren't able to resolve the hostname in DNS, so the host probably doesn't exist.
  • PINGFAIL – The ICMP ping didn't receive a response; the host may be offline, configured such that it doesn't respond to pings, or behind a router that filters out certain ICMP packets.
  • NETFAIL - An unknown network error has occurred.
  • UNKNOWNTYPE – We were not able to identify the host, suggesting that neither the SSH port nor the SMB port were open.


The ultimate goal is to identify whether the target machine is Windows or UNIX-like, so we will also return a host-type of WINDOWS or UNIX.


Based on the above, an adapter response might look like this:

 

<host-adapter-response>
  <metadata>
    <status>success</status>
  </metadata>
  <host-type>WINDOWS</host-type>
</host-adapter-response>









 

And an error response like this:

 

<host-adapter-response>
  <metadata>
    <status>error</status>
    <error>PINGFAIL</error>
    <description> An ICMP ping request to the target host did not receive a response</description>
  </metadata>
</host-adapter-response>









 

Expanding the Configuration Class

And so we get to the good stuff! Using the configuration definition above, the configuration class can be updated to define the permitted configuration parameters.


The code is getting a bit long to be fully included inline, so we’ll just look at the pertinent changes from the skeleton version. The full source is attached in the file HostIdentifierActorConfig.java.


The configuration parameter names, default values, and some error strings are defined first. For simplicity we’re not using proper internationalisation of any of the strings; they are just hard-coded in English. Internationalisation will be covered in a later article.


A call to initValidKeys() is added to the class constructors so that the adapter manager knows which configuration parameters are understood. For example:

 

// Constructor - when passed the adapter ID as a string
public HostIdentifierActorConfig(String adapterId) {
  super(adapterId);
  this.initValidKeys();
  System.out.println("Adapter config created with adapter ID: " + adapterId);
}









 

This is a new method that is defined later in the source file:

 

private void initValidKeys() {
  super.addValidKey(CONFIG_ALIVE);
  super.addValidKey(CONFIG_SSHPORT);
  super.addValidKey(CONFIG_SMBPORT);
}









 

Note that we don’t specify any required keys -- once a key has been defined as valid, you can then make it mandatory by calling, for example, super.addRequiredKey(CONFIG_ALIVE). If such required parameters are not specified in the adapter configuration, the adapter will not start.


Next we build up the validate() method so that it checks for ports in the valid TCP range 1-65535 (technically 0 is a valid port, but it’s reserved for special purposes). The actual check is farmed out to the helper method isValidPortString(), so the entries in validate() are like so:

 

// Validate SSH port is in the range 1-65535
if (!isValidPortString(super.getProperty(CONFIG_SSHPORT))) {
  throw new InvalidConfigurationException(new Message(INVALID_SSH_PORT, INVALID_SSH_PORT_DETAIL));
}










If the helper reports that the port number isn’t valid, we throw InvalidConfigurationException to advise the Adapter Manager that we’re not happy with the supplied config. If this happens, the adapter will not start.


Finally, for the configuration class at least, we add some getter/setter methods to allow retrieval of the configured or default parameter values as appropriate. These follow the form:

 

public int getSmbPort() {
  if (super.getProperty(CONFIG_SMBPORT) == null) {
    return DEFAULT_SMBPORT;
  }
  return super.getIntegerProperty(CONFIG_SMBPORT);
}









 

Each has a simple check whether a value has been set in the adapter configuration. If so, it is returned to the caller. If not, the default value is returned instead.


Fleshing Out the Actor

Now that the configuration class has been beefed up, it’s time to add some muscle to the actor adapter class itself. Again there are too many changes to include everything inline, so refer to the attached file HostIdentifierActor.java. The majority of our changes will be to the performAction() method and we will be adding code to:


  • Check the action that we’ve been passed.
  • Retrieve parameters from defaults, adapter configuration, or adapter request as appropriate.
  • Perform a ping check against the target host if check-alive is true.
  • Attempt a connection on the SSH port.
  • Attempt a connection on the SMB port.
  • Interpret the results from the above and build a suitable adapter response to return to the caller.


Retrieving the action is simple as the AdapterRequest class provides us with the getAction() method. At this point it might be helpful to take a look at the adapter request as defined in Dev Studio:


2014-07-11 15_23_19-Windows 7 Basic client [Running] - Oracle VM VirtualBox.png

And then a complete adapter request as it is passed to our adapter:

 

<adapter-request>
  <target-adapter>HostIdentifierAdapter</target-adapter>
  <peer-location>
    <location>this</location>
  </peer-location>
  <request-action>Identify Host</request-action>
  <request-data>
    <host-adapter-request>
      <target>192.168.66.11</target>
      <check-alive>true</check-alive>
    </host-adapter-request>
  </request-data>
</adapter-request>









 

This is what we get if we simply dump the whole request to XML and then display it as a string using adapterRequest.toXML().toPrettyString(). Since we can grab the request-action directly as described above, what we really want to play with is just the adapter request as passed in to the adapter call from workflow. With adapterRequest.getData() we can do this, and from adapterRequest.getData().toPrettyString() we get:

 

<host-adapter-request>
  <target>192.168.66.11</target>
  <check-alive>true</check-alive>
</host-adapter-request>









 

This is a bit easier to work with, since all of our parameters are now children of the root element. Therefore we can grab them easily using getChild() rather than having to navigate the XML tree or use XPath. This is how it’s done:


XML requestData = adapterRequest.getData();
String targetHost = requestData.getChild("target").getText();










To prevent Null Pointer Exceptions, we actually wrap up the getChild() method calls with checks to determine if the elements exist, and if not we grab the values from the adapter configuration.


The ping and port checks are using basic Java network functions, so I won’t cover them in detail here.

Some logic is applied to the results from the network checks to decide whether we were successful or not, and from that we build our adapter response XML. Lastly we create the AdapterResponse object which is passed back to the caller. These steps are performed using this code:

 

XML responseXml = this.buildResponseXml(status, errorCode, errorDescription, hostType);
AdapterResponse adapterResponse = new AdapterResponse(System.currentTimeMillis() - adapterStartTime, null, responseXml, Status.SUCCESS);
return adapterResponse;









 

Here’s a real example of the adapter output as seen in Dev Studio:


2014-07-11 15_20_47-Windows 7 Basic client [Running] - Oracle VM VirtualBox.png


It is worth noting at this point that the code has fairly minimal error checking and throws some adapter exception failures where we might want to be a bit more helpful by failing less dramatically. I’ve mentioned before that I don’t wish to hold this code up as a shining example of well-written Java, so please take it as a functional example.


Test, Test, and Test Some More

The JUnit test class has been updated to be a bit more thorough in checking the output from the configuration and adapter classes. This is attached as HostIdentifierActorTest.java. This test class still isn't particularly extensive, but it does perform some basic checks that might help prevent silly errors making it to your adapter on the grid (failing to pass an adapter response, for example).


Two resource files are included to support the test cases: config.xml and adapter-request.xml.


And Now For Something Completely Different

Here we are with a functional actor adapter that can be used to interrogate the OS type of remote machines (in a very simplistic way, granted). Hopefully this provides enough code for you to start working on some wonderful actor adapters of your own! In the next part of the series we’ll move on to monitor adapters which are a slightly different beast...


Share:|

Writing BAO Adapters - Index

 

Introduction

With apologies for the premature release of this article previously, here is the complete version

 

In part 2 of this series, we took a look at the basic configuration of a development environment. In this post, we'll create a skeleton actor adapter -- this will include the adapter class, its configuration class, and a test class. It's going to be necessarily code-heavy this time around (and in most of the subsequent posts in this series).

 

Our Example Actor Adapter

For this series, we're going to create a fairly simply actor adapter that attempts to identify a remote host as Windows or UNIX based on its open ports. This is not going to be anything near as complex as nmap, we'll just look at a couple of ports and assume that:

  • If the SSH port (default TCP 22) is open, the host is UNIX.
  • If the SMB port (default TCP 445) is open, the host is Windows (provided the SSH port isn't also open).

 

We'll allow the user to specify the ports in the adapter configuration. For the purposes of this article, we're only going to create a basic shell -- the minimum parts needed to have our adapter run on the grid.

 

Configuration Parameters

Each named adapter on the grid has an XML configuration document that allows the user to set global parameters for that adapter. You can also allow the user to over-ride this in individual adapter requests.

 

The only required method for the actor adapter configuration (in addition to the constructor) is validate(). This method is called when BAO wishes to check that the configuration is valid; if you find any configuration parameters that are not suitable, you throw an InvalidConfigurationException.

 

 

The Actor Adapter Life-cycle

It's important to understand the basic life-cycle of an actor adapter before we put together the supporting code.

Adapter States.png

When an actor adapter is loaded, it begins its life in the STOPPED state. The Adapter Manager calls the adapter's initalize() method, after which the adapter is marked as RUNNING. At this point it is expected to respond to calls to its performAction() method.

 

If the Adapter Manager wishes to stop an adapter -- as a result of a user request, or during peer shutdown for example -- it will mark the adapter as STOPPING and call the adapter's shutdown() method. Note that the adapter's shutdown() method is expected to set the final adapter state of STOPPED once it has finished any clean-up activities (this is not done automatically by the Adapter Manager).

 

Required Methods

There are only a small number of methods required to create the actor adapter, as touched on in the previous section:

 

public string getAdapterConfigurationClassName()

public void initalize()

public AdapterResponse performAction(AdapterRequest aRequest)

public void shutdown()

 

The getAdapterConfigurationClassName() method must return the class-name of the adapter's configuration class -- this is how BAO knows which configuration class is associated with the adapter class.

 

In the initialize() method, you can set up any persistent objects, create connections to external systems, and perform any other activities needed to prepare the adapter. This method can be empty (bar a call to the same method in the super-class).

 

The performAction() method is called each time your workflows make a call to this adapter. This is where the primary capabilities of the adapter are included.

 

Finally, the shutdown() method allows you to perform clean-up activities when the adapter has been asked to stop.

 

The Configuration Class

So with the above said, here is our template "empty" configuration class:

 

package com.example.bao.adapter.hostid.configuration;


import com.realops.common.configuration.InvalidConfigurationException;
import com.realops.foundation.adapterframework.configuration.BaseAdapterConfiguration;


/**
* Configuration class for the Host Identifier Adapter
*/
public class HostIdentifierActorConfig extends BaseAdapterConfiguration{


    // Constructor - when passed the adapter ID as a string
    public HostIdentifierActorConfig(String adapterId) {
        super(adapterId);
        System.out.println("Adapter config created with adapter ID: " + adapterId);
    }


    // Constructor - when passed a BaseAdapterConfiguration object
    public HostIdentifierActorConfig(BaseAdapterConfiguration newConfiguration) {
        super(newConfiguration.getAdapterId());
        setAdapterName(newConfiguration.getAdapterName());
        System.out.println("Adapter config created with config object.");
    }


    @Override
    public void validate() throws InvalidConfigurationException {
        super.validate();
        System.out.println("Configuration validation requested.");
    }
}
    

 

The Actor Adapter Class

Here we have our template actor adapter class:

 

package com.example.bao.adapter.hostid;


import com.example.bao.adapter.hostid.configuration.HostIdentifierActorConfig;
import com.realops.common.enumeration.StateEnum;
import com.realops.foundation.adapterframework.AbstractActorAdapter;
import com.realops.foundation.adapterframework.AdapterException;
import com.realops.foundation.adapterframework.AdapterRequest;
import com.realops.foundation.adapterframework.AdapterResponse;


/**
* Host Identifier Actor Adapter
*/
public class HostIdentifierActor extends AbstractActorAdapter {


    @Override
    public void initialize() throws AdapterException {
        super.initialize();
        System.out.println("Adapter initialisation complete.");
    }


    @Override
    public AdapterResponse performAction(AdapterRequest adapterRequest) throws AdapterException, InterruptedException {
        System.out.println("Adapter was requested to perform an action.");
        return null;
    }


    @Override
    public void shutdown() throws AdapterException {
        System.out.println("Adapter shutdown request received.");
        super.setState(StateEnum.STOPPED);
    }


    @Override
    public String getAdapterConfigurationClassName() {
        return HostIdentifierActorConfig.class.getName();
    }
}
    

 

 

Test Class

And lastly we have a test class, which so far I have only mentioned in passing. Because BAO adapters are a type of plug-in, you can't execute them directly in your Java IDE. Instead, you must wrap them in a basic test class that simulates some of the behaviours of the Atrium Orchestrator grid. Primarily this means creating an instance of the adapter, creating and passing a configuration object, calling the initialize() method, doing some test performAction() calls, and finally sending the shutdown() message. In other words, we're simulating the life-cycle the adapter would go through on the grid, which allows much more effective debugging than uploading the adapter classes to the grid in order to test them.

 

There are a few ways of doing this. One is to create instances of the actual BAO class loader, adapter manager and other functions. This provides the most similar environment to a real grid, but has the disadvantages of being more complex and requiring more code. So, for simplicity, we're going to just create an instance of our adapter class using standard Java functions.

 

For your own sanity, you should probably place any test classes in a separate module to the adapter classes -- in this way you can easily exclude them from the JAR file creation described below. Just ensure that you specify the dependency of the test module on the main adapter module so that you can import the adapter classes. At this stage we'll just create one simple test class to run through the adapter life-cycle.

 

I'm using JUnit to run the tests. You will need to ensure your IDE is configured to support the JUnit framework -- that is outside of the scope of this document, but Google is your friend! You don't have to use JUnit, of course; it's possible to run a set of tests with just a regular class that kicks off from a main() method. The choice is yours.

 

Important note: Up until now, we've only required three or four of the libraries from an Atrium Orchestrator grid so that we can compile the adapter. In order to perform unit testing in the IDE, you're going to need more. You could add just those that are required, which at this stage would be:

  • jaxen-1.1.2.jar
  • jdom-1.0.jar
  • log4j-1.2.15.jar
  • utilities-7.6.03.jar

 

However, a better long-term approach is to copy all of the libraries from your peer's lib folder into a local folder, then add that folder to your IDE. In IntelliJ IDEA these are conveniently shown as a single entry in the external libraries list:

 

IntelliJ IDEA BAO Libraries.png

 

Within that BAO7.6.03 library folder are the 180+ libraries copied from my CDP. This means that, whatever functions I use in my adapter, I should be able to perform unit tests for them. On to the test code:

 

package com.example.bao.adapter.hostidTest;

import static junit.framework.Assert.*;
import com.example.bao.adapter.hostid.HostIdentifierActor;
import com.realops.common.xml.XML;
import com.realops.foundation.adapterframework.AdapterRequest;
import com.realops.foundation.adapterframework.AdapterResponse;
import org.apache.log4j.xml.DOMConfigurator;

/**
* Junit Tests for Host Identifier Actor
*/
public class HostIdentifierActorTest {

    private static HostIdentifierActor adapter = null;
    private static final String adapterName = "HostIdentifierAdapter";
    private static final String adapterId = "HostIdentifierID";

    public HostIdentifierActorTest() {
        DOMConfigurator.configure("log4j.xml");
    }

    @org.junit.Before
    public void setUp() throws Exception {
        try {
            // Create an instance of the Host Identifier Actor Adapter
            adapter = new HostIdentifierActor();
            adapter.createConfiguration(adapterId);
            adapter.setName(adapterName);
            adapter.initialize();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @org.junit.After
    public void tearDown() throws Exception {
        adapter.shutdown();
        adapter = null;
    }

    @org.junit.Test
    public void testPerformAction() throws Exception {
        XML dummyRequest = new XML("dummy");
        AdapterResponse response = null;
        AdapterRequest request;
        try {
            request = new AdapterRequest(adapterName, null, dummyRequest);
            response = adapter.performAction(request);
            assertNull("Expected a null response from the adapter", response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
    

 

 

Uploading to the Grid

So at this point we have a couple of .class files containing our compiled configuration and adapter classes. They're not doing us much good sat on our local disks, so we need to upload them to a BAO grid so that they can perform their null operations on a real grid. At this stage in the tutorial, we're going to perform this upload in an ugly, slow, error-prone way; later on we'll look at building AROAR files to streamline the deployment process, but for now this will get us up and running.

 

You need to output the classes as a .jar file. In IntelliJ IDEA you can have this generated automatically by configuring a JAR artifact under File -> Project Structure -> Artifacts. Add a jar "from modules with dependencies" and set the output layout to only include the compile output (you don't need to include the BAO libraries). Also, create a manifest file in the default location using the "Create Manifest..." button. The resulting JAR can then be found under the out/artifacts folder.

 

In Eclipse, right-click on your project and choose Export -> Java -> JAR File. Choose the actor adapter and configuration classes, and a suitable output name. You can choose to "save the description of this JAR in the workspace" so that future exports are done by right-clicking the resulting .jardesc file and choosing "Create JAR".

 

Now that we have our classes safely contained in a JAR, we can open up Grid Manager and navigate to Manage / Adapters. Click that oh-so-tempting "Upload Adapter" button and fill out the form:

 

upload_adapter_the_hard_way3.png

Click OK and your adapter should upload. In the left-hand side of the screen you will see "example host identifier 0.0.1 rev 1". Click this tick-box next to this and add it to the grid with an empty configuration like so:

 

add_adapter_to_grid.png

(Note the use of "Core_" as a prefix is just a convention of mine -- when working with segmented and firewalled networks, it is often helpful to tag adapters based on whether they are going to run as part of the core infrastructure or a 'remote' network.)

 

Now you just need to enable the adapter on one or more peers and that's it:

 

Running Adapter.png

What's Next?

Our adapter is now running on the grid and will respond to stop and start commands. Of course, at this stage the adapter doesn't actually do anything useful, but that will change in the next episode!

 

Addendum

If you require a log4j.xml file, the following is a basic example:

 

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender" >
        <layout class="org.apache.log4j.SimpleLayout"/>
    </appender>
    <category name="com.realops" >
      <priority value="all" />
    </category>
    <category name="com.bmc.adapter" >
      <priority value="all" />
    </category>
    <root>
        <priority  value="debug" />
        <!-- <appender-ref  ref="RollingFile" />-->
        <appender-ref  ref="STDOUT" /> 
    </root>
</log4j:configuration>

Share:|

Writing BAO Adapters - Index

 

Introduction

The previous article in this series gave a high-level view of Atrium Orchestrator adapters. In this instalment we look at preparing your development environment.

 

Setting Up Your AO Adapter Development Environment

It should be possible to set up a suitable development environment with any Java IDE, or even using command-line tools. BMC does not recommend any specific toolset; however, I'm going to broadly describe the setup for IntelliJ IDEA 13 and Eclipse 4.3 (I will assume you are familiar with your chosen IDE, so will only describe key configuration points).

 

After this initial setup, I'm going to focus on the use of IntelliJ IDEA since that's my preferred development environment. After the initial setup, very little of what I describe is IDE-specific.

 

These articles are written for use with BMC Atrium Orchestrator 7.6.03. The principles also apply to older and newer versions of BAO, though you may need to adjust the JDK version appropriately (see below).

 

One final note: both Eclipse and IntelliJ IDEA are available on Windows, Linux, and Mac. I'm going to be using Windows to put my examples together, so keep this in mind if using another platform; I will confirm that everything works on Linux as well, but won't be able to check on the Mac I'm afraid.

 

Overview

For the impatient, we will perform the following steps:

  1. Install an appropriate JDK.
  2. Install your IDE or development toolchain.
  3. Import the Atrium Orchestrator Libraries

 

Pre-requisites

A suitable JDK must be installed. The versions recommended for (or supplied with) various BAO versions are:

 

AO VersionJava Version
7.5.021.5.0_21
7.6.021.6.0_21
7.6.031.6.0_45
7.7.001.7.0_7


Note that any JDK that matches the major version used by BAO should be fine, so for 7.6.02 and 7.6.03, the latest JDK 1.6 is likely your best bet. I recommend 64-bit Java for both development and the BAO platform itself.


I'm going to install the latest Java SE 6 Development Kit at the time of writing: the 64-bit JDK 1.6.0_45 (also known as JDK 6u45).

 

The Atrium Orchestrator SDK

Documentation is available for the BMC Atrium Orchestrator APIs. Currently this must be requested from BMC Support.

 

Atrium Orchestrator Libraries

In order to make use of the Atrium Orchestrator classes you will need to import a few libraries into your development environment. These libraries can be found in an installed copy of the product under one of the following locations:

CDP: $INSTALL_PATH/tomcat/webapps/baocdp/WEB-INF/lib/lib/shared folder

AP: $INSTALL_PATH/tomcat/webapps/baoap/WEB-INF/lib/lib/shared folder

 

The following three files are required:

  • foundation-7.6.xx.xx.jar
  • common-7.6.xx.xx.jar
  • i18n-7.6.xx.xx.jar

 

If you wish to use the Integration Mapping Wizard, you will also need:

  • adapter-protocol-7.6.xx.xx.jar

 

When working with AO 7.5 or 7.7, adjust the versions in the file-names accordingly.

 

Copy the needed library files to a local folder location on your development machine (best to ensure they're the only files in this folder).

 

IntelliJ IDEA 13

The IntelliJ IDEA Community Edition can be downloaded free from JetBrains' website.

 

Once installed, and you have created a Java project (make sure you select the chosen JDK version during project creation), add the BAO libraries to your project by opening "Project Structure" from the File menu. Then choose Libraries, click the green plus symbol, select Java, and finally choose the three or four library files from the location you used above (or just select the folder containing them). When you click OK, the libraries should appear under the External Libraries section of the project tool window.

 

Eclipse 4.3

The Eclipse IDE for Java Developers is available free from the Eclipse Project website. It should also be possible to use other Eclipse packages so long as they include the Java development tools.

 

Before creating your project, ensure that Eclipse is configured to use the 1.6 JRE (Preferences -> Java -> Installed JREs). If you don't have the JRE6 entry, add it now using the "Add..." button and select the folder to which you installed it (by default: c:\Program Files\Java\jre6). Then, in the Execution Environments sub-section, ensure that JavaSE-1.6 uses the JRE6 entry by default.

 

When creating a project for your adapter, choose the appropriate JRE (for example, JavaSE-1.6 for BAO 7.6.x). During project creation, on the Libraries tab choose "Add External JARs" and select the three or four library files from the location you used above. These will appear under the "Referenced Libraries" branch of your project in the Package Explorer.

 

What's Next?

At this point you should have a bare-bones project ready to go. In the next instalment, we'll create the basic skeleton of an actor adapter, together with its configuration class and a test class.

Share:|

Writing BAO Adapters - Index

 

What's This All About?

It's all about integration, but you won't need to remember any calculus for this (probably).

 

This is the first article in a short series aimed at getting you started with BMC Atrium Orchestrator adapter development. Through these posts I'll demonstrate the creation of both monitor and actor adapters, the use of adapter configuration parameters, test classes, and other aspects of adapter development. Ultimately this will give you the foundation needed to integrate with systems not currently supported by the standard Atrium Orchestrator content, or perform bizarre custom actions involving chickens. Your choice.

 

I don't aim to teach you Java or how to work with any specific IDE (short of some basic initial setup instructions). I will also add a caveat that any code given will be simply functional rather than a showcase of coding style or Java knowledge.

 

Your feedback is very welcome; please use the comments section below!

 

An Overview of BAO Adapters

BMC Atrium Orchestrator provides a resilient platform from which a multitude of automation use cases can be addressed. Using its out-of-the-box monitor and actor adapters, it is possible to communicate with many external systems using both open standards – including web services, FTP, and POP3 – and application-specific interfaces for products such as BMC Remedy and Microsoft Exchange.

 

Why Build an Adapter?

With this wide range of connectivity options, it is possible to integrate with most systems that offer an external API or command-line tool-set. But what if you want to connect to a system that uses a proprietary API? Even if such systems offer command-line tools, they aren't always efficient to use; the overhead of creation and destruction of shell processes can slow down your workflow, and if you are running a high-throughput automation use case these overheads add up pretty quickly.

 

I can offer an example of a customer who built a set of workflows in version 3 of the product (back when it was Run Book Automation) and were calling out to Perl in order to do time-stamp formatting and conversion. This was done two or three times per workflow execution, and there were thousands of workflow executions per hour. Switching to the in-built "basic transformation" time-stamp functions – added somewhere around v7.5 I think – increased their throughput significantly and reduced the memory footprint by a sizeable amount at the same time. Though not a direct example of a switch to a specialised adapter, this illustrates the potential gains from running your functions in-process and in Java.

 

So there are some good reasons for looking to perform certain integrations directly in Java using Atrium Orchestrator's adapter framework, especially where API limitations or performance requirements are a central factor.

 

Adapter Framework

Both types of adapter are hosted by the Adapter Framework. The Adapter Framework package holds the classes that provide services to support the basic capabilities of adapters; for example, defining adapters, reading adapter configuration, sending events, interpreting requests, and building responses.

 

Adapter Configuration

When you create an adapter on your grid, you specify an optional set of configuration parameters. A configuration class is provided from which you can create your own configuration-handling class. Your configuration class defines which parameters are available, which are mandatory, what validation rules you wish to apply, and default values to use where appropriate.

 

Monitor Adapters

The primary purpose of a monitor adapter is to react to an external stimulus by generating an event on the AO grid. The external stimulus could be an active push by another system, the result of polling, or the firing of a timer. The resulting event is then pushed through the rules processor, and one or more workflows can be triggered (technically, zero or more workflows can be triggered but the zero case is generally not so useful).

 

In reality, a monitor adapter doesn't need to generate events in response to truly external factors; you could quite easily create a monitor adapter that simply fires an event every 30 seconds.

 

So the typical trigger for a monitor adapter is one of:

  • A timer
  • Polling
  • An inbound network message (either on an existing connection, or directed to a listening port)

 

Actor Adapters

Actor adapters offer a structured interface through which your workflows can trigger actions externally. Your workflow builds up the input required as an XML document, and then this is passed to the named adapter to be processed. In some cases you might push in a significant amount of data and receive a simple success or failure message back, for example inserting into a database using the SQL adapter. In other cases you will provide a small amount of up-front data and receive a large chunk back, such as when reading in a file using the File adapter.

 

Similar to the monitor adapter, there is no hard requirement for an actor adapter to actually perform its actions against an external system. We could create an actor adapter that takes two numbers as its input and returns the sum in its response. If we were feeling really adventurous, we could use the action type to specify whether to sum, multiply, subtract, or divide the numbers.

 

What Adapters Cannot Do

Adapters are intended as the link between your workflow and the outside world, so they are provided a set of services to support this goal. There are no documented or supported methods to:

  • Access the Repository
  • Interface with the Access Manager or Atrium SSO
  • Modify the configuration of the grid
  • Retrieve performance or status information for the grid
  • Teach your pet hamster to fetch a ball

 

Note that in some of the above cases there might be SOAP,  REST, or other interfaces that will allow actions to be performed. I'm referring to the classes and methods provided in the Atrium Orchestrator SDK – for the most part your adapter is unaware of the grid environment in which it runs.

 

Supportability

Something to keep in mind is that a badly-written adapter could affect the stability of your BAO peers and grid. When reporting issues to BMC Support, it is strongly recommended that you re-produce your issue with custom adapers disabled, or at least notify Support that you are running such adapters if that is not possible.

 

What's Next?

In the next article, I'll provide a short introduction to the set-up of your development environment.

Filter Blog

By date:
By tag: