6 MWS Plugins (Beta)
This section describes MWS Plugins, their use, and their creation in Moab Web Services.
MWS Plugins are currently in beta. Interfaces may change significantly in future releases.
6.1 Plugin Overview
This section provides an overview of the plugin layer in web services. The following areas will be
covered:
6.1.1 Introduction
Moab Web Services plugins provide a highly extensible interface to interact with Moab, MWS, and
external resources. Plugins can perform some of the same functions as Moab Resource Managers (RMs),
while also providing many other features not available to RMs. This section will discuss the
main features of plugins, some basic terminology, and how MWS plugins can interact with Moab.
Features
Plugins can
- be created, modified, and deleted without restarting Moab Workload Manager or MWS.
- be defined in Groovy and uploaded to MWS without restarting.
- have individual data storage space and configuration.
- access MWS configuration and RESTful web services.
- log to a standard location configured in MWS.
- be polled at a regular interval (configured on a per-plugin basis).
- report current state data to Moab Workload Manager through the Resource Manager interface (see MWM Resource Manager Integration for more information).
- be informed of important system events.
- be individually stopped, started, paused, and resumed.
- expose secured and unsecured custom web services for external use.
- be manipulated via a full RESTful API (see Resources for more information).
- be manipulated via a full user interface in a web browser.
Terminology
There are two distinct terms in the plugin layer: plugin types and plugins (also called plugin instances).
Plugin Types
Plugin Types can be considered plugin templates with built-in logic. In object-oriented programming
languages, this relates to the concept of a class. They possess certain abilities, or methods, that
can be called by Moab Web Services to query or update information about certain resources. They also can
define methods which will be exposed to external clients as web services. They do not contain any
configuration or current data, but they are often tied to a
type of component, such as components
that communicate with Moab's WIKI Protocol or those that are built on a certain product.
They can define several types of methods:
- Instance methods that return information about the current plugin, such as
getState
. While these are defined in the plugin type, the plugin type itself does not have a state.
- The poll event method that is called at a configured interval.
- Lifecycle event methods of plugins created from the plugin type, such as
beforeStart
and afterStart
.
- RM event methods that are called by Moab when certain events occur.
- Web service methods that expose custom functionality as public web services.
Some examples of plugin types include the
Native and
vCenter plugin types.
Plugins (Instances)
Plugins (also called plugin instances) are created from plugin types. They contain current data
or configuration and use the plugin type methods to interact with resources.
Interactions with MWM as a Resource Manager
The plugin layer in MWS is integrated with Moab Workload Manager via the Native Resource Manager (RM) interface.
When utilizing plugins, MWS is configured as a RM in Moab as explained in the next section. Events
from Moab are pushed through the RM interface to MWS which is then pushed to each plugin in turn.
The relationship between Moab Web Services, Moab, and plugins is shown in the following image:
See
Consolidating Data and
Reporting State Data for more
information.
6.1.2 Lifecycle States
During the course of a plugin's use, the state of the plugin may change many
times. Plugins have four possible states:
Stopped
,
Started
,
Paused
, and
Errored
.
The flow of a plugin through the states is shown in the following image:
To see descriptions of each state, see the
PluginState API.
Events that occur during lifecycle state changes may be found in the
Events section.
6.1.3 Events
Plugins use an event based model in that methods are called on the plugin when certain criteria are
met or situations arise. Events currently exist for polling, lifecycle state changes, and RM events
from Moab. See
Handling Events for more information.
6.1.4 Custom Web Services
Although the events interface typically serves most cases, there are some instances where
an event is not supported that is desired. This is especially true when an external resource
is the source of the event. To address these issues, plugins can expose custom web services
to external resources. These web services may be named freely and do anything they wish within
the plugin framework.
For example, suppose a resource needs to notify a plugin that provisioning of a virtual machine
has been completed. Instead of having the plugin poll the resource to verify that the
provisioning was finished, the plugin could expose a custom web service to handle notification
from the resource itself.
def vmProvisionFinished(Map params) {
// Handle event
return [messages:["Event successfully processed"]]
}
Additionally, plugin types may define web services which are unsecured, meaning that a user
or application account is not required to access it. A full explanation of the syntax and
creation of custom secured and unsecured web services may be seen on the
Exposing Web Services page.
For information how resources can access plugin web services, see
Accessing Plugin Web Services.
6.1.5 Utility Services
Several features of plugins are only available by utilizing bundled services. These include:
All bundled services are covered on their respective pages in the Quick Reference guide under "Plugin Services".
It may also be necessary or desired to create additional utility services when creating new plugin types.
The easiest way to do this is to create a utility service which is called by convention a
Translator, since it typically can "translate" from a specific resource or
API to data which can be used by the plugin type.
Finally,
custom components may be used to fulfill use cases not covered by
bundled services or custom translators.
6.1.6 Data Consolidation
At times, plugins can report differing or even contradictory data for nodes, virtual machines, and
jobs. This is called a data "collision". The act of resolving these collisions is called "Consolidation".
Plugins also have the concept of "precedence", where the plugins with the lowest precedence value are
considered more authoritative than the greater precedence values plugins. For example, a plugin with a precedence
value of 1 has a higher precedence and is considered more authoritative than a plugin with a precedence value of 5.
If no precedence is provided when creating plugins, the plugin is automatically assigned to the lowest precedence,
or 1 greater than the highest precedence value. The precedence value may not be less than 1.
When data from one plugin "collides" with another, the data from the highest precedence plugin will be
considered the authoritative source for information. If multiple sets of data (reports) are provided by the
same plugin, the latest set of data will take precedence. Additionally, MWS supports the concept of treating
node and virtual machine data with state information
optimistically,
pessimistically, or
neither. This
is configured using the
plugins.stateConsolidationPolicy
configuration property in the MWS configuration file.
If this property is set to
optimistic
and
any plugin reports the state for a node or VM as "Up", the consolidated
state will be "Up". Inversely, if the property is set to
pessimistic
and
any plugin reports the state as "Down",
the consolidated state will be "Down". If it is set to
null
(neither), consolidation will occur for the state
field just as with any other field, with higher precedence and later reports being considered authoritative.
Node, virtual machine, and job unique identifiers (IDs) are all converted to lower-case before consolidation,
so the node "NODE1" is equivalent to "node1".
When MWS is upgraded to a version that supports plugin precedence from an older version, existing plugins will not
have the precedence field set. The administrator should assign precedence to each plugin manually through the
REST API or through the user interface to ensure that
the consolidation will occur as expected. By default, data from a plugin without a precedence defaults to a precedence
of 1, or the highest precedence.
Consolidation Examples
Suppose two plugins exist,
pluginA
and
pluginB
. Plugin A has a precedence of 1, and plugin B
has a precedence of 2, meaning that plugin A is more authoritative. These plugins both report
data for a node with an ID of
node1
. However, each reports a different node power state. Plugin A
reports the power as
ON
, while plugin B reports the power as
OFF
. The data collision that occurs
due to these two contradictory reports is resolved by the precedence of the plugins. Since plugin A has
a higher precedence (lower number), it is considered authoritative and the node will be reported as
ON
.
Now suppose that the plugins also report differing node state for
node1
. In this case, the node state
would depend on the
plugins.stateConsolidationPolicy
property. The different combinations of report values
compared to the state consolidation policy and the final reported state are shown in the table below.
Plugin A Node State | Plugin B Node State | State Consolidation Policy | Consolidated Node State |
---|
ON | OFF | null (neither) | ON |
OFF | ON | null (neither) | OFF |
ON | OFF | optimistic | ON |
OFF | ON | optimistic | ON |
ON | OFF | pessimistic | OFF |
OFF | ON | pessimistic | OFF |
In general, it is recommended that no two plugins report the same resource or that they report different
properties of the same resource. For example, if plugin A only modified the power state and plugin B only
modified the available disk resource, these two plugins would work in harmony to provide
a consistent view of the node resource.
See
Reporting State Data and
RM Queries sections for more information.
6.1.7 Routing
Because Moab Web Services is configured as a Resource Manager (RM) in Moab Workload Manager, events
are sometimes triggered by Moab through the RM interface. These actions could be migrating a virtual machine,
starting a job, submitting a job, modifying a node, and so forth. The decisions regarding which plugins are affected and
notified is termed
routing .
Currently all plugins receive all commands from Moab. This means that each plugin will receive the command to start
a job if sent from Moab, even if that plugin does not handle the job. This means that plugins must ensure they
handle actions or commands only for resources which they report or handle.
6.2 Plugin Developer's Guide
Plugin types comprise the methods by which Moab may communicate with resource managers or other external components.
They define all operations that can be performed for a "type" or "class" of plugins, hence the name "plugin type".
Several plugin types are provided with Moab Web Services (see Plugin Types in the
Quick Reference), but it is easy to create additional plugin types and add their functionality to web services.
This involves using
Groovy, which is based on the
Java programming language. This section describes the
general guidelines and specifics of implementing new plugin types.
API Classes and Interfaces
There are several packages and classes available to assist in creating plugin types.
These can all be found in the
API documentation
under the
com.adaptc.mws.plugins package.
6.2.1 Requirements
This section discusses the requirements to create a basic functional plugin. The
com.adaptc.mws.plugins
package
contains the abstract class
AbstractPlugin
that should form the basis of any new plugin type. However, this class need not be extended to
create a functional plugin type. Only two requirements must be fulfilled for this:
- The class name must end in
Plugin
.
- There must exist
id
field getter and setter methods:
* public String getId();
* public void setId(String id);
The
id
field may be stored in whichever way desired as long as the getter and setter are available as shown above,
but will most likely be implemented as follows:
class BasicPlugin {
String id
}
In this case,
String id
will be expanded by the Groovy compiler to the full getter and setter method
definitions given above. In other words, no explicit method definitions are actually needed.
Note that the
BasicPlugin
shown above is able to be uploaded as a plugin type to MWS, but does not actually do
anything.
It must also be noted that the
AbstractPlugin
class already implements an
id
field. Therefore, a plugin type
that extends this class does not need to define the field as shown in the following example.
import com.adaptc.mws.plugins.AbstractPluginclass BasicPlugin extends AbstractPlugin {
// No ID field is needed since it exists in AbstractPlugin
}
6.2.2 Dynamic Methods
Several methods are dynamically inserted onto each plugin. These methods do not need to be included in the
plugin class, and will be overwritten if included. Additionally, a logger is inserted into each plugin as discussed
in the next section. The inserted methods are shown below (full definitions can be found in
AbstractPlugin and
AbstractPluginInfo):
public void start() throws PluginStartException;
(Equivalent to the start
method in the Plugin Control Service)
public void stop() throws PluginStopException;
(Equivalent to the stop
method in the Plugin Control Service)
public Log getLog();
(Discussed in Logging)
public ConfigObject getAppConfig();
(Discussed in Configuration)
public String message(Map parameters);
(Discussed in i18n Messaging)
public String getPluginType();
public PluginState getState();
public Integer getPollInterval();
public Boolean getAutoStart();
public Map<String, Object> getConfig();
(See Configuration)
Many of these methods are provided for convenience and are discussed in the linked pages or the following sections.
6.2.3 Logging
Logging in plugin types uses the
Apache Commons Logging
and
log4j libraries. Each plugin is injected with a method called
getLog
which can be used to access the configured logger. It returns an instance of
org.apache.commons.logging.Log.
Examples of using the logger are shown below.
The logger may used to register messages to the MWS log at several levels (in order of severity):
- trace
- debug
- info
- warn
- error
- fatal
Each of these levels is available as a method on the logger, for example:
public void poll() {
getLog().debug("getLog() is equivalent to just using 'log' in Groovy")
log.debug("This is a debug message and is used for debugging purposes only")
log.info("This is a informational message")
log.warn("This is a warning")
log.error("This is an error message")
}
Logger Name
Each logger in the MWS logging configuration has a name. In the case of plugins, it is composed of the full class
name, including the package, prepended by "plugins.". For example, a plugin class of "example.LoggingPlugin" will
have access to a logger configured as "plugins.example.LoggingPlugin".
Logging Configuration
The logging configuration is done through the MWS configuration file. See the MWS
Configuration
page for more information on configuring loggers. A good configuration for developing plugin types may be to add
"plugins" at the debug level. Be sure to set the log level threshold down for the desired appender.
log4j = {
…
// Appender configuration
... debug "plugins"
}
6.2.4 i18n Messaging
Plugins, translators, and custom components all have access to
i18n messages. Utilizing messages requires the
two following steps:
- Including a file (or multiple files) that ends in "messages.properties" in the plugin JAR file.
- Using the
message
method on a plugin type, translator, or custom component.
Including Messages in Plugin JAR File
Messages are defined using property files. These may be named anything as long as they end with "messages.properties"
and must be placed at the root or top level of the plugin JAR file. If they are present, they will be loaded
automatically. Multiple property files may be used within a single plugin JAR file.
Each property file consists of an arbitrary amount of lines that define a message property (also called a code) with
letters, numbers, and periods, associated with a human-readable message that can span multiple lines, have quotes,
or contain arguments. These are demonstrated in the following example.
first.message.code=This is the first message
second.message=This message can span multiple lines, \\
and will not show the linebreaks when retrieved
message.with.arguments=This message has arguments: first - {0}, second - {1}, third - {2}, etc.
message.with.quotes=This message uses single quotes around ''this phrase''.
It is recommended to namespace the messages by using the property definitions and multiple property files if necessary.
For example, suppose a plugin JAR existed which actually contained two plugin types:
Message1Plugin
and
Message2Plugin
. The first suggestion is to namespace the messages for each plugin by the property definition,
such as the following:
message1Plugin.first.message=This is a message for Message1Plugin
message2Plugin.first.message=This is a message for Message2Plugin
These messages could be stored in a file named "messages.properties" in the root of the plugin JAR file. If there
are many messages contained for each plugin type, it may be necessary to split each plugin type's messages
into a separate file, such as "message1-messages.properties" and "message2-messages.properties". Note that it is
essential that each property file ends with "messages.properties" so that it is registered correctly.
It is important that no two message codes are identical within a single plugin JAR file, even if they are defined
in separate property files. If this is done, a conflict will exist with the messages and behavior is undefined.
Using the Message Method
Each plugin, translator, and custom component is injected with a method named
message
. This method takes a Map
as its parameter, which can contain one or several of the following properties:
Parameter | Type | Description |
---|
code | String | The message property definition (everything before the equals sign in the property file for a single message), i.e. "first.message.code". |
args | List<Object> | A list of arguments to insert into the message. |
default | String | A default message to be used when the message code cannot be resolved. |
error | org.springframework.context.MessageSourceResolvableĀ | An object that represents a hierarchy of message codes. This is typically used to display errors. |
The most utilized parameters are
code
and
args
, as these combined provide great flexibility in generating messages.
If a message cannot be resolved, or in other words the message definition does not exist, the
code
will simply be
returned as the resolved message. Below are several examples of messages resolved using the property files given
above. While these are contained in the polling method, the
message
may be used anywhere within a plugin type.
package exampleimport com.adaptc.mws.plugins.AbstractPluginclass MessagingPlugin extends AbstractPlugin {
def poll() {
assert message(code:"first.message.code")=="This is the first message"
assert message(code:"message.with.arguments", args:[
"1st", 2, true
])=="This message has arguments: first - 1st, second - 2, third - true, etc."
assert message(code:"message.with.quotes")=="This message uses single quotes around 'this phrase'."
assert message(code:"invalid.message.code")=="invalid.message.code"
}
}
6.2.5 Configuration
Plugin types can access two different kinds of configuration: an individual plugin's configuration, and the global
MWS application configuration.
Individual Plugin Configuration
The individual plugin configuration is separate for each instance of a plugin. This may be used to store current
configuration information such as access information for linked resources. It should
not be used to store cached
information or non-configuration related data. The
Individual Datastore should be used instead
for these cases.
It is accessed by using the
getConfig
method discussed in
Dynamic Methods.
public void poll() {
def configFromMethod = getConfig()
// OR an even simpler method…
def configFromMethod = config
}
A common case is to retrieve the configuration in the
configure
method, verify that it matches
predetermined criteria, and utilize it perform initial setup of the plugin (e.g. initialize libraries needed to
communicate with external resources). For example, to verify that the configuration contains the keys "username" and
"password", the following code may be used.
public void configure() throws InvalidPluginConfigurationException {
def myConfig = config
// This checks to make sure the key exists in the configuration Map and that the value is not empty or null
if (!myConfig.containsKey("username") || !myConfig.username)
throw new InvalidPluginConfigurationException("The username configuration parameter must be provided")
if (!myConfig.containsKey("password") || !myConfig.password)
throw new InvalidPluginConfigurationException("The password configuration parameter must be provided")
}
Access MWS Configuration
The MWS application configuration can also be accessed in plugin types. This configuration is global for the entire
application and can be modified by the administrator as shown in the MWS
Configuration section.
It is accessed by using the
getAppConfig
method discussed in
Dynamic Methods. This is
demonstrated below.
public void poll() {
// Retrieve the current MWS_HOME location
def mwsHome = appConfig.mws.home.location
// OR an even simpler method…
def mwsHome = getAppConfig().mws.home.location
}
Any of the properties shown in the
Configuration reference may be accessed. Custom properties
may also be registered and accessed:
plugins.custom.property = "This is my custom property"
public void poll() {
assert appConfig.plugins.custom.property=="This is my custom property"
}
6.2.6 Configuration Constraints
Plugin types can optionally define validation constraints for the polling interval and plugin configuration. These
parameters are then checked against the defined constraints during the creation of a new plugin. If the validation
fails, meaning the configuration provided does not pass the constraints defined by the plugin type, the plugin will
fail to be created with error messages based on the parameters and constraints defined.
Defining Constraints
To define constraints for a plugin type and therefore for all plugins created using it, use the following syntax:
import com.adaptc.mws.plugins.*class ConstrainedPlugin extends AbstractPlugin {
static constraints = {
// Set plugin's default polling interval
pollInterval defaultValue:60
// The "myParam" configuration parameter is automatically required and cannot be blank
myParam blank:false
// The "myEnum" configuration parameter is not required and must set to one of the values in the list
myEnum required:false, inList:["val1", "val2", "val3]
// Insert additional constraints here…
}
}
In the table below, all available constraints are shown, as well as the expected value type, an example, the default
message code, and the message suffix. The message columns are described in greater detail in the "Messaging" section
below.
Constraint | Default Value | Type | Example Value | Default Message Code | Message Suffix | Description |
---|
blank | - | Boolean | false | default.blank.message | blank | If false, the parameter (if present) cannot be a blank string. |
creditCard | - | Boolean | true | default.invalid.creditCard.message | creditCard.invalid | If true, uses org.apache.commons.validator.CreditCardValidator to determine if the parameter (if present) is a valid credit card number. |
defaultValue | - | Object | 60 | - | - | If the parameter is not present, it will be set to this default value. Does not return any error messages. |
email | - | Boolean | true | default.invalid.email.message | email.invalid | If true, the parameter (if present) must be a valid email address. |
inList | - | List | ["first", "second"] | default.not.inlist.message | not.inList | The parameter (if present) must be set to one of the values specified. |
matches | - | String | "[a-z][A-Z]+" | default.doesnt.match.message | matches.invalid | The parameter (if present) must match the specified regular expression. |
max | - | Integer | 10 | default.invalid.max.size.message | max.exceeded | The parameter (if present) must not be greater than the defined value. |
*maxSize | - | Integer | 10 | default.invalid.max.size.message | maxSize.exceeded | The parameter's (if present) size must not be greater than the defined value. |
min | - | Integer | 1 | default.invalid.min.size.message | min.notmet | The parameter (if present) must not be less than the defined value. |
*minSize | - | Integer | 1 | default.invalid.min.message | minSize.notmet | The parameter's (if present) size must not be less than the defined value. |
notEqual | - | Object | "Invalid Value" | default.not.equal.message | notEqual | The parameter (if present) must not be set to the defined value. |
nullable | true | Boolean | false | default.null.message | nullable | If true, the parameter (if present) must be non-null value. See "required" for how to enforce the parameter to be present. |
password | - | Boolean | true | - | - | If true, the parameter (if present) is hidden from the user both on input and display when managing plugin configuration. It is not, however, hidden in the REST API. Does not return any error messages. |
range | - | Range | 10..20 | default.invalid.range.message | range.toosmall/range.toobig | Uses a groovy range to validate that the value is within a specified range. |
required | true | Boolean | false | default.required.message | required | If true, the parameter must be present and non-null for the plugin to be created successfully. Implies the "nullable:false" constraint. |
scale | - | Integer | 2 | - | - | Only valid for Double parameters. Rounds the parameter (if present) to the specified number of digits. Does not return any error messages. |
*size | - | Range | 1..10 | default.invalid.size.message | size.toosmall/size.toobig | Uses a groovy range to restrict the size of a collection, string, or a number. |
*type | - | Class | Integer.class | typeMismatch | typeMismatch | See the "Type Inferencing" section below. |
url | - | Boolean | true | default.invalid.url.message | url.invalid | If true, uses org.apache.commons.validator.UrlValidator to determine if the parameter (if present) is a valid URL. Does not support exec or file scheme URLs. |
scriptableUrl | - | Boolean | true | default.invalid.scriptable.url.message | scriptableUrl.invalid | Identical to the "url" validator, but adds support for exec and file scheme URLs. |
validator | - | Closure | See below | default.invalid.validator.message | validator.error | See the "Custom Validator" section below. |
- The user interface (see Plugin Management) does not support parameters whose type is a subclass of Collection (a List for example). Such parameters are therefore not recommended.
- The polling interval constraints must always apply to Integer types. If this specification is violated, the plugin type cannot be added or updated.
Messaging
When defined constraints are violated for a plugin, error messages are retrieved based on the configuration
parameters and the applied constraints using
i18n Messaging codes. First, the most specific error
message will be attempted to be resolved from a message code generated from the plugin type name, the configuration
parameter, and the constraint. This code takes the format of "pluginTypeName.parameterName.suffix" where the plugin
type's name has a lowercase first letter and the suffix is shown in the table above. If this message code is not
defined, the default message code (as shown in the table above) will be used.
For example, if the "url" constraint validation failed for the "ExamplePlugin" plugin type's "endpoint" configuration
parameter, the following message codes would be resolved in order:
- examplePlugin.endpoint.url.invalid
- default.invalid.url.message
Plugin types that have two or more uppercase letters at the start of the name will not be converted to have a
lowercase first letter for error message codes. i.e. for the example just given using "VCenterPlugin" instead of
"ExamplePlugin", the following message codes would be resolved in order:
- VCenterPlugin.endpoint.url.invalid
- default.invalid.url.message
Default Messages
Default messages may be contained in any
messages.properties
file included in the plugin JAR file as explained
in
i18n Messaging. Arguments for each constraint vary, but they always include these
argument indices:
- {0}: The configuration parameter name (i.e. endpoint)
- {1}: The plugin type class name (i.e. my.package.ExamplePlugin)
- {2}: The value of the configuration parameter
If default messages are not defined in the plugin project, the following messages will be used:
default.doesnt.match.message=The ''{0}'' configuration parameter value ({2}) does not match the required pattern ''{3}''
default.invalid.url.message=The ''{0}'' configuration parameter value ({2}) is not a valid URL
default.invalid.scriptable.url.message=The ''{0}'' configuration parameter value ({2}) is not a valid scriptable URL
default.invalid.creditCard.message=The ''{0}'' configuration parameter value ({2}) is not a valid credit card number
default.invalid.email.message=The ''{0}'' configuration parameter value ({2}) is not a valid e-mail address
default.invalid.range.message=The ''{0}'' configuration parameter value ({2}) does not fall within the valid range from {3} to {4}
default.invalid.size.message=The ''{0}'' configuration parameter value ({2}) does not fall within the valid size range from {3} to {4}
default.invalid.max.message=The ''{0}'' configuration parameter value ({2}) is greater than the maximum value of {3}
default.invalid.min.message=The ''{0}'' configuration parameter value ({2}) is less than the minimum value of {3}
default.invalid.max.size.message=The ''{0}'' configuration parameter value ({2}) exceeds the maximum size of {3}
default.invalid.min.size.message=The ''{0}'' configuration parameter value ({2}) is less than the minimum size of {3}
default.invalid.validator.message=The ''{0}'' configuration parameter value ({2}) does not pass custom validation
default.not.inlist.message=The ''{0}'' configuration parameter value ({2}) is not contained within the list [{3}]
default.blank.message=The ''{0}'' configuration parameter cannot be blank
default.not.equal.message=The ''{0}'' configuration parameter value ({2}) cannot be equal to ''{3}''
default.null.message=The ''{0}'' configuration parameter cannot be null
default.required.message=The ''{0}'' configuration parameter is required and cannot be null
typeMismatch=The ''{0}'' configuration parameter value ({2}) does not match the required type ''{3}''
Labels and Help Messages
Message codes may also be provided for configuration parameters to aid the admin user with human readable property
labels and help messages. Similar to the validation error message codes, labels and help message codes may be defined
using the "pluginTypeName.parameterName.label" and "pluginTypeName.parameterName.help" message codes. These values
are used only in
Plugin Type Management and are not exposed through the REST API.
Type Inferencing and Conversion
Due to the dynamic nature of configuration parameters, the expected type or class of values for each parameter are
inferred from constraints. The following rules govern how type is inferred, in priority order:
- If the "type" constraint is applied to a parameter, the constraint value will be used as the expected type.
- If the "inList" or "range" constraints are applied to a parameter, the class of the first element in the constraint value array is used as the expected type.
- If the "minSize" or "maxSize" constraints are applied to a parameter,
java.lang.Collection
is used as the expected type.
- If the "max", "min", or "notEqual" constraints are applied to a parameter, the class of the constraint value is used as the expected type.
- If none of the above apply,
java.lang.String
is used as the expected type.
Only the String, Date, Double, Integer, and Boolean classes are supported for the "type" constraint. If Float or Long is
desired, use Double and Integer respectively as the type.
If the configuration parameter values can be converted to the expected types, this will occur automatically. Otherwise,
the "type" constraint is violated and the applicable error messages will be generated.
Custom Validator
In cases where the built-in constraints prove inadequate for validation, custom validators may be used. The "validator"
constraint expects a Groovy Closure parameter which has one or (optionally) two arguments: the value of the
configuration parameter and the plugin object. With these parameters, complex validation logic may be defined.
Additionally, custom message codes and arguments may be defined by validator constraints and these will be used in
generating error messages when validation fails.
For example, suppose that the parameter "user" cannot be set to the same value as parameter "creator". Additionally,
the "creator" parameter must not be equal to either "bob" or "joe". The existing constraints are inadequate to fulfill
this use case, but the following code using validators would perform exactly as expected:
import com.adaptc.mws.plugins.*class ConstrainedPlugin extends AbstractPlugin {
static constraints = {
user validator:{ val, obj ->
if (val==obj.config.creator)
return "invalid.equal.to.creator"
}
creator validator:{ val ->
if ("val"=="joe")
return ["invalid.equal", "joe"]
if (val=="bob")
return ["invalid.equal", "bob"]
}
}
}
In the examples above, the message codes and output on validation failure is shown below:
constrainedPlugin.user.invalid.equal.to.creator=The user configuration parameter value ({2}) must not be equal to the creator parameter.
constrainedPlugin.creator.invalid.equal=The creator configuration parameter must not be equal to {3}.
For user = "jill", creator = "jill"
"The user configuration parameter value (jill) must not be equal to the creator parameter."For user = "jill", creator = "bob"
"The creator configuration parameter must not be equal to bob."For user = "jill", creator = "joe"
"The creator configuration parameter must not be equal to joe."
The validator Closure may return:
- Nothing (
null
) or true
if the validation succeeded without errors.
false
if a validation error occurred (in this case the default validator message suffix would be used).
- A string which will be used as the message code suffix in the "pluginTypeName.propertyName.suffix" format.
- A list with the first element being the message code suffix, and all other elements being arguments for the message indexed starting at 3 (as shown in the example above).
All validator constraints automatically have the
appConfig
property available which contains the application
configuration as discussed in the
Configuration section. Additionally, services may
be retrieved as explained in the next section.
Retrieving Services
At times it may be necessary to use
Bundled Services in custom validators. A method
named
getService
which takes a single string parameter of the name of the service (as used during injection) is
provided to be used in these cases. For example, if a plugin needs a valid server certificate file, the
SSL Service may be used as follows:
import com.adaptc.mws.plugins.*class ConstrainedPlugin extends AbstractPlugin {
static constraints = {
certificateFile validator:{ val ->
ISslService sslService = getService("sslService")
try {
sslService.getSocketFactory(val)
} catch(Exception e) {
// Certificate file is invalid, return an error
return ["invalid", e.message]
}
}
}
}
The getService
method does not work with translators, custom components,
RM services, or the datastore service.
6.2.7 Individual Datastore
Each plugin has access to an individual, persistent datastore which may be used for a variety of reasons.
The datastore is not designed to store Moab data such as nodes, jobs, or virtual machines, but custom,
arbitrary data pertinent only to the individual plugin. This may include storing objects in a persistent cache,
state information for currently running processes, or any other arbitrary data. The individual datastore
has the following properties:
- Data is persisted to the Mongo database and will be available even if the plugin or MWS is restarted.
- The data must be stored in groups of data called collections. These correspond directly to MongoDB collections.
- Each plugin may have an arbitrary number of collections.
- Collections are guaranteed not to collide if there are identically named collections between two plugin types or even two plugin instances.
- Each collection contains multiple objects or entries. These correspond directly to MongoDB documents.
- The values of entries may be any object which can be serialized to MongoDB: simple types (int or Integer), Maps, and Lists.
- A collection is automatically created whenever an entry is added to it, it does not need to be specifically initialized.
To utilize the datastore, the
Plugin Datastore Service must be used. Operations are provided
to add, query, and remove data from each collection.
Simple key/value storage is not currently provided with the datastore. It may easily be done, however, by storing
data in the format of {name:"key", value:"value"} and then retrieving this entry later by querying on name equals
"key".
Example
The example below demonstrates two
web services. The first adds multiple entries
containing various types of data to an arbitrarily named collection. The second retrieves the data and
returns it to the user.
package exampleimport com.adaptc.mws.plugins.*class DatastorePlugin extends AbstractPlugin {
IPluginDatastoreService pluginDatastoreService def storeData(Map params) {
def collectionName = params.collectionName
def data = [[boolVal:true], [stringVal:"String"], [intVal:1], [nullVal:null]]
if (pluginDatastoreService.addData(collectionName, data))
log.info("Data successfully added")
else
log.info("There was an error adding the data")
return [success:true]
} def retrieveData(Map params) {
def collectionName = params.collectionName
return pluginDatastoreService.getCollection(collectionName)
}
}
6.2.8 Exposing Web Services
Any number of methods may be exposed as public, custom web services by satisfying several criteria:
- The method must declare that it returns
Object
or def
.
- The method must define a single argument of type
Map
.
- The method must actually return a
List
or Map
.
- The method must not be declared as private or protected; only public or unscoped methods will be recognized as web services.
Parameters and Request Body
The Map argument will contain all parameters passed into the web service by the client. See
Accessing Plugin Web Services for additional details.
Parameters may be passed into the web service call as normal URL parameters such as
?param=value¶m2=value2
, as
key-value pairs in the POST body of a request, or as JSON in the body.
For the first two cases, the parameters will be available on the Map argument passed into the web service
call as key value pairs matching those of the request. Note that in these cases all keys and values will be
interpreted as strings. However, the parameters object has several helper methods to convert from Strings to simple
types, such as booleans, integers, doubles, floats, and lists. If the value is not a valid simple type, null is
returned.
GET <webServiceUrl>?key=value&key2=true&key3=5&list=1&list=2def serviceMethod(Map params) {
assert params.key=="value"
assert params.key2=="true"
assert params.bool('key2')==true
assert params.key3=="5"
assert params.int('key3')==5
assert params.list('list')==[1, 2] // Null is returned if the conversion is invalid
assert params.int('key')==null
}
When the body possesses JSON, the parsed JSON object or array will be available within a parameter called
body
in the Map argument. In this scenario, the types of the values are preserved by the JSON format.
POST <webServiceUrl> with JSON body of
{"key":"value","key2":true,"key3":5}def serviceMethod(Map params) {
assert params.body.key=="value"
assert params.body.key2==true
assert params.body.key3==5
}
Unsecured Web Services
There are times when it is desirable to create a plugin with a publicly available web service
that does not require a valid
Application Account in order to access it.
In these cases, the
Unsecured annotation may be used on the
plugin web service method. No authentication will be performed on Unsecured web services. An
example of using the annotation is given below.
@Unsecured
def retrievePublicData(Map params) {
return [data:["data item 1", "data item 2"]]
}
Be cautious in using this annotation as it may potentially present a security risk
if sensitive data is returned from the web service.
Returning Errors
In order to signify an error occurred or invalid data was provided, the
WebServiceException class may be thrown from any custom web service.
This exception contains constructors and fields for a list of messages and a HTTP response code. For example,
suppose that the user provided inadequate information. The web service could use the following code to notify
the user and prompt them to take action with custom messages.
def service(Map params) {
// Handle invalid input
if (!params.int('a'))
throw new WebServiceException("Invalid parameter 'a' specified, please specify an integer!", 400)
// Use params.a correctly …
}
For the example above, a 400 response code (bad request) would be returned with a response body as follows:
{
"messages":[
"Invalid parameter 'a' specified, please specify an integer!"
]
}
If any other exception is thrown from a web service (ie Exception, IllegalArgumentException, etc), a 500 response
code will be returned with the following response body:
{
"messages":[
"A problem occurred while processing the request",
"Message provided in the exception constructor"
]
}
See the
Responses and Return Codes section for more information on error formats in MWS.
Accessing the HTTP Request Method
The HTTP method used for the request is available from the Map parameters argument. The key used to access it
is stored as a static field in
PluginConstants called
WEB_SERVICES_METHOD
.
The value is a string which can be
GET
,
POST
,
PUT
, or
DELETE
. The following example demonstrates how this
could be used with the
WebServiceException
to create a REST API with a plugin.
def serviceMethod(Map params) {
// Check to make sure that this request used the HTTP GET method
// Throw a 405 error (method not supported) if not
if (params[PluginConstants.WEB_SERVICES_METHOD]!="GET")
throw new WebServiceException("Method is not supported", 405)
}
6.2.9 Reporting State Data
As long as Moab Workload Manager is
configured with MWS as a Resource Manager (RM),
plugins may report state information on nodes, virtual machines, and jobs to MWM. This is done through
Reports that are generated by the plugin and passed to the bundled RM services
(
Node RM Service,
Virtual Machine RM Service, and
Job RM Service).
Each report is for a specific type of object: node, virtual machine, or job. Each contains current state
information on the specific attributes of the type it is for.
Generating Reports
To generate a report, simply create a new instance of a report depending on the type of object to be reported:
Each report has a single required parameter for creating a new instance - the ID of the object which is being reported.
Once the report instance has been created, any property may be modified as shown in the API documentation links in
the table above. The following example shows the creation of a simple node report and modification of a few properties:
public void poll() {
NodeReport node = new NodeReport("node1")
node.timestamp = new Date()
node.image = "centos-5.4-stateless"
… // Set other properties and persist the report
}
Special Cases in Field Values
All complex types, such as Lists, Maps, and objects (not including Enumerated values such
as
NodeReportState and
JobReportState)
have default values set for them and are not required to be instantiated before use. For example, the
metrics
property of a node report may be modified as follows:
public void poll() {
NodeReport node = new NodeReport("node1")
// The following assignments are equivalent in their functionality
node.features.add("FEAT1")
node.features << "FEAT2"
// The following assignments are equivalent in their functionality
node.metrics.METRIC1 = 4d
node.metrics["METRIC2"] = 125.5
… // Set other properties and persist the report
}
For the
resources
and
requirements
(jobs only) properties, assignments may be made easily without checking
for previously existing values or null objects. For example, resources may be added to the
resources
property
simply by accessing it as a Map:
public void poll() {
NodeReport node = new NodeReport("node1")
node.resources.RES1.total = 10
node.resources.RES1.available = 3
node.resources["RES2"].total = 10
node.resources["RES2"].available = 10
… // Set other properties and persist the report
}
The job report's
requirements
property has some additional handling to allow it to be accessed as a single
JobReportRequirement object, such as in the following example:
public void poll() {
JobReport job = new JobReport("job.1")
job.nodeCountMinimum = 4
job.processorCountMinimum = 2
job.requiredNodeFeatures << "FEAT1"
job.preferredNodeFeatures << "FEAT2"
… // Set other properties and persist the report
}
Although multiple requirements may be added to the requirements
list to provide consistency with the MWS
Job resource, only the first requirement object's properties will be reported to MWM through
the RM interface.
Managing Images for Nodes
In order to have Moab Workload Manager recognize a node as a virtual machine hypervisor, it must have a valid
associated
Image. In particular, the
image
property on a node report must set to a valid
image name. The image's
hypervisorType
and
virtualizedImages
properties are then used to report
the correct hypervisor type and supported virtual machine images to MWM.
If the
image
is invalid, it will be ignored and the node will not be recognized as a hypervisor. If the
image
is valid, but no
hypervisorType
value is present, the
extensions.xcat.hvType
field value will be used. If that is
also not present, the configuration parameter for default hypervisor type (See
Configuration)
will be used instead.
Persisting a Report
After a report has been generated and all desired fields have been updated, the report must be sent to one of the three
bundled RM services for persisting. If this is not done, the report will be discarded and will not be considered
when reporting state information to MWM. The RM services are shown below according to the object type that they handle:
Each service has two methods:
save
and
update
. The difference between these is that the
save
method first
removes all previous reports from the plugin calling the method, and then persists the new reports, thereby only
persisting the latest reports, while the
update
method does not remove any reports before persisting the new
reports. Typically, the
save
method will be used while a plugin is being polled, while the
update
method will
be used in incremental event based reporting. An example of using the
save
method is shown below.
INodeRMService nodeRMServicepublic void poll() {
NodeReport node = new NodeReport("node1")
// Change the state
node.state = NodeReportState.BUSY
// Persist
nodeRMService.save([node])
}
Once this is done, the reports will be persisted to MongoDB and will be included in
RM Queries
from Moab Workload Manager or users.
6.2.10 Controlling Lifecycle
At times a plugin developer may wish to modify the current state of a plugin or even create plugins programatically.
This may be done with the
Plugin Control Service. Operations exist on the service to:
- create plugin instances dynamically with specific configuration.
- retrieve plugin instances by ID or based on configuration properties.
- start or stop plugin instances.
- verify plugin instance configuration.
Creating Plugins
Several methods are provided to allow on-the-fly creation of new plugins. Generally, they allow a plugin with
a specific ID and plugin type (as a string or as a Groovy Class) to be created with optional configuration properties.
These properties should match the fields in the
Plugin API.
If any configuration properties are omitted, the defaults will be used as described in the
Setting Default Plugin Configuration section. A boolean value is also returned
indicating whether the creation succeeded or not.
Note that the
createPlugin
methods will initialize the plugin for retrieval or usage and attempt to start the
plugin if the
autoStart
property is true.
Retrieving Plugins
Plugins may be retrieved by using an ID, querying by plugin type, or even querying based on configuration parameters.
Several methods are provided to perform these functions as shown on the
Plugin Control Service
page.
Starting and Stopping Plugins
Plugins may also be started or stopped on demand. These two methods are exposed directly as
start
and
stop
on the plugin control service. Although each method does not return any data, exceptions are thrown if errors
are encountered.
Verifying Plugin Configuration
Finally, the plugin control service may be used to verify plugin configuration at any point instead of just when
the plugin is started or modified. This may be useful to attempt to modify plugin configuration directly through
the
setConfig
dynamic method and then verify that the new configuration is valid for the plugin.
Exceptions are thrown if the plugin or the configuration is invalid.
Examples
If an error state is detected it may be necessary to stop the current plugin instance until corrective
action can be taken. This may be done using the following code:
package exampleimport com.adaptc.mws.plugins.*class ErrorPlugin {
IPluginControlService pluginControlService public void poll() {
// Error is detected, stop plugin instance!
try {
log.warn("An error was detected, trying to stop the plugin ${id}")
pluginControlService.stop(id)
log.warn("The plugin was successfully stopped")
} catch(PluginStopException e) {
log.error("Plugin instance ${id} could not be stopped", e)
}
}
}
6.2.11 Accessing MWS REST Resources
Often a plugin type may need to access existing MWS REST Resources in order to extend or complement default MWS
functionality. This may be done with the
Moab Rest Service, which allows a plugin type developer
to utilize the existing
Resources documentation to perform these tasks.
All accesses to resources require a HTTP method to use (such as GET, POST, PUT, or DELETE) and a relative URL
(such as /rest/jobs). Although it mimics the REST resource interface, no actual requests are made and no data
is transmitted through the network.
Authentication
All resources are available to the Moab REST Service, and no authentication or Application Accounts are needed.
Caution must be used when developing plugin types, as there are no restrictions to what may be done with the
Moab REST Service. This is especially true when not utilizing hooks as discussed below.
Hooks
If
Pre and Post-Processing Hooks are utilized in MWS, the plugin type developer may choose whether
or not they are executed when performing a "request" through the Moab REST service. This is done through the
hooks
option as documented on the
Moab Rest Service page.
Verifying API Version Support
The Moab REST Service provides a method for easily determining which API versions are supported by the current
version of MWS. This method includes checks to make sure that the API version will work as expected, including
verifying any configuration or external services are running.
moabRestService.isAPIVersionSupported(1)
moabRestService.isAPIVersionSupported(2)
URL Parameters
URL parameters, such as
query
,
sort
,
proxy-user
, and others should be not be appended directly to the URL.
Instead, these may be specified with the
params
option:
// Query images that are hypervisors
moabRestService.get("/rest/images", params:[query:'{"hypervisor":true}'])
// Sort images by osType
moabRestService.get("/rest/images", params:[sort:'{"osType":1}'])
Examples
This code retrieves a list of all nodes, and is equivalent to the
Get All Nodes task.
package exampleimport com.adaptc.mws.plugins.*
import net.sf.json.*class RestPlugin {
IMoabRestService moabRestService public void poll() {
def result = moabRestService.get("/rest/nodes")
// OR with the hook enabled…
def result = moabRestService.get("/rest/nodes", hooks:true) assert result instanceof MoabRestResponse
assert nodes instanceof List log.debug("Nodes list:")
nodes.each { JSON node ->
log.debug(node.id)
}
}
}
This code adds a flag to a job, and is equivalent to the
Modify Job Attributes task.
This request also enables the hook (if one is configured) for the "request" and uses a URL parameter. This is the
equivalent of making a call to
/rest/jobs/job.1?proxy-user=adaptive
.
package exampleimport com.adaptc.mws.plugins.*
import net.sf.json.*class RestPlugin {
IMoabRestService moabRestService public void poll() {
def jobId = "job.1"
def result = moabRestService.put("/rest/jobs/"+jobId, hooks:true, params:['proxy-user':'adaptive']) {
[flags:["RESTARTABLE"]]
}
assert result.isSuccess()
}
}
6.2.12 Handling Events
Plugin types may handle specific events by containing methods defined by the conventions below.
All events are optional.
The Polling Event
To maintain current information, each plugin is polled at a specified time interval. The following method
definition is required to utilize the polling event.
Typically this polling method is used to report node and virtual machine information. By default, the
polling interval is set to 30 seconds, but can be modified for all or individual plugins as explained in
Plugin Management.
When a polling event occurs, the
poll
method on the target plugin is called. This method may perform
any function desired and should typically make calls to the
Node RM Service, the
Virtual Machine RM Service, and the
Job RM Service services to report the
current state of nodes and virtual machines. For example, the
poll
method in the
Native
plugin type is implemented as follows:
This is an extremely simplified version of what is actually implemented in the Native plugin type.
INodeRMService nodeRMService;
IVirtualMachineRMService virtualMachineRMService;public void poll() {
nodeRMService.save(getNodes());
virtualMachineRMService.save(getVirtualMachines());
}
This simple poll method calls two other helper methods called
getNodes
and
getVirtualMachines
to retrieve
node and virtual machine reports. These reports are then sent to the appropriate RM service. See
Reporting State Data for more information on the RM services, but the
objective of this example is to demonstrate one possible use of the poll event handler. Other plugin types, on the
other hand, may use the poll event to update internal data from pertinent resources or make calls to external APIs.
Lifecycle Events
Events are also triggered for certain lifecycle state changes. The following method definitions are
required to receive lifecycle events.
public void configure() throws InvalidPluginConfigurationException { … }
public void beforeStart() { … }
public void afterStart() { … }
public void beforeStop() { … }
public void afterStop() { … }
Each event is described in the table below with the associated state change when the event is triggered.
State Change | Event | Description |
---|
configure | Configure | Triggered before beforeStart and after the plugin has been configured. May be used to verify configuration and perform any setup needed any time configuration is loaded or modified. |
beforeStart | Start | Triggered just before starting a plugin. |
afterStart | Start | Triggered just after a plugin has been started. |
beforeStop | Stop | Triggered just before stopping a plugin. |
afterStop | Stop | Triggered just after stopping a plugin. |
Currently, no events are triggered for pausing, resuming, erroring, or clearing errors for plugins.
RM Events
When MWS is configured as a Moab Resource Manager (see
MWM Resource Manager Integration
and more specifically
Configuring MWM), RM events are sent from Moab to each plugin according
to the
Routing specification. The following method definitions are required to
receive these events.
public boolean jobCancel(List<String> jobs) { … }
public boolean jobModify(List<String> jobs, Map<String, String> properties) { … }
public boolean jobRequeue(List<String> jobs) { … }
public boolean jobResume(List<String> jobs) { … }
public boolean jobStart(String jobId, String taskList, String username) { … }
public boolean jobStart(String jobId, String taskList, String username, Map<String, String> properties) { … }
public boolean jobSubmit(Map<String, String> properties) { … }
public boolean jobSuspend(List<String> jobs) { … }
public boolean nodeModify(List<String> nodes, Map<String, String> properties) { … }
public boolean nodePower(List<String> nodes, NodeReportPower state) { … }
public boolean resourceCreate(String type, String id, Map<String, String> attributes) { … }
public boolean systemModify(Map<String, String> properties) { … }
public List<String> systemQuery(List<String> attributes) { … }
public boolean virtualMachineMigrate(String vmId, String hypervisorId, String operationId) { … }
These calls are equivalent to the Moab RM URLs as described in the
Native
"Native Plugin Interface Comparison" section. All method definitions are documented in the
AbstractPlugin API documentation.
6.2.13 Handling Exceptions
The
com.adaptc.mws.plugins
package contains several exceptions that may be used and in some cases, should be caught.
All exceptions end with "Exception", as in
PluginStartException.
There are several specific cases where Exceptions should or can be used:
- The
reload
method on the Plugin Control Service can throw the InvalidPluginConfigurationException to signify that the configuration contains errors.
- Various methods on the Plugin Control Service throw plugin exceptions which must be caught to diagnose errors when creating plugin types.
- Any exception (including the Exception class) can be thrown from a custom web service to display a 500 Internal Server Error to the client requesting the service with the given error message.
6.2.14 Managing SSL Connections
At times it is desirable to load and use self-signed certificates, certificates generated from a single
trusted certificate authority (CA), or even simple server certificates. It may also be necessary to use
client certificates to communicate with external resources. To ease this process, the
SSL Service may be utilized. This service provides methods to load client and server certificates
from the filesystem. Methods are also present to aid in creating connections which automatically trust all
server certificates and connections.
Several points should be noted when using the SSL Service:
- Certificate files may be in the PEM file format and do not need to be in the DER format (as is typical of Java security).
- Each method returns an instance of SSLSocketFactory, which may then be used to create simple sockets or, in combination with another client library of choice, create a connection.
- If the client certificate password is non-null, it will be used to decrypt the protected client certificate.
- This service is not needed when performing SSL communications with trusted certificates, such as those for HTTPS enabled websites that do not have a self-signed certificate.
- If the file name of the certificate file (client or server) is relative (no leading '/' character), it will be loaded from the
mws.certificates.location
Configuration parameter.
- The default value of
mws.certificates.location
is MWS_HOME/etc/ssl.crt
.
- Both the client certificate alias and password may be
null
. In this case, the client certificate must not be encrypted and the client certificate's default alias (the first subject CN) will be used.
- The lenient socket factory and hostname verifier automatically trust all server certificates. Because of this, they present a large security hole. Only use these methods in development or in fully trusted environments.
Example
To create a socket to a server that requires a client certificate, the following code may be used.
package exampleimport com.adaptc.mws.plugins.*class SSLConnectionPlugin extends AbstractPlugin {
ISslService sslService public void poll() {
// This certificate is not encrypted and will be the only certificate presented to the
// connecting end of the socket.
// This file will be loaded from MWS_HOME + mws.certificates.location + my-cert.pem.
String clientCert = "my-cert.pem" def socketFactory = sslService.getSocketFactory(clientCert, null, null)
def socket = socketFactory.createSocket("hostname.com", 443)
// Write and read from the socket as desired…
}
}
To create a HTTPS URL connection to a server that has a self-signed certificate, the following code may be used.
Note that this is very typical of client libraries - they have a method to set the SSL socket factory used when
creating connections.
package exampleimport com.adaptc.mws.plugins.*class SSLConnectionPlugin extends AbstractPlugin {
ISslService sslService public void poll() {
// This certificate represents either the server public certificate or the CA's certificate.
// Since the path is absolute it will not be loaded from the MWS_HOME directory.
String serverCert = "/etc/ssl/certs/server-cert.pem" def socketFactory = sslService.getSocketFactory(serverCert) // Open connection to URL
HttpsURLConnection conn = "https://hostname.com:443/test".toURL().openConnection()
conn.setSSLSocketFactory(socketFactory) // Retrieve page content and do with as desired…
def pageContent = conn.getInputStream().text
}
}
6.2.15 Utilizing Services or Custom "Helper" Classes
There are three general types of services available for use in plugins:
- Bundled services such as the Moab Rest Service.
- Custom built translators loaded by convention of their name.
- Other custom built helper classes registered with Annotations.
These will each be described in this section.
6.2.15.1 Bundled Services
Bundled services are utility classes that are included and injected by default onto all plugin types. It is not
required to use any of these services, but they enable several core features of plugin types as discussed in the
Utility Services section.
More information may be found on each bundled service in the Quick Reference section under "Plugin Services".
See especially the "Usage" page under "Plugin Services" to understand generally how they are to be used.
6.2.15.2 Using Translators
Often a plugin type class file becomes so complex that it is desirable to split some of its logic into separate
utility service classes. The most typical use case for this is to split out the logic for "translating" from
a specific resource API to a format of data that the plugin type can natively understand and utilize. For this reason,
there is a convention defined to easily add these helper classes called "Translators".
Simply end any class name with "Translator", and it will be automatically injected just as bundled services onto
plugin types, other translators, or even
custom registered components. The
injection occurs only if a field exists on the class matching the name of the translator with the first letter
lower-cased. For example, a translator class called "MyTranslator" would be injected on plugin types, other
translators, and custom components that define a field called "myTranslator" as
def myTranslator
or
MyTranslator myTranslator
.
Do not use two upper-case letters to start the class name of a Translator. Doing this may cause
injection to work improperly. i.e. use RmTranslator instead of RMTranslator as the class name.
Be careful not to declare translator and custom component injection such that a cyclic dependency is created.
Logging in Translators
All translators automatically have a "getLog" method injected on them which can be used to access the configured logger.
It returns an instance of
org.apache.commons.logging.Log.
package exampleclass ExampleTranslator {
public void myMethod() {
// log will be translated to getLog() by the groovy compiler
log.info("Starting my method")
}
}
See the
Logging section for more information on logging configuration and usage.
Example
Suppose that a translator needs to be created to handle a connection to access an external REST resource. The
translator could be defined as follows:
package exampleclass ExampleTranslator {
public int getExternalNumber() {
def number = … // Make call to external resource
return number
}
}
A plugin type can then use the translator by defining a field called "exampleTranslator". Note that an instance
does not need to be explicitly created.
package exampleclass ExamplePlugin {
def exampleTranslator
// OR …
//ExampleTranslator exampleTranslator public void poll() {
// Use the translator
log.info("The current number is "+exampleTranslator.getExternalNumber())
}
}
To extend the example, the translator may also be injected into another translator:
package exampleclass AnotherTranslator {
def exampleTranslator public int modifyNumber(int number) {
return number + exampleTranslator.getExternalNumber()
}
}
This translator may be used in the plugin type just as the other translator.
6.2.15.3 Registering Custom Components
There are cases where the concept of a "Translator" does not fit the desired use of a utility class. In these
cases, it is possible to register any arbitrary class as a component to be injected just as a translator would be.
This is done using the Spring Framework's annotation
org.springframework.stereotype.Component
. When this annotation
is used, the class is automatically registered to be injected just as translators onto plugin types and translators.
All annotations are available in the dependencies declared by the plugins-commons artifact.
Do not use two upper-case letters to start the class name of a custom component. Doing this may cause
injection to work improperly. i.e. use RmUtility instead of RMUtility as the class name.
Changing Scope
By default, when a custom component is injected, only a single instance is created for all classes which inject it.
This is referred to as the 'singleton' scope. Another scope that is available is 'prototype', which creates a new
instance every time it is injected. This is useful when the class contains state data or fields that are modified
by multiple methods. To change the scope, use the
org.springframework.context.annotation.Scope
on the class with a
single String parameter specifying 'singleton' or 'prototype'.
Injecting Translators or Components
The need may arise to inject translators or other custom components onto custom components. This is done using
the
org.springframework.beans.factory.annotation.Autowired
or
javax.annotation.Resource
annotations. The
Autowired
annotation is used to inject class instances by the type (i.e.
MyTranslator myTranslator
) while
the
Resource
annotation is used to inject class instances by the name (i.e.
def myTranslator
). Add the desired
annotation to the field that needs to be injected.
Note that using the Autowired
annotation does injection by type which differs from translator and plugin
type injection. These are done by name just as the Resource
annotation allows. Due to this fact, a type
of "def" cannot be used when doing injection onto custom components using the Autowired
annotation.
See the example below.Injection of custom components onto translators and plugin types are still done by name, only fields injected
using the Autowired
annotation are affected.
Be careful not to declare translator and custom component injection such that a cyclic dependency is created.
Logging in Custom Components
Unlike plugins and translators, custom components do
not automatically have a "getLog" method injected on them.
In order to log with custom components, you must use the Apache Commons Logging classes to retrieve a new log.
The
PluginConstants
class contains the value of the logger prefix that is used for all plugins and translators. The
following is an example of how to retrieve and use a logger correctly in a custom component.
package exampleimport com.adaptc.mws.plugins.PluginConstants
import org.apache.commons.logging.Log
import org.apache.commons.logging.LogFactory
import org.springframework.stereotype.Component@Component
class ExampleComponent {
private static final Log log = LogFactory.getLog(PluginConstants.LOGGER_PREFIX+this.name) public void myMethod() {
log.info("Starting my method")
}
}
See the
Logging for more information on logging configuration and usage.
Example
Suppose that a custom utility class is needed to perform complex logic. A custom component could be defined as
follows (notice the optional use of the
Scope
annotation):
package exampleimport org.springframework.stereotype.Component
import org.springframework.context.annotation.Scope@Component
@Scope("prototype")
class ComplexLogicHandler {
def handleLogic() {
… // Perform complex logic and return
}
}
A plugin type or translator could then be defined to inject this component:
package exampleclass CustomPlugin {
def complexLogicHandler public void poll() {
complexLogicHandler.handleLogic()
}
}
Now suppose another custom component needs to use the ComplexLogicHandler in its code. It can inject it using
the
Autowired
annotation:
package exampleimport org.springframework.stereotype.Component
import org.springframework.beans.factory.annotation.Autowired@Component
class AnotherHandler {
// Note that this is injected by type, so 'def' may not be used
@Autowired
ComplexLogicHandler complexLogicHandler def wrapLogic() {
complexLogicHandler.handleLogic()
}
}
To perform the same injection but by name (as translators and plugin types are injected), use the
Resource
annotation:
package exampleimport org.springframework.stereotype.Component
import javax.annotation.Resource@Component
class AnotherHandler {
// Note that this is injected by name based solely on the name defined in
// the annotation. The name of the field itself does not affect the injection.
@Resource(name="complexLogicHandler")
def complexLogicHandler def wrapLogic() {
complexLogicHandler.handleLogic()
}
}
6.2.16 Packaging Plugins
Plugin types may be packaged in two different ways to upload to MWS:
- A simple Groovy file containing a single plugin type definition.
- A JAR file containing one or more plugin types, translators, and custom components.
While each may be uploaded to MWS using the REST API or the User Interface as described in
Add or Update Plugin Types, using a JAR file is recommended. Using a simple Groovy file
is useful for testing and generating proof of concept work, but does not allow the use of several features of plugins.
The principles of packaging a plugin type or set of plugin types in a JAR file are very simple. Simply compile the
classes and package in a typical JAR file. All classes ending in "Plugin" are automatically attempted to be loaded as
a plugin type, all classes ending in "Translator" are attempted to be loaded as a translator, and all classes annotated
as a custom component will be attempted to be loaded. It is recommended that a build framework is used to help with
compiling and packaging the JAR file, such as
Gradle. This makes it easy to declare a
dependency on the necessary JAR files used in plugin development and to debug, compile, and test plugin code.
In addition to using utility services such as translators, packaging plugin types in JAR files allows the creation of
a single project for multiple related plugin types and bundling of external dependencies. These two features are
discussed in the following sections.
6.2.16.1 Plugin Projects and Metadata
Each plugin type has information attached to it, called metadata, which describes the origin and purpose of the
plugin type. Additionally, a JAR file may also contain a project file which defines default metadata attributes for
all plugin types in the JAR. Initial plugins, or plugins that will be created on loading of the JAR file if they
do not exist, are also able to be defined on a project file. In all cases, metadata declared on a plugin type will
override the metadata defined on the project file.
To define a project file, simply add a class to JAR file that ends in "Project". This file will attempted to be
loaded as the project file. Every field on a project file, and even the file itself, is optional. All available
fields are shown in the example below.
class SampleProject {
// Plugin information
String title = "Sample"
String description = "Sample plugin types"
String author = "Our Company."
String website = "http://example.com"
String email = "[email protected]" // Versioning properties
String version = "0.1"
String mwsVersion = "7.1 > *"
String commonsVersion = "0.9 > *"
String license = "APACHE" // Documentation properties
String issueManagementLink = "http://example.com/ticket-system/sample-plugins"
String documentationLink = "http://example.com/docs/sample-plugins"
String scmLink = "http://example.com/git/sample-plugins" // Plugins that are to be created with these properties only when they do NOT exist
// This does not override any existing plugin instance configuration
def initialPlugins = {
/*
// Multiple instances of plugins may be defined here.
// In this case, 'sample' is the id of the plugin
sample {
pluginType = "Sample"
// All properties except for "pluginType" are optional
pollInterval = 30
autoStart = true
// Although it is possible to set plugin precedence, it is not recommended since this precedence
// may already be taken and plugin creation will fail in this case
precedence = 5
config {
configParam = "value"
}
}
}
// Another plugin with an ID of 'sample2'
sample2 {
…
*/
}
}
As can be seen, metadata information about the plugin type(s), versions, and documentation are available. These
are displayed when viewing plugin information in the User Interface or through the REST API.
Any of these properties except for
initialPlugins
,
mwsVersion
, and
commonsVersion
may be overwritten by the
plugin type class itself by using static properties. A simple example is shown below.
package exampleclass SamplePlugin {
// Properties may be typed, untyped, final, or otherwise,
// but they MUST be static
static version = "0.2"
static title = "Sample plugin"
static description = "This sample plugin is used to demonstrate metadata information"
static author = "Separate Division" … // Rest of the plugin type definition
}
MWS and Commons Versions
The
mwsVersion
and
commonsVersion
fields are used to restrict the versions of MWS and plugin framework
with which the plugin project may be used. Each field is of the format
FIRST_VERSION > LAST_VERSION
,
where
FIRST_VERSION
is the first supported MWS or plugin framework version (inclusive), and
LAST_VERSION
is the
last supported MWS or plugin framework version (inclusive). Each version must take the format of #.# or #.#.#, as in
7.1, or 7.1.2. An asterisk (*) is used to denote any version, and may be used for the first or the last version.
Although support for restricting both the MWS and commons versions are provided, it is recommended to use the
commons version restriction always and the MWS version restriction where necessary. Restrictions on the commons
version prevent plugin loading errors while restrictions on the MWS version prevent runtime errors such as missing
support for certain MWS API versions.
Typically the
mwsVersion
and
commonsVersion
fields are set as shown above, with the first version set to a
specific number, and the last version set to any (an asterisk). This is the recommended approach for setting both
fields. It is not recommended to use any version (asterisk) for the first version. Some examples of
mwsVersion
and
commonsVersion
values are shown below with explanations of how they behave.
String mwsVersion = "7.1 > *" // Any MWS version 7.1.0 and greater is supported (including 7.2, etc)
String mwsVersion = "7.1.3 > *" // Any MWS version 7.1.3 and greater is supported (including 7.2, etc)
String mwsVersion = "7.1 > 7.1.3" // Any MWS version between 7.1.0 and 7.1.3 is supported
String mwsVersion = "* > *" // Any MWS version is supported (not recommended!)
String mwsVersion = "* > 7.2" // Any MWS version up to 7.2 is supported (not recommended!)String commonsVersion = "0.9 > *" // Any framework version 0.9.0 and greater is supported (including 1.0, etc)
String commonsVersion = "0.9.3 > *" // Any framework version 0.9.3 and greater is supported (including 1.0, etc)
String commonsVersion = "0.9 > 0.9.3" // Any framework version between 0.9.0 and 0.9.3 is supported
String commonsVersion = "* > *" // Any framework version is supported (not recommended!)
String commonsVersion = "* > 1.0" // Any framework version up to 1.0 is supported (not recommended!)
If the
mwsVersion
or
commonsVersion
fields are formatted incorrectly, the plugin project will fail to load.
If a plugin project is uploaded to MWS and the version check fails, the project will fail to load with an error
message about the
mwsVersion
or
commonsVersion
.
The mwsVersion
and commonsVersion
fields cannot be overridden by a single plugin type, but can be set only at
the plugin project level. This prevents mixing of MWS and commons version requirements within a single project.
Initial Plugins
The initial plugins closure provides the flexibility to insert plugin instances when the JAR is loaded. This occurs
at two points: when the plugin JAR is first uploaded to MWS, and when MWS is restarted. As shown in the example
above, the ID, pluginType, and other properties may be configured for multiple plugins.
The nature of Groovy closures means that programmatic definition of initial plugins is possible. This may even be based
on the MWS application configuration. Two properties are automatically available in the initialPlugins closure:
appConfig
- Contains the MWS application configuration. Any configuration parameter is available for access as documented on the Configuration page.
suite
- Contains the currently configured suite that MWS is running in. This is equivalent to the mws.suite
configuration parameter, and is an instance of Suite.
Native Plugin Case Study
The Native JAR file utilizes many of the features discussed above. In the root of the JAR file, a compiled class
called NativeProject exists which defines all of the metadata fields, including
initialPlugins
. Trying to create an
initial plugin presents two distinct problems:
- The plugin should be initialized only if the suite is CLOUD.
- The plugin type configuration must contain an entry referencing the configured
mws.home.location
parameter, or the configured MWS_HOME location.
The
initialPlugins
closure is defined as follows:
import com.adaptc.mws.plugins.Suiteclass NativeProject {
… // Metadata fields def initialPlugins = {
// Initialize the cloud-native plugin only if the suite is CLOUD
if (suite==Suite.CLOUD) {
'cloud-native' {
pluginType = "Native"
pollInterval = 30
config {
// Use the appConfig property to retrieve the current MWS_HOME location
getCluster = "file://${appConfig.mws.home.location}/etc/nodes.txt"
}
}
}
}
}
6.2.16.2 Managing External Dependencies
External dependencies (e.g. JAR files) may be included and referenced in JAR files. Certain rules must also be
followed in order to have the dependencies loaded from the JAR file correctly:
- The plugin type must bundle all external dependency JARs in the root of the plugin type JAR file.
- An entry must be included in the
MANIFEST.MF
file that references each of these bundled JAR files as a space separated list:
Class-Path: dependency1.jar dependency2.jar dependency3.jar
Assuming that these rules are followed and that the plugin type is uploaded using the REST API or the User Interface,
the dependent JARs will first be loaded and then the new plugin type and associated files will be loaded.
6.2.17 Example Plugin Types
Several plugin types are provided by Adaptive Computing for use in Moab Web Services. Examples of these include
the
Native and
vCenter plugin types.
Please see the Plugin Types item in the Quick Reference menu for all bundled plugin types.
A sample plugin type in Groovy would resemble the following:
package sampleimport com.adaptc.mws.plugins.*class SamplePlugin extends AbstractPlugin {
static author = "Adaptive Computing"
static description = "A simple plugin in groovy"
static version = "0.1" INodeRMService nodeRMService public void configure() throws InvalidPluginConfigurationException {
def myConfig = config // "config" is equivalent to getConfig() in groovy
def errors = []
if (!myConfig.arbitraryKey)
errors << "Missing arbitraryKey!"
if (errors)
throw new InvalidPluginConfigurationException(errors)
} public void poll() {
NodeReport node = new NodeReport("node1")
node.resources.RES1.total = 5
node.resources.RES1.available = 5
node.state = NodeReportState.IDLE
nodeRMService.save([node])
} // Access at /rest/plugins/<id>/services/example-service
public def exampleService(Map params) {
return [success:true]
}
}
6.3 MWM Resource Manager Integration
Moab Workload Manager possesses the concept of Resource Managers (RMs). While plugins can be related to RMs, they
often provide greater functionality and serve more purposes than a typical RM. MWS must be represented in MWM
as a RM to enable certain plugin features such as state reporting and handling RM events. This section describes
the process of configuring MWM and additional details of its queries to MWS.
6.3.1 Configuring MWM
Moab Workload Manager must be configured to use MWS as a resource manager. The following lines must be in the
/opt/moab/etc/moab.cfg
file or one of its included files:
RMCFG[mws] TYPE=NATIVE
RMCFG[mws] FLAGS=UserSpaceIsSeparate
RMCFG[mws] CLUSTERQUERYURL=exec://$TOOLSDIR/mws/cluster.query.mws.pl
RMCFG[mws] WORKLOADQUERYURL=exec://$TOOLSDIR/mws/workload.query.mws.pl
RMCFG[mws] JOBCANCELURL=exec://$TOOLSDIR/mws/job.cancel.mws.pl
RMCFG[mws] JOBMIGRATEURL=exec://$TOOLSDIR/mws/vm.migrate.mws.pl
RMCFG[mws] JOBMODIFYURL=exec://$TOOLSDIR/mws/job.modify.mws.pl
RMCFG[mws] JOBREQUEUEURL=exec://$TOOLSDIR/mws/job.requeue.mws.pl
RMCFG[mws] JOBRESUMEURL=exec://$TOOLSDIR/mws/job.resume.mws.pl
RMCFG[mws] JOBSTARTURL=exec://$TOOLSDIR/mws/job.start.mws.pl
RMCFG[mws] JOBSUBMITURL=exec://$TOOLSDIR/mws/job.submit.mws.pl
RMCFG[mws] JOBSUSPENDURL=exec://$TOOLSDIR/mws/job.suspend.mws.pl
RMCFG[mws] NODEMODIFYURL=exec://$TOOLSDIR/mws/node.modify.mws.pl
RMCFG[mws] NODEPOWERURL=exec://$TOOLSDIR/mws/node.power.mws.pl
RMCFG[mws] RESOURCECREATEURL=exec://$TOOLSDIR/mws/resource.create.mws.pl
RMCFG[mws] SYSTEMMODIFYURL=exec://$TOOLSDIR/mws/system.modify.mws.pl
RMCFG[mws] SYSTEMQUERYURL=exec://$TOOLSDIR/mws/system.query.mws.pl
The next step is to edit the MWS values in
/opt/moab/etc/cloud.cfg
. Here
are the default values:
CONFIG[default] MWS_URL=http://localhost:8080/mws
CONFIG[default] MWS_USERNAME=admin
CONFIG[default] MWS_PASSWORD=adminpw
MWS_USERNAME
and MWS_PASSWORD
must match the values of
auth.defaultUser.username
and auth.defaultUser.password
, respectively,
found in /opt/mws/etc/mws-config.groovy
.
The
*.mws.pl
scripts should be located in the
tools/mws
of the Moab home directory.
The
Moab/WebServices.pm
module must also be available to the scripts. All of these files
may be found in the
tools/mws
and
lib/perl5
directories of the Moab tar file. They
are automatically installed if Moab is configured with the
--with-mws
flag or they can be
copied directly from there to the
tools
folder in your Moab home directory.
To enable such actions as submitting jobs as different users, the
ENABLEPROXY=TRUE
option must be present in the
ADMINCFG
configuration line and the
OSCREDLOOKUP
option must be set to
NEVER
as follows:
ADMINCFG[1] USERS=root ENABLEPROXY=TRUE
OSCREDLOOKUP NEVER
6.3.2 RM Queries
During each iteration of Moab Workload Manager's cycle, it will query MWS through the RM interface to access current
node, virtual machine, and job information. At this point, all reports are loaded from the database and consolidated
into a single report of each object as explained in the
Data Consolidation section.
All unset, or null, values for properties on reports are ignored.
In some cases it may be desired to query MWS directly for the current consolidated node, virtual machine, and job
reports. This may be done using the following URLs which return data in the Wiki interface format (see the
Native plugin type for more information). Note that the content type of these URLs is
plain/text
,
not
application/json
.
query | Description |
---|
/rest/plugins/all/rm/cluster-query | Retrieves consolidated node and virtual machine reports from all plugins. All VMs will have a CONTAINERNODE attribute present. |
/rest/plugins/<ID>/rm/cluster-query | Retrieves consolidated node and virtual machine reports for the specified plugin ID. All VMs will have a CONTAINERNODE attribute present. |
/rest/plugins/all/rm/workload-query | Retrieves consolidated job reports from all plugins. |
/rest/plugins/<ID>/rm/workload-query | Retrieves consolidated job reports for the specified plugin ID. |
These queries have no effect on the data itself. In other words, reports are not removed or manipulated when
RM queries are performed. These are manipulated only the RM services as described in
Reporting State Data.
Examples
The following example uses
cURL to perform the query.
$ curl -u admin:adminpw http://localhost:8080/mws/rest/plugins/all/rm/cluster-query
SC=0 RESPONSE=Success
n1.test STATE=Up;UPDATETIME=1344488025;CPROC=4;CMEMORY=8191;AMEMORY=7205;CPULOAD=0.0182;POWER=On
n2.test STATE=Up;UPDATETIME=1344488025;CPROC=4;CMEMORY=10239;AMEMORY=9227;CPULOAD=0.0233;POWER=On
n3.test STATE=Up;UPDATETIME=1344488025;CPROC=4;CMEMORY=10239;AMEMORY=9230;CPULOAD=0.019633333333333336;POWER=On
n4.test STATE=Up;UPDATETIME=1344488025;CPROC=4;CMEMORY=10239;AMEMORY=9230;CPULOAD=0.020033333333333334;POWER=On
management.test STATE=Up;UPDATETIME=1344488025;CPROC=24;CMEMORY=49150;AMEMORY=39339;CPULOAD=0.23799999999999996;POWER=On
ldapdns STATE=Up;CONTAINERNODE=management.test;UPDATETIME=1344488025;CPROC=1;CMEMORY=1024;AMEMORY=152;CPULOAD=0.0104;POWER=On
6.4 Plugin Type Management
Plugin types may be managed and accessed with Moab Web Services dynamically, even while running. Operations are
provided to upload (add or update) plugin types and to list or show current plugin types. The available fields
that are displayed with plugin types are given in the
PluginType API. For more
information on how these fields are set, see the
Plugin Projects and Metadata section.
Plugin Type JAR or groovy files should never be manually copied into the MWS_HOME/plugins
directory.
They must be managed using the methods shown in this section or through the REST API.
6.4.1 Listing Plugin Types
To list all plugin types, browse to the MWS home page (
https://servername/mws
for example). Log in as the admin user,
then click
Plugins and then
Plugin Types.
6.4.2 Displaying Plugin Types
To show information about a plugin type, go to the
Plugin Type List page and click the desired plugin type.
6.4.3 Add or Update Plugin Types
Plugin types can be uploaded into Moab Web Services using a Groovy file, a Java Archive
(
JAR) file, or pasted Groovy code. To access the plugin type upload page,
navigate to the
Plugin Type List page and click
Add or Update Plugin Type. The default interface of this page
enables the uploading of a single Groovy class file or a JAR file.
When a plugin type is updated, by default all corresponding plugins created from the plugin type will be recreated.
If this behavior is not desired, clear the "Do you want to reload all plugins to use this new version?" checkbox before
uploading the plugin type.
Single Class File
Groovy files containing a single plugin type may be uploaded at the
/mws/admin/plugin-types/create URL.
Simply click
Add files..., select the
.groovy
class file, and click the
Start upload button. If the plugin
type was successfully uploaded and initialized, the size of the file uploaded will be displayed along with the
name of the plugin loaded.
If the upload failed or an error occurred during initialization of the plugin, an error message will be displayed.
JAR File
A JAR file as described in the
Packaging Plugins section containing one or more plugins
may also be uploaded using the same process as the Groovy file.
Click
Add files..., select the
.jar
file, and click the
Start upload button. If the upload failed or an
error occurred during initialization of the plugin(s), an error message will be displayed.
The JAR upload process differs from the single file in that if successful, the name of the JAR file itself is
displayed instead of the plugin name(s).
Code
To paste or type code directly into MWS and have it be loaded as a single class file, click
Type or Paste Code and
type or paste the code into the presented text box.
When the code is in the box, click
Create. If the upload succeeded and the code was able to be compiled as Groovy,
the browser will be redirected to the
Show Plugin Type page. If the upload failed or an error occurred during
compilation or initialization of the plugin, an error message will be displayed.
The MWS log file file may need to be referred to for additional details and error messages
in the case of a failure.
6.5 Plugin Management
Plugins may be managed and accessed with Moab Web Services dynamically, even while running. This includes
plugin instance and lifecycle management. Additionally, default configuration values may be set for new plugins.
In order to access custom web services, the REST API must be utilized as described in the
Accessing Plugin Web Services section. The available fields that are displayed
with plugins are given in the
PluginInstance API.
6.5.1 Listing Plugins
To list all plugins, browse to the MWS home page (
https://servername/mws
for example). Log in as the admin user,
then click
Plugins and then
Plugins.
6.5.2 Creating a Plugin
To create a plugin, go to the
Plugin List page and click
Add Plugin. First, a
Plugin Type must be selected to
continue to actually create the plugin.
The page is automatically built to support the
plugin type's constraints. The
ID field will be automatically filled in with a suggested
value, and the
Poll Interval field will be displayed only if the plugin type has a
poll
method. The required
configuration fields are displayed by default, and optional fields may be selected and added to the configuration
from the drop down at the top of the configuration section. See the
PluginInstance API
for more information on the fields.
6.5.3 Displaying a Plugin
To show information about a plugin, go to the
Plugin List page and click the desired plugin ID.
6.5.4 Modifying a Plugin
To modify a plugin, go to the
Plugin List page, click the desired plugin ID, and then click
Edit. See
the
PluginInstance API for more information on available fields.
6.5.5 Deleting a Plugin
To delete a plugin, go to the
Plugin List page, click the desired plugin ID, and then click
Delete. A confirmation
message is shown. If the
OK button is clicked, the plugin is deleted from the system and cannot be recovered,
including all configuration.
6.5.6 Monitoring and Lifecycle Controls
To monitor and control the lifecycle of plugins, browse to the MWS home page (
https://servername/mws
for example).
Log in as the admin user, then click
Plugins and then
Plugin Monitoring. This page displays the current state
of all plugins as well as their polling status.
If plugins are created from plugin types which do not have a poll
method, their lifecycle controls will
be limited. Any information below which mentions polling does not apply to the 'no-polling' plugin shown
in the screenshots.
Active Plugins
Active plugins are those which are in the Started or Paused states. These are available to receive
events such as polling. If paused, a plugin will not receive events but is not actually stopped, therefore
no stop events are triggered.
The following images demonstrate the status of plugins in the active states.
Started plugins which can include the relative time of the last poll as well as the time of the next poll in
a countdown format. Action buttons are available to stop or pause the plugin as well as trigger an immediate
poll event.
Paused plugins which can include only the last polling time. Action buttons are available to stop or resume
the plugin, as well as trigger an immediate poll event.
Disabled Plugins
Disabled plugins are those which are in the Stopped or Errored states. These plugins do not receive events
such as polling. If errored, a plugin may either be stopped, which represents a "clearing" of the error,
or started normally. However, if no action is taken on an errored plugin, it likely will not start due to
the fact that most plugins are put into the errored state during startup of the plugin.
The following images demonstrate the representation of plugins in the disabled states.
Stopped plugins. A single action button is available to attempt to start the plugin.
An errored plugin. As mentioned previously, action buttons are available to stop the plugin or clear the
error as well as attempt to start the plugin. If the start fails, an error message will be displayed.
6.5.7 Setting Default Plugin Configuration
Configuration of default values for plugin configuration parameters involves setting fields in the MWS configuration
file. These values are used if no values are provided when creating a new plugin. Additionally, the default values
will be displayed to the user on the
Create Plugin page.
The parameters to configure are documented on the MWS
Configuration page and comprise most values
starting with
plugins
.