mirror of https://github.com/apache/cloudstack.git
198 lines
7.3 KiB
Java
198 lines
7.3 KiB
Java
// Licensed to the Apache Software Foundation (ASF) under one
|
|
// or more contributor license agreements. See the NOTICE file
|
|
// distributed with this work for additional information
|
|
// regarding copyright ownership. The ASF licenses this file
|
|
// to you under the Apache License, Version 2.0 (the
|
|
// "License"); you may not use this file except in compliance
|
|
// with the License. You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
package com.cloud.stack;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.InputStreamReader;
|
|
import java.lang.reflect.Type;
|
|
import java.net.HttpURLConnection;
|
|
import java.net.URL;
|
|
import java.net.URLConnection;
|
|
import java.util.List;
|
|
|
|
import org.apache.log4j.Logger;
|
|
|
|
import com.cloud.bridge.util.JsonAccessor;
|
|
import com.google.gson.Gson;
|
|
import com.google.gson.JsonElement;
|
|
import com.google.gson.JsonParser;
|
|
|
|
/**
|
|
* CloudStackClient implements a simple CloudStack client object, it can be used to execute CloudStack commands
|
|
* with JSON response
|
|
*
|
|
*/
|
|
public class CloudStackClient {
|
|
protected final static Logger logger = Logger.getLogger(CloudStackClient.class);
|
|
|
|
private String _serviceUrl;
|
|
|
|
private long _pollIntervalMs = 2000; // 1 second polling interval
|
|
private long _pollTimeoutMs = 600000; // 10 minutes polling timeout
|
|
|
|
public CloudStackClient(String serviceRootUrl) {
|
|
assert(serviceRootUrl != null);
|
|
|
|
if(!serviceRootUrl.endsWith("/"))
|
|
_serviceUrl = serviceRootUrl + "/api?";
|
|
else
|
|
_serviceUrl = serviceRootUrl + "api?";
|
|
}
|
|
|
|
public CloudStackClient(String cloudStackServiceHost, int port, boolean bSslEnabled) {
|
|
StringBuffer sb = new StringBuffer();
|
|
if(!bSslEnabled) {
|
|
sb.append("http://" + cloudStackServiceHost);
|
|
if(port != 80)
|
|
sb.append(":").append(port);
|
|
} else {
|
|
sb.append("https://" + cloudStackServiceHost);
|
|
if(port != 443)
|
|
sb.append(":").append(port);
|
|
}
|
|
|
|
//
|
|
// If the CloudStack root context path has been from /client to some other name
|
|
// use the first constructor instead
|
|
//
|
|
sb.append("/client/api");
|
|
sb.append("?");
|
|
_serviceUrl = sb.toString();
|
|
}
|
|
|
|
public CloudStackClient setPollInterval(long intervalMs) {
|
|
_pollIntervalMs = intervalMs;
|
|
return this;
|
|
}
|
|
|
|
public CloudStackClient setPollTimeout(long pollTimeoutMs) {
|
|
_pollTimeoutMs = pollTimeoutMs;
|
|
return this;
|
|
}
|
|
|
|
public <T> T call(CloudStackCommand cmd, String apiKey, String secretKey, boolean followToAsyncResult,
|
|
String responseName, String responseObjName, Class<T> responseClz) throws Exception {
|
|
|
|
assert(responseName != null);
|
|
|
|
JsonAccessor json = execute(cmd, apiKey, secretKey);
|
|
if(followToAsyncResult && json.tryEval(responseName + ".jobid") != null) {
|
|
long startMs = System.currentTimeMillis();
|
|
while(System.currentTimeMillis() - startMs < _pollTimeoutMs) {
|
|
CloudStackCommand queryJobCmd = new CloudStackCommand("queryAsyncJobResult");
|
|
queryJobCmd.setParam("jobId", json.getAsString(responseName + ".jobid"));
|
|
|
|
JsonAccessor queryAsyncJobResponse = execute(queryJobCmd, apiKey, secretKey);
|
|
|
|
if(queryAsyncJobResponse.tryEval("queryasyncjobresultresponse") != null) {
|
|
int jobStatus = queryAsyncJobResponse.getAsInt("queryasyncjobresultresponse.jobstatus");
|
|
switch(jobStatus) {
|
|
case 2:
|
|
throw new Exception(queryAsyncJobResponse.getAsString("queryasyncjobresultresponse.jobresult.errorcode") + " " +
|
|
queryAsyncJobResponse.getAsString("queryasyncjobresultresponse.jobresult.errortext"));
|
|
|
|
case 0 :
|
|
try {
|
|
Thread.sleep( _pollIntervalMs );
|
|
} catch( Exception e ) {}
|
|
break;
|
|
|
|
case 1 :
|
|
if(responseObjName != null)
|
|
return (T)(new Gson()).fromJson(queryAsyncJobResponse.eval("queryasyncjobresultresponse.jobresult." + responseObjName), responseClz);
|
|
else
|
|
return (T)(new Gson()).fromJson(queryAsyncJobResponse.eval("queryasyncjobresultresponse.jobresult"), responseClz);
|
|
|
|
default :
|
|
assert(false);
|
|
throw new Exception("Operation failed - invalid job status response");
|
|
}
|
|
} else {
|
|
throw new Exception("Operation failed - invalid JSON response");
|
|
}
|
|
}
|
|
|
|
throw new Exception("Operation failed - async-job query timed out");
|
|
} else {
|
|
if (responseObjName != null)
|
|
return (T)(new Gson()).fromJson(json.eval(responseName + "." + responseObjName), responseClz);
|
|
else
|
|
return (T)(new Gson()).fromJson(json.eval(responseName), responseClz);
|
|
}
|
|
}
|
|
|
|
// collectionType example : new TypeToken<List<String>>() {}.getType();
|
|
public <T> List<T> listCall(CloudStackCommand cmd, String apiKey, String secretKey,
|
|
String responseName, String responseObjName, Type collectionType) throws Exception {
|
|
|
|
assert(responseName != null);
|
|
|
|
JsonAccessor json = execute(cmd, apiKey, secretKey);
|
|
|
|
|
|
|
|
if(responseObjName != null)
|
|
try {
|
|
return (new Gson()).fromJson(json.eval(responseName + "." + responseObjName), collectionType);
|
|
} catch(Exception e) {
|
|
// this happens because responseObjName won't exist if there are no objects in the list.
|
|
logger.debug("Unable to find responseObjName:[" + responseObjName + "]. Returning null! Exception: " + e.getMessage());
|
|
return null;
|
|
}
|
|
return (new Gson()).fromJson(json.eval(responseName), collectionType);
|
|
}
|
|
|
|
public JsonAccessor execute(CloudStackCommand cmd, String apiKey, String secretKey) throws Exception {
|
|
JsonParser parser = new JsonParser();
|
|
URL url = new URL(_serviceUrl + cmd.signCommand(apiKey, secretKey));
|
|
|
|
if(logger.isDebugEnabled())
|
|
logger.debug("Cloud API call + [" + url.toString() + "]");
|
|
|
|
URLConnection connect = url.openConnection();
|
|
|
|
int statusCode;
|
|
statusCode = ((HttpURLConnection)connect).getResponseCode();
|
|
if(statusCode >= 400) {
|
|
logger.error("Cloud API call + [" + url.toString() + "] failed with status code: " + statusCode);
|
|
String errorMessage = ((HttpURLConnection)connect).getResponseMessage();
|
|
if(errorMessage == null){
|
|
errorMessage = connect.getHeaderField("X-Description");
|
|
}
|
|
|
|
if(errorMessage == null){
|
|
errorMessage = "CloudStack API call HTTP response error, HTTP status code: " + statusCode;
|
|
}
|
|
|
|
throw new IOException(errorMessage);
|
|
}
|
|
|
|
InputStream inputStream = connect.getInputStream();
|
|
JsonElement jsonElement = parser.parse(new InputStreamReader(inputStream));
|
|
if(jsonElement == null) {
|
|
logger.error("Cloud API call + [" + url.toString() + "] failed: unable to parse expected JSON response");
|
|
|
|
throw new IOException("CloudStack API call error : invalid JSON response");
|
|
}
|
|
|
|
if(logger.isDebugEnabled())
|
|
logger.debug("Cloud API call + [" + url.toString() + "] returned: " + jsonElement.toString());
|
|
return new JsonAccessor(jsonElement);
|
|
}
|
|
}
|