Repository: jclouds Updated Branches: refs/heads/1.8.x 4c0f7e766 -> 8122f0b38
JCLOUDS-281: Support Nova Block Device Mapping v2 Boot Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/8122f0b3 Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/8122f0b3 Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/8122f0b3 Branch: refs/heads/1.8.x Commit: 8122f0b38da0ab017ac15b380a072f813a12b8b1 Parents: 4c0f7e7 Author: Jeremy Daggett <[email protected]> Authored: Tue Oct 7 20:54:16 2014 -0700 Committer: Jeremy Daggett <[email protected]> Committed: Tue Oct 7 23:17:39 2014 -0700 ---------------------------------------------------------------------- .../nova/v2_0/domain/BlockDeviceMapping.java | 358 ++++++++++--------- .../v2_0/extensions/ExtensionNamespaces.java | 23 +- .../nova/v2_0/options/CreateServerOptions.java | 232 ++++++------ .../extensions/VolumeAttachmentApiLiveTest.java | 6 +- .../nova/v2_0/features/ServerApiExpectTest.java | 43 ++- .../nova/v2_0/features/ServerApiLiveTest.java | 53 ++- .../src/test/resources/extension_list_full.json | 16 + .../resources/new_server_networks_response.json | 2 +- .../test/resources/new_server_no_adminpass.json | 2 +- .../resources/server_details_without_image.json | 2 +- 10 files changed, 424 insertions(+), 313 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/jclouds/blob/8122f0b3/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/domain/BlockDeviceMapping.java ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/domain/BlockDeviceMapping.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/domain/BlockDeviceMapping.java index 1a01531..7e05614 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/domain/BlockDeviceMapping.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/domain/BlockDeviceMapping.java @@ -16,92 +16,152 @@ */ package org.jclouds.openstack.nova.v2_0.domain; -import static com.google.common.base.Preconditions.checkNotNull; +import java.beans.ConstructorProperties; import javax.inject.Named; -import java.beans.ConstructorProperties; import org.jclouds.javax.annotation.Nullable; import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; /** - * A representation of a block device that should be attached to the Nova instance to be launched - * + * A representation of a block device that can be used to boot a Nova instance. */ public class BlockDeviceMapping { - @Named("delete_on_termination") - String deleteOnTermination = "0"; - @Named("device_name") - String deviceName = null; - @Named("volume_id") - String volumeId = null; - @Named("volume_size") - String volumeSize = ""; - - @ConstructorProperties({"volume_id", "volume_size", "device_name", "delete_on_termination"}) - private BlockDeviceMapping(String volumeId, String volumeSize, String deviceName, String deleteOnTermination) { - checkNotNull(volumeId); - checkNotNull(deviceName); - this.volumeId = volumeId; - this.volumeSize = volumeSize; + private String uuid; + @Named("device_name") + private String deviceName; + @Named("device_type") + private String deviceType; + @Named("volume_size") + private Integer volumeSize; + @Named("source_type") + private String sourceType; + @Named("destination_type") + private String destinationType; + @Named("disk_bus") + private String diskBus; + @Named("no_device") + private Boolean noDevice; + @Named("guest_format") + private String guestFormat; + @Named("boot_index") + private Integer bootIndex; + @Named("delete_on_termination") + private Boolean deleteOnTermination; + + @ConstructorProperties({"uuid", "device_name", "device_type", "volume_size", "source_type", "destination_type", + "disk_bus", "no_device", "guest_format", "boot_index", "delete_on_termination"}) + protected BlockDeviceMapping(String uuid, String deviceName, String deviceType, Integer volumeSize, + String sourceType, String destinationType, String diskBus, Boolean noDevice, String guestFormat, + Integer bootIndex, Boolean deleteOnTermination) { + this.uuid = uuid; this.deviceName = deviceName; - if (deleteOnTermination != null) { - this.deleteOnTermination = deleteOnTermination; - } + this.deviceType = deviceType; + this.volumeSize = volumeSize; + this.sourceType = sourceType; + this.destinationType = destinationType; + this.diskBus = diskBus; + this.noDevice = noDevice; + this.guestFormat = guestFormat; + this.bootIndex = bootIndex; + this.deleteOnTermination = deleteOnTermination; } /** - * Default constructor. + * @return the uuid of the volume */ - private BlockDeviceMapping() {} + @Nullable + public String getUuid() { + return uuid; + } /** - * Copy constructor - * @param blockDeviceMapping + * @return the device name */ - private BlockDeviceMapping(BlockDeviceMapping blockDeviceMapping) { - this(blockDeviceMapping.volumeId, - blockDeviceMapping.volumeSize, - blockDeviceMapping.deviceName, - blockDeviceMapping.deleteOnTermination); + @Nullable + public String getDeviceName() { + return deviceName; } /** - * @return the volume id of the block device + * @return the device type */ @Nullable - public String getVolumeId() { - return volumeId; + public String getDeviceType() { + return deviceType; } /** - * @return the size of the block device + * @return the size of the volume */ @Nullable - public String getVolumeSize() { + public Integer getVolumeSize() { return volumeSize; } /** - * @return the device name to which the volume is attached + * @return the source type of the block device */ @Nullable - public String getDeviceName() { - return deviceName; + public String getSourceType() { + return sourceType; } /** - * @return whether the volume should be deleted on terminating the instance + * @return the destination type of the block device */ - public String getDeleteOnTermination() { - return deviceName; + @Nullable + public String getDestinationType() { + return destinationType; + } + + /** + * @return the disk bus of the block device + */ + @Nullable + public String getDiskBus() { + return diskBus; + } + + /** + * @return true if there is no block device + */ + @Nullable + public Boolean getNoDevice() { + return noDevice; + } + + /** + * @return the guest format of the block device + */ + @Nullable + public String getGuestFormat() { + return guestFormat; + } + + /** + * @return the boot index of the block device + */ + @Nullable + public Integer getBootIndex() { + return bootIndex; + } + + /** + * @return true if the block device should terminate on deletion + */ + @Nullable + public Boolean getDeleteOnTermination() { + return deleteOnTermination; } @Override public int hashCode() { - return Objects.hashCode(volumeId, volumeSize, deviceName, deleteOnTermination); + return Objects.hashCode(uuid, deviceName, deviceType, volumeSize, sourceType, destinationType, diskBus, + noDevice, guestFormat, bootIndex, deleteOnTermination); } @Override @@ -111,168 +171,134 @@ public class BlockDeviceMapping { if (obj == null || getClass() != obj.getClass()) return false; BlockDeviceMapping that = BlockDeviceMapping.class.cast(obj); - return Objects.equal(this.volumeId, that.volumeId) - && Objects.equal(this.volumeSize, that.volumeSize) + return Objects.equal(this.uuid, that.uuid) && Objects.equal(this.deviceName, that.deviceName) + && Objects.equal(this.deviceType, that.deviceType) + && Objects.equal(this.volumeSize, that.volumeSize) + && Objects.equal(this.sourceType, that.sourceType) + && Objects.equal(this.destinationType, that.destinationType) + && Objects.equal(this.diskBus, that.diskBus) + && Objects.equal(this.noDevice, that.noDevice) + && Objects.equal(this.guestFormat, that.guestFormat) + && Objects.equal(this.bootIndex, that.bootIndex) && Objects.equal(this.deleteOnTermination, that.deleteOnTermination); } - @Override - public String toString() { + protected ToStringHelper string() { return Objects.toStringHelper(this) - .add("volumeId", volumeId) - .add("volumeSize", volumeSize) + .add("uuid", uuid) .add("deviceName", deviceName) - .add("deleteOnTermination", deleteOnTermination) - .toString(); + .add("deviceType", deviceType) + .add("volumeSize", volumeSize) + .add("sourceType", sourceType) + .add("destinationType", destinationType) + .add("diskBus", diskBus) + .add("noDevice", noDevice) + .add("guestFormat", guestFormat) + .add("bootIndex", bootIndex) + .add("deleteOnTermination", deleteOnTermination); } - /* - * Methods to get the Create and Update builders follow - */ - - /** - * @return the Builder for creating a new block device mapping - */ - public static CreateBuilder createOptions(String volumeId, String deviceName) { - return new CreateBuilder(volumeId, deviceName); + @Override + public String toString() { + return string().toString(); } - /** - * @return the Builder for updating a block device mapping - */ - public static UpdateBuilder updateOptions() { - return new UpdateBuilder(); + public static Builder builder() { + return new Builder(); } - private abstract static class Builder<ParameterizedBuilderType> { - protected BlockDeviceMapping blockDeviceMapping; + public Builder toBuilder() { + return builder().fromBlockDeviceMapping(this); + } - /** - * No-parameters constructor used when updating. - * */ - private Builder() { - blockDeviceMapping = new BlockDeviceMapping(); + public static class Builder { + protected String uuid; + protected String deviceName; + protected String deviceType; + protected Integer volumeSize; + protected String sourceType; + protected String destinationType; + protected String diskBus; + protected Boolean noDevice; + protected String guestFormat; + protected Integer bootIndex; + protected Boolean deleteOnTermination; + + public Builder uuid(String uuid) { + this.uuid = uuid; + return this; } - protected abstract ParameterizedBuilderType self(); - - /** - * Provide the volume id to the BlockDeviceMapping's Builder. - * - * @return the Builder. - * @see BlockDeviceMapping#getVolumeId() - */ - public ParameterizedBuilderType volumeId(String volumeId) { - blockDeviceMapping.volumeId = volumeId; - return self(); + public Builder deviceName(String deviceName) { + this.deviceName = deviceName; + return this; } - /** - * Provide the volume size in GB to the BlockDeviceMapping's Builder. - * - * @return the Builder. - * @see BlockDeviceMapping#getVolumeSize() - */ - public ParameterizedBuilderType volumeSize(int volumeSize) { - blockDeviceMapping.volumeSize = Integer.toString(volumeSize); - return self(); + public Builder deviceType(String deviceType) { + this.deviceType = deviceType; + return this; } - /** - * Provide the deviceName to the BlockDeviceMapping's Builder. - * - * @return the Builder. - * @see BlockDeviceMapping#getDeviceName() - */ - public ParameterizedBuilderType deviceName(String deviceName) { - blockDeviceMapping.deviceName = deviceName; - return self(); + public Builder volumeSize(Integer volumeSize) { + this.volumeSize = volumeSize; + return this; } - /** - * Provide an option indicated to delete the volume on instance deletion to BlockDeviceMapping's Builder. - * - * @return the Builder. - * @see BlockDeviceMapping#getVolumeSize() - */ - public ParameterizedBuilderType deleteOnTermination(boolean deleteOnTermination) { - blockDeviceMapping.deleteOnTermination = deleteOnTermination ? "1" : "0"; - return self(); + public Builder sourceType(String sourceType) { + this.sourceType = sourceType; + return this; } - } - /** - * Create and Update builders (inheriting from Builder) - */ - public static class CreateBuilder extends Builder<CreateBuilder> { - /** - * Supply required properties for creating a Builder - */ - private CreateBuilder(String volumeId, String deviceName) { - blockDeviceMapping.volumeId = volumeId; - blockDeviceMapping.deviceName = deviceName; + public Builder destinationType(String destinationType) { + this.destinationType = destinationType; + return this; } - /** - * @return a CreateOptions constructed with this Builder. - */ - public CreateOptions build() { - return new CreateOptions(blockDeviceMapping); + public Builder diskBus(String diskBus) { + this.diskBus = diskBus; + return this; } - protected CreateBuilder self() { + public Builder noDevice(Boolean noDevice) { + this.noDevice = noDevice; return this; } - } - /** - * Create and Update builders (inheriting from Builder) - */ - public static class UpdateBuilder extends Builder<UpdateBuilder> { - /** - * Supply required properties for updating a Builder - */ - private UpdateBuilder() { + public Builder guestFormat(String guestFormat) { + this.guestFormat = guestFormat; + return this; } - /** - * @return a UpdateOptions constructed with this Builder. - */ - public UpdateOptions build() { - return new UpdateOptions(blockDeviceMapping); + public Builder bootIndex(Integer bootIndex) { + this.bootIndex = bootIndex; + return this; } - protected UpdateBuilder self() { + public Builder deleteOnTermination(Boolean deleteOnTermination) { + this.deleteOnTermination = deleteOnTermination; return this; } - } - /** - * Create and Update options - extend the domain class, passed to API update and create calls. - * Essentially the same as the domain class. Ensure validation and safe typing. - */ - public static class CreateOptions extends BlockDeviceMapping { - /** - * Copy constructor - */ - private CreateOptions(BlockDeviceMapping blockDeviceMapping) { - super(blockDeviceMapping); - checkNotNull(blockDeviceMapping.volumeId, "volume id should not be null"); - checkNotNull(blockDeviceMapping.deviceName, "device name should not be null"); + public BlockDeviceMapping build() { + return new BlockDeviceMapping(uuid, deviceName, deviceType, volumeSize, sourceType, destinationType, diskBus, + noDevice, guestFormat, bootIndex, deleteOnTermination); } - } - /** - * Create and Update options - extend the domain class, passed to API update and create calls. - * Essentially the same as the domain class. Ensure validation and safe typing. - */ - public static class UpdateOptions extends BlockDeviceMapping { - /** - * Copy constructor - */ - private UpdateOptions(BlockDeviceMapping blockDeviceMapping) { - super(blockDeviceMapping); + public Builder fromBlockDeviceMapping(BlockDeviceMapping in) { + return this + .uuid(in.getUuid()) + .deviceName(in.getDeviceName()) + .deviceType(in.getDeviceType()) + .volumeSize(in.getVolumeSize()) + .sourceType(in.getSourceType()) + .destinationType(in.getDestinationType()) + .diskBus(in.getDiskBus()) + .noDevice(in.getNoDevice()) + .bootIndex(in.getBootIndex()) + .deleteOnTermination(in.getDeleteOnTermination()) + .guestFormat(in.getGuestFormat()); } } + } http://git-wip-us.apache.org/repos/asf/jclouds/blob/8122f0b3/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/extensions/ExtensionNamespaces.java ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/extensions/ExtensionNamespaces.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/extensions/ExtensionNamespaces.java index 503f32e..396c52b 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/extensions/ExtensionNamespaces.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/extensions/ExtensionNamespaces.java @@ -17,11 +17,9 @@ package org.jclouds.openstack.nova.v2_0.extensions; /** - * Extension namespaces - * - * @see <a href= "http://nova.openstack.org/api_ext/" /> + * OpenStack Nova Extension Namespaces */ -public interface ExtensionNamespaces { +public final class ExtensionNamespaces { /** * Keypair Support */ @@ -31,6 +29,10 @@ public interface ExtensionNamespaces { */ public static final String VOLUMES = "http://docs.openstack.org/ext/volumes/api/v1.1"; /** + * Volume attachment support + */ + public static final String VOLUME_ATTACHMENTS = "http://docs.openstack.org/compute/ext/os-volume-attachment-update/api/v2"; + /** * Volume types support */ public static final String VOLUME_TYPES = "http://docs.openstack.org/ext/volume_types/api/v1.1"; @@ -90,24 +92,29 @@ public interface ExtensionNamespaces { * Admin Action extension */ public static final String ADMIN_ACTIONS = "http://docs.openstack.org/ext/admin-actions/api/v1.1"; - /** * Extended Server Status extension */ public static final String EXTENDED_STATUS = "http://docs.openstack.org/compute/ext/extended_status/api/v1.1"; - /** * Disk Config extension */ public static final String DISK_CONFIG = "http://docs.openstack.org/compute/ext/disk_config/api/v1.1"; - /** * Aggregates extension */ public static final String AGGREGATES = "http://docs.openstack.org/ext/aggregates/api/v1.1"; - /** * Consoles extension */ public static final String CONSOLES = "http://docs.openstack.org/compute/ext/os-consoles/api/v2"; + /** + * Block Device Mapping v2 Boot Extension + */ + public static final String BLOCK_DEVICE_MAPPING_V2_BOOT = + "http://docs.openstack.org/compute/ext/block_device_mapping_v2_boot/api/v2"; + + private ExtensionNamespaces() { + throw new AssertionError("intentionally unimplemented"); + } } http://git-wip-us.apache.org/repos/asf/jclouds/blob/8122f0b3/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/options/CreateServerOptions.java ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/options/CreateServerOptions.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/options/CreateServerOptions.java index 147f94c..6cf181d 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/options/CreateServerOptions.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/options/CreateServerOptions.java @@ -109,7 +109,7 @@ public class CreateServerOptions implements MapBinder { private Set<Network> novaNetworks = ImmutableSet.of(); private String availabilityZone; private boolean configDrive; - private Set<BlockDeviceMapping> blockDeviceMapping = ImmutableSet.of(); + private Set<BlockDeviceMapping> blockDeviceMappings = ImmutableSet.of(); @Override public boolean equals(Object object) { @@ -118,12 +118,14 @@ public class CreateServerOptions implements MapBinder { } if (object instanceof CreateServerOptions) { final CreateServerOptions other = CreateServerOptions.class.cast(object); - return equal(keyName, other.keyName) && equal(securityGroupNames, other.securityGroupNames) - && equal(metadata, other.metadata) && equal(personality, other.personality) - && equal(adminPass, other.adminPass) && equal(diskConfig, other.diskConfig) - && equal(adminPass, other.adminPass) && equal(networks, other.networks) + return equal(keyName, other.keyName) && equal(adminPass, other.adminPass) + && equal(securityGroupNames, other.securityGroupNames) && equal(metadata, other.metadata) + && equal(personality, other.personality) + && equal(diskConfig, other.diskConfig) + && equal(networks, other.networks) && equal(availabilityZone, other.availabilityZone) - && equal(configDrive, other.configDrive); + && equal(configDrive, other.configDrive) + && equal(blockDeviceMappings, other.blockDeviceMappings); } else { return false; } @@ -131,11 +133,12 @@ public class CreateServerOptions implements MapBinder { @Override public int hashCode() { - return Objects.hashCode(keyName, securityGroupNames, metadata, personality, adminPass, networks, availabilityZone, configDrive); + return Objects.hashCode(keyName, adminPass, securityGroupNames, metadata, personality, networks, availabilityZone, + configDrive, blockDeviceMappings); } protected ToStringHelper string() { - ToStringHelper toString = Objects.toStringHelper("").omitNullValues(); + ToStringHelper toString = Objects.toStringHelper(this); toString.add("keyName", keyName); if (securityGroupNames.size() > 0) toString.add("securityGroupNames", securityGroupNames); @@ -150,10 +153,10 @@ public class CreateServerOptions implements MapBinder { toString.add("userData", userData == null ? null : new String(userData)); if (!networks.isEmpty()) toString.add("networks", networks); - toString.add("availability_zone", availabilityZone == null ? null : availabilityZone); + toString.add("availabilityZone", availabilityZone == null ? null : availabilityZone); toString.add("configDrive", configDrive); - if (!blockDeviceMapping.isEmpty()) - toString.add("blockDeviceMapping", blockDeviceMapping); + if (!blockDeviceMappings.isEmpty()) + toString.add("blockDeviceMappings", blockDeviceMappings); return toString; } @@ -180,8 +183,8 @@ public class CreateServerOptions implements MapBinder { Set<Map<String, String>> networks; @Named("config_drive") String configDrive; - @Named("block_device_mapping") - Set<BlockDeviceMapping> blockDeviceMapping; + @Named("block_device_mapping_v2") + Set<BlockDeviceMapping> blockDeviceMappings; private ServerRequest(String name, String imageRef, String flavorRef) { this.name = name; @@ -217,11 +220,9 @@ public class CreateServerOptions implements MapBinder { if (adminPass != null) { server.adminPass = adminPass; } - if (diskConfig != null) { server.diskConfig = diskConfig; } - if (!networks.isEmpty() || !novaNetworks.isEmpty()) { server.networks = Sets.newLinkedHashSet(); // ensures ordering is preserved - helps testing and more intuitive for users. for (Network network : novaNetworks) { @@ -242,9 +243,8 @@ public class CreateServerOptions implements MapBinder { server.networks.add(ImmutableMap.of("uuid", network)); } } - - if (!blockDeviceMapping.isEmpty()) { - server.blockDeviceMapping = blockDeviceMapping; + if (!blockDeviceMappings.isEmpty()) { + server.blockDeviceMappings = blockDeviceMappings; } return bindToRequest(request, ImmutableMap.of("server", server)); @@ -318,7 +318,7 @@ public class CreateServerOptions implements MapBinder { * Custom user-data can be also be supplied at launch time. * It is retrievable by the instance and is often used for launch-time configuration * by instance scripts. - * Pass userData unencdoed, as the value will be base64 encoded automatically. + * Pass userData unencoded, as the value will be base64 encoded automatically. */ public CreateServerOptions userData(byte[] userData) { this.userData = userData; @@ -339,37 +339,91 @@ public class CreateServerOptions implements MapBinder { /** * A keypair name can be defined when creating a server. This key will be * linked to the server and used to SSH connect to the machine + * @see #getKeyPairName() */ - public String getKeyPairName() { - return keyName; + public CreateServerOptions keyPairName(String keyName) { + this.keyName = keyName; + return this; } /** - * The availability zone in which to launch the server. - * - * @return the availability zone to be used + * @see #getAvailabilityZone() */ - public String getAvailabilityZone() { - return availabilityZone; + public CreateServerOptions availabilityZone(String availabilityZone) { + this.availabilityZone = availabilityZone; + return this; } /** - * @see #getKeyPairName() + * @see #getSecurityGroupNames() */ - public CreateServerOptions keyPairName(String keyName) { - this.keyName = keyName; + public CreateServerOptions securityGroupNames(String... securityGroupNames) { + return securityGroupNames(ImmutableSet.copyOf(checkNotNull(securityGroupNames, "securityGroupNames"))); + } + + /** + * @see #getSecurityGroupNames() + */ + public CreateServerOptions securityGroupNames(Iterable<String> securityGroupNames) { + for (String groupName : checkNotNull(securityGroupNames, "securityGroupNames")) + checkNotNull(emptyToNull(groupName), "all security groups must be non-empty"); + this.securityGroupNames = ImmutableSet.copyOf(securityGroupNames); return this; } /** - * @see #getAvailabilityZone() + * @see #getDiskConfig() */ - public CreateServerOptions availabilityZone(String availabilityZone) { - this.availabilityZone = availabilityZone; + public CreateServerOptions diskConfig(String diskConfig) { + this.diskConfig = diskConfig; return this; } /** + * @see #getNetworks() + */ + public CreateServerOptions networks(Iterable<String> networks) { + this.networks = ImmutableSet.copyOf(networks); + return this; + } + + /** + * @see #getNetworks() + * Overwrites networks supplied by {@link #networks(Iterable)} + */ + public CreateServerOptions novaNetworks(Iterable<Network> networks) { + this.novaNetworks = ImmutableSet.copyOf(networks); + return this; + } + + /** + * @see #getNetworks() + */ + public CreateServerOptions networks(String... networks) { + return networks(ImmutableSet.copyOf(networks)); + } + + /** + * @see #getBlockDeviceMappings() + */ + public CreateServerOptions blockDeviceMappings(Set<BlockDeviceMapping> blockDeviceMappings) { + this.blockDeviceMappings = ImmutableSet.copyOf(blockDeviceMappings); + return this; + } + + /** + * A keypair name can be defined when creating a server. This key will be + * linked to the server and used to SSH connect to the machine + */ + public String getKeyPairName() { + return keyName; + } + + public String getAvailabilityZone() { + return availabilityZone; + } + + /** * Security groups the user specified to run servers with. * <p/> * <h3>Note</h3> @@ -402,23 +456,6 @@ public class CreateServerOptions implements MapBinder { } /** - * @see #getSecurityGroupNames - */ - public CreateServerOptions securityGroupNames(String... securityGroupNames) { - return securityGroupNames(ImmutableSet.copyOf(checkNotNull(securityGroupNames, "securityGroupNames"))); - } - - /** - * @see #getSecurityGroupNames - */ - public CreateServerOptions securityGroupNames(Iterable<String> securityGroupNames) { - for (String groupName : checkNotNull(securityGroupNames, "securityGroupNames")) - checkNotNull(emptyToNull(groupName), "all security groups must be non-empty"); - this.securityGroupNames = ImmutableSet.copyOf(securityGroupNames); - return this; - } - - /** * When you create a server from an image with the diskConfig value set to * {@link Server#DISK_CONFIG_AUTO}, the server is built with a single partition that is expanded to * the disk size of the flavor selected. When you set the diskConfig attribute to @@ -434,14 +471,6 @@ public class CreateServerOptions implements MapBinder { } /** - * @see #getDiskConfig - */ - public CreateServerOptions diskConfig(String diskConfig) { - this.diskConfig = diskConfig; - return this; - } - - /** * Determines if a configuration drive will be attached to the server or not. * This can be used for cloud-init or other configuration purposes. */ @@ -450,56 +479,24 @@ public class CreateServerOptions implements MapBinder { } /** - * @see #getNetworks - */ - public CreateServerOptions networks(Iterable<String> networks) { - this.networks = ImmutableSet.copyOf(networks); - return this; - } - - /** - * @see #getNetworks - * Overwrites networks supplied by {@link #networks(Iterable)} + * Block devices that should be attached to the instance at boot time. */ - public CreateServerOptions novaNetworks(Iterable<Network> networks) { - this.novaNetworks = ImmutableSet.copyOf(networks); - return this; - } - - /** - * @see #getNetworks - */ - public CreateServerOptions networks(String... networks) { - return networks(ImmutableSet.copyOf(networks)); - } - - /** - * @see #getBlockDeviceMapping - */ - public CreateServerOptions blockDeviceMapping(Set<BlockDeviceMapping> blockDeviceMapping) { - this.blockDeviceMapping = ImmutableSet.copyOf(blockDeviceMapping); - return this; - } - - /** - * Block volumes that should be attached to the instance at boot time. - * - * @see <a href="http://docs.openstack.org/trunk/openstack-ops/content/attach_block_storage.html">Attach Block Storage<a/> - */ - public Set<BlockDeviceMapping> getBlockDeviceMapping() { - return blockDeviceMapping; + public Set<BlockDeviceMapping> getBlockDeviceMappings() { + return blockDeviceMappings; } public static class Builder { - /** - * @see CreateServerOptions#writeFileToPath + * @see CreateServerOptions#writeFileToPath(byte[], String) */ public static CreateServerOptions writeFileToPath(byte[] contents, String path) { CreateServerOptions options = new CreateServerOptions(); return options.writeFileToPath(contents, path); } + /** + * @see CreateServerOptions#adminPass(String) + */ public static CreateServerOptions adminPass(String adminPass) { CreateServerOptions options = new CreateServerOptions(); return options.adminPass(adminPass); @@ -514,7 +511,7 @@ public class CreateServerOptions implements MapBinder { } /** - * @see #getKeyPairName() + * @see CreateServerOptions#keyPairName(String) */ public static CreateServerOptions keyPairName(String keyName) { CreateServerOptions options = new CreateServerOptions(); @@ -522,67 +519,62 @@ public class CreateServerOptions implements MapBinder { } /** - * @see CreateServerOptions#getSecurityGroupNames + * @see CreateServerOptions#securityGroupNames(String...) */ public static CreateServerOptions securityGroupNames(String... groupNames) { CreateServerOptions options = new CreateServerOptions(); + if (new CreateServerOptions().securityGroupNames(groupNames) == CreateServerOptions.class.cast(options.securityGroupNames(groupNames))) + System.out.println("They are fucking equal, dump the cast!!!"); return CreateServerOptions.class.cast(options.securityGroupNames(groupNames)); } /** - * @see CreateServerOptions#getSecurityGroupNames + * @see CreateServerOptions#securityGroupNames(Iterable) */ public static CreateServerOptions securityGroupNames(Iterable<String> groupNames) { - CreateServerOptions options = new CreateServerOptions(); - return CreateServerOptions.class.cast(options.securityGroupNames(groupNames)); + return CreateServerOptions.class.cast(new CreateServerOptions().securityGroupNames(groupNames)); } /** - * @see CreateServerOptions#getDiskConfig + * @see CreateServerOptions#diskConfig(String) */ public static CreateServerOptions diskConfig(String diskConfig) { - CreateServerOptions options = new CreateServerOptions(); - return CreateServerOptions.class.cast(options.diskConfig(diskConfig)); + return CreateServerOptions.class.cast(new CreateServerOptions().diskConfig(diskConfig)); } /** - * @see CreateServerOptions#getNetworks + * @see CreateServerOptions#networks(String...) */ public static CreateServerOptions networks(String... networks) { - CreateServerOptions options = new CreateServerOptions(); - return CreateServerOptions.class.cast(options.networks(networks)); + return CreateServerOptions.class.cast(new CreateServerOptions().networks(networks)); } /** - * @see CreateServerOptions#getNetworks + * @see CreateServerOptions#networks(Iterable) */ public static CreateServerOptions networks(Iterable<String> networks) { - CreateServerOptions options = new CreateServerOptions(); - return CreateServerOptions.class.cast(options.networks(networks)); + return CreateServerOptions.class.cast(new CreateServerOptions().networks(networks)); } /** - * @see CreateServerOptions#getNetworks + * @see CreateServerOptions#novaNetworks(Iterable) */ public static CreateServerOptions novaNetworks(Iterable<Network> networks) { - CreateServerOptions options = new CreateServerOptions(); - return CreateServerOptions.class.cast(options.novaNetworks(networks)); + return CreateServerOptions.class.cast(new CreateServerOptions().novaNetworks(networks)); } /** - * @see org.jclouds.openstack.nova.v2_0.options.CreateServerOptions#getAvailabilityZone() + * @see CreateServerOptions#availabilityZone(String) */ public static CreateServerOptions availabilityZone(String availabilityZone) { - CreateServerOptions options = new CreateServerOptions(); - return options.availabilityZone(availabilityZone); + return new CreateServerOptions().availabilityZone(availabilityZone); } /** - * @see org.jclouds.openstack.nova.v2_0.options.CreateServerOptions#getBlockDeviceMapping() + * @see CreateServerOptions#blockDeviceMappings(Set) */ - public static CreateServerOptions blockDeviceMapping(Set<BlockDeviceMapping> blockDeviceMapping) { - CreateServerOptions options = new CreateServerOptions(); - return options.blockDeviceMapping(blockDeviceMapping); + public static CreateServerOptions blockDeviceMappings(Set<BlockDeviceMapping> blockDeviceMappings) { + return new CreateServerOptions().blockDeviceMappings(blockDeviceMappings); } } http://git-wip-us.apache.org/repos/asf/jclouds/blob/8122f0b3/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/extensions/VolumeAttachmentApiLiveTest.java ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/extensions/VolumeAttachmentApiLiveTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/extensions/VolumeAttachmentApiLiveTest.java index 14d14c8..d26b8a6 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/extensions/VolumeAttachmentApiLiveTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/extensions/VolumeAttachmentApiLiveTest.java @@ -23,11 +23,9 @@ import static org.testng.Assert.assertTrue; import java.util.Set; -import org.jclouds.openstack.nova.v2_0.domain.BlockDeviceMapping; import org.jclouds.openstack.nova.v2_0.domain.Volume; import org.jclouds.openstack.nova.v2_0.domain.VolumeAttachment; import org.jclouds.openstack.nova.v2_0.internal.BaseNovaApiLiveTest; -import org.jclouds.openstack.nova.v2_0.options.CreateServerOptions; import org.jclouds.openstack.nova.v2_0.options.CreateVolumeOptions; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; @@ -36,7 +34,6 @@ import org.testng.annotations.Test; import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.base.Predicate; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; /** @@ -154,6 +151,7 @@ public class VolumeAttachmentApiLiveTest extends BaseNovaApiLiveTest { } } + /* @Test(dependsOnMethods = "testCreateVolume") public void testAttachmentAtBoot() { if (volumeApi.isPresent()) { @@ -188,5 +186,5 @@ public class VolumeAttachmentApiLiveTest extends BaseNovaApiLiveTest { } } } - } + }*/ } http://git-wip-us.apache.org/repos/asf/jclouds/blob/8122f0b3/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/features/ServerApiExpectTest.java ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/features/ServerApiExpectTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/features/ServerApiExpectTest.java index 78cd0a3..e5b3599 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/features/ServerApiExpectTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/features/ServerApiExpectTest.java @@ -200,8 +200,7 @@ public class ServerApiExpectTest extends BaseNovaApiExpectTest { new ParseCreatedServerTest().expected().toString()); } - public void testCreateServerWithAttachedDiskWhenResponseIs202() throws Exception { - + public void testCreateServerWithBootVolumeWhenResponseIs202() throws Exception { HttpRequest createServer = HttpRequest .builder() .method("POST") @@ -209,10 +208,9 @@ public class ServerApiExpectTest extends BaseNovaApiExpectTest { .addHeader("Accept", "application/json") .addHeader("X-Auth-Token", authToken) .payload(payloadFromStringWithContentType( - "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"1241\",\"flavorRef\":\"100\",\"block_device_mapping\":[{\"volume_size\":\"\",\"volume_id\":\"f0c907a5-a26b-48ba-b803-83f6b7450ba5\",\"delete_on_termination\":\"1\",\"device_name\":\"vdb\"}]}}", "application/json")) + "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"\",\"flavorRef\":\"12345\",\"block_device_mapping_v2\":[{\"volume_size\":100,\"uuid\":\"f0c907a5-a26b-48ba-b803-83f6b7450ba5\",\"destination_type\":\"volume\",\"source_type\":\"image\"}]}}", "application/json")) .build(); - HttpResponse createServerResponse = HttpResponse.builder().statusCode(202).message("HTTP/1.1 202 Accepted") .payload(payloadFromResourceWithContentType("/new_server.json", "application/json; charset=UTF-8")).build(); @@ -220,12 +218,43 @@ public class ServerApiExpectTest extends BaseNovaApiExpectTest { NovaApi apiWithNewServer = requestsSendResponses(keystoneAuthWithUsernameAndPasswordAndTenantName, responseWithKeystoneAccess, createServer, createServerResponse); - BlockDeviceMapping blockDeviceMapping = BlockDeviceMapping.createOptions("f0c907a5-a26b-48ba-b803-83f6b7450ba5", "vdb").deleteOnTermination(true).build(); - assertEquals(apiWithNewServer.getServerApiForZone("az-1.region-a.geo-1").create("test-e92", "1241", - "100", new CreateServerOptions().blockDeviceMapping(ImmutableSet.of(blockDeviceMapping))).toString(), + BlockDeviceMapping blockDeviceMapping = BlockDeviceMapping.builder() + .uuid("f0c907a5-a26b-48ba-b803-83f6b7450ba5").sourceType("image").destinationType("volume") + .volumeSize(100).build(); + + assertEquals(apiWithNewServer.getServerApiForZone("az-1.region-a.geo-1").create("test-e92", "", + "12345", new CreateServerOptions().blockDeviceMappings(ImmutableSet.of(blockDeviceMapping))).toString(), new ParseCreatedServerTest().expected().toString()); } + public void testCreateServerWithBootVolumeWhenResponseIs404() throws Exception { + HttpRequest createServer = HttpRequest + .builder() + .method("POST") + .endpoint("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v2/3456/servers") + .addHeader("Accept", "application/json") + .addHeader("X-Auth-Token", authToken) + .payload(payloadFromStringWithContentType( + "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"\",\"flavorRef\":\"12345\",\"block_device_mapping_v2\":[{\"volume_size\":100,\"uuid\":\"f0c907a5-a26b-48ba-b803-83f6b7450ba5\",\"destination_type\":\"volume\",\"source_type\":\"image\"}]}}", "application/json")) + .build(); + + HttpResponse createServerResponse = HttpResponse.builder().statusCode(404).build(); + + NovaApi apiWithNewServer = requestsSendResponses(keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, createServer, createServerResponse); + + BlockDeviceMapping blockDeviceMapping = BlockDeviceMapping.builder() + .uuid("f0c907a5-a26b-48ba-b803-83f6b7450ba5").sourceType("image") + .destinationType("volume").volumeSize(100).build(); + + try { + apiWithNewServer.getServerApiForZone("az-1.region-a.geo-1").create("test-e92", "", "12345", new CreateServerOptions().blockDeviceMappings(ImmutableSet.of(blockDeviceMapping))); + fail("Expected an exception."); + } catch (Exception e) { + // expected + } + } + public void testCreateServerWithDiskConfigAuto() throws Exception { HttpRequest createServer = HttpRequest.builder() .method("POST") http://git-wip-us.apache.org/repos/asf/jclouds/blob/8122f0b3/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/features/ServerApiLiveTest.java ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/features/ServerApiLiveTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/features/ServerApiLiveTest.java index 691999b..e887196 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/features/ServerApiLiveTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/features/ServerApiLiveTest.java @@ -19,12 +19,11 @@ package org.jclouds.openstack.nova.v2_0.features; import static org.jclouds.openstack.nova.v2_0.domain.Server.Status.ACTIVE; import static org.jclouds.openstack.nova.v2_0.predicates.ServerPredicates.awaitActive; import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; -import com.google.common.base.Optional; import org.jclouds.http.HttpResponseException; +import org.jclouds.openstack.nova.v2_0.domain.BlockDeviceMapping; import org.jclouds.openstack.nova.v2_0.domain.Network; import org.jclouds.openstack.nova.v2_0.domain.Server; import org.jclouds.openstack.nova.v2_0.domain.ServerCreated; @@ -34,9 +33,11 @@ import org.jclouds.openstack.nova.v2_0.options.CreateServerOptions; import org.jclouds.openstack.nova.v2_0.options.RebuildServerOptions; import org.jclouds.openstack.v2_0.domain.Link.Relation; import org.jclouds.openstack.v2_0.domain.Resource; +import org.jclouds.openstack.v2_0.features.ExtensionApi; import org.jclouds.openstack.v2_0.predicates.LinkPredicates; import org.testng.annotations.Test; +import com.google.common.base.Optional; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; @@ -132,6 +133,50 @@ public class ServerApiLiveTest extends BaseNovaApiLiveTest { } } + /** + * This test creates a new server with a boot device from an image. + * + * This needs to be supported by the provider, and is usually not supported. + * + * TODO: Configurable system properties for flavor/image refs. + */ + @Test + public void testCreateWithBlockDeviceMapping() { + String serverId = null; + // Rackspace Performance Flavor + String flavorRef = "performance1-2"; + // Rackspace CentOS 6.5 image + String imageRef = "3ab30cc6-c503-41d3-8a37-106fda7848a7"; + for (String zoneId : zones) { + ServerApi serverApi = api.getServerApiForZone(zoneId); + ExtensionApi extensionApi = api.getExtensionApiForZone(zoneId); + + // check for the existence of the block mapping v2 boot extension + if (extensionApi.get("os-block-device-mapping-v2-boot") != null) { + try { + BlockDeviceMapping blockDeviceMappings = BlockDeviceMapping.builder() + .uuid(imageRef).sourceType("image").destinationType("volume") + .volumeSize(100).bootIndex(0).build(); + + CreateServerOptions options = CreateServerOptions.Builder + .blockDeviceMappings(ImmutableSet.of(blockDeviceMappings)); + + ServerCreated server = serverApi.create(hostName, "", flavorRef, options); + serverId = server.getId(); + + awaitActive(serverApi).apply(server.getId()); + + Server serverCheck = serverApi.get(serverId); + assertEquals(serverCheck.getStatus(), ACTIVE); + } finally { + if (serverId != null) { + serverApi.delete(serverId); + } + } + } + } + } + @Test public void testCreateInWrongAvailabilityZone() { String serverId = null; @@ -154,9 +199,7 @@ public class ServerApiLiveTest extends BaseNovaApiLiveTest { @Test public void testRebuildServer() { - String serverId = null; - for (String zoneId : zones) { ServerApi serverApi = api.getServerApiForZone(zoneId); try { @@ -213,6 +256,6 @@ public class ServerApiLiveTest extends BaseNovaApiLiveTest { private void checkServer(Server server) { checkResource(server); - assertFalse(server.getAddresses().isEmpty()); + assertNotNull(server.getFlavor()); } } http://git-wip-us.apache.org/repos/asf/jclouds/blob/8122f0b3/apis/openstack-nova/src/test/resources/extension_list_full.json ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/src/test/resources/extension_list_full.json b/apis/openstack-nova/src/test/resources/extension_list_full.json index c54a010..541f555 100644 --- a/apis/openstack-nova/src/test/resources/extension_list_full.json +++ b/apis/openstack-nova/src/test/resources/extension_list_full.json @@ -279,6 +279,22 @@ "namespace": "http://docs.openstack.org/compute/ext/availabilityzone/api/v1.1", "alias": "os-availability-zone", "description": "1. Add availability_zone to the Create Server v1.1 API.\n 2. Add availability zones describing.\n " + }, + { + "updated":"2013-07-08T00:00:00+00:00", + "name":"BlockDeviceMappingV2Boot", + "links":[], + "namespace":"http://docs.openstack.org/compute/ext/block_device_mapping_v2_boot/api/v2", + "alias":"os-block-device-mapping-v2-boot", + "description":"Allow boot with the new BDM data format." + }, + { + "alias": "os-volume-attachment-update", + "description": "Support for updating a volume attachment.", + "links": [], + "name": "VolumeAttachmentUpdate", + "namespace": "http://docs.openstack.org/compute/ext/os-volume-attachment-update/api/v2", + "updated": "2013-06-20T00:00:00Z" } ] } http://git-wip-us.apache.org/repos/asf/jclouds/blob/8122f0b3/apis/openstack-nova/src/test/resources/new_server_networks_response.json ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/src/test/resources/new_server_networks_response.json b/apis/openstack-nova/src/test/resources/new_server_networks_response.json index 1e0568a..1b891e4 100644 --- a/apis/openstack-nova/src/test/resources/new_server_networks_response.json +++ b/apis/openstack-nova/src/test/resources/new_server_networks_response.json @@ -39,4 +39,4 @@ "metadata": {}, "OS-DCF:diskConfig": "AUTO" } -} \ No newline at end of file +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/8122f0b3/apis/openstack-nova/src/test/resources/new_server_no_adminpass.json ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/src/test/resources/new_server_no_adminpass.json b/apis/openstack-nova/src/test/resources/new_server_no_adminpass.json index 5a97cab..eac6996 100644 --- a/apis/openstack-nova/src/test/resources/new_server_no_adminpass.json +++ b/apis/openstack-nova/src/test/resources/new_server_no_adminpass.json @@ -37,4 +37,4 @@ "id": 71752, "metadata": {} } -} \ No newline at end of file +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/8122f0b3/apis/openstack-nova/src/test/resources/server_details_without_image.json ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/src/test/resources/server_details_without_image.json b/apis/openstack-nova/src/test/resources/server_details_without_image.json index bd4b9c8..1ea1294 100644 --- a/apis/openstack-nova/src/test/resources/server_details_without_image.json +++ b/apis/openstack-nova/src/test/resources/server_details_without_image.json @@ -71,4 +71,4 @@ } ] } -} \ No newline at end of file +}
