Pattern-type (Part 16): Create a plugin that modify automatically all generated Topology for SCAS

Somebody asked me the following question:

“I have to create a framework for future development of pattern-type, each server generated by plugins in this framework must have a specific software when a given role is used. I would like this behavior as much as possible transparent for the developer who use this framework. I think I have to use the TopologyProcessor”.

That’s right, you have to create a TopologyProcessor which will modify the generated topology of the plugin to insert the “role” which will make the generic configuration.

You can set your framework plugin to adjust all generated topologies by set the primary pattern-type as “foundation” and only some of them by adding a kind of filter in your topology framework.

Let’s look at the following scenario:
I want a plugin that runs some python scripts each time a topology containing the role “slave” is generated. I will call this plugin “plugin.mynameFoundationLab”, it will contain a role “config” and a topology processor. The topology processor will scan the generated topology and if the “slave” is found, it will insert my “config” role, the related package. You can also play with the dependency and standard IWD Python scripts to have all scripts running in the correct order (See Deployment Documentation) but you will certainly then have to provide also some guidance to the pattern-type developers to be sure that the “config” scripts are called at the correct moment.

Here are the steps:
1) Create a new plugin called “plugin.mynameFoundationLab”.
2) Create a new part called “configure.scripts”
3) Adapt the config.json to include the part.
4) Create a new role called “config”.
5) Create a new OSGI Service Component for the TopologyProcessor.
6) Adapt the TopologyProcessor java class to modify the generated topology.
7) Test

Create a new plugin:
Please visit Part 1 to learn how to do that. Call the plugin “plugin.mynameFoundationLab”.
PS: The PDK 1.0.0.4 version has a small defect, go to the MANIFEST.MF and remove the “singleton” part.

Now you have to change the config.json to specify the related pattern-type. We must put as primary pattern-type the “foundation” and as we want to scan all pattern-types to search for the “slave” role, the secondary must be set as *.*. If you know the pattern-types you want to scan list here the concerned pattern-type.

   "patterntypes": {
      "primary": {
         "foundation": "2.0"
      },
      "secondary": [
        {
          "*": "*"
    	}
      ]
    }

Create a new part:
Right-click on the plugin directory and select “New” -> “Plug-in Part” and enter the name as follow:

SCAWS TopologyProcess New PartAdapt the config.json to enter this new part:

   "packages": {
      "PLUGIN.CONFIGURATION": [
         {
            "parts": [
               {
                  "part": "parts\/configuration.scripts.tgz"
               }
            ]
         }
      ]
   }

Create a new role:
Using the PDK, right-click and select “New”->”Plug-In Role” and enter the following information.
SCAWS TopologyProcess Create Role Create the OSGI Service Component for TopologyProcessor:
Right-click, “New” -> “OSGI Service Component”… and enter the following information:SCAWS TopologyProcessor CreateOSGIAdapt the TopologyProcessor Java class:

When we created the OSGI service automatically a java class called “ConfigurationTopologyProcessor” has been created. We have to implement our business logic in the “processTopology” method of this class. In our example, we decided to modify the topology only if the role “slave” is present, you could choose for other logic based on other attributes of the topology.

Here the code I used to add the role and package.

	public JSONObject processTopology(JSONObject topology) throws Exception {
		//Log the received topology
		logger.info("topology:" + topology);
		//Retrieve the "vm-templates" element.
		JSONArray vmTemplates = (JSONArray) topology.get("vm-templates");
		//For each template in the vm-templates element.
		for (Object vmTemplateObj : vmTemplates) {
			JSONObject vmTemplate = (JSONObject) vmTemplateObj;
			//Retrieve the roles element
			JSONArray roles = (JSONArray) vmTemplate.get("roles");
                        //Search for the "slave" role.
			boolean found = false;
			for (Object obj : roles) {
				JSONObject role = (JSONObject) obj;
				String type = (String) role.get("type");
				if ("slave".equals(type)) {
					found = true;
					break;
				}
			}
			//If the "slave" role exists add the package and the "config" role.
			if (found) {
				JSONArray packages = null;
				//If the packages element doesn't exist then create it.
				if (vmTemplate.containsKey("packages")) {
					packages = (JSONArray) vmTemplate.get("packages");
				} else {
					packages = new JSONArray();
					vmTemplate.put("packages", packages);
				}
				//add the package for the config plugin.
				packages.add("PLUGIN.CONFIGURATION");
				//Create the config role.
				JSONObject configRole = new JSONObject();
				configRole.put("type", "config");
				configRole.put("name", "config");
				configRole.put("global", true);
				configRole.put("plugin", getPluginName() + "/"
						+ getPluginVersion());
				configRole.put("dashboard.visible", false);
				//Add the config role to the set of roles.
				roles.add(configRole);
				//log the modified topology.
				logger.info("topology Modified:" + topology);
			}
		}
		return topology;
	}

Some remarks on this code:
– the “global”:true means that the role will be shown only once in UI in case of multiple instance of the “config” role will be generated.
– the “dashbord.visible”:false means that the role “config” must not be shown in the UI.
– You will notice also the usage of the getPluginName() and getPluginVersion() methods usage which are methods provided by the TopologyProcessor class. These methods allow me to retrieve the current value of the plugin name and version in order to construct the correct plugin path.

N.B.: if you want to build a dependency, don’t forget that the role of the dependency must be the concatenation of the template name where the role is defined + “.” + role.

Test:
Let’s build our plugin and upload it in the environment as a System-plugin.
Then let’s launch the pattern containing a “slave” role as described in the previous exercise.
Once the deployment is launched, we can directly look at the generated topology and we will see:
In the parts section:

{

    "part": "https://127.0.0.1:9444/storehouse/admin/plugins/plugin.mynameFoundationLab/1.0.0.16/parts/configuration.scripts.tgz"

}

In the packages section:

packages": [

    "PLUGIN.SLAVE",
    "logbackup",
    "AUTOSCALING",
    "SSH",
    "MAINTENANCE",
    "sharedservice",
    "MONITORING",
    "AGENT",
    "PLUGIN.CONFIGURATION"

],

In the roles section:

{

    "parms": {
        "DirToMonitor": "/home/idcuser/files",
        "SlaveAttr1": "Test"
    },
    "type": "slave",
    "name": "slave"

},
...
{

    "plugin": "plugin.mynameFoundationLab/1.0.0.16",
    "global": true,
    "dashboard.visible": false,
    "type": "config",
    "name": "config"

}

Once the pattern is deployed, you can look at the logs:
– config:

[Fri 01 Mar 2013 02:10:09 PM UTC] config/start.py 140099705157376 pid=30233 DEBUG Start Config

– slave:

[Fri 01 Mar 2013 02:10:10 PM UTC] slave/start.py 139730821113600 pid=30245 DEBUG Start Slave Test