The departments and/or individuals listed above should be notified in advance and given a sufficient time period to review this document.
Modification History
Modification history should be maintained for this document. EDCS maintains this history, the modification history table may be removed and the following note inserted where the table was. For Modification History see EDCS. Revision Date Originator Comments 0.1 3/13/2013 Parthasarathy First Draft venkatavaradhan 0.2 3/19/2013 Parthasarathy Added several sections describing the module, task, lists, Venkavaradhan tables etc.
After the requirement(s) have/has been documentent completely within the new document, the notes and coloring coding should be removed from the body of the document.
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
1 Overview
Cloupia UIC provides a rich set of functionality for Day1 and Day2 management of datacenter infrastructure. The abililty to create complex Infrastructure provisioning workflows with a simple drag-and-drop UI, has been the flaghship feature of CUIC since its first release. Though CUIC has tens of hundreds of built-in tasks that can be used for creating workflows, there are situations where the customer would want to extend the functionality and contribute tasks to the existing task library. The ability to let developers contribute to Cloupias platform is made possible by Cloupias inherent modular architecture of the platform and its features. A developer can take advantage of this and provide customization and extension by adding several components to CUIC.
2 Scope
This document covers description about how to develop a custom module using the Cloupia SDK.
3 Audience
This document is for Java developers who want to develop custom modules for extending the functionality of CUIC.
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
Make sure to include the CUIC SDK jars in your classpath. Ideally your project setup should mirror the setup we have provided in the CUIC SDK example.
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
sample classes as well with your own implementations. Otherwise you might run into some runtime exceptions preventing your module from being loaded.
Module 2
Module 3
Module 4
Tasks Tasks
Modul N
Wizards Wizards
Triggers Triggers
Annotatio Annotatio ns ns
Lists Lists
Tables Tables
Connector Connector ss
Logs Logs
Cloupias Platform architecture consists of two distinct components - the Cloupia Platform Runtime and the set of modules that gets executed on the platform. The platform itself provides necessary APIs and housekeeping functionality for the Modules so that the modules can perform the intended functions. The set of modules provide the necessary intelligence and features. The platform provides a loosely-coupled plugin architecture where modules can be developed, deployed and executed against the platform.
Copyright 2010 Cisco Systems 4 Company Highly Confidential - Controlled Access
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
This document shall describe how to develop and deploy a module that can be executed on a Cloupia Platform runtime. The development of the platform itself is available only to Engineers working on the Cloupia UIC solution and not yet for others. For further questions, please contact ask-cuic@cisco.com alias.
6 Module
A module is the top-most logical entry point into CUIC. To add or extend any functionality a module needs to be developed and deployed on the CUIC. The following steps must be performed for developing and deploying a module : Create a Module by implementing a Class that extends AbstractCloupiaModule Create Tasks by implementing necessary Task interfaces Package the module by running ANT Deploy the package on a CUIC
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
itself. A sample properties file is available in Appendix B4. NOTE: A module.properties will be provided to you by Cloupia, which should never be altered by a developer. CUIC will validate the properties file to prevent tampering. The contents include: moduleID Provides an unique readable string defining the feature status Experimental or Validated. A developer version of the module is always experimental whereas if the module is validated by Cloupia team then the status will be validated. categoryID A unique string to categorize the modules tasks in. categoryLabel A user friendly string to accompany the categoryID. org Defines the name of the organization to which this module belongs to. Sub-org Defines the name of the sub-organization to which this module belongs to. Feature for future use. Key for future use.
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
From the build.xml, run the ANT target build which will generate the necessary ZIP file and save it to the base directory of your project. If your module is dependent on jars not provided with the sample source code, you will need to make sure to include them in the build.xml so they will be included in the zip. Here is an example of how a module layout containing a third party jar would look like:
Note that the module jar and .feature are at the top level of the zip. Then you have your third party jars under /moduleID/lib/. Although not necessary it is best practice to place your third party jars under /moduleID/lib, then any other sub directories you may want to add. The corresponding .feature file will look like this:
Note that references to the jar files always start with features/, this is mandatory. In your .feature file when you list out the jars, it should always start with features/, then you can include the path to the jar you want. The path of each jar should be same as it is in your zip file. It is also best practice to lead with your module jar then its dependencies, so you ensure your module gets loaded.
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
Add Module Select the action button upload a new module. to add a new module.The the following UI form opens up to
Provide the module name and module description and upload the module zip file and submit the form. Note: For the newly added module to be loaded , the module need to be made active and CUIC services need to be restarted after upload. Activate Module To activate a module select the module in the Modules tabular report and click button on the tabular report The following for pops up.
action
Click Submit to activate the module. Deactivate Module To deactivate a module select the module in the Modules tabular report and click action button on the tabular report The following for pops up.
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
Click Submit to activate the module. Once the module is deactivated to unload the module need to restart the CUIC services. Modify Module To modify a module select the module from the Modules tabular report and click button. The following form pops up. action
We can upload new module zip file and also change the module description. We need to stop start CUIC services to unload the already loaded module and reload the new module.
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
As mentioned previously, Cloupia provides you with a properties file for validation purposes. This properties MUST be packaged at the root level of your module jar. The SDK example provides you with a build file that handles the packaging process. So in summary, there are 3 required items that need to be in place for your custom module to work. 1. A class extending AbstractCloupiaModule. 2. A .feature file specifying your dependent jars and module class. 3. A jar containing your compiled classes with the Cloupia provided properties file.
Each of the above major components shall make use of one or more of the following subcomponents: Object Store Annotations Lists Tables Connectors Logs
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
Object Store component provides simple APIs for database persistence. A module that wants to persist objects into the database shall primarily make use of the Object Store APIs to perform all the CRUD operations. CUIC uses MySQL as its database. The platform runtime makes use of Java Data Object(JDO) library provided by DataNucleus to abstract all the SQL operations through and Object Query representation. This simplifies and speeds up the development in terms of data persistence. The section shows how CRUD operations are realized using JDO.
package com.cloupia.lib.cIaaS.netapp.model; @PersistenceCapable(detachable = "true", table = foo_netapp_filer) public class NetAppFiler { Attached onon top ofof the class Attached top the class declaration. table attribute @Persistent declaration. table attribute specifies the name ofof the table to to specifies the name the table private String filerName; be used. foo is the name of the @Persistent private String accountName; @Persistent private String dcName; } The above class is annotated with two annotations @PersistenceCapable and @Persistent. These are defined in the JDO and CUIC Platform runtime expects that all persistent classes be marked with these two annotations. CUIC uses a flat schema and hence creating of a nested schema, though possible and allowed in JDO, is not recommended in a Cloupia Module.
be used. foo is the name of the module and the rest ofof the string module and the rest the string can bebe arbitrary. can arbitrary.
Attached to to the Attached the field that needs field that needs persistence persistence
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
A class that is marked with suitable JDO annotation has to be published so that the Cloupia Platform Runtime can pick up the class. The following need to be done to publish a persistence class: Create a file with the name jdo.files in the same directory (package) as that of the persistence class and add the name of the class to the file as follows: Linux# cat jdo.files
// // // // // // // // // // Copyright (C) 2010 Cisco Inc. All rights reserved. Note: all blank lines and lines that start with // are ignored Each package that has Persistable Objects shall have a file called jdo.files Each line here indicates one class that represents a persistable object. Any line that starts with a + means package name is relative to current package If a line starts without +, then it must be complete fully qualified java class name (for example: com.cloupia.lib.xyz.MyClass)
+NetAppFiler Linux#
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
8.2 Annotations
Annotations are one of the most important part of Cloupia Module development. Most of the artifacts are driven by annotations. This makes the development effort all the more easy and convenient. Annotations are used for persistence, report generation, wizard generation and tasks.
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
Please refer to the section on Marking a Class for Persistence that talks in detail about the annotations that are used for persistence.
8.3 Lists
Lists represent the drop-down list shown to the user to get inputs for a task. Developers can reuse an existing list or create their own list to show in the Task UI.
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
user * name will be used for uniqueness . */ @Override public FormLOVPair[] getLOVs(WizardSession session) { // Simple case showing hard-coded list values FormLOVPair http = new FormLOVPair("http", "HTTP"); // http is the name, HTTP is the label FormLOVPair https = new FormLOVPair("https", "HTTPs"); FormLOVPair[] pairs = new FormLOVPair[2]; pairs[0] = http; pairs[1] = https; return pairs; } }
A complex LOVProvider is also possible where the values are retrieved from the devices are from the database using the ObjStore.
8.4 Tables
Please refer to Appendix A to use some of the already existing tabular report for user selection.
8.5 Tasks
Workflow Tasks provide the necessary artifacts to contribute to the Task library maintained by the Cloupia UIC. The task can then be used in a Workflow definition.
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
8.5.1.1 TaskConfigIf
A class that implements this interface will become a Tasks input. In other words, a task that wants to accept inputs for its execution shall depend on a class that implements TaskConfigIf. The class that implements this interface shall also contain all the input field definition suitable annotated for prompting the user. The class should also have JDO annotations to enable the Platform runtime to persist this object in the database. A sample Config class is shown in Appendix B1:
8.5.1.2 AbstractTask
A task implementation shall extend the AbstractTask abstract class and shall provide implementation for all the abstract methods. This shall be the main class where all the business logic pertaining to the task go. The most important method in this class where the business logic implementation will be scripted is executeCustomAction(). Rest of the methods provide suitable context to the Platform runtime so that the task appears in the Orchestration designer tree and that it can be dragged and dropped in a Workflow. A sample AbstractTask implementation is shown in Appendix B2.
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
You must provide a PersistenceCapable annotation with a table name that is prefixed with your module ID. It is very important that you follow this convention, CUIC will prevent a task from being registered if you try to use a table name not prefixed your module ID. This is in place to prevent developers from accidentally interfering with existing CUIC tables. Next, youll see a couple of fields.
The handler name is just the name of the task, it should be a unique string, you may run into issues if you use the same name throughout multiple tasks. Every task will need to have a configEntryId and actionId, exactly as shown above. You will need corresponding getter and setters for these two fields. These two fields are absolutely mandatory, you will need to make sure you have these in your config object. Next youll see the data we actually need to perform our task.
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
So we needed the IP address of the device, in this example, we are using a LOV to get that, refer to section 8.2 for more detail about LOVs. You may also note in this example, the user input field annotation, which we will go discuss more in later sections. We also needed the login and password, that will need to entered in by the user. So we use the form field annotations to mark these fields as data that will come in for the user. You will also need accompanying getters and setters for each of these fields. With that we have declared all the data we need in our config object. Once your config object is completed, you will need to mark it for JDO enhancement. This is done by including a jdo.files file in the same package of your config objects. Take a look at the jdo.files in the SDK example. In the jdo.files (must be named exactly like this), you specify all the classes that need to go through JDO enhancement. This will be taken care of for you if you use the SDK supplied build script assuming you have followed this step properly. The handler object is where you actually execute your custom code. A handler object must implement CustomActionHandlerIf. The executCustomAction method is where you can retrieve the corresponding config object you developed previously for use to execute your code.
executeCustomAction is where all your custom logic takes place. The first thing worth noting is how you retrieve your config object. When you call context.loadConfigObject() you can cast it to the config object you defined earlier. This allows you to retrieve all the details you need to perform your task. In this example, after I get the config object, I use the SSH utilities we provide to execute the enable snmp commands (Refer to sample code itself). Something you may also note is the use of change tracker. When a workflow is rollbacked, a task must provide a way to undo the changes it has made.
In this line of code here, Im basically informing the system that the undo task of my Enable SNMP task is this Disable SNMP task. All you really need to do is provide the undo config object and its name, (the rest of the arguments are mostly just logging data, which you may or may not want to provide). Note the DisableConfig actually takes in the EnableConfig, the reason for this is because you may want the details of the task you are undoing. In my case, the enable
Copyright 2010 Cisco Systems 18 Company Highly Confidential - Controlled Access
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
config contains the device details, so when the Disable SNMP task is called I will know specifically which device to disable snmp on. You will also need to make sure to implement getTaskConfigImplementation.
As you can see here, we are just instantiating an instance of our config object in returning it. The most important thing about this method is just to make sure you are specifying the config object you intend to use with this task. After this, all you need to do is to include this task in your module and it will be ready for use in CUIC.
You can develop your own input types in CUIC. However, you need to make sure they are prefixed with your module ID. So in this example, ModuleConstants.NEXUS_DEVICE_LIST actually resolves to foo_nexus_device_list. The custom inputs registered will be available in the Workflow inputs table that appears in the second page of Workflow creation in the UI.
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
We can add as workflow input and map the input to the task having same input type as show below for the Enable SNMP Task.
The input will be taken when the workflow is executed and the same will be passed to the task as show below.
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
2. Need to implement the method getTaskoutputDefinitions() method int the task implementation and return the output definitions that the task is suppose to return.
The outputs associated with the task can be seen in the first screen of the task definition in the UI.
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
iGroupTypeList vdiCatalogList hpServerBootActionTypes netappAllClusterAssocNonAssocIfGroupList dfmStorageServiceList NetworkDevicePortProfileModeList ucsServiceProfileList hostNodeList vdiMcsCatalogTypeList netappAllClusterVLANList vdcList netappAllClusterAggrList NetworkDeviceBasicList netappAllClusterPortList MSPGroupList sizeUnitList amazonVMList ec2AccountList ucsBladeList emcSizeUnits vdiMemorySizesList VMwareDVSUplinkPortgroupList hpServerAutoPowerActionTypes hpServerBootSourceAction hpServerPowerSaverActionTypes osTypeList vdiCPUsList vscResizeDatastoreList UcsAccountList containerProvider amazonVMActions dfmGroupList hpServerPowerActionTypes kvmVMActions netappAllClusterNodeList vdiAccountList NetworkDevicePortProfileTypeList ec2VolumeSizeList vCPUsList userGroupsLOV vmwarePortGroupLoVProvider kvmVMList
NetApp Initiator Group Type VDI Catalog Selector HP Boot Mode NetApp Cluster All IfGroup Selector NetApp OnCommand Storage Services Networking Port Profile Mode UCS Service Profile Selector Host Node Selector VDI MCS Catalog Type Selector NetApp Cluster vLAN Selector vDC Selector NetApp Cluster Aggregate Selector Networking Device NetApp Cluster Port Selector MSP Group NetApp Size Units Amazon VM Selector Amazon Cloud Selector UCS Server EMC Size Units VDI Memory Size Selector VMware DVSwitch Uplink Portgroup HP Auto Power Mode HP Boot Source HP Power Saver Mode NetApp OS Type VDI CPU Selector NetApp VSC Resize Datastore Selector UCS Account Service Container Generic VM Action Selector NetApp OnCommand Groups HP Server Power Mode KVM VM Action Selector NetApp Cluster Node Selector VDI Account Selector Networking Port Profile Type EC2 Volume Size Selector vCPU Selector User Group VMware Port Group KVM VM Selector
APPENDIX B1
Class showing a sample implementation of TaskConfigIf
@PersistenceCapable(detachable = "true", table = "foo_enable_snmp_config") public class EnableSNMPConfig implements TaskConfigIf { public static final String HANDLER_NAME = "Enable SNMP for Nexus"; //configEntryId and actionId are mandatory fields @Persistent private long configEntryId; @Persistent private long actionId;
Copyright 2010 Cisco Systems 23 Company Highly Confidential - Controlled Access
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
//this is the ip address to the nexus device you want to enable snmp on @FormField(label = "Host IP Address", help = "Host IP Address", mandatory = true, type = FormFieldDefinition.FIELD_TYPE_EMBEDDED_LOV, lovProvider = ModuleConstants.NEXUS_DEVICES_LOV_PROVIDER) @UserInputField(type = ModuleConstants.NEXUS_DEVICE_LIST) @Persistent private String ipAddress = ""; @FormField(label = "Login", help = "Login", mandatory = true) @Persistent private String login; Update the getters and setters so that they return actionid and configEntryId for the return values @FormField(label = "Password", help = "Password", mandatory = true, type = FormFieldDefinition.FIELD_TYPE_PASSWORD) @Persistent private String password; // Getters and setters are removed for brevity }
APPENDIX B2
A Task implementation Class example.
public void executeCustomAction(CustomActionTriggerContext context, CustomActionLogger actionLogger) throws Exception { long configEntryId = context.getConfigEntry().getConfigEntryId(); //retrieving the corresponding config object for this handler EnableSNMPConfig config = (EnableSNMPConfig) context.loadConfigObject();
Copyright 2010 Cisco Systems 24 Company Highly Confidential - Controlled Access
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
if (config == null) { throw new Exception("No enable snmp configuration found for custom action " + context.getActionDef().getName() + " entryId " + configEntryId); } //create a ssh client with the provided credentials SSHClient client = new SSHClient(config.getIpAddress(), 22, config.getLogin(), config.getPassword()); try { logger.info("CONNECTING TO " + config.getIpAddress()); //mandatory step: connect to the device client.connect(); //open a session SshSession session = client.openShell(511, 25); //cache the session's output stream, you use this to send commands to the device OutputStream out = session.getOutputStream(); PrintWriter pw = new PrintWriter(out, true); //WARNING: THIS IS SPECIFIC TO NEXUS DEVICES!!! DON'T USE THIS FOR ANYTHING ELSE!! //wait until login is complete before executing commands //this is a hacky way to determine when the terminal is ready for input, //this loop is reading the output from the terminal and spinning until it sees the last line //of the intro message has been printed logger.info("---- WAITING FOR TERMINAL TO BE READY ----"); StringBuffer sb = new StringBuffer(); while (sb.indexOf("http://www.opensource.org/licenses/lgpl-2.1.php") != -1) { byte[] buffer = new byte[4096]; int n; InputStream in = session.getInputStream(); while ((n = in.read(buffer)) >= 0) { sb.append(new String(buffer, 0, n)); Thread.sleep(10L);
Copyright 2010 Cisco Systems 25 Company Highly Confidential - Controlled Access
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
} } logger.info("... TERMINAL READY FOR INPUT ..."); //this terminal is now ready for input, but i'm gonna wait a second just to make sure ... Thread.sleep(1000L); //go into config mode, then add enable snmp on community string "whitney" pw.print("conf t \nsnmp-server community whitney ro\n"); pw.flush(); logger.info("... ENABLE SNMP COMMAND EXECUTED ..."); //wait for another 2.5 seconds just to make sure everything is clean Thread.sleep(2500L); //make sure to disconnect from the device for proper clean up client.disconnect(); actionLogger.addInfo("SNMP enabled on " + config.getIpAddress()); //if user decides to rollback a workflow containing this task, then using the change tracker //we can take care of rolling back this task (i.e, disabling snmp) context.getChangeTracker().undoableResourceAdded("assetType", "idString", "SNMP enabled", "SNMP enabled on " + config.getIpAddress(), DisableSNMPConfig.HANDLER_NAME, new DisableSNMPConfig(config)); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); actionLogger.addWarning("Action " + EnableSNMPConfig.HANDLER_NAME + ":" + e.getMessage()); } }
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
@Override public WorkFlowStep redfineWorkFlowStep(CustomActionTriggerContext context) throws Exception { // TODO Auto-generated method stub return null; } @Override public String getTaskName() { return EnableSNMPConfig.HANDLER_NAME; } Provide more details as to how to define task output using TaskOutputDefinition @Override public TaskOutputDefinition[] getTaskOutputDefinitions() { return null; } @Override public TaskConfigIf getTaskConfigImplementation() { return new EnableSNMPConfig(); } }
APPENDIX B3
A sample implementation of Module
public class FooModule extends AbstractCloupiaModule { @Override public AbstractTask[] getTasks() { AbstractTask task1 = new EnableSNMPHandler(); AbstractTask task2 = new DisableSNMPHandler(); AbstractTask[] tasks = new AbstractTask[2]; tasks[0] = task1; tasks[1] = task2;
Copyright 2010 Cisco Systems 27 Company Highly Confidential - Controlled Access
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.
return tasks; } }
APPENDIX B4
A sample module.properties file
APPENDIX B5
A sample .feature file. The name of the file will be <moduleID>-module.feature
A printed copy of this document is considered uncontrolled. Refer to the online version for the controlled revision.