Extend the IBM WebSphere AS plugin

You can extend the IBM WebSphere Application Server (WAS) plugin through XL Deploy’s plugin API type system and using custom, user-defined Python scripts.

The WAS plugin associates Create, Modify, Destroy and Inspect operations received from XL Deploy with WAS Python scripts that need to be executed for the specific operation to be performed. The operation-specific script is given a Python object representation of the Deployed that triggered the operation. The script is then executed using wsadmin.

An advanced method to extend the plugin exists, but the implementation of this form of extension needs to be written in the Java programming language and consists of writing so-called Deployed contributors, PlanPreProcessors and Contributors.

Extending using XML and Python scripts

The easiest way to extend the WAS plugin is by using XML and Python scripts. This method does not require you to write Java code. You can extend the behavior of the plugin by simply defining the necessary deployables and deployeds for the specific environment.

When XL Deploy starts up, it reads a file called synthetic.xml from the server class-path (the ext directory of the server). The synthetic.xml file contains the type definitions of the deployables and the deployeds, as well as which Python scripts should be executed for a particular operation (Create, Modify, Destroy and Inspect). The scripts have all of the information from the Deployed at their disposal to perform their work.

For example, here is the type definition of a virtual host as it appears in synthetic.xml:

    <type type="was.VirtualHost" extends="was.Resource" deployable-type="was.VirtualHostSpec" container-type="was.Cell">
        <generate-deployable type="was.VirtualHostSpec" extends="was.Deployable" />
        <property name="createScript" default="was/virtualhost/create-virtual-host.py" hidden="true" />
        <property name="destroyScript" default="was/virtualhost/destroy-virtual-host.py" hidden="true" />
        <property name="inspectScript" default="was/virtualhost/inspect-virtual-host.py" hidden="true" />
        <property name="aliases" kind="set_of_string" description="Virtual host aliases - enter alias as: hostname:port" />
    </type>

Reviewing this type definition more closely, it indicates that a virtual host, the type of which is was.VirtualHost will be created on its target infrastructure, called container, with a container-type of was.Cell.

The extends attribute tells the plugin what resource is extended by this definition. In this case, it is a simple basic resource so it extends the type was.Resource. Since multiple virtual hosts may be created this way (each with its own set of properties) a specification of what will be deployed is created, a so-called deployable-type, that is itself of type was.VirtualHostSpec.

Within the type definition, you can specify properties of exactly how the virtual host is to be created. The first property is called createScript and specifies the script to be executed by wsadmin for the creation of the virtual host. An extension of this plugin could specify a different creation script. The plugin comes with a default creation script (createScript):

create-virtual-host.py

    import re

    pattern = re.compile('^[^:]+:\d{,5}')
    virtualHostParent = AdminConfig.getid('/Cell:%s/' % (deployed.container.cellName))
    attributes = [['name', deployed.name]]
    attributes.append(['aliases', [[['hostname', alias.split(':')[0]], ['port', alias.split(':')[1]]]
                        for alias in deployed.aliases if pattern.match(alias) != None]])
    re.purge()

    print "Creating virtual host %s on target scope %s with attribute(s) %s" % (deployed.name, virtualHostParent, attributes)
    AdminConfig.create('VirtualHost', virtualHostParent, attributes)

This script shows that aliases are also created for the specified virtual host. You can specify aliases using the property aliases using a set of strings. For example:

    <property name='aliases' kind='set_of_string' value='www.my-domain.com:80,www.proxy-domain.com:8443'/>

When the script executes a predefined variable, deployed has a reference to the deployed that is being deployed.

The script executed on the host is created by appending a number of library scripts and adding the script from the type last. By default you will get the following runtime scripts added:

  • From the Python plugin: python/runtime/base.py
  • From the WAS plugin: was/runtime/base.py
  • From the deployed itself, scripts defined in the libraryScripts hidden property

In addition to a creation script, a destruction script (destroyScript) must also be specified:

destroy-virtual-host.py

    virtualHostContainmentPath = '/Cell:%s/VirtualHost:%s' % (deployed.container.cellName, deployed.name)
    virtualHostId = validateNotEmpty(AdminConfig.getid(virtualHostContainmentPath),
    "Cannot find virtual host with id: %s" % (virtualHostContainmentPath))

    print "Destroying virtual host %s" % (deployed.name)
    AdminConfig.remove(virtualHostId)

For the destroy operation, a predefined deployed variable is available with the deployed being destroyed.

You can also specify a modification script (modifyScript). If that script is not present, the destruction script is invoked to remove the resource with the old settings and then the creation script is invoked to create the resource with the new settings.

The modify script also has access to the deployed variable.

Inspection

The WAS plugin includes a property called inspectScript. When the Domain manager of WAS is known, it is possible for XL Deploy to automatically discover most of the WAS topology. This specific script is called upon by XL Deploy when it is attempting, in our example, to discover a virtual host on a WAS cell. This is a simplified script implementation shipped with the WAS plugin:

inspect-virtual-host.py

    virtualhosts = AdminConfig.list('VirtualHost')
    if virtualhosts != "":
        for virtualhost in virtualhosts.splitlines():
            if virtualhost.find('/nodes/') == -1:
                virtualHostName = AdminConfig.showAttribute(virtualhost, 'name')
                virtualHostId = '%s/%s' % (container.id, virtualHostName)
                discovered(virtualHostId, 'was.VirtualHost')
                virtualHostContainmentPath = '/Cell:%s/VirtualHost:%s' % (container.cellName, virtualHostName)
                virtualHostAliases = AdminConfig.getid(virtualHostContainmentPath + '/HostAlias:/').split()
                inspectedProperty(virtualHostId, 'aliases', [
                    AdminConfig.showAttribute(a, 'hostname') + ':' + AdminConfig.showAttribute(a, 'port')
                    for a in virtualHostAliases if a != ""])
                inspectedItem(virtualHostId)

An inspection script generally performs the following steps:

  • Collects information from WebSphere.
  • Indicates to XL Deploy that an object with a certain configuration item ID and a certain type was detected. This is achieved by calling discovered with the configuration item ID that the object has to get inside XL Deploy and the type.
  • Sets the properties on the object. This is done by repeatedly calling inspectedProperty. This property takes three parameters: the ID of the configuration item being discovered, the name of the property, and the value. The value should be representable as either a string, or a list of strings.
  • Indicates to XL Deploy that the complete object has been processed by calling inspectedItem with the configuration item ID.

The discovered, inspectedProperty and inspectedItem functions are defined in the Python plugin’s python/runtime/base.py. This file also contains some additional helper functions.

Discovery starts at the Cell level. If you need to inspect other levels of the hierarchy you need to traverse the children of the Cell yourself. For convenience, a function findAllContainers is defined in the runtime script runtime/base.py.

When the discovery script executes, two predefined variables are available:

  • prototype contains a ‘prototype’ deployed which can be used to get the type of the CI being discovered
  • container contains the Cell

Adding a property

The architecture of the WAS plugin enables the transfer of properties from the deployed (was.VirtualHost in this example) to the accompanying Python scripts defined with the properties createScript and destroyScript. In order for this to be possible, the properties are bound to an object called deployed and can be accessed as deployed.<property-name>. This can be seen in the creation script, where the property aliases is available as deployed.aliases and the name of the virtual host as deployed.name. Using this convention, as many properties as needed by the scripts can be bound to the type definition. Ensure that the scripts use the same type as specified in the definition. For example, if a property defines a kind=integer, the script should also treat the value of this property as being of type integer.

An example of adding another property is:

    <property name='index-range' kind='integer' value='999' description='maximum index of an array of 1000 items'/>

Fixing a property

When you need a specific property to always contain a fixed predefined value, you should use the attribute hidden=true on the definition of the property. This way, an end user performing the deployment using the XL Deploy user interface will not be able to see this property and therefore not be able to modify it. This is typically done for the Python scripts which, once written, are not allowed to be changed by an end user.

Using the example of the previous section with the goal of fixing the maximum range, the example would be:

    <property name='index-range' kind='integer' value='999' hidden='true' description='maximum index of an array of 1000 items'/>

Excluding a property

If you do not want certain properties of a deployable to be exposed to Python scripts, you can use the hidden property additionalPropertiesNotToExpose to exclude them. For example:

      <type-modification type="was.WmqQueue">
            <property name="additionalPropertiesNotToExpose" default="jmsProvider,wasType,customProperties" hidden="true" />
      </type-modification>

Execution order

A deployment process consists of a series of steps that are executed sequentially. Plugins offer the ability to influence the order of execution of the steps contributed to the deployment process in relation to other contributed steps and operations, not necessarily contributed by the same plugin(s), that are part of the deployment process.

The order of execution allows for the chaining of scripts or operations to create a logical sequence of events. In order to specify the order, you can use the properties createOrder and destroyOrder with an attribute called default to specify the order ordinal.

For example, the following synthetic.xml snippet states that creation of the virtual host, default=60, will happen before any step with a higher order, for example default=70, but after any step with a lower order, i.e. lower than default=60.

    <type type="was.VirtualHost" extends="was.Resource" deployable-type="was.VirtualHostSpec" container-type="was.Cell">
        <generate-deployable type="was.VirtualHostSpec" extends="was.Deployable" />

        <property name="createScript" default="was/virtualhost/create-virtual-host.py" hidden="true" />
        <property name="createVerb" default="Deploy" hidden="true" />
        <property name="createOrder" kind="integer" default="60" hidden="true" />

        <property name="destroyScript" default="was/virtualhost/destroy-virtual-host.py" hidden="true" />
        <property name="destroyVerb" default="Undeploy" hidden="true" />
        <property name="destroyOrder" kind="integer" default="30" hidden="true" />

        <property name="inspectScript" default="was/virtualhost/inspect-virtual-host.py" hidden="true" />
        <property name="aliases" kind="set_of_string" description="Virtual host aliases - enter alias as: hostname:port" />
    </type>

Note that the destroyOrder has a low order because when executing a deployment, the virtual host should be destroyed before it can be created again.

Extending the plugin with custom control task

You can add control tasks to was.ExtensibleDeployed or python.PythonManagedContainer. You can specify the control task as a Python script that will be executed using wsadmin on the target host or as an OS shell script that will be run on the target host. The OS shell script is first processed with FreeMarker before being executed.

Creating a Python script control task to test datasources

synthetic.xml snippet:

    <type-modification type="was.Datasource">
      <method name="testDatasource" script="was/resources/ds/test-ds.py" language="python"/>
    </type>

test-ds.py snippet:

    datasource = AdminConfig.getid("%s/JDBCProvider:%s/DataSource:%s/" %
     (deployed.container.containmentPath, deployed.jdbcProvider, deployed.name))
    if datasource == '':
      print "WARN: No JDBC DataSource '%s' found. Nothing to do" % (deployed.name)
    else:
      print "Testing JDBC DataSource '%s' (config ID '%s')" % (deployed.name, datasource)
      AdminControl.testConnection(datasource)

Creating an OS script control task to start the DeploymentManager

synthetic.xml snippet:

    <type-modification type="was.DeploymentManager">
      <method name="start" script="was/container/start-dm" language="os"/>
    </type-modification>

start-dm.sh snippet for Unix:

    ${container.wasHome}/bin/startManager.sh

start-dm.bat snippet for Windows:

    ${container.wasHome}\bin\startManager.bat