Polyglot Plugin Patterns – Creating Go and Java plugins for Cloudify Pt2

In a previous post, I discussed a general approach to writing Cloudify plugins in languages other than Python. I got as far as discussing the implementation of an example Go plugin, but stopped short of describing a Java plugin, which is the topic of this post. I encourage you to read the previous post for context, as this post assumes you have that background.

Test-drive Cloudify’s Multi-Cloud Lab Free!    Get Lab

A Quick Recap

A cloudify plugin is a Python package containing Cloudify DSL definitions (typically types, relationships, and/or workflows) that permits easy re-use across blueprints. Since a plugin is a Python package, there needs to be a “bridge” of sorts between Python and the target language (Java in this case). This “bridge” takes two forms in my examples; the “operation” bridge – a small “shim” of Python code that executes a process, and the “context” bridge – a small HTTP server that permits the foreign language code to access the Cloudify context which is crucial for logging and setting and getting runtime properties.

In the case of Go code, which must be built for each target architecture, the source code itself was bundled in the plugin project. When first accessed, an executable is built which is then executed by the plugin Python “shim”.

The Java Plugin Approach

With all approaches, a non-Python plugin needs an executable to run. In the case of Java, this is an executable jar. Unlike Go, Java doesn’t need to be built per-architecture, so it can be built in advance and the resulting jar packaged with the plugin. By convention, this plugin is named plugin.jar. Look at the project example for usage. The plugin defines a single example type (in a real plugin, you’d define your own type), that includes a couple of sample properties. The part as a plugin author that you would reuse is the create lifecycle operation mapping.

    interfaces:
      cloudify.interfaces.lifecycle:
        create:
          implementation: javaplugin.java_adapter.java.calljava
          inputs:
            opclass:
              description: the java operation implementation class name
              type: string
            args:
              description: arg array to pass
              default: []

Note that the implementation definition points to a calljava function. This is the Python “shim” that ultimately calls the executable jar. The “shim” takes a couple of inputs:

  • opclass – this is the class name of the operation you want to run (more on that later)
  • args – an array of arguments to pass the opclass.

Defining Operations In Java

Included in the plugin is a sample Java Maven project (in java-src) that shows an example implementation of Java operations. It also includes a little toolkit and some conventions for plugin writing.

Plugin.java

Look down in java-src/src/main/java/com/cloudify/plugin and note the Plugin.java class. This is the home of the Java main method that serves as the entry point for the jar. The implementation is extremely simple: it takes the opclass input mentioned earlier, creates and instance of it, and passes the supplies args input as parameters. Operations are defined as classes that implement the com.def.plugin.operations.Operation class. This class defines a single execute method:

public interface Operation {
	void execute(int ctx_port, String [] args) throws Exception;
}

Note the first argument: the context port. This is the port that the context proxy mentioned earlier is listening on. It is started by the Python shim.

Operations

Operations are implementations of the Operation interface. An example is provided:

public void execute(int ctx_port, String[] args) throws Exception{
  Context ctx = new Context(ctx_port);

  ctx.call_context("instance","runtime_properties","some_prop","some_val");
  ctx.call_context("logger","info","set some_prop = some_val");
  String resp = ctx.call_context("instance", "runtime_properties", "some_prop");
  ObjectMapper mapper = new ObjectMapper();
  JsonNode root = mapper.readTree(resp);
  String val = root.findValue("payload").asText();
  ctx.call_context("logger","info","fetched some_prop = "+ val);
				
}

Note that, like the Go plugin, it is calling the Cloudify context to get and write properties and attributes, as well as log messages. This functionality is provided by a class we haven’t discussed yet: Context.

The Context Class

The last part of the little toolkit is the Context class. It’s a java implementation of a client for the Cloudify context proxy. If you recall, the context proxy is a little HTTP endpoint that translates a JSON request into Python Cloudify context calls and vice-versa. Since there is no typesafe definition of the context API, strings are used to marshal back and forth. We saw the equivalent of this in the Go example. Usage should be clear from the example.

Intended Usage Process

To use this project for your own plugins, there is an intended process:

1. Copy the java-src directory to your own Java dev environment. This directory is packaged with the plugin only for the convenience of the example. A real plugin wouldn’t have the java-src directory in it.
2. Decide what operations your plugin needs, and write them in java as implementations of the Operation interface.
3. Build an executable jar using the example Maven config ( or use something other than Maven ).
4. Place the jar in the plugin directory structure in the java_adapter directory.
5. Design your TOSCA type. Map your type operations to your Operation class names.
6. Build the plugin with wagon like any other plugin.
7. Upload it and use it.

Running The Example

To run the example, you just need a Cloudify CLI ( pip install cloudify):

  • install a JRE
  • cd to the example directory
  • run cfy init -r local-blueprint.yaml --install-plugins
  • run cfy install local-blueprint.yaml

The example just shows the operation calling the java code, accessing the proxy object to set and get runtime properties, and use Cloudify logging.

Conclusion

This post concludes a short series about writing plugins for Cloudify in languages other than Python. At this point I think a pattern should have become apparent and the same approach can be applied to other languages. With this post I think I’ve covered at least 80% of the world’s programmers language-wise. The source for the example is here. Comments and questions welcome as always.



Leave a Reply