ZStack作为阿里云的一个成熟的云资源管理平台,已经成为当前很多云计算公司的首肯。
我们今天做的不是ZStack的部署,而是当ZStack搭建起来,如何使用API接口来调用ZStack中的功能,在其他的平台来实现ZStack中的创建工单等等请求的。这个功能源自于 公司有一个需求,就是在OA上直接 创建工单和 查看当前ZStack的资源。
参考的是这个ZStack的文档:
https://www.zstack.io/help/dev_manual/dev_guide/11.3.html#c11_3_7
但,这个文档完整吗?
我的答案是 NO,还是第一次根据接口开发功能的我在这个过程当中十分挣扎,接下来我们根据功能模块来查看我的开发过程。
1、怎么程序调用查看当前云平台的剩余资源。
OA的使用人员想要在提申请的时候能查看到当前云平台的资源还剩多少? 主要是主存、内存和CPU的使用情况。
于是我去翻了上面的开发文档,第一遍翻过去的时候只看到CPU和内存的相关接口,十分兴奋,但是查下去的数量又不是很对,于是请教了他们那块的服务人员,但是由于我们这购买的是ZStack的部署,如果是开发后期开发的话是要另外收钱的,所以每次询问这方面问题的时候,都得到的偏模棱两可的答案,毕竟没有买人家那部分的服务,咱也心知肚明,错就错在,我当时把首页
截了一张图给它,然后它们那边回复说,首页这块大屏监控是根据ZWatch来做的,我心想好,就去琢磨了ZWatch的接口,结果好家伙,在ZStack里,我这个时间点的这个版本,足足提供了288个标签来查看ZStack中的剩余资源使用量(而且不提供具体的解释文档),并且还分为Labelname和metricData两种方法,说实话我到现在都没理清这两个方法的区别,应该是一个对应的例如说CPU的个数,另一个对应的CPU的容量,大概是这样。那我肯定按照MetricData去做,考虑到这个是叫云主机,不能是什么NetWork或者image的名字,最后确定了两个名称空间,一个是vm,一个是hosts,值得一提的是,二者的查询方法可能都是一样的,比如说查询总容量、cpu个数、内存容量,但对应的是系统内的不同的部分。果然,host对应的是物理机,vm才是对应的云主机,我是怎么确定云主机的,就是其中的那个机器数量跟我们大屏那边云主机那块对上了,方法名也对了,名称空间也对了,是不是觉得胜利快到了,好家伙,vm一共40多个标签查下去,没有一个数字跟大屏那边对上的。
我非常心情复杂告诉了主管,又去交流了一下,说,咱这个不对啊,结果emm,这次我应该表述清楚了对方也认真去考虑了,ZStack的接口放在了开发文档的其他部分,我之所以没找到一是因为我关键词不对,二是因为它放的位置确实和其他的资源查询在不一样的地方。所以假如说有谁看到这个地方的话,我建议大家可以在开发文档中搜索 GetCpuMemoryCapacity 和 GetPrimaryStorageCapacity 这两个关键词,是的,他们不在文档的ZWatch这个板块内,在其他的地方。
如上,内存和CPU的查询被写在了同一个方法,而主存的查询单独出了一个方法。说到这要说到一个概念,叫超分率,就是说假如CPU的超分率,那么意思就是我们可以一个CPU当两个CPU使用,而最终计算的比例的时候,是要用两个CPU的数量来做的除的,我们在调用得到totalCapacity的时候得到的是超分率乘完之后的数据。可以不用再去查系统中的设置的超分率是多少,这一步和他们那边给我的教程的出发点多少是不一样的。
/***
* @param accountSessionId 登录管理员账户得到的sessionId
* @return 云主机剩余CPU和内存(个数)容量和百分比
*/
public static Map<String,String> getCpuMemoryCapacity(String accountSessionId){
DecimalFormat df=new DecimalFormat("0.00");
List zoneList = new ArrayList( );
zoneList.add( "905a5ae5ad4946afae7cb7609b1ca835" );
GetCpuMemoryCapacityAction action = new GetCpuMemoryCapacityAction();
action.zoneUuids = zoneList;
action.sessionId = accountSessionId;
action.hypervisorType = "KVM";
GetCpuMemoryCapacityAction.Result res = action.call(); //res是我们查询到的数据,解析它
long availableCpu = res.value.getAvailableCpu();
long totalCpu = res.value.getTotalCpu();
long totalMemory = res.value.getTotalMemory();
long availableMemory = res.value.getAvailableMemory();
double availableCpuPercent = availableCpu*1.0/totalCpu;
double availableMemoryPercent = availableMemory*1.0/totalMemory;
Map<String,String> map1 = new HashMap<>( );
map1.put( "availableCpu",Long.toString( availableCpu ));
map1.put( "availableMemory",df.format( availableMemory*1.0/1024/1024/1024/1024 ));
map1.put( "availableCpuPercent",df.format( availableCpuPercent*100));
map1.put( "availableMemoryPercent",df.format( availableMemoryPercent*100));
return map1;
}
提示:
1、 zoneList一定要写,不能因为只有一个区域就不写,不行的,因为如果你选的是all,它查到的是所有资源是你拥有的所有资源,这个是不对的,这里的资源应该是你映射好的资源。
2、查询之后第一时间得到的数据是long类型的以B为单位的容量,百分比则需要你自己计算,容量要变成TB精确到小数点后两位,百分比也精确到小数点后两位。我这边用的方法一点也不讨巧,我先乘以1.0将它转为double,然后DecimalFormat将它转为固定小数点,我感觉应该有其他的办法才对。查询主存的方法和它差不去多少,这里就不贴出来了。
3、OA那边是个老系统用的jsp,我jsp已经生疏了很多,他们那边告诉我让我把java代码块放在jsp当中全程全拿一个jsp做然后嵌套进去,哈哈哈哈哈哈我一定要把这段代码贴出来,算是憨憨阿辉的青春。(我已经把查询的结果放在其他地方,这边只是请求一个地址得到对应的字符串)
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.net.*"%>
<%@ page import="java.io.*"%>
<%@ page import="java.util.*"%>
<%@ page import="com.alibaba.fastjson.JSONObject"%>
<%!
String remoteAddress = "**************";
%>
<%
StringBuffer intactUrl = new StringBuffer();
intactUrl.append(remoteAddress);
try
{
URL url = new URL(intactUrl.toString().trim());
HttpURLConnection urlConn = (HttpURLConnection)url.openConnection();
urlConn.connect();
InputStream in = urlConn.getInputStream();
BufferedReader read = new BufferedReader(new InputStreamReader(in));
String get;
StringBuffer finalInfo = new StringBuffer();;
while((get = read.readLine()) != null)
{
finalInfo.append(get);
}
JSONObject jsonObject = JSONObject.parseObject( finalInfo.toString());
availableMemory = jsonObject.getString("availableMemory");
availablePrimaryStorage = jsonObject.getString("availablePrimaryStorage");
availablePrimaryStoragePercent = jsonObject.getString("availablePrimaryStoragePercent");
availableCpuPercent = jsonObject.getString("availableCpuPercent");
availableCpu = jsonObject.getString("availableCpu");
availableMemoryPercent = jsonObject.getString("availableMemoryPercent");
urlConn.disconnect();
}
catch(Exception e)
{
out.println(e.toString());
}
%>
<h3>云平台资源使用情况</h3>
<div class="remainResource">
<div class="remainCPU">
<p>
cpu剩余个数: <input type="text" id="cpuCapacity" value="<%=availableCpu%>" style="width: 60px" readonly/>个
cpu剩余百分比:<input type="text" id="cpuCapacityPercent" value="<%=availableCpuPercent%>" style="width: 60px" readonly/> %
</p>
</div>
2、怎么程序调用来创建工单。
这个功能是先做的功能,也正是这个做这个功能的时候,我才得到的结论,ZStack的开发文档有不完整的地方。最后解决问题是询问了他们之后,拿到的文档里头的没有的创建条件所需要的部分,才创建成功的。ZStack的SDK的确可以实现操作页面的完整还原,但就是 用到这里的人好像真的不多,想到这我又琢磨了一下需求,当时真的是脑壳很疼。
我先把这个点明确的指出来,开发文档设置action的时候少了hypervisor的说明,这个是必填项。
这是它们文档里的截图,我们来分析一下
配合文档里的关于可甜和必甜的说明,我们需要定义这三个实体类。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class WorkOrderRequest {
private String requestName;
private String apiName;
private String executeTimes;
private WorkOrderApiBody apiBody;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class WorkOrderApiBody {
private String name;
private String instanceOfferingUuid;
private String imageUuid;
private List<String> l3NetworkUuids;
private String strategy ;
private String timeout ;
private WorkOrderHeaders headers;
private String id;
private String hypervisorType;
private String defaultL3NetworkUuid;
}
public class WorkOrderHeaders {
}
抓住 三个重要的输入条件,分别是镜像id、网络id、计算规格id,我们的创建过程是这样写的。
@ResponseBody
@RequestMapping("/createWorkOrder")
public String createWorkOrder (HttpServletRequest request) throws Exception {
String createOrderRes = null;
String name = request.getParameter("name" ); //云主机名称
String sqr = request.getParameter("sqr"); //申请人的名称
String instanceOfferingUuid = request.getParameter("instanceOfferingUuid");
String imageUuid = request.getParameter("imageUuid");
String l3NetworkUuids = request.getParameter("l3NetworkUuids");
String password = "password";
Map<String, String> accountSystemContext = new HashMap<String, String>();
String projectUuid = null;
String virtualIDUuid = null;
List<String> roleUuidList = new ArrayList<>();
String zstackAccountSessionId = yfZStackService.logInByAccountNameAndPassword();
//平台管理中添加用戶
Boolean userWheatherExist = yfZStackService.checkZstackUserForLoginExist( sqr, zstackAccountSessionId );
if (false == userWheatherExist) {
yfZStackService.createUserForLoginZstack( sqr, password, zstackAccountSessionId );
}
//高級功能中添加用戶以及賦予項目,以及項目權限
Boolean IAM2UserWheatherExist = yfZStackService.checkZstackIAM2UserExist( sqr, zstackAccountSessionId );
//假如不存在就赋予项目以及项目管理员角色
if (false == IAM2UserWheatherExist) {
List<IAM2ProjectInventory> projectList = yfZStackService.getProjectList( zstackAccountSessionId );
List<String> virtualIDUuidList = null;
for (IAM2ProjectInventory project : projectList) {
if ("云主机申请".equals( project.getName() )) {
projectUuid = project.getUuid();
IAM2VirtualIDInventory zstackIAM2User = yfZStackService.createZstackIAM2User( sqr, password, zstackAccountSessionId );
virtualIDUuid = zstackIAM2User.getUuid();
virtualIDUuidList = new ArrayList<>();
virtualIDUuidList.add( virtualIDUuid );
yfZStackService.addIAM2VirtualIDsToProject( projectUuid, virtualIDUuidList, zstackAccountSessionId );
}
}
List<RoleInventory> projectRoleList = yfZStackService.getAllProjectRoleList( zstackAccountSessionId );
for (RoleInventory role : projectRoleList) {
roleUuidList = new ArrayList<>();
if ("PROJECT_OPERATOR_ROLE".equals( role.getName() )) {
roleUuidList.add( role.getUuid() );
yfZStackService.addRolesToIAM2VirtualID( roleUuidList, virtualIDUuid, zstackAccountSessionId );
}
}
} else {
//如果高级功能中存在该用户,就查看该用户是否在《云主机申请》这个项目中,且权限是否是项目管理员
List<IAM2ProjectInventory> projectList = yfZStackService.getProjectList( zstackAccountSessionId );
List<String> virtualIDUuidList = null;
for (IAM2ProjectInventory project : projectList) {
if ("云主机申请".equals( project.getName() )) {
projectUuid = project.getUuid();
String IAM2UserSessionId = yfZStackService.getZstackIAM2UserSessionId( sqr, zstackAccountSessionId );
virtualIDUuid = IAM2UserSessionId;
virtualIDUuidList = new ArrayList<>();
virtualIDUuidList.add( virtualIDUuid );
yfZStackService.addIAM2VirtualIDsToProject( projectUuid, virtualIDUuidList, zstackAccountSessionId ); }
}
List<RoleInventory> projectRoleList = yfZStackService.getAllProjectRoleList( zstackAccountSessionId );
for (RoleInventory role : projectRoleList) {
roleUuidList = new ArrayList<>();
if ("PROJECT_OPERATOR_ROLE".equals( role.getName() )) {
roleUuidList.add( role.getUuid() );
yfZStackService.addRolesToIAM2VirtualID( roleUuidList, virtualIDUuid, zstackAccountSessionId );
}
}
}
//管理员登出,刚刚注册的用户登录进去
yfZStackService.logOut( zstackAccountSessionId );
String commonUserSessionId = yfZStackService.logInByUsernameAndPassword(sqr,password);
List<WorkOrderRequest> workOrderRequests = new ArrayList<>( );
WorkOrderApiBody workOrderApiBody = new WorkOrderApiBody();
workOrderApiBody.setImageUuid(imageUuid);
workOrderApiBody.setInstanceOfferingUuid(instanceOfferingUuid );
workOrderApiBody.setHypervisorType( "KVM" );
workOrderApiBody.setDefaultL3NetworkUuid( l3NetworkUuids );
workOrderApiBody.setName( name );
List netSmallList = new ArrayList( );
netSmallList.add( l3NetworkUuids );
workOrderApiBody.setL3NetworkUuids( netSmallList );
WorkOrderRequest workOrderRequest = new WorkOrderRequest();
workOrderRequest.setApiName( "ecology" );
workOrderRequest.setExecuteTimes( "1" );
workOrderRequest.setApiName( "org.zstack.header.vm.APICreateVmInstanceMsg" );
workOrderRequest.setapiBody( workOrderApiBody );
workOrderRequests.add( workOrderRequest );
createOrderRes = yfZStackService.CreateWorkOrder( projectUuid,virtualIDUuid,name,commonUserSessionId,workOrderRequests);
if(createOrderRes == null){
return null;
}else{
return createOrderRes;
}
}
这个流程你可以这么理解。
我们写了一些方法,在ZStack的方法当中可能并不好找。
/**
* 查看该账户在平台管理一栏里下是否有此用户
* @param employeeNumber
* @param zstackAccountSessionId
* @return true为存在 false为不存在
*/
public static Boolean checkZstackUserForLoginExist(String employeeNumber,String zstackAccountSessionId){
QueryUserAction queryUserAction = new QueryUserAction();
queryUserAction.sessionId = zstackAccountSessionId;
QueryUserAction.Result queryUserActionResult = queryUserAction.call();
queryUserActionResult.throwExceptionIfError();
List<UserInventory> userList = queryUserActionResult.value.getInventories();
for (UserInventory user : userList){
if (employeeNumber.equals(user.getName())){
return true;
}
}
return false;
}
/***
* 获取当前账户下的项目列表
* @param zstackAccountSessionId
* @return
*/
public static List<IAM2ProjectInventory> getProjectList(String zstackAccountSessionId){
QueryIAM2ProjectAction queryIAM2ProjectAction = new QueryIAM2ProjectAction();
queryIAM2ProjectAction.sessionId = zstackAccountSessionId;
QueryIAM2ProjectAction.Result queryResult = queryIAM2ProjectAction.call();
List<IAM2ProjectInventory> projectInventoryList = queryResult.value.getInventories();
return projectInventoryList;
}
/**
* 將用戶添加到某個工程下
* @param projectUuid
* @param virtualIDUuidList
* @param zstackAccountSessionId
* @return
*/
public static Boolean addIAM2VirtualIDsToProject(String projectUuid,List<String> virtualIDUuidList,String zstackAccountSessionId){
AddIAM2VirtualIDsToProjectAction addIAM2VirtualIDsToProjectAction = new AddIAM2VirtualIDsToProjectAction();
addIAM2VirtualIDsToProjectAction.sessionId = zstackAccountSessionId;
addIAM2VirtualIDsToProjectAction.projectUuid = projectUuid;
addIAM2VirtualIDsToProjectAction.virtualIDUuids = virtualIDUuidList;
AddIAM2VirtualIDsToProjectAction.Result addIAM2VirtualIDsToProjectResult = addIAM2VirtualIDsToProjectAction.call();
addIAM2VirtualIDsToProjectResult.throwExceptionIfError();
return true;
}
/**
* 將項目的某個角色賦予項目中的某個用戶 true表示成功
* @param virtualIDUuid
* @param zstackAccountSessionId
* @return
*/
public static Boolean addRolesToIAM2VirtualID(List<String> roleUuidList,String virtualIDUuid,String zstackAccountSessionId){
AddRolesToIAM2VirtualIDAction addRolesToIAM2VirtualIDAction = new AddRolesToIAM2VirtualIDAction();
addRolesToIAM2VirtualIDAction.virtualIDUuid = virtualIDUuid;
addRolesToIAM2VirtualIDAction.roleUuids = roleUuidList;
addRolesToIAM2VirtualIDAction.sessionId = zstackAccountSessionId;
AddRolesToIAM2VirtualIDAction.Result addRolesToIAM2VirtualIDResult = addRolesToIAM2VirtualIDAction.call();
addRolesToIAM2VirtualIDResult.throwExceptionIfError();
return true;
}
/**
* 獲取指定賬戶下項目的所有角色
* @param zstackAccountSessionId
* @return
*/
public static List<RoleInventory> getAllProjectRoleList(String zstackAccountSessionId){
QueryRoleAction queryRoleAction = new QueryRoleAction();
queryRoleAction.sessionId = zstackAccountSessionId;
QueryRoleAction.Result queryRoleResult = queryRoleAction.call();
queryRoleResult.throwExceptionIfError();
return queryRoleResult.value.getInventories();
}
/**
* 检查高级功能下的用户列表是否有该用户,必须是工号,姓名可能一样,但是工号绝对不一样
* @param employeeNumber
* @param zstackAccountSessionId
* @return true 代表有,false代表没有
*/
public static Boolean checkZstackIAM2UserExist(String employeeNumber,String zstackAccountSessionId){
QueryIAM2VirtualIDAction queryIAM2VirtualIDAction = new QueryIAM2VirtualIDAction();
queryIAM2VirtualIDAction.sessionId = zstackAccountSessionId;
QueryIAM2VirtualIDAction.Result QueryIAM2VirtualIDResult = queryIAM2VirtualIDAction.call();
QueryIAM2VirtualIDResult.throwExceptionIfError();
List<IAM2VirtualIDInventory> IAM2VirtualIDList = QueryIAM2VirtualIDResult.value.getInventories();
for (IAM2VirtualIDInventory IAM2VirtualID : IAM2VirtualIDList){
if (employeeNumber.equals(IAM2VirtualID.getName())){
return true;
}
}
return false;
}
/**
* 获取VirtualID
* @param employeeNumber
* @param zstackAccountSessionId
* @return
*/
public static String getZstackIAM2UserSessionId(String employeeNumber,String zstackAccountSessionId){
QueryIAM2VirtualIDAction queryIAM2VirtualIDAction = new QueryIAM2VirtualIDAction();
queryIAM2VirtualIDAction.sessionId = zstackAccountSessionId;
QueryIAM2VirtualIDAction.Result QueryIAM2VirtualIDResult = queryIAM2VirtualIDAction.call();
QueryIAM2VirtualIDResult.throwExceptionIfError();
List<IAM2VirtualIDInventory> IAM2VirtualIDList = QueryIAM2VirtualIDResult.value.getInventories();
for (IAM2VirtualIDInventory IAM2VirtualID : IAM2VirtualIDList){
if (employeeNumber.equals(IAM2VirtualID.getName())){
return IAM2VirtualID.getUuid();
}
}
return null;
}
/**
*
* @param employeeNumber 用于登录,唯一性!!一个一个名字可以有多个employeeNumber只对应一个名字
* @param password 密码
* @param zstackAccountSessionId 当前账户
* @return
*/
public static IAM2VirtualIDInventory createZstackIAM2User(String employeeNumber, String password, String zstackAccountSessionId){
CreateIAM2VirtualIDAction createIAM2VirtualIDAction = new CreateIAM2VirtualIDAction();
createIAM2VirtualIDAction.name = employeeNumber;
createIAM2VirtualIDAction.password = encryptToSHA512(password);
createIAM2VirtualIDAction.sessionId = zstackAccountSessionId;
CreateIAM2VirtualIDAction.Result createIAM2VirtualIDResult = createIAM2VirtualIDAction.call();
createIAM2VirtualIDResult.throwExceptionIfError();
return createIAM2VirtualIDResult.value.getInventory();
}
注意:
1、开发接口是可以完整还原程序操作页面的,所以当你看到一些自己的程序出现了例如InterError:1001的错误,不妨先查看自己的关于类的定义是否正确,尤其是变量的名称和大小写,如果说没有办法对上,那绝对给你报内部错误了就。
2、确定自己的参数没有错误,又怀疑自己action的赋值条件不够的时候,不妨去看看操作页面需要哪些条件,好比指定KVM/vmare这个东西是操作页面有的但你没有在开发文档看到,那就可以质疑 这份文档是否对不对了,事实是,这个字段是hypervisor且是必填的。
3、创建工单的功能可以做,但是也不要忽略了之前的一系列判断的过程,包括判断用户是否存在,不在就创建用户啊,创建用户不得给它添加到工程里,添加到工程里不得给他赋予一个项目角色嘛。
4、创建工单是这样,它有分创建工单和创建工单流程,创建工单是按照工单流程来的,所以我们事先手动建了一个工单流程 ,给谁审批啊,给谁创建啊,例如叫做 “云主机申请”,后面但凡是创建工单都是按照这个流程来,程序还原的,是最后这部分的功能。
这是项目地址(欢迎star)