Starting 4.1, everything that provides an API in CloudStack is a PluggableService. This means all APIs exposed by plugins such as ACL, network, hypervisors and even ManagementServer is a PluggableService, and all PluggableService expose an interface "getCommands()" which returns a list of api cmd classes. This helps other components within CloudStack to discover each other, for example ApiServer and ApiDiscoveryService uses this interface to get commands provided by all the PluggableService implementations.
How to write an Apache CloudStack 4.1+ API:
1. Create a directory off of the plugins folder. The name does not matter. Example: cloudstack/plugins/api/timeofday
2. Create a pom.xml in cloudstack/plugins/api/timeofday. Make sure your "parent" relative path points back to plugins/pom.xml, otherwise it won't build. The main entries that matter are the artifactId (cloud-plugin-timeofday), as well as the dependencies.
Code Block |
---|
lang | xml |
---|
title | plugins/api/timeofday/pom.xml |
---|
borderStyle | solid |
---|
|
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-plugin-api-timeofday</artifactId>
<name>Apache CloudStack Plugin - TimeOfDay</name>
<parent>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloudstack-plugins</artifactId>
<version>4.1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-utils</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${cs.mysql.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<defaultGoal>install</defaultGoal>
<sourceDirectory>src</sourceDirectory>
<testSourceDirectory>test</testSourceDirectory>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
<executions>
<execution>
<phase>integration-test</phase>
<goals>
<goal>test</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
|
3. Add a module entry in the modules section of cloudstack/plugins/pom.xml for the new plugin, which will let mvn know that the plugin module needs to be built:
Code Block |
---|
|
<module>api/timeofday</module>
|
4. Now create a src, target and test directory off of cloud-plugin-timeofday.
5. Create your code hierarchy ie: com.company.gadget.plugin. For this example I use plugins/api/timeofday/src/com/cloud/test
6. Create an interface that extends from PluggableService
Code Block |
---|
lang | java |
---|
title | plugins/api/timeofday/src/com/cloud/test/TimeOfDayManager.java |
---|
borderStyle | solid |
---|
|
package com.cloud.test;
import com.cloud.utils.component.PluggableService;
public interface TimeOfDayManager extends PluggableService { }
|
7. Create an implementation of your newly created interface, overriding the getCommands method, and populating it with the classes of each command you want to expose.
Code Block |
---|
lang | java |
---|
title | plugins/api/timeofday/src/com/cloud/test/TimeOfDayManagerImpl.java |
---|
borderStyle | solid |
---|
|
package com.cloud.test;
import com.cloud.utils.component.PluggableService;
import java.util.List;
import java.util.ArrayList;
import org.apache.log4j.Logger;
import com.cloud.test.GetTimeOfDayCmd;
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Component;
import javax.ejb.Local;
@Component
@Local(value = { TimeOfDayManager.class })
public class TimeOfDayManagerImpl implements TimeOfDayManager {
private static final Logger s_logger = Logger.getLogger(TimeOfDayManagerImpl.class);
public TimeOfDayManagerImpl() {
super();
}
@Override
public List<Class<?>> getCommands() {
List<Class<?>> cmdList = new ArrayList<Class<?>>();
cmdList.add(GetTimeOfDayCmd.class);
return cmdList;
}
}
|
8. Write a command class that implements the correct annotations for a command:
Code Block |
---|
lang | java |
---|
title | plugins/api/timeofday/src/com/cloud/test/GetTimeOfDayCmd.java |
---|
borderStyle | solid |
---|
|
package com.cloud.test;
import javax.inject.Inject;
import org.apache.log4j.Logger;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.Parameter;
@APICommand(name = "getTimeOfDay", description="Get Cloudstack's time of day", responseObject = GetTimeOfDayCmdResponse.class, includeInApiDoc=true)
public class GetTimeOfDayCmd extends BaseCmd {
public static final Logger s_logger = Logger.getLogger(GetTimeOfDayCmd.class.getName());
private static final String s_name = "gettimeofdayresponse";
@Parameter(name="example", type=CommandType.STRING, required=false, description="Just an example string that will be uppercased")
private String example;
public String getExample() {
return this.example;
}
@Override
public void execute()
{
GetTimeOfDayCmdResponse response = new GetTimeOfDayCmdResponse();
if ( this.example != null ) {
response.setExampleEcho(example);
}
response.setObjectName("timeofday"); // the inner part of the json structure
response.setResponseName(getCommandName()); // the outer part of the json structure
this.setResponseObject(response);
}
@Override
public String getCommandName() {
return s_name;
}
@Override
public long getEntityOwnerId() {
return 0;
}
}
|
9. Write a Response class for the command:
Code Block |
---|
lang | java |
---|
title | plugins/api/timeofday/src/com/cloud/test/GetTimeOfDayCmdResponse.java |
---|
borderStyle | solid |
---|
|
package com.cloud.test;
import org.apache.cloudstack.api.ApiConstants;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
import org.apache.cloudstack.api.BaseResponse;
import java.util.Date;
import java.text.SimpleDateFormat;
@SuppressWarnings("unused")
public class GetTimeOfDayCmdResponse extends BaseResponse {
@SerializedName(ApiConstants.IS_ASYNC) @Param(description="true if api is asynchronous")
private Boolean isAsync;
@SerializedName("timeOfDay") @Param(description="The time of day from CloudStack")
private String timeOfDay;
@SerializedName("exampleEcho") @Param(description="An upper cased string")
private String exampleEcho;
public GetTimeOfDayCmdResponse(){
this.isAsync = false;
SimpleDateFormat dateformatYYYYMMDD = new SimpleDateFormat("yyyyMMdd hh:mm:ss");
this.setTimeOfDay( (new StringBuilder( dateformatYYYYMMDD.format( new Date() ) )).toString() );
}
public void setAsync(Boolean isAsync) {
this.isAsync = isAsync;
}
public boolean getAsync() {
return isAsync;
}
public void setTimeOfDay(String timeOfDay) {
this.timeOfDay = timeOfDay;
}
public void setExampleEcho(String exampleEcho) {
this.exampleEcho = exampleEcho.toUpperCase();
}
}
|
10. Update client/tomcatconf/componentContext.xml.in and add your new manager to its configuration:
Code Block |
---|
|
<bean id="timeOfDayManagerImpl" class="com.cloud.test.TimeOfDayManagerImpl"> </bean>
|
11. Update client/tomcatconf/commands.properties.in, and add the command name (for the example this would be getTimeOfDay as stated in the @APICommand annotation).
12. Along with this you need to add your plugin as a dependency to client/pom.xml. You will see examples within the pom, you will require your plugins artifact ID.
13. You can test your new command via the browser, cloudmonkey or even curl. Here is a simple perl script that shows how to test the new getTimeOfDay command.
Code Block |
---|
lang | javascript |
---|
borderStyle | solid |
---|
|
#!/usr/bin/env perl
use strict;
use Data::Dumper;
use LWP::Simple;
use Mojo::JSON 'j';
my $content = j(get("http://localhost:8096/client/api?command=getTimeOfDay&response=json&example=moo"));
print Dumper($content);
|
And here is the output
Code Block |
---|
lang | javascript |
---|
borderStyle | solid |
---|
|
$VAR1 = {
'gettimeofdayresponse' => {
'timeofday' => {
'timeOfDay' => '20130404 09:24:00',
'exampleEcho' => 'MOO',
'isasync' => bless( do{\(my $o = 0)}, 'Mojo::JSON::_Bool' )
}
}
};
|