fix for JCLOUDS-655

This commit is contained in:
Taylor Jones 2014-12-04 09:47:29 -05:00 committed by Everett Toews
parent bc48aad54d
commit f708d20355
7 changed files with 398 additions and 15 deletions

View File

@ -18,17 +18,25 @@ package org.jclouds.openstack.nova.v2_0.config;
import java.beans.ConstructorProperties; import java.beans.ConstructorProperties;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeMap;
import javax.inject.Singleton; import javax.inject.Singleton;
import com.google.common.collect.Maps;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import org.jclouds.javax.annotation.Nullable; import org.jclouds.javax.annotation.Nullable;
import org.jclouds.json.config.GsonModule; import org.jclouds.json.config.GsonModule;
import org.jclouds.json.config.GsonModule.DateAdapter; import org.jclouds.json.config.GsonModule.DateAdapter;
import org.jclouds.openstack.nova.v2_0.domain.Address; import org.jclouds.openstack.nova.v2_0.domain.Address;
import org.jclouds.openstack.nova.v2_0.domain.BlockDeviceMapping;
import org.jclouds.openstack.nova.v2_0.domain.HostResourceUsage; import org.jclouds.openstack.nova.v2_0.domain.HostResourceUsage;
import org.jclouds.openstack.nova.v2_0.domain.Image;
import org.jclouds.openstack.nova.v2_0.domain.Server; import org.jclouds.openstack.nova.v2_0.domain.Server;
import org.jclouds.openstack.nova.v2_0.domain.ServerExtendedAttributes; import org.jclouds.openstack.nova.v2_0.domain.ServerExtendedAttributes;
import org.jclouds.openstack.nova.v2_0.domain.ServerExtendedStatus; import org.jclouds.openstack.nova.v2_0.domain.ServerExtendedStatus;
@ -58,7 +66,8 @@ public class NovaParserModule extends AbstractModule {
return ImmutableMap.<Type, Object>of( return ImmutableMap.<Type, Object>of(
HostResourceUsage.class, new HostResourceUsageAdapter(), HostResourceUsage.class, new HostResourceUsageAdapter(),
ServerWithSecurityGroups.class, new ServerWithSecurityGroupsAdapter(), ServerWithSecurityGroups.class, new ServerWithSecurityGroupsAdapter(),
Server.class, new ServerAdapter() Server.class, new ServerAdapter(),
Image.class, new ImageAdapter()
); );
} }
@ -173,4 +182,61 @@ public class NovaParserModule extends AbstractModule {
} }
} }
} }
@Singleton
public static class ImageAdapter implements JsonDeserializer<Image> {
public static final String METADATA = "metadata";
public static final String BLOCK_DEVICE_MAPPING = "block_device_mapping";
@Override
public Image deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context)
throws JsonParseException {
JsonObject json = jsonElement.getAsJsonObject();
Map<String, String> metadata = null;
List<BlockDeviceMapping> blockDeviceMapping = null;
JsonElement meta = json.get(METADATA);
if (meta != null && meta.isJsonObject()) {
metadata = Maps.newTreeMap();
for (Map.Entry<String, JsonElement> e : meta.getAsJsonObject().entrySet()) {
Object value;
if (e.getValue().isJsonArray()) {
value = context.deserialize(e.getValue().getAsJsonArray(), ArrayList.class);
} else if (e.getValue().isJsonObject()) {
value = context.deserialize(e.getValue().getAsJsonObject(), TreeMap.class);
} else if (e.getValue().isJsonPrimitive()) {
value = e.getValue().getAsJsonPrimitive().getAsString();
} else {
continue;
}
//keep non-string members out of normal metadata
if (value instanceof String) {
metadata.put(e.getKey(), (String) value);
} else if (value instanceof List && BLOCK_DEVICE_MAPPING.equals(e.getKey())) {
blockDeviceMapping = context.deserialize(e.getValue(), new TypeToken<List<BlockDeviceMapping>>(){}.getType());
}
}
json.remove(METADATA);
}
return apply(context.<ImageInternal>deserialize(json, ImageInternal.class), metadata, blockDeviceMapping);
}
public Image apply(ImageInternal in, Map<String, String> metadata, List<BlockDeviceMapping> blockDeviceMapping) {
return in.toBuilder().metadata(metadata).blockDeviceMapping(blockDeviceMapping).build();
}
private static class ImageInternal extends Image {
@ConstructorProperties({
"id", "name", "links", "updated", "created", "tenant_id", "user_id", "status", "progress", "minDisk", "minRam", "blockDeviceMapping", "server", "metadata"
})
protected ImageInternal(String id, @Nullable String name, java.util.Set<Link> links, @Nullable Date updated, @Nullable Date created,
String tenantId, @Nullable String userId, @Nullable Status status, int progress, int minDisk, int minRam,
@Nullable List<BlockDeviceMapping> blockDeviceMapping, @Nullable Resource server, @Nullable Map<String, String> metadata) {
super(id, name, links, updated, created, tenantId, userId, status, progress, minDisk, minRam, blockDeviceMapping, server, metadata);
}
}
}
} }

View File

@ -20,10 +20,12 @@ import static com.google.common.base.Preconditions.checkNotNull;
import java.beans.ConstructorProperties; import java.beans.ConstructorProperties;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.Map; import java.util.Map;
import javax.inject.Named; import javax.inject.Named;
import com.google.common.collect.ImmutableList;
import org.jclouds.javax.annotation.Nullable; import org.jclouds.javax.annotation.Nullable;
import org.jclouds.openstack.v2_0.domain.Link; import org.jclouds.openstack.v2_0.domain.Link;
import org.jclouds.openstack.v2_0.domain.Resource; import org.jclouds.openstack.v2_0.domain.Resource;
@ -85,6 +87,7 @@ public class Image extends Resource {
protected int minDisk; protected int minDisk;
protected int minRam; protected int minRam;
protected Resource server; protected Resource server;
protected List<BlockDeviceMapping> blockDeviceMapping = ImmutableList.of();
protected Map<String, String> metadata = ImmutableMap.of(); protected Map<String, String> metadata = ImmutableMap.of();
/** /**
@ -159,6 +162,11 @@ public class Image extends Resource {
return self(); return self();
} }
public T blockDeviceMapping(List<BlockDeviceMapping> blockDeviceMapping){
this.blockDeviceMapping = blockDeviceMapping;
return self();
}
/** /**
* @see Image#getMetadata() * @see Image#getMetadata()
*/ */
@ -168,7 +176,7 @@ public class Image extends Resource {
} }
public Image build() { public Image build() {
return new Image(id, name, links, updated, created, tenantId, userId, status, progress, minDisk, minRam, server, metadata); return new Image(id, name, links, updated, created, tenantId, userId, status, progress, minDisk, minRam, blockDeviceMapping, server, metadata);
} }
public T fromImage(Image in) { public T fromImage(Image in) {
@ -203,15 +211,16 @@ public class Image extends Resource {
private final int progress; private final int progress;
private final int minDisk; private final int minDisk;
private final int minRam; private final int minRam;
private final List<BlockDeviceMapping> blockDeviceMapping;
private final Resource server; private final Resource server;
private final Map<String, String> metadata; private final Map<String, String> metadata;
@ConstructorProperties({ @ConstructorProperties({
"id", "name", "links", "updated", "created", "tenant_id", "user_id", "status", "progress", "minDisk", "minRam", "server", "metadata" "id", "name", "links", "updated", "created", "tenant_id", "user_id", "status", "progress", "minDisk", "minRam", "server", "blockDeviceMapping", "metadata"
}) })
protected Image(String id, @Nullable String name, java.util.Set<Link> links, @Nullable Date updated, @Nullable Date created, protected Image(String id, @Nullable String name, java.util.Set<Link> links, @Nullable Date updated, @Nullable Date created,
String tenantId, @Nullable String userId, @Nullable Status status, int progress, int minDisk, int minRam, String tenantId, @Nullable String userId, @Nullable Status status, int progress, int minDisk, int minRam,
@Nullable Resource server, @Nullable Map<String, String> metadata) { @Nullable List<BlockDeviceMapping> blockDeviceMapping, @Nullable Resource server, @Nullable Map<String, String> metadata) {
super(id, name, links); super(id, name, links);
this.updated = updated; this.updated = updated;
this.created = created; this.created = created;
@ -221,6 +230,7 @@ public class Image extends Resource {
this.progress = progress; this.progress = progress;
this.minDisk = minDisk; this.minDisk = minDisk;
this.minRam = minRam; this.minRam = minRam;
this.blockDeviceMapping = blockDeviceMapping == null ? ImmutableList.<BlockDeviceMapping>of() : blockDeviceMapping;
this.server = server; this.server = server;
this.metadata = metadata == null ? ImmutableMap.<String, String>of() : ImmutableMap.copyOf(metadata); this.metadata = metadata == null ? ImmutableMap.<String, String>of() : ImmutableMap.copyOf(metadata);
} }
@ -262,6 +272,11 @@ public class Image extends Resource {
return this.minRam; return this.minRam;
} }
@Nullable
public List<BlockDeviceMapping> getBlockDeviceMapping(){
return this.blockDeviceMapping;
}
@Nullable @Nullable
public Resource getServer() { public Resource getServer() {
return this.server; return this.server;

View File

@ -0,0 +1,93 @@
/*
* 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 org.jclouds.openstack.nova.v2_0.config;
import static com.google.common.collect.Iterables.getOnlyElement;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import java.io.IOException;
import org.jclouds.json.config.GsonModule;
import org.jclouds.openstack.nova.v2_0.domain.Image;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import com.google.gson.Gson;
import com.google.inject.Guice;
import com.google.inject.Injector;
@Test(groups = "unit")
public class ImageAdapterTest {
private Gson gson;
@BeforeTest
public void setup() {
Injector injector = Guice.createInjector(new GsonModule(), new NovaParserModule());
gson = injector.getInstance(Gson.class);
}
public void testDeserializeWithBlockDeviceMappingAndMetadata() throws Exception {
ImageContainer container = gson.fromJson(stringFromResource("image_details_with_block_device_mapping.json"), ImageContainer.class);
// Note that the block device mapping keys are removed from the metadata by the adapter.
assertNotNull(container.image.getMetadata());
assertEquals(container.image.getMetadata().size(), 2);
assertEquals("Gold", container.image.getMetadata().get("ImageType"));
assertEquals("1.5", container.image.getMetadata().get("ImageVersion"));
assertNotNull(container.image.getBlockDeviceMapping());
assertEquals(container.image.getBlockDeviceMapping().size(), 1);
assertEquals(new Integer(2), getOnlyElement(container.image.getBlockDeviceMapping()).getBootIndex());
assertEquals("snapshot", getOnlyElement(container.image.getBlockDeviceMapping()).getSourceType());
}
public void testDeserializeWithoutBlockDeviceMapping() throws Exception {
ImageContainer container = gson.fromJson(stringFromResource("image_details.json"), ImageContainer.class);
assertNotNull(container.image.getMetadata());
assertEquals(container.image.getMetadata().size(), 2);
assertEquals("Gold", container.image.getMetadata().get("ImageType"));
assertEquals("1.5", container.image.getMetadata().get("ImageVersion"));
assertNotNull(container.image.getBlockDeviceMapping());
assertEquals(0, container.image.getBlockDeviceMapping().size());
}
public void testDeserializeWithoutBlockDeviceMappingOrMetadata() throws Exception {
ImageContainer container = gson.fromJson(stringFromResource("image_details_without_metadata.json"), ImageContainer.class);
assertNotNull(container.image.getMetadata());
assertEquals(container.image.getMetadata().size(), 0);
assertNotNull(container.image.getBlockDeviceMapping());
assertEquals(0, container.image.getBlockDeviceMapping().size());
}
private String stringFromResource(String resource) throws IOException {
return Resources.toString(Resources.getResource(resource), Charsets.UTF_8);
}
// Note that the ImageApi methods use the "@SelectJson" annotation to unwrap the object inside the "image" key
// We use this container to deserialize the Image object to simulate that behavior and use a *real* json
// in the tests.
public static class ImageContainer {
public Image image;
}
}

View File

@ -0,0 +1,56 @@
/*
* 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 org.jclouds.openstack.nova.v2_0.features;
import com.google.common.collect.FluentIterable;
import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.MockWebServer;
import org.jclouds.openstack.nova.v2_0.NovaApi;
import org.jclouds.openstack.nova.v2_0.domain.BlockDeviceMapping;
import org.jclouds.openstack.nova.v2_0.domain.Image;
import org.jclouds.openstack.v2_0.internal.BaseOpenStackMockTest;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
@Test(groups = "unit")
public class ImageApiMockTest extends BaseOpenStackMockTest<NovaApi> {
public void testImageWithBlockDeviceMapping() throws Exception {
MockWebServer server = mockOpenStackServer();
server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/image_list_with_block_device_mapping.json"))));
try {
NovaApi novaApi = api(server.getUrl("/").toString(), "openstack-nova");
ImageApi imageApi = novaApi.getImageApiForZone("RegionOne");
FluentIterable<? extends Image> images = imageApi.listInDetail().concat();
Image img = images.get(0);
assertNotNull(img.getMetadata());
assertEquals(10, img.getMetadata().size());
assertNotNull(img.getBlockDeviceMapping());
assertEquals(1, img.getBlockDeviceMapping().size());
BlockDeviceMapping blockDeviceMapping = img.getBlockDeviceMapping().get(0);
assertEquals("snapshot", blockDeviceMapping.getSourceType());
assertEquals(new Integer(2), blockDeviceMapping.getBootIndex());
} finally {
server.shutdown();
}
}
}

View File

@ -0,0 +1,57 @@
{
"image": {
"id": "52415800-8b69-11e0-9b19-734f5736d2a2",
"name": "My Server Backup",
"updated": "2010-10-10T12:00:00Z",
"created": "2010-08-10T12:00:00Z",
"tenant_id": "12345",
"user_id": "joe",
"status": "SAVING",
"progress": 80,
"minDisk": 5,
"minRam": 256,
"metadata": {
"ImageType": "Gold",
"ImageVersion": 1.5,
"block_device_mapping": [
{
"guest_format": null,
"boot_index": 2,
"no_device": null,
"volume_id": null,
"volume_size": null,
"disk_bus": null,
"image_id": null,
"source_type": "snapshot",
"device_type": null,
"snapshot_id": "a900a56c-61b7-4438-9150-76312fa1aa10",
"destination_type": "volume",
"delete_on_termination": null
}
]
},
"server": {
"id": "52415800-8b69-11e0-9b19-734f335aa7b3",
"links": [
{
"rel": "self",
"href": "http://servers.api.openstack.org/v2/1234/servers/52415800-8b69-11e0-9b19-734f335aa7b3"
},
{
"rel": "bookmark",
"href": "http://servers.api.openstack.org/1234/servers/52415800-8b69-11e0-9b19-734f335aa7b3"
}
]
},
"links": [
{
"rel": "self",
"href": "http://servers.api.openstack.org/v2/1234/images/52415800-8b69-11e0-9b19-734f5736d2a2"
},
{
"rel": "bookmark",
"href": "http://servers.api.openstack.org/1234/images/52415800-8b69-11e0-9b19-734f5736d2a2"
}
]
}
}

View File

@ -0,0 +1,38 @@
{
"image": {
"id": "52415800-8b69-11e0-9b19-734f5736d2a2",
"name": "My Server Backup",
"updated": "2010-10-10T12:00:00Z",
"created": "2010-08-10T12:00:00Z",
"tenant_id": "12345",
"user_id": "joe",
"status": "SAVING",
"progress": 80,
"minDisk": 5,
"minRam": 256,
"metadata": {},
"server": {
"id": "52415800-8b69-11e0-9b19-734f335aa7b3",
"links": [
{
"rel": "self",
"href": "http://servers.api.openstack.org/v2/1234/servers/52415800-8b69-11e0-9b19-734f335aa7b3"
},
{
"rel": "bookmark",
"href": "http://servers.api.openstack.org/1234/servers/52415800-8b69-11e0-9b19-734f335aa7b3"
}
]
},
"links": [
{
"rel": "self",
"href": "http://servers.api.openstack.org/v2/1234/images/52415800-8b69-11e0-9b19-734f5736d2a2"
},
{
"rel": "bookmark",
"href": "http://servers.api.openstack.org/1234/images/52415800-8b69-11e0-9b19-734f5736d2a2"
}
]
}
}

View File

@ -0,0 +1,58 @@
{
"images": [
{
"status": "ACTIVE",
"updated": "2014-08-08T04:43:36Z",
"links": [
{
"href": "http://192.168.24.16:8774/v2/d312a9d1acee46499e04fc2c0cd7e540/images/cd9d57a9-0978-45f3-9cbc-edb99347be6b",
"rel": "self"
},
{
"href": "http://192.168.24.16:8774/d312a9d1acee46499e04fc2c0cd7e540/images/cd9d57a9-0978-45f3-9cbc-edb99347be6b",
"rel": "bookmark"
},
{
"href": "http://192.168.24.16:9292/d312a9d1acee46499e04fc2c0cd7e540/images/cd9d57a9-0978-45f3-9cbc-edb99347be6b",
"type": "application/vnd.openstack.image",
"rel": "alternate"
}
],
"id": "cd9d57a9-0978-45f3-9cbc-edb99347be6b",
"OS-EXT-IMG-SIZE:size": 0,
"name": "t11",
"created": "2014-08-08T04:43:36Z",
"minDisk": 0,
"progress": 100,
"minRam": 0,
"metadata": {
"block_device_mapping": [
{
"guest_format": null,
"boot_index": 2,
"no_device": null,
"volume_id": null,
"volume_size": null,
"disk_bus": null,
"image_id": null,
"source_type": "snapshot",
"device_type": null,
"snapshot_id": "a900a56c-61b7-4438-9150-76312fa1aa10",
"destination_type": "volume",
"delete_on_termination": null
}
],
"checksum": "32c08d302f9206668030d47789b77858",
"min_ram": "1",
"disk_format": "qcow2",
"image_name": "Ubuntu LTS 14.04",
"bdm_v2": "True",
"image_id": "cfefefc1-eba2-4b1e-9b07-a8c74a872d65",
"root_device_name": "/dev/vda",
"container_format": "bare",
"min_disk": "8",
"size": "254149120"
}
}
]
}