Merge pull request #1266 from jclouds/cloudstack-deployparse

allow SelectJson to specify multiple field names; fix cloudstack 4.1 field rename problem
This commit is contained in:
Adrian Cole 2013-01-30 10:21:58 -08:00
commit 964ae3e2aa
9 changed files with 157 additions and 51 deletions

View File

@ -38,7 +38,6 @@ import org.jclouds.rest.annotations.OnlyElement;
import org.jclouds.rest.annotations.QueryParams;
import org.jclouds.rest.annotations.RequestFilters;
import org.jclouds.rest.annotations.SelectJson;
import org.jclouds.rest.annotations.Unwrap;
import com.google.common.util.concurrent.ListenableFuture;
@ -80,7 +79,7 @@ public interface VirtualMachineAsyncClient {
*/
@GET
@QueryParams(keys = "command", values = "deployVirtualMachine")
@Unwrap
@SelectJson({ "deployvirtualmachine", "deployvirtualmachineresponse" })
@Consumes(MediaType.APPLICATION_JSON)
ListenableFuture<AsyncCreateResponse> deployVirtualMachineInZone(@QueryParam("zoneid") String zoneId,
@QueryParam("serviceofferingid") String serviceOfferingId, @QueryParam("templateid") String templateId,

View File

@ -26,13 +26,10 @@ import org.jclouds.Fallbacks.EmptySetOnNotFoundOr404;
import org.jclouds.Fallbacks.NullOnNotFoundOr404;
import org.jclouds.cloudstack.internal.BaseCloudStackAsyncClientTest;
import org.jclouds.cloudstack.options.AssignVirtualMachineOptions;
import org.jclouds.cloudstack.options.DeployVirtualMachineOptions;
import org.jclouds.cloudstack.options.ListVirtualMachinesOptions;
import org.jclouds.fallbacks.MapHttp4xxCodesToExceptions;
import org.jclouds.functions.IdentityFunction;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.functions.ParseFirstJsonValueNamed;
import org.jclouds.http.functions.UnwrapOnlyJsonValue;
import org.jclouds.rest.internal.GeneratedHttpRequest;
import org.testng.annotations.Test;
@ -104,31 +101,6 @@ public class VirtualMachineAsyncClientTest extends BaseCloudStackAsyncClientTest
}
HttpRequest deployVirtualMachine = HttpRequest.builder().method("GET")
.endpoint("http://localhost:8080/client/api")
.addQueryParam("response", "json")
.addQueryParam("command", "deployVirtualMachine")
.addQueryParam("zoneid", "6")
.addQueryParam("serviceofferingid", "4")
.addQueryParam("templateid", "5").build();
public void testDeployVirtualMachineInZone() throws SecurityException, NoSuchMethodException, IOException {
Invokable<?, ?> method = method(VirtualMachineAsyncClient.class, "deployVirtualMachineInZone", String.class, String.class,
String.class, DeployVirtualMachineOptions[].class);
GeneratedHttpRequest httpRequest = processor.createRequest(method, ImmutableList.<Object> of(6, 4, 5));
assertRequestLineEquals(httpRequest, deployVirtualMachine.getRequestLine());
assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\n");
assertPayloadEquals(httpRequest, null, null, false);
assertResponseParserClassEquals(method, httpRequest, UnwrapOnlyJsonValue.class);
assertSaxResponseParserClassEquals(method, null);
assertFallbackClassEquals(method, MapHttp4xxCodesToExceptions.class);
checkFilters(httpRequest);
}
public void testRebootVirtualMachine() throws SecurityException, NoSuchMethodException, IOException {
Invokable<?, ?> method = method(VirtualMachineAsyncClient.class, "rebootVirtualMachine", String.class);
GeneratedHttpRequest httpRequest = processor.createRequest(method, ImmutableList.<Object> of(5));

View File

@ -26,6 +26,7 @@ import java.security.cert.CertificateException;
import org.jclouds.cloudstack.CloudStackApiMetadata;
import org.jclouds.cloudstack.CloudStackContext;
import org.jclouds.cloudstack.domain.AsyncCreateResponse;
import org.jclouds.cloudstack.domain.EncryptedPasswordAndPrivateKey;
import org.jclouds.cloudstack.functions.WindowsLoginCredentialsFromEncryptedData;
import org.jclouds.cloudstack.internal.BaseCloudStackExpectTest;
@ -60,11 +61,8 @@ public class VirtualMachineClientExpectTest extends BaseCloudStackExpectTest<Vir
"-----END RSA PRIVATE KEY-----";
VirtualMachineClient client = requestSendsResponse(
HttpRequest.builder()
.method("GET")
.endpoint(
URI.create("http://localhost:8080/client/api?response=json&" +
"command=getVMPassword&id=1&apiKey=identity&signature=SVA2r1KRj4yG03rATMLPZWS%2BKnw%3D"))
HttpRequest.builder().method("GET")
.endpoint("http://localhost:8080/client/api?response=json&command=getVMPassword&id=1&apiKey=identity&signature=SVA2r1KRj4yG03rATMLPZWS%2BKnw%3D")
.addHeader("Accept", "application/json")
.build(),
HttpResponse.builder()
@ -84,6 +82,41 @@ public class VirtualMachineClientExpectTest extends BaseCloudStackExpectTest<Vir
assertEquals(passwordDecrypt.apply(
EncryptedPasswordAndPrivateKey.builder().encryptedPassword(actual).privateKey(privateKey).build()).getPassword(), "bX7vvptvw");
}
HttpRequest deployVirtualMachineInZone = HttpRequest.builder().method("GET")
.endpoint("http://localhost:8080/client/api")
.addQueryParam("response", "json")
.addQueryParam("command", "deployVirtualMachine")
.addQueryParam("zoneid", "zone1")
.addQueryParam("serviceofferingid", "serviceOffering1")
.addQueryParam("templateid", "template1")
.addQueryParam("apiKey", "identity")
.addQueryParam("signature", "pBjjnTq7/ezN94Uj0gpy2T//cJQ%3D")
.addHeader("Accept", "application/json")
.build();
public void testDeployVirtualMachineIs2xxVersion3x() {
HttpResponse deployVirtualMachineInZoneResponse = HttpResponse.builder().statusCode(200)
.payload(payloadFromResource("/deployvirtualmachineresponse.json")).build();
VirtualMachineClient client = requestSendsResponse(deployVirtualMachineInZone, deployVirtualMachineInZoneResponse);
AsyncCreateResponse async = client.deployVirtualMachineInZone("zone1", "serviceOffering1", "template1");
assertEquals(async, AsyncCreateResponse.builder().id("1234").jobId("50006").build());
}
public void testDeployVirtualMachineIs2xxVersion4x() {
HttpResponse deployVirtualMachineInZoneResponse = HttpResponse.builder().statusCode(200)
.payload(payloadFromResource("/deployvirtualmachineresponse4x.json")).build();
VirtualMachineClient client = requestSendsResponse(deployVirtualMachineInZone, deployVirtualMachineInZoneResponse);
AsyncCreateResponse async = client.deployVirtualMachineInZone("zone1", "serviceOffering1", "template1");
assertEquals(
async,
AsyncCreateResponse.builder().id("1cce6cb7-2268-47ff-9696-d9e610f6619a")
.jobId("13330fc9-8b3e-4582-aa3e-90883c041ff0").build());
}
@Override
protected VirtualMachineClient clientFrom(CloudStackContext context) {

View File

@ -0,0 +1,43 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 org.jclouds.cloudstack.parse;
import org.jclouds.cloudstack.domain.AsyncCreateResponse;
import org.jclouds.json.BaseItemParserTest;
import org.jclouds.rest.annotations.SelectJson;
import org.testng.annotations.Test;
/**
*
* @author Adrian Cole
*/
@Test(groups = "unit", testName = "DeployVirtualMachineResponse3xTest")
public class DeployVirtualMachineResponse3xTest extends BaseItemParserTest<AsyncCreateResponse> {
@Override
public String resource() {
return "/deployvirtualmachineresponse.json";
}
@Override
@SelectJson({ "deployvirtualmachine", "deployvirtualmachineresponse" })
public AsyncCreateResponse expected() {
return AsyncCreateResponse.builder().id("1234").jobId("50006").build();
}
}

View File

@ -0,0 +1,44 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 org.jclouds.cloudstack.parse;
import org.jclouds.cloudstack.domain.AsyncCreateResponse;
import org.jclouds.json.BaseItemParserTest;
import org.jclouds.rest.annotations.SelectJson;
import org.testng.annotations.Test;
/**
*
* @author Adrian Cole
*/
@Test(groups = "unit", testName = "DeployVirtualMachineResponse3xTest")
public class DeployVirtualMachineResponse4xTest extends BaseItemParserTest<AsyncCreateResponse> {
@Override
public String resource() {
return "/deployvirtualmachineresponse4x.json";
}
@Override
@SelectJson({ "deployvirtualmachine", "deployvirtualmachineresponse" })
public AsyncCreateResponse expected() {
return AsyncCreateResponse.builder().id("1cce6cb7-2268-47ff-9696-d9e610f6619a")
.jobId("13330fc9-8b3e-4582-aa3e-90883c041ff0").build();
}
}

View File

@ -0,0 +1 @@
{ "deployvirtualmachineresponse" : {"id":"1cce6cb7-2268-47ff-9696-d9e610f6619a","jobid":"13330fc9-8b3e-4582-aa3e-90883c041ff0"}, "cloudstack-version": "4.1.0-SNAPSHOT" }

View File

@ -53,12 +53,16 @@ public class ParseFirstJsonValueNamed<T> implements Function<HttpResponse, T> {
private final GsonWrapper json;
private final TypeLiteral<T> type;
private final String name;
private final ImmutableSet<String> nameChoices;
public ParseFirstJsonValueNamed(GsonWrapper json, TypeLiteral<T> type, String name) {
/**
* @param nameChoices
* tried in order, first match wins
*/
public ParseFirstJsonValueNamed(GsonWrapper json, TypeLiteral<T> type, String... nameChoices) {
this.json = checkNotNull(json, "json");
this.type = checkNotNull(type, "type");
this.name = checkNotNull(name, "name");
this.nameChoices = ImmutableSet.copyOf(checkNotNull(nameChoices, "nameChoices"));
}
@Override
@ -72,20 +76,19 @@ public class ParseFirstJsonValueNamed<T> implements Function<HttpResponse, T> {
reader.setLenient(true);
AtomicReference<String> name = Atomics.newReference();
JsonToken token = reader.peek();
for (; token != JsonToken.END_DOCUMENT && nnn(this.name, reader, token, name); token = skipAndPeek(token,
reader)) {
for (; token != JsonToken.END_DOCUMENT && nnn(reader, token, name); token = skipAndPeek(token, reader)) {
}
if (name.get() == null) {
logger.trace("did not object named %s in json from response %s", this.name, arg0);
logger.trace("did not object named %s in json from response %s", nameChoices, arg0);
return nothing();
} else if (name.get().equals(this.name)) {
} else if (nameChoices.contains(name.get())) {
return json.delegate().<T> fromJson(reader, type.getType());
} else {
return nothing();
}
} catch (IOException e) {
throw new RuntimeException(String.format(
"error reading from stream, parsing object named %s from http response %s", this.name, arg0), e);
"error reading from stream, parsing object named %s from http response %s", nameChoices, arg0), e);
} finally {
Closeables.closeQuietly(reader);
arg0.getPayload().release();
@ -93,7 +96,7 @@ public class ParseFirstJsonValueNamed<T> implements Function<HttpResponse, T> {
}
@SuppressWarnings("unchecked")
protected T nothing() {
private T nothing() {
if (type.getRawType().isAssignableFrom(Set.class))
return (T) ImmutableSet.of();
else if (type.getRawType().isAssignableFrom(List.class))
@ -103,11 +106,10 @@ public class ParseFirstJsonValueNamed<T> implements Function<HttpResponse, T> {
return null;
}
protected boolean nnn(String toFind, JsonReader reader, JsonToken token, AtomicReference<String> name)
throws IOException {
private boolean nnn(JsonReader reader, JsonToken token, AtomicReference<String> name) throws IOException {
if (token == JsonToken.NAME) {
String name2 = reader.nextName();
if (toFind.equals(name2)) {
if (nameChoices.contains(name2)) {
name.set(name2);
return false;
}
@ -116,7 +118,7 @@ public class ParseFirstJsonValueNamed<T> implements Function<HttpResponse, T> {
}
public JsonToken skipAndPeek(JsonToken token, JsonReader reader) throws IOException {
private JsonToken skipAndPeek(JsonToken token, JsonReader reader) throws IOException {
switch (token) {
case BEGIN_ARRAY:
reader.beginArray();

View File

@ -36,9 +36,8 @@ import java.lang.annotation.Target;
public @interface SelectJson {
/**
*
* @return
* Each of the keys are tried in order. This helps in the case the server renamed a field in json.
*/
String value();
String[] value();
}

View File

@ -70,6 +70,19 @@ public class ParseFirstJsonValueNamedTest {
}, "event").apply(response);
assertEquals(val.toString(), "[(name=GREETINGS, source=guest)]");
}
/**
* scenario: server renames field from event to _event
*/
public void testParseRenamedField() throws IOException {
String nested = "{ \"count\":1 ,\"_event\" : [ {name:'GREETINGS',source:'guest'} ] }";
HttpResponse response = HttpResponse.builder().statusCode(200).message("goodie")
.payload(Payloads.newPayload(nested)).build();
List<Event> val = new ParseFirstJsonValueNamed<List<Event>>(json, new TypeLiteral<List<Event>>() {
}, "event", "_event").apply(response);
assertEquals(val.toString(), "[(name=GREETINGS, source=guest)]");
}
public void testParseNestedElementsWhenNotFoundIsEmpty() throws IOException {
String nested = "{ \"count\":1 ,\"evant\" : [ {name:'GREETINGS',source:'guest'} ] }";