转载来源:CloudStack中文社区
转载链接地址:http://www.cloudstack-china.org/2013/12/2851.html
CloudStack4.3离发布也不远了,自从CloudStack4.1以后,其耦合度一步步下降,这使开发变得更加容易,今天我们就以CloudStack4.3版本为基础,来感受一下如何添加一个新的API。
首先,CloudStack4.3里所有的API都可认为是一个插件提供的服务,诸如ACL,网络,主机以及管理服务器;并且这些服务在启动的时候会自动发现并添加。那么我们该如何在CloudStack4.3下编写API呢?我们接下来从一个简单的例子入手来一步步实现。
需求:调用API获得管理服务器的系统信息,比如操作系统,CPU,内存等信息。
API名称:getMgmtCapacity
CloudStack下的开发假定你熟悉Java,Maven以及Eclipse集成开发环境。
如上所说,CloudStack里所有的API都是插件服务了,与其它模块的依赖关系没那么强,因此,除了要加入工程编译动到其它的部分,基本上不需要依赖其它的模块。以下是具体的步骤,如何添加新的API到CloudStack工程:
我们假定CloudStack的源代码根目录是/opt/cloudstack, 我们用git clone源码后,通过git checkout 4.3来转到4.3的分支,然后我们要为开发的插件创建一个新的目录,是/opt/cloudstack/plugins/api/mgmtcap
执行: cd /opt; git clone https://git-wip-us.apache.org/repos/asf/cloudstack.git 然后执行: git checkout 4.3在/opt/cloudstack/plugins/api/mgmtcap下新建一个Maven项目文件pom.xml,内容如下(去掉ACL2.0的声明):
< 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- mgmtcap</ artifactId> < name>Apache CloudStack Plugin – Management Capacity</name> < parent> < groupId>org .apache . cloudstack</ groupId> < artifactId> cloudstack-plugins</ artifactId> < version>4.3.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> </ dependencies> < build> < defaultGoal>install</ defaultGoal> < sourceDirectory> src</ sourceDirectory> < testSourceDirectory>test</ testSourceDirectory> < plugins> < plugin> < groupId>org .apache .maven .plugins</ groupId> < 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>在/opt/cloudstack/plugins/pom.xml文件中添加一行,这样Maven知道我们新加的模块也要加入编译:
<module>api/mgmtcap</module>
创建Maven标准的源码目录结构,需要创建src, target和test目录
创建resource目录,这个目录是为了Spring框架自动发现你的服务而设计的,里面有你的Bean的定义
创建你的API代码架构,比如org.apache.cloudstack.mgmtcap以及CloudStack下基本的API代码框架,比如org.apache.cloudstack.api.command以及response,最终你的目录树看起来是这个样子的:
├── pom.xml ├── resources │ └── META-INF │ └── cloudstack │ └── mgmtcap │ ├── module.properties │ └── spring- mgmtcap-context .xml ├── src │ └── org │ └── apache │ └── cloudstack │ ├── api │ │ ├── command │ │ │ └── user │ │ │ └── GetMgmtCapacityCmd.java │ │ └── response │ │ └── GetMgmtCapacityCmdResponse.java │ └── mgmtcap │ ├── ApiMgmtCapacityService.java │ └── ApiMgmtCapacityServiceImpl.java ├── target └── test对于以上的目录及文件结构,我们来分析一下各文件的作用
1) pom.xml是我们新加的API模块的Maven项目配置文件,用于管理这个API的整个生命周期,其内容已经在前面展现。
2) resources/META-INF/cloudstack/mgmtcap目录下的两个文件,用于标识新的的模块属于哪个父模块,以及Spring实例化Bean;
module.properties内容:
name=mgmtcap parent=apispring-mgmtcap-context.xml内容:
<beans xmlns=”http://www.springframework.org/schema/beans” xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance” xmlns:context=”http://www.springframework.org/schema/context” xmlns:aop=”http://www.springframework.org/schema/aop” xsi:schemaLocation=”http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd” > <bean id=”apiMgmtCapacityServiceImpl“/> </beans>3) 在src目录的API的命令及响应源码:
API Cmd:org/apache/cloudstack/api/command/user/GetMgmtCapacityCmd.java
API Response:org/apache/cloudstack/api/response/GetMgmtCapacityCmdResponse.java
由于我们的API命令允许普通用户调用,所有约定其所在的包是org.apache.cloudstack.api.command.user; 以下是GetMgmtCapacityCmd.java的源码,忽略ACL2.0的许可声明:
package org.apache.cloudstack.api.command.user; import javax . inject .Inject; import org .apache . cloudstack . api .APICommand; import org .apache . cloudstack . api .BaseCmd; import org .apache . cloudstack . api .ServerApiException; import org .apache . cloudstack . api .response .GetMgmtCapacityCmdResponse; import org .apache . cloudstack . mgmtcap .ApiMgmtCapacityService; import org .apache .log4j .Logger; @APICommand (name = “ getMgmtCapacity“, responseObject = GetMgmtCapacityCmdResponse .class, description = “get management server capacity information by ApiMgmtCapacityService plugin”, since = “4.2.0″) public class GetMgmtCapacityCmd extends BaseCmd { public static final Logger s_logger = Logger . getLogger (GetMgmtCapacityCmd .class . getName ()); private static final String s_name = “ getmgmtcapacityresponse“; @Inject ApiMgmtCapacityService _apiMgmtCapacityService; @Override public void execute () throws ServerApiException { GetMgmtCapacityCmdResponse response = new GetMgmtCapacityCmdResponse (); response . setObjectName (“ mgmtcapacity“); response . setResponseName ( getCommandName ()); _apiMgmtCapacityService . formatMgmtServerCapacity (response); this . setResponseObject (response); } @Override public String getCommandName () { return s_name; } @Override public long getEntityOwnerId() { // no owner is needed for list command return 0; } }以下是响应文件GetMgmtCapacityCmdResponse.java的源码内容:
package org.apache.cloudstack.api.response; import org.apache.cloudstack.api.ApiConstants; import com.cloud.serializer.Param; import com.google.gson.annotations.SerializedName; import org.apache.cloudstack.api.BaseResponse; import org .apache . cloudstack . api .BaseCmd; import org .apache . cloudstack . api .APICommand; import org .apache . cloudstack . api .ServerApiException; import java.util.HashSet; import java.util.Set; @SuppressWarnings(“unused”) public class GetMgmtCapacityCmdResponse extends BaseResponse { @SerializedName(ApiConstants.NAME) @Param(description=”the name of the api command”) private String name; @SerializedName(ApiConstants.DESCRIPTION) @Param(description=”description of the api”) private String description; @SerializedName(ApiConstants.SINCE) @Param(description=”version of CloudStack the api was introduced in”) private String since; @SerializedName(ApiConstants.IS_ASYNC) @Param(description=”true if api is asynchronous”) private Boolean isAsync; @SerializedName(ApiConstants.TYPE) @Param(description=”response field type”) private String type; @SerializedName(“cpucore”) @Param(description=”cpu core number”) private Integer cpucore; @SerializedName(“memtotal”) @Param(description=”total memory in MB”) private Integer memtotal; @SerializedName(“memfree”) @Param(description=”free memory in MB”) private Integer memfree; @SerializedName(“osname”) @Param(description=”the os name”) private String osname; @SerializedName(“osversion”) @Param(description=”the os version”) private String osversion; public GetMgmtCapacityCmdResponse(){ isAsync = false; since = “4.2.0″; } public void setName(String name) { this.name = name; } public String getName() { return name; } public void setDescription(String description) { this.description = description; } public String getDescription() { return description; } public void setSince(String since) { this.since = since; } public String getSince() { return since; } public void setAsync(Boolean isAsync) { this.isAsync = isAsync; } public boolean getAsync() { return isAsync; } public void setCpuCore(Integer num){ this.cpucore = num; } public Integer getCpuCore(){ return this.cpucore; } public void setOsName(String name){ this.osname = name; } public String getOsName(){ return this.osname; } public void setOsVersion(String version){ this.osversion = version; } public String getOsVersion(){ return this.osversion; } public void setMemTotal(Integer total){ this.memtotal = total; } public Integer getMemTotal(){ return this.memtotal; } public void setMemFree(Integer free){ this.memfree = free; } public Integer getMemFree(){ return this.memfree; } } 接下来我们看一下API的接口与实现的源码,接口文件: org/apache/cloudstack/mgmtcap/ApiMgmtCapacityService.java: package org.apache.cloudstack.mgmtcap; import org .apache . cloudstack . api .response .GetMgmtCapacityCmdResponse; import com.cloud.utils.component.PluggableService; public interface ApiMgmtCapacityService extends PluggableService { public void formatMgmtServerCapacity(GetMgmtCapacityCmdResponse response); }
实现文件:
org/apache/cloudstack/mgmtcap/ApiMgmtCapacityServiceImpl.java:
package org.apache.cloudstack.mgmtcap; import java.util.ArrayList; import java.util.List; import javax.ejb.Local; import org.apache.cloudstack.api.command.user.GetMgmtCapacityCmd; import org .apache . cloudstack . api .response .GetMgmtCapacityCmdResponse; import org .apache .log4j .Logger; import org.springframework.stereotype.Component; @Component @Local(value = ApiMgmtCapacityService.class) public class ApiMgmtCapacityServiceImpl implements ApiMgmtCapacityService{ private static final Logger s_logger = Logger.getLogger(ApiMgmtCapacityServiceImpl.class); protected ApiMgmtCapacityServiceImpl() { super(); } @Override public List<Class<?>> getCommands() { List<Class<?>> cmdList = new ArrayList<Class<?>>(); cmdList.add(GetMgmtCapacityCmd.class); return cmdList; } @Override public void formatMgmtServerCapacity(GetMgmtCapacityCmdResponse response){ response.setCpuCore(Runtime.getRuntime().availableProcessors()); response.setOsName(System.getProperty(“os.name”)); response.setOsVersion(System.getProperty(“os.version”)); response.setMemTotal(Integer.valueOf((int)(Runtime.getRuntime() . t o talMemory()/1024))); response.setMemFree(Integer.valueOf((int)(Runtime.getRuntime().freeMemory()/1024))); } }如果你是在Eclipse里导入整个CloudStack的Maven项目,那新加的mgmtcap的工程也会在其中:
进入/opt/cloudstack/client目录,配置pom.xml,这样在编译client时也会编译到我们新加的API,在/opt/cloudstack/client/pom.xml的依赖里添加:
< dependency> < groupId>org .apache . cloudstack</ groupId> < artifactId>cloud-plugin- api- mgmtcap</ artifactId> < version>$ {project .version}</version> </ dependency>在/opt/cloudstack/client/tomcatconf/commands.properties.in里最后添加:
#### get mgt server cap getMgmtCapacity=15使用如下的方式编译并测试新加的API
1)进入目录/opt/cloudstack
2)执行: mvn clean install
根据机器及网络环境,执行些命令需要几分钟到十几分钟,成功执行后,可以看到我们新加的API也构建了:
[INFO] Apache CloudStack Plugin – Management Capacity …. SUCCESS [10.824s]
3) 初始化数据库:mvn -P developer -Ddeploydb -pl developer
4)运行web container: mvn -pl client jetty:run
以上命令执行完以后,CloudStack管理服务器就在运行状态了,接下来测试一下我们新加的API是否起作用,因为是开发环境,API服务器的8096端口默认就已经打开,我们可以直接测试如下:
1)执行getMgmtCapacity命令,注意大小写,可以通过浏览器或curl命令进行:
curl “http://localhost:8096/client/api?command=getMgmtCapacity”
默认响应的类型是XML,上述命令返回:
<?xml version=”1.0″ encoding=”UTF-8″?><getmgmtcapacityresponse cloud-stack-version=”4.3.0-SNAPSHOT”><mgmtcapacity><since>4.2.0</since><isasync>false</isasync><cpucore>8</cpucore><memtotal>261480</memtotal><memfree>97227</memfree><osname>Mac OS X</osname><osversion>10.9</osversion></mgmtcapacity></getmgmtcapacityresponse>
也可以返回json格式,命令: curl “http://localhost:8096/client/api?command=getMgmtCapacity&response=json”
上述命令返回:
{ “getmgmtcapacityresponse” : { “mgmtcapacity” : {“since”:”4.2.0″,”isasync”:false,”cpucore”:8,”memtotal”:261480,”memfree”:95800,”osname”:”Mac OS X”,”osversion”:”10.9″} } }
2)调用API命令listApis看我们新加的API是否能找到:
命令:curl “http://localhost:8096/client/api?command=listApis&name=getMgmtCapacity”
上述命令返回:
<?xml version=”1.0″ encoding=”UTF-8″?><listapisresponse cloud-stack-version=”4.3.0-SNAPSHOT”><count>1</count><api><name>getMgmtCapacity</name><description>get management server capacity information by ApiMgmtCapacityService plugin</description><since>4.2.0</since><isasync>false</isasync><related></related><response><name>osversion</name><description>the os version</description><type>string</type></response><response><name>type</name><description>response field type</description><type>string</type></response><response><name>name</name><description>the name of the api command</description><type>string</type></response><response><name>memfree</name><description>free memory in MB</description><type>integer</type></response><response><name>since</name><description>version of CloudStack the api was introduced in</description><type>string</type></response><response><name>memtotal</name><description>total memory in MB</description><type>integer</type></response><response><name>description</name><description>description of the api</description><type>string</type></response><response><name>osname</name><description>the os name</description><type>string</type></response><response><name>cpucore</name><description>cpu core number</description><type>integer</type></response><response><name>isasync</name><description>true if api is asynchronous</description><type>boolean</type></response></api></listapisresponse>
大功告成!以上只是按步骤写了如何添加,我们接下来看几个关键点,弄清楚这些,才是真正掌握。
一、CloudStack4.3里模块的层级结构
在CloudStack管理服务器启动时,会搜索固定目录下的一些文件,比如resources/META-INF/cloudstack/*/module.properties和*-context.xml,这些大家可以参照cloud-framework-spring-module工程。在初始化时,模块的上下文就如上图所示,我们新开发的API的父级模块是api,再往上是core,模块的实例化从bootstrap开始,整个树型结构就会遍历并完成对象的创建。
二、Spring框架的使用,Bean的发现和实例化
在CloudStack4.2及之前的版本,你很容易以Java Web应用的入口web.xml顺藤摸瓜了解整个CloudStack的全貌,便从CloudStack4.3开始,由于解耦合的关系,Spring框架的使用与之前不同,加入了模块的自动发现的加载。这从web.xml的变化可以看出,你要跟踪启动的代码,那就从org.apache.cloudstack.spring.module.web.CloudStackContextLoaderListener看起吧。
三、API中Annotation的使用
大家也许会对我们上面的代码感到不解或者惊讶如何做到的?实际上这与几个Annotation的使用不无关系,下面一一介绍。
1)@APICommand
在系统启动时,会解析所有定义此Annotation的类,因此@APICommand是在运行时依然有效的。它的一些域包括name, description, usage, includeInApiDoc, since以及responseObject。具体每个域的含义大家可参照源码,其实也是相当容易理解的。
2)@Parameter (未出现在本次实现的API中)
API命令请求时的参加,由于getMgmtCapacity中没有什么参数传给后台处理,所以也就没有这个Annotation,但一般情况下,我们在请求API命令时会包含一个或多个参数,比如要传账户的ID,可以这样定义:
@Parameter(name=ApiConstants.ACCOUNT, type=CommandType.UUID, entityType=AccountResponse.class,
description=”the ID of the acount whose limit to be reset”)
private Long accountId;
3)@Component
这个是借用Spring框架里的Annotation定义,说明这个类是一个组件,需要Spring框架做auto-detect和auto-wiring的运作。这个Annotation的应用情况在邮件组里有过激烈的争论,不建议使用此注解的人认为这个借用Spring的注解有误用的现象,何况现在不用这个注解依然可以完成模块的自动发现和装配,所以建议今后不要使用。我们这里暂且不去深究这个注解使用的是否合适,毕竟CloudStack里很多的组件仍然在使用。
4)@SerializedName
API命令的响应需要对返回的信息进行串行化操作,这里使用的是Google的gson库,可以很方便的返回XML或JSON格式的信息,如果想深入研究,不妨参考一下google-gson库。
后记
实际在CloudStack4.3里,UI部分也插件化了,但本篇并未涉及到UI的入口,感兴趣的朋友可以自行试着添加UI的入口。另外大家看到这里只是展示如何添加API,并未涉及比较复杂的操作如数据库的CRUD,当然,如果你添加的功能或插件需要添加新的数据库表或更改现有的数据库表结构,那你不仅要考虑数据库初始化的场景,还要考虑升级安装的场景,但数据库各字段的操作都有现成的封装供你调用。如果对数据库的操作感兴趣,不妨参照一下系统系统时如何获得API服务器的端口的。