cloudstack api post and ssl fix

Signed-off-by: Sebastien Goasguen <runseb@gmail.com>
This commit is contained in:
Dmitry Batkovich 2014-03-24 18:46:45 +04:00 committed by Sebastien Goasguen
parent fb498a40ba
commit d5333f4596
7 changed files with 232 additions and 466 deletions

View File

@ -1,248 +0,0 @@
// 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.bridge.util;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.cloud.bridge.service.exception.InternalErrorException;
/**
* JsonAccessor provides the functionality to allow navigating JSON object graph using simple expressions,
* for example, following property access expressions are all valid ones
*
* rootobj.level1obj[1].property
* this[0].level1obj[1].property
*
*/
public class JsonAccessor {
private JsonElement _json;
Pattern _arrayAccessorMatcher = Pattern.compile("(.*)\\[(\\d+)\\]");
public JsonAccessor(JsonElement json) {
assert (json != null);
_json = json;
}
public BigDecimal getAsBigDecimal(String propPath) {
JsonElement jsonElement = eval(propPath);
return jsonElement.getAsBigDecimal();
}
public BigInteger getAsBigInteger(String propPath) {
JsonElement jsonElement = eval(propPath);
return jsonElement.getAsBigInteger();
}
public boolean getAsBoolean(String propPath) {
JsonElement jsonElement = eval(propPath);
return jsonElement.getAsBoolean();
}
public byte getAsByte(String propPath) {
JsonElement jsonElement = eval(propPath);
return jsonElement.getAsByte();
}
public char getAsCharacter(String propPath) {
JsonElement jsonElement = eval(propPath);
return jsonElement.getAsCharacter();
}
public double getAsDouble(String propPath) {
JsonElement jsonElement = eval(propPath);
return jsonElement.getAsDouble();
}
public float getAsFloat(String propPath) {
JsonElement jsonElement = eval(propPath);
return jsonElement.getAsFloat();
}
public int getAsInt(String propPath) {
JsonElement jsonElement = eval(propPath);
return jsonElement.getAsInt();
}
public long getAsLong(String propPath) {
JsonElement jsonElement = eval(propPath);
return jsonElement.getAsLong();
}
public Number getAsNumber(String propPath) {
JsonElement jsonElement = eval(propPath);
return jsonElement.getAsNumber();
}
public short getAsShort(String propPath) {
JsonElement jsonElement = eval(propPath);
return jsonElement.getAsShort();
}
public String getAsString(String propPath) {
JsonElement jsonElement = eval(propPath);
return jsonElement.getAsString();
}
public boolean isBoolean(String propPath) {
JsonElement jsonElement = eval(propPath);
if (jsonElement instanceof JsonPrimitive)
return ((JsonPrimitive)jsonElement).isBoolean();
return false;
}
public boolean isNumber(String propPath) {
JsonElement jsonElement = eval(propPath);
if (jsonElement instanceof JsonPrimitive)
return ((JsonPrimitive)jsonElement).isNumber();
return false;
}
public boolean isString(String propPath) {
JsonElement jsonElement = eval(propPath);
if (jsonElement instanceof JsonPrimitive)
return ((JsonPrimitive)jsonElement).isString();
return false;
}
/*
* Return
* -1 : property expression can not be resolved
* 0 : match to a null JSON object
* 1+ : matched, for array element, the count of the elements inside the array
*/
public int getMatchCount(String propPath) {
JsonElement jsonElement = tryEval(propPath);
if (jsonElement == null)
return -1;
if (jsonElement.isJsonNull())
return 0;
if (jsonElement.isJsonArray())
return ((JsonArray)jsonElement).size();
return 1;
}
public JsonElement eval(String propPath) {
JsonElement jsonElement = tryEval(propPath);
if (jsonElement == null)
throw new InternalErrorException("Property " + propPath + " is resolved to null JSON element on object: " + _json.toString());
return jsonElement;
}
public JsonElement tryEval(String propPath) {
assert (propPath != null);
String[] tokens = propPath.split("\\.");
ArrayList<Resolver> resolverChain = new ArrayList<Resolver>();
for (String token : tokens) {
Matcher matcher = _arrayAccessorMatcher.matcher(token);
if (matcher.find()) {
String propStr = matcher.group(1);
String indexStr = matcher.group(2);
resolverChain.add(new ArrayPropertyResolver(propStr, Integer.parseInt(indexStr)));
} else {
resolverChain.add(new PropertyResolver(token));
}
}
JsonElement jsonElementToResolveAt = _json;
for (Resolver resolver : resolverChain) {
jsonElementToResolveAt = resolver.resolve(jsonElementToResolveAt);
if (jsonElementToResolveAt == null)
break;
}
return jsonElementToResolveAt;
}
//
// Property resolvers
//
private static interface Resolver {
public JsonElement resolve(JsonElement jsonElementToResolveAt);
}
private static class PropertyResolver implements Resolver {
protected String _propName;
public PropertyResolver(String propName) {
_propName = propName;
}
public JsonElement resolve(JsonElement jsonElementToResolveAt) {
if ("this".equals(_propName))
return jsonElementToResolveAt;
if (jsonElementToResolveAt.isJsonObject())
return ((JsonObject)jsonElementToResolveAt).get(_propName);
if (jsonElementToResolveAt.isJsonNull())
throw new NullPointerException(String.format("Property %s points to a null element on object: %s", _propName, jsonElementToResolveAt.toString()));
throw new InternalErrorException("Unable to evaluate JSON accessor property: " + _propName + ", on object: " + jsonElementToResolveAt.toString());
}
}
private static class ArrayPropertyResolver extends PropertyResolver {
protected int _index;
public ArrayPropertyResolver(String propName, int index) {
super(propName);
_index = index;
}
public JsonElement resolve(JsonElement jsonElementToResolveAt) {
if (!"this".equals(_propName)) {
if (jsonElementToResolveAt.isJsonObject()) {
jsonElementToResolveAt = ((JsonObject)jsonElementToResolveAt).get(_propName);
} else {
if (jsonElementToResolveAt.isJsonNull())
throw new NullPointerException(String.format("Property %s points to a null element on object: %s", _propName, jsonElementToResolveAt.toString()));
throw new InternalErrorException("Unable to evaluate JSON accessor property: " + _propName + ", on object: " + jsonElementToResolveAt.toString());
}
}
if (jsonElementToResolveAt instanceof JsonArray) {
return ((JsonArray)jsonElementToResolveAt).get(_index);
}
if (jsonElementToResolveAt.isJsonNull())
throw new NullPointerException(String.format("Property %s points to a null element on object: %s", _propName, jsonElementToResolveAt.toString()));
throw new InternalErrorException("Unable to evaluate JSON accessor property: " + _propName + ", on object: " + jsonElementToResolveAt.toString());
}
}
}

View File

@ -0,0 +1,45 @@
package com.cloud.bridge.util;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
/**
* @author Dmitry Batkovich
*
* For more complex cases (JsonArrays or other) it can be rewrite to matcher pattern
*/
public final class JsonElementUtil {
private JsonElementUtil() {}
public static JsonElement getAsJsonElement(final JsonElement jsonElement, final String... path) {
JsonElement currentElement = jsonElement;
for (final String propertyName : path) {
if (currentElement == null) {
return null;
}
if (jsonElement.isJsonObject()) {
currentElement = ((JsonObject) currentElement).get(propertyName);
} else {
return null;
}
}
return currentElement;
}
public static Integer getAsInt(final JsonElement jsonElement, final String... path) {
final JsonElement targetElement = getAsJsonElement(jsonElement, path);
if (targetElement == null || !targetElement.isJsonPrimitive()) {
return null;
}
final JsonPrimitive asPrimitive = (JsonPrimitive) targetElement;
return asPrimitive.getAsInt();
}
public static String getAsString(final JsonElement jsonElement, final String... path) {
final JsonElement targetElement = getAsJsonElement(jsonElement, path);
return targetElement == null ? null : targetElement.getAsString();
}
}

View File

@ -0,0 +1,21 @@
package com.cloud.stack;
/**
* @author Dmitry Batkovich
*/
public class CloudStackClientException extends Exception {
public CloudStackClientException() {
}
public CloudStackClientException(final String s) {
super(s);
}
public CloudStackClientException(final String s, final Throwable throwable) {
super(s, throwable);
}
public CloudStackClientException(final Throwable throwable) {
super(throwable);
}
}

View File

@ -1,149 +0,0 @@
// 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.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
/**
* CloudStackCommand wraps command properties that are being sent to CloudStack
*
*/
public class CloudStackCommand {
Map<String, String> _params = new HashMap<String, String>();
public CloudStackCommand(String cmdName) {
this(cmdName, "json");
}
public CloudStackCommand(String cmdName, String responseType) {
_params.put("command", cmdName);
if (responseType != null)
_params.put("response", responseType);
}
public CloudStackCommand setParam(String paramName, String paramValue) {
assert (paramName != null);
assert (paramValue != null);
_params.put(paramName, paramValue);
return this;
}
public String signCommand(String apiKey, String secretKey) throws SignatureException {
assert (_params.get("command") != null);
List<String> paramNames = new ArrayList<String>();
for (String paramName : _params.keySet())
paramNames.add(paramName);
paramNames.add("apikey");
Collections.sort(paramNames);
StringBuffer sb = new StringBuffer();
for (String name : paramNames) {
String value;
if ("apikey".equals(name))
value = apiKey;
else
value = _params.get(name);
assert (value != null);
value = urlSafe(value);
if (sb.length() == 0) {
sb.append(name).append("=").append(value);
} else {
sb.append("&").append(name).append("=").append(value);
}
}
String signature = calculateRFC2104HMAC(sb.toString().toLowerCase(), secretKey);
return composeQueryString(apiKey, signature);
}
private String composeQueryString(String apiKey, String signature) {
StringBuffer sb = new StringBuffer();
String name;
String value;
// treat command specially (although not really necessary )
name = "command";
value = _params.get(name);
if (value != null) {
value = urlSafe(value);
sb.append(name).append("=").append(value);
}
for (Map.Entry<String, String> entry : _params.entrySet()) {
name = entry.getKey();
if (!"command".equals(name)) {
value = urlSafe(entry.getValue());
if (sb.length() == 0)
sb.append(name).append("=").append(value);
else
sb.append("&").append(name).append("=").append(value);
}
}
sb.append("&apikey=").append(urlSafe(apiKey));
sb.append("&signature=").append(urlSafe(signature));
return sb.toString();
}
private String calculateRFC2104HMAC(String signIt, String secretKey) throws SignatureException {
String result = null;
try {
SecretKeySpec key = new SecretKeySpec(secretKey.getBytes(), "HmacSHA1");
Mac hmacSha1 = Mac.getInstance("HmacSHA1");
hmacSha1.init(key);
byte[] rawHmac = hmacSha1.doFinal(signIt.getBytes());
result = new String(Base64.encodeBase64(rawHmac));
} catch (Exception e) {
throw new SignatureException("Failed to generate keyed HMAC on soap request: " + e.getMessage());
}
return result.trim();
}
private String urlSafe(String value) {
try {
if (value != null)
return URLEncoder.encode(value, "UTF-8").replaceAll("\\+", "%20");
else
return null;
} catch (UnsupportedEncodingException e) {
assert (false);
}
return value;
}
}

View File

@ -0,0 +1,118 @@
// 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.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.SignatureException;
import java.util.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import com.cloud.utils.StringUtils;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
/**
* CloudStackQueryBuilder wraps command properties that are being sent to CloudStack
*/
public class CloudStackQueryBuilder {
private static final String HMAC_SHA_1 = "HmacSHA1";
private static final String COMMAND_KEY = "command";
private static final String APIKEY_KEY = "apikey";
private final Collection<NameValuePair> params = new ArrayList<NameValuePair>();
public CloudStackQueryBuilder(final String commandName) {
params.add(new NameValuePair("command", commandName));
params.add(new NameValuePair("response", "json"));
}
public static CloudStackQueryBuilder create(final String commandName) {
return new CloudStackQueryBuilder(commandName);
}
/**
* parameter will be stored to query only if paramValue != null. This assumption is very useful
* for optional parameters
* @param paramName - not null parameter name
* @param paramValue - nullable parameter value
*/
public CloudStackQueryBuilder addParam(final String paramName, final Object paramValue) {
if (paramName == null) {
throw new NullPointerException();
}
if (paramValue != null) {
params.add(new NameValuePair(paramName, String.valueOf(paramValue)));
}
return this;
}
public HttpMethod buildRequest(final String apiKey, final String secretKey) throws SignatureException {
final PostMethod postMethod = new PostMethod();
for (final NameValuePair param : params) {
postMethod.addParameter(param);
}
postMethod.addParameter(APIKEY_KEY, apiKey);
postMethod.addParameter("signature", calculateSignature(secretKey, apiKey));
return postMethod;
}
private String calculateSignature(final String secretKey, final String apiKey) throws SignatureException {
final List<NameValuePair> paramsCopy = new ArrayList<NameValuePair>(params.size() + 1);
paramsCopy.addAll(params);
paramsCopy.add(new NameValuePair(APIKEY_KEY, urlSafe(apiKey)));
Collections.sort(paramsCopy, new Comparator<NameValuePair>() {
@Override
public int compare(final NameValuePair nameValuePair, final NameValuePair nameValuePair2) {
return nameValuePair.getName().compareTo(nameValuePair2.getName());
}
});
final List<String> serializedParameters = new ArrayList<String>(paramsCopy.size());
for (final NameValuePair pair : paramsCopy) {
serializedParameters.add(pair.getName() + "=" + urlSafe(pair.getValue()));
}
final String toSign = StringUtils.join(serializedParameters, "&").toLowerCase();
return calculateRFC2104HMAC(toSign, secretKey);
}
private static String calculateRFC2104HMAC(final String signIt, final String secretKey) throws SignatureException {
try {
final Mac hmacSha1 = Mac.getInstance(HMAC_SHA_1);
hmacSha1.init(new SecretKeySpec(secretKey.getBytes(), HMAC_SHA_1));
final byte[] rawHmac = hmacSha1.doFinal(signIt.getBytes());
return new String(Base64.encodeBase64(rawHmac)).trim();
} catch (final Exception e) {
throw new SignatureException("Failed to generate keyed HMAC on soap request: " + e.getMessage());
}
}
private static String urlSafe(final String value) {
try {
return value == null ? null : URLEncoder.encode(value, "UTF-8").replaceAll("\\+", "%20");
} catch (final UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -1,69 +0,0 @@
// 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.gate.util;
import junit.framework.Assert;
import org.apache.log4j.Logger;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.cloud.bridge.util.JsonAccessor;
import com.cloud.gate.testcase.BaseTestCase;
import com.cloud.stack.models.CloudStackSnapshot;
public class JsonAccessorTestCase extends BaseTestCase {
protected final static Logger logger = Logger.getLogger(UtilTestCase.class);
public void testJsonAccessor() {
JsonParser parser = new JsonParser();
JsonElement json = parser.parse("{firstName: 'Kelven', lastName: 'Yang', arrayObj: [{name: 'elem1'}, {name: 'elem2'}]}");
JsonAccessor jsonAccessor = new JsonAccessor(json);
Assert.assertTrue("Kelven".equals(jsonAccessor.getAsString("firstName")));
Assert.assertTrue("Kelven".equals(jsonAccessor.getAsString("this.firstName")));
Assert.assertTrue("Yang".equals(jsonAccessor.getAsString("lastName")));
Assert.assertTrue("Yang".equals(jsonAccessor.getAsString("this.lastName")));
Assert.assertTrue("elem1".equals(jsonAccessor.getAsString("arrayObj[0].name")));
Assert.assertTrue("elem2".equals(jsonAccessor.getAsString("arrayObj[1].name")));
Assert.assertTrue("elem1".equals(jsonAccessor.getAsString("this.arrayObj.this[0].name")));
Assert.assertTrue("elem2".equals(jsonAccessor.getAsString("this.arrayObj.this[1].name")));
Assert.assertTrue(jsonAccessor.getMatchCount("firstName") == 1);
Assert.assertTrue(jsonAccessor.getMatchCount("middleName") == -1);
Assert.assertTrue(jsonAccessor.getMatchCount("arrayObj") == 2);
Assert.assertTrue(jsonAccessor.getMatchCount("arrayObj[0]") == 1);
}
public void testGson() {
String response =
"{ \"queryasyncjobresultresponse\" : {\"jobid\":5868,\"jobstatus\":1,\"jobprocstatus\":0,\"jobresultcode\":0,\"jobresulttype\":\"object\",\"jobresult\":{\"snapshot\":{\"id\":3161,\"account\":\"admin\",\"domainid\":1,\"domain\":\"ROOT\",\"snapshottype\":\"MANUAL\",\"volumeid\":186928,\"volumename\":\"KY-DATA-VOL\",\"volumetype\":\"DATADISK\",\"created\":\"2011-06-02T05:05:41-0700\",\"name\":\"i-2-246446-VM_KY-DATA-VOL_20110602120541\",\"intervaltype\":\"MANUAL\",\"state\":\"BackedUp\"}}}}";
JsonParser parser = new JsonParser();
JsonElement json = parser.parse(response);
JsonAccessor jsonAccessor = new JsonAccessor(json);
Gson gson = new Gson();
CloudStackSnapshot snapshot = gson.fromJson(jsonAccessor.eval("queryasyncjobresultresponse.jobresult.snapshot"), CloudStackSnapshot.class);
Assert.assertTrue("BackedUp".equals(snapshot.getState()));
}
}

View File

@ -0,0 +1,48 @@
// 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.gate.util;
import com.cloud.bridge.util.JsonElementUtil;
import com.google.gson.JsonArray;
import junit.framework.Assert;
import com.cloud.gate.testcase.BaseTestCase;
import com.cloud.stack.models.CloudStackSnapshot;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
public class JsonElementUtilTestCase extends BaseTestCase {
private final static JsonParser JSON_PARSER = new JsonParser();
public void testJsonElementUtils() {
JsonElement json = JSON_PARSER.parse("{firstName: 'Kelven', lastName: 'Yang', arrayObj: [{name: 'elem1'}, {name: 'elem2'}], level1: {level2: 'some'}}");
assertEquals("Kelven", JsonElementUtil.getAsString(json, "firstName"));
assertEquals("Yang", JsonElementUtil.getAsString(json, "lastName"));
assertEquals("some", JsonElementUtil.getAsString(json, "level1", "level2"));
assertTrue(JsonElementUtil.getAsJsonElement(json, "arrayObj") instanceof JsonArray);
}
public void testGson() {
String response = "{ \"queryasyncjobresultresponse\" : {\"jobid\":5868,\"jobstatus\":1,\"jobprocstatus\":0,\"jobresultcode\":0,\"jobresulttype\":\"object\",\"jobresult\":{\"snapshot\":{\"id\":3161,\"account\":\"admin\",\"domainid\":1,\"domain\":\"ROOT\",\"snapshottype\":\"MANUAL\",\"volumeid\":186928,\"volumename\":\"KY-DATA-VOL\",\"volumetype\":\"DATADISK\",\"created\":\"2011-06-02T05:05:41-0700\",\"name\":\"i-2-246446-VM_KY-DATA-VOL_20110602120541\",\"intervaltype\":\"MANUAL\",\"state\":\"BackedUp\"}}}}";
JsonElement json = JSON_PARSER.parse(response);
Gson gson = new Gson();
CloudStackSnapshot snapshot = gson.fromJson(JsonElementUtil.getAsJsonElement(json, "queryasyncjobresultresponse", "jobresult", "snapshot"), CloudStackSnapshot.class);
Assert.assertTrue("BackedUp".equals(snapshot.getState()));
}
}