Skip to content

How to create a Java Plugin

Follow below steps to create an MCT Java Plugin.

1. Fork and clone Git repo mct-java-plugin (if you have not done yet)

  1. Clone the origin repo to your local laptop: git clone git@sqbu-github.cisco.com:Monitoring/mct-java-plugin.git
  2. Click Forks button on the right top, and you will get your own fork.
  3. Add your own fork to your local remote: git remote add {your-cec} git@sqbu-github.cisco.com:{your-cec}/mct-java-plugin.git

2. Create a fresh maven project

Create a new maven project under folder plugins/java/, then assign groupId, artifactId, version and any other needed properties to it. Below is an example of a Hello World plugin:

mvn archetype:generate \
  -DgroupId=com.webex.mct \
  -DartifactId=hello-world \
  -Dversion=1.0.0 \
  -DinteractiveMode=false \

Of course, you can also create it from you IDE (Intellij or eclipse).

Now, your pom.xml file should look like this:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.webex.mct</groupId>
  <artifactId>hello-world</artifactId>
  <packaging>jar</packaging>
  <version>1.0.0</version>
  <name>hello-world</name>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

If need to define a script file to execute during Java Plugin installation (e.g. scheduleMeetingMonitor Java Plugin setup.sh installs chrome) , the script file should meet new cloud agent requirement.


3. Inherit pom java-plugin-parent

Then, you need to inherit a parent pom java-plugin-parent, which provides some templates and methods, so that the plugin could be recognized by MCT Platform. The pom is located on engci-maven repo, so you need to add this repo address too.

Now, your pom.xml file should look like this:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.webex.mct</groupId>
  <artifactId>hello-world</artifactId>
  <packaging>jar</packaging>
  <version>1.0.0</version>
  <name>hello-world</name>

  <parent>
    <groupId>com.webex.mct</groupId>
    <artifactId>java-plugin-parent</artifactId>
    <!-- please use the latest version of "java-plugin-parent" -->
    <version>1.1</version>
  </parent>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <!-- add engci maven repo, so that "java-plugin-parent" could be downloaded  -->
  <repositories>
    <repository>
      <id>bms-artifactory-rel</id>
      <name>bms-artifactory-releases</name>
      <url>https://engci-maven.cisco.com/artifactory/cscmse-release</url>
    </repository>
  </repositories>
</project>

Currently, Java Plugin still need to overwrite java-plugin-parent rpm-maven-plugin section to define correct rpm package name for plugin release (e.g. WBXmctPluginjmeter) . MCT is working on automating this work.


4. Start develop your own plugin logic in Java

After that, you can customize your own logic now.

4.1 Define input arguments

There will be two types of input arguments: dynamic (passed from command line) and static (read from configuration file). For easier understanding, let's take an example. Assuming that you want to develop an url check plugin, which simple tests if target url is working, the brief logic would be like below.

  1. visit target url.
  2. get response (need two parameters ).
  3. check if response content contains certain pattern.

In this case, the dynamic arguments are url (the api address) and positiveMatch (pattern), and the static arguments can be connectTimeout and socketTimeout (parameters of http client).

4.1.1 Define dynamic arguments (if any)

In Java Plugin, the dynamic arguments are called Parameter, they are defined in a parameters.xml file (default location is resources/parameters.xml), below is an example from url check plugin:

<server-template>
  <parameter name="url" key="url" type="text" desc="target url" check-regular="" required="true"></parameter>
  <parameter name="positiveMatch" key="positiveMatch" type="text" desc="positive match of content" check-regular=""></parameter>
</server-template>

Above file defines two parameters: url and positiveMatch, Read Plugin Parameter Manual for more details about how to define more complicated parameters.

In addition, you need a Java Entity to hold those parameters at run time, on this example, the entity is:

// entity to hold command line arguments
public class Parameter {
    private String url;
    private String positiveMatch;
    // getter and setter
}

4.1.2 Define static properties (if any)

In Java Plugin, the static arguments are called Configuration, and they should be defined in a config.properties file, which located under resources folder. below is an example from url check plugin:

connectionTimeout = 10000
socketTimeout = 10000

Above file defines two parameters: connectTimeout and socketTimeout.

Same as dynamic arguments, you need a Java Entity to hold those parameters at plugin run time, on this example, the entity is:

// entity to hold configuration properties
public class Config {
    private int connectionTimeout;
    private int socketTimeout;
    // getter and setter
}

Read Input Arguments for Java Plugin for more details about support arguments format.

4.2 Extend AbstractWMSPlugin

Create a class (let's say App.java) and extend AbstractWMSPlugin, which is the abstract layer of all Java Plugin, and override execute() method. If there do have dynamic / static arguments, you can provide your Parameter and Configuration classes as Generic Types while extending, hence instances of them with populated value will be passed to execute() method as arguments. Example in our url check plugin:

// define "Parameter" and "Config" as generic type
public class App extends AbstractWMSPlugin<Parameter, Config> {
    // instances of "Parameter" and "Config" will be passed to this callback method
    @Override
    int execute(Parameter parameter, Config config) {
    }
}

4.3 Write plugin logic

Next steps, you can write your own monitor logic in execute() method now. You can customize whatever test logic you like, meanwhile, please keep in mind for below few rules.

4.3.1 Return a code as test result

An integer should be returned in execute() method, this is the exit code of your plugin, please make sure returns the correct value, which will impact the monitoring result in MCT Platform, below are the pre-defined values:

  • 0: test pass. you should always return this value if everything runs as expected.
  • 5: test failed due to plugin issue (not target service's issue). if plugin exit unexpectedly, such as ended up with an OutOfMemoryError, which means the error does not related to target service being tested, you should return this value.
  • 478 / 1157 / 1205 / 493: test no result. usually you don't need to handle this case, just don't use those error code.
  • > 10000: test timeout. if the test timeout, you can return this value. also return a self defined error code in this case is legal too.

Apart from above values, all other code would be considered as an error, you can define any error code you like. Example in our url check plugin:

public enum ErrorCode {
    SERVICE_UNAVAILABLE(1001),
    SERVICE_ABNORMAL(1002),
    SERVICE_RUNNING(0);

    private int code;
    ErrorCode(int code) {
        this.code = code;
    }

    public int getCode() {
        return code;
    }
}

4.3.2 Print needed log

There is a utility class named PluginLogger to handle log printing. it supports writing messages to log file (on server), and printing information to MCT Platform (MCT page). Use print() and println() to print your log to page; use debug(), info(), warn() and error() to print your log to file, see How to Use PluginLogger. Please make sure all needed tracking information being printed, especially there is a test error. Highly recommend to write message / stacktrace whenever error occurred. Example in our url check plugin (pay attention on PluginLogger):

// 1. visit target url
// 2. check if response content matches certain pattern.
@Override
int execute(Parameter parameter, Config config) {
    // 1. create a http client
    RequestConfig requestConfig = RequestConfig.custom()
            .setConnectionRequestTimeout(config.getConnectionTimeout())
            .setConnectTimeout(config.getConnectionTimeout())
            .setSocketTimeout(config.getSocketTimeout()).build();
    HttpClient httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).build();

    PluginLogger.info("Visiting target url: " + parameter.getUrl());// track information
    HttpGet request = new HttpGet(parameter.getUrl());
    HttpResponse response; String result;
    try {
        // 2. visit target url
        response = httpClient.execute(request);
        // 3. handle for different cases
        result = EntityUtils.toString(response.getEntity());
    } catch (IOException e) {
        // '.error()' will print message to log file on server
        PluginLogger.error("Visit target url " + parameter.getUrl() + " failed.", e);
        // '.println()' will print message detail status page on MCT
        PluginLogger.println("Failed to visit target url: " + parameter.getUrl());
        return ErrorCode.SERVICE_UNAVAILABLE.getCode();
    }
    PluginLogger.debug("Target url returns: " + result);// track information
    if (response.getStatusLine().getStatusCode() != HttpURLConnection.HTTP_OK) {
        PluginLogger.error("Visit target url returns error " + response.getStatusLine().getStatusCode() + ", " + result);
        PluginLogger.println("Failed to visit target url.");
        return ErrorCode.SERVICE_UNAVAILABLE.getCode();
    } else {
        if (result.contains(parameter.getPositiveMatch())) {
            PluginLogger.println("Response contains target pattern [" + parameter.getPositiveMatch() + "], test pass.");// track information
            return ErrorCode.SERVICE_RUNNING.getCode();
        } else {
            PluginLogger.println("Response DOESN'T contains target pattern [" + parameter.getPositiveMatch() + "]");
            PluginLogger.error("Response: [" + result + "], provided pattern: [" + parameter.getPositiveMatch() + "]");
            return ErrorCode.SERVICE_ABNORMAL.getCode();
        }
    }
}

4.4 Test plugin logic

After writing plugin logic, you may need to run and test if it works normally. To do this, you can add a main() method, and pass required parameters in a string array. Example:

public static void main(String[] args) {
    // customize your input parameters here
    String[] paramArray = new String[]{"-url", "https://mct.webex.com/healthcheck", "-positiveMatch", "OKOKOK"};
    // below are fixed format
    int result = new App().launch(paramArray, System.out);
    System.exit(result);
}

Of course, you can pass parameters through Program arguments on your IDE, if so, simply change new App().launch(paramArray, System.out) to new App().launch(args, System.out) (using parameter args of main() method).

Run and debug your plugin to make it all works fine!

5. Provide needed metadata of your plugin

Assuming the plugin all works good, you are almost all set! but you still need to provide some metadata (properties in pom.xml), to help MCT Platform better identify and execute your plugin.

  1. plugin's main class (plugin.mainClass): required, MCT Platform need to know where is the plugin's entrance.
  2. plugin's provider (plugin.provider): required, MCT Platform need to know who is the author of the plugin.
  3. location of plugin's parameter file (plugin.parameterFileLocation): optional if you put your parameters.xml under folder resources; required if you put your parameter file on a different location, you can provide an absolute path, or a relative path based on the project's root (e.g. src/main/resources/parameters.xml).
  4. plugin's runbook (plugin.runbook): optional, wiki / doc of your plugin.
  5. version of "plugin-manager" (pluginManager.version): optional, default is the latest version.

Example pom.xml of url check plugin:

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>

    <!-- latest version -->
    <pluginManager.version>1.1.0</pluginManager.version>

    <plugin.mainClass>com.webex.mct.App</plugin.mainClass>
    <plugin.provider>Rocky Chi</plugin.provider>
    <plugin.parameterFileLocation>src/main/resources/parameters.xml</plugin.parameterFileLocation>
    <plugin.runbook>developer: zhochi@cisco.com, home page: https://sqbu-github.cisco.com/Monitoring/mct-java-plugin/tree/main/plugins/examples/hello-world</plugin.runbook>
  </properties>

6. Commit your code and raise a PR

Finally, your fantastic plugin is ready to go, let's integrate it to MCT Platform! Again, please make sure your plugin project is under folder plugins/java/, then commit your code and raise a PR to merge into Monitoring/mct-java-plugin/main. last step, inform MCT Engineer to have a code review (either send the PR link to Teams Room Ask MCT or send a mail to csg-hz-mct@cisco.com would work).

7. Configure your test on MCT

MCT Engineer will help register the plugin to MCT Platform, after that, you are able to configure real tests on MCT, enjoy monitoring:-)

Reference

hello-world-example