| // 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.errortext") + " Error Code - " + |
| queryAsyncJobResponse.getAsString("queryasyncjobresultresponse.jobresult.errorcode")); |
| |
| 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("CloudSatck API response doesn't contain responseObjName:" + responseObjName + " because response is empty"); |
| 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; |
| } |
| errorMessage = errorMessage.concat(" Error Code - " + Integer.toString(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); |
| } |
| } |