Extend the IBM WebSphere AS plugin
You can extend the IBM WebSphere Application Server (WAS) plugin through 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 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 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 Deploy to automatically discover most of the WAS topology. This specific script is called upon by 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 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 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 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 discoveredcontainer
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 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