mirror of
https://github.com/apache/jclouds.git
synced 2025-03-03 14:59:06 +00:00
JCLOUDS-514: Support attaching volumes at boot in Nova
This commit is contained in:
parent
0ac7dfd377
commit
3f2b9376a1
@ -0,0 +1,279 @@
|
||||
/*
|
||||
* 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.domain;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import javax.inject.Named;
|
||||
import java.beans.ConstructorProperties;
|
||||
|
||||
import org.jclouds.javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
/**
|
||||
* A representation of a block device that should be attached to the Nova instance to be launched
|
||||
*
|
||||
*/
|
||||
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;
|
||||
this.deviceName = deviceName;
|
||||
if (deleteOnTermination != null) {
|
||||
this.deleteOnTermination = deleteOnTermination;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
private BlockDeviceMapping() {}
|
||||
|
||||
/**
|
||||
* Copy constructor
|
||||
* @param blockDeviceMapping
|
||||
*/
|
||||
private BlockDeviceMapping(BlockDeviceMapping blockDeviceMapping) {
|
||||
this(blockDeviceMapping.volumeId,
|
||||
blockDeviceMapping.volumeSize,
|
||||
blockDeviceMapping.deviceName,
|
||||
blockDeviceMapping.deleteOnTermination);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the volume id of the block device
|
||||
*/
|
||||
@Nullable
|
||||
public String getVolumeId() {
|
||||
return volumeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the size of the block device
|
||||
*/
|
||||
@Nullable
|
||||
public String getVolumeSize() {
|
||||
return volumeSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the device name to which the volume is attached
|
||||
*/
|
||||
@Nullable
|
||||
public String getDeviceName() {
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the volume should be deleted on terminating the instance
|
||||
*/
|
||||
public String getDeleteOnTermination() {
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(volumeId, volumeSize, deviceName, deleteOnTermination);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
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)
|
||||
&& Objects.equal(this.deviceName, that.deviceName)
|
||||
&& Objects.equal(this.deleteOnTermination, that.deleteOnTermination);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("volumeId", volumeId)
|
||||
.add("volumeSize", volumeSize)
|
||||
.add("deviceName", deviceName)
|
||||
.add("deleteOnTermination", deleteOnTermination)
|
||||
.toString();
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Builder for updating a block device mapping
|
||||
*/
|
||||
public static UpdateBuilder updateOptions() {
|
||||
return new UpdateBuilder();
|
||||
}
|
||||
|
||||
private abstract static class Builder<ParameterizedBuilderType> {
|
||||
protected BlockDeviceMapping blockDeviceMapping;
|
||||
|
||||
/**
|
||||
* No-parameters constructor used when updating.
|
||||
* */
|
||||
private Builder() {
|
||||
blockDeviceMapping = new BlockDeviceMapping();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the deviceName to the BlockDeviceMapping's Builder.
|
||||
*
|
||||
* @return the Builder.
|
||||
* @see BlockDeviceMapping#getDeviceName()
|
||||
*/
|
||||
public ParameterizedBuilderType deviceName(String deviceName) {
|
||||
blockDeviceMapping.deviceName = deviceName;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a CreateOptions constructed with this Builder.
|
||||
*/
|
||||
public CreateOptions build() {
|
||||
return new CreateOptions(blockDeviceMapping);
|
||||
}
|
||||
|
||||
protected CreateBuilder self() {
|
||||
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() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a UpdateOptions constructed with this Builder.
|
||||
*/
|
||||
public UpdateOptions build() {
|
||||
return new UpdateOptions(blockDeviceMapping);
|
||||
}
|
||||
|
||||
protected UpdateBuilder self() {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -16,8 +16,8 @@
|
||||
*/
|
||||
package org.jclouds.openstack.nova.v2_0.options;
|
||||
|
||||
import static com.google.common.base.Objects.equal;
|
||||
import static com.google.common.base.MoreObjects.toStringHelper;
|
||||
import static com.google.common.base.Objects.equal;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
@ -33,6 +33,7 @@ import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import org.jclouds.http.HttpRequest;
|
||||
import org.jclouds.openstack.nova.v2_0.domain.BlockDeviceMapping;
|
||||
import org.jclouds.openstack.nova.v2_0.domain.Network;
|
||||
import org.jclouds.rest.MapBinder;
|
||||
import org.jclouds.rest.binders.BindToJsonPayload;
|
||||
@ -109,6 +110,7 @@ public class CreateServerOptions implements MapBinder {
|
||||
private Set<Network> novaNetworks = ImmutableSet.of();
|
||||
private String availabilityZone;
|
||||
private boolean configDrive;
|
||||
private Set<BlockDeviceMapping> blockDeviceMapping = ImmutableSet.of();
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
@ -151,6 +153,8 @@ public class CreateServerOptions implements MapBinder {
|
||||
toString.add("networks", networks);
|
||||
toString.add("availability_zone", availabilityZone == null ? null : availabilityZone);
|
||||
toString.add("configDrive", configDrive);
|
||||
if (!blockDeviceMapping.isEmpty())
|
||||
toString.add("blockDeviceMapping", blockDeviceMapping);
|
||||
return toString;
|
||||
}
|
||||
|
||||
@ -177,6 +181,8 @@ public class CreateServerOptions implements MapBinder {
|
||||
Set<Map<String, String>> networks;
|
||||
@Named("config_drive")
|
||||
String configDrive;
|
||||
@Named("block_device_mapping")
|
||||
Set<BlockDeviceMapping> blockDeviceMapping;
|
||||
|
||||
private ServerRequest(String name, String imageRef, String flavorRef) {
|
||||
this.name = name;
|
||||
@ -238,6 +244,10 @@ public class CreateServerOptions implements MapBinder {
|
||||
}
|
||||
}
|
||||
|
||||
if (!blockDeviceMapping.isEmpty()) {
|
||||
server.blockDeviceMapping = blockDeviceMapping;
|
||||
}
|
||||
|
||||
return bindToRequest(request, ImmutableMap.of("server", server));
|
||||
}
|
||||
|
||||
@ -459,6 +469,23 @@ public class CreateServerOptions implements MapBinder {
|
||||
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 static class Builder {
|
||||
|
||||
/**
|
||||
@ -545,6 +572,14 @@ public class CreateServerOptions implements MapBinder {
|
||||
CreateServerOptions options = new CreateServerOptions();
|
||||
return options.availabilityZone(availabilityZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.jclouds.openstack.nova.v2_0.options.CreateServerOptions#getBlockDeviceMapping()
|
||||
*/
|
||||
public static CreateServerOptions blockDeviceMapping (Set<BlockDeviceMapping> blockDeviceMapping) {
|
||||
CreateServerOptions options = new CreateServerOptions();
|
||||
return options.blockDeviceMapping(blockDeviceMapping);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -23,9 +23,11 @@ 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;
|
||||
@ -34,6 +36,7 @@ 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;
|
||||
|
||||
/**
|
||||
@ -148,7 +151,42 @@ public class VolumeAttachmentApiLiveTest extends BaseNovaApiLiveTest {
|
||||
if (server_id != null)
|
||||
api.getServerApi(region).delete(server_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(dependsOnMethods = "testCreateVolume")
|
||||
public void testAttachmentAtBoot() {
|
||||
if (volumeApi.isPresent()) {
|
||||
String server_id = null;
|
||||
BlockDeviceMapping blockDeviceMapping = BlockDeviceMapping.createOptions(testVolume.getId(), "/dev/vdf").build();
|
||||
try {
|
||||
CreateServerOptions createServerOptions =
|
||||
CreateServerOptions.Builder.blockDeviceMapping(ImmutableSet.of(blockDeviceMapping));
|
||||
final String serverId = server_id = createServerInRegion(region, createServerOptions).getId();
|
||||
|
||||
Set<? extends VolumeAttachment> attachments = volumeAttachmentApi.get()
|
||||
.listAttachmentsOnServer(serverId).toSet();
|
||||
VolumeAttachment attachment = Iterables.getOnlyElement(attachments);
|
||||
|
||||
|
||||
VolumeAttachment details = volumeAttachmentApi.get()
|
||||
.getAttachmentForVolumeOnServer(attachment.getVolumeId(), serverId);
|
||||
assertNotNull(details.getId()); // Probably same as volumeId? Not necessarily true though
|
||||
assertEquals(details.getVolumeId(), testVolume.getId());
|
||||
assertEquals(details.getDevice(), "/dev/vdf");
|
||||
assertEquals(details.getServerId(), serverId);
|
||||
|
||||
assertEquals(volumeApi.get().get(testVolume.getId()).getStatus(), Volume.Status.IN_USE);
|
||||
|
||||
assertTrue(volumeAttachmentApi.get().detachVolumeFromServer(testVolume.getId(), serverId),
|
||||
"Could not detach volume " + testVolume.getId() + " from server " + serverId);
|
||||
assertEquals(volumeAttachmentApi.get().listAttachmentsOnServer(serverId).size(), 0,
|
||||
"Number of volumes on server " + serverId + " was not zero.");
|
||||
} finally {
|
||||
if (server_id != null) {
|
||||
api.getServerApi(region).delete(server_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import static org.testng.Assert.fail;
|
||||
import org.jclouds.http.HttpRequest;
|
||||
import org.jclouds.http.HttpResponse;
|
||||
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.Server;
|
||||
import org.jclouds.openstack.nova.v2_0.internal.BaseNovaApiExpectTest;
|
||||
import org.jclouds.openstack.nova.v2_0.options.CreateServerOptions;
|
||||
@ -199,6 +200,32 @@ public class ServerApiExpectTest extends BaseNovaApiExpectTest {
|
||||
new ParseCreatedServerTest().expected().toString());
|
||||
}
|
||||
|
||||
public void testCreateServerWithAttachedDiskWhenResponseIs202() 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\":\"1241\",\"flavorRef\":\"100\",\"block_device_mapping\":[{\"volume_size\":\"\",\"volume_id\":\"f0c907a5-a26b-48ba-b803-83f6b7450ba5\",\"delete_on_termination\":\"1\",\"device_name\":\"vdb\"}]}}", "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();
|
||||
|
||||
|
||||
NovaApi apiWithNewServer = requestsSendResponses(keystoneAuthWithUsernameAndPasswordAndTenantName,
|
||||
responseWithKeystoneAccess, createServer, createServerResponse);
|
||||
|
||||
BlockDeviceMapping blockDeviceMapping = BlockDeviceMapping.createOptions("f0c907a5-a26b-48ba-b803-83f6b7450ba5", "vdb").deleteOnTermination(true).build();
|
||||
assertEquals(apiWithNewServer.getServerApi("az-1.region-a.geo-1").create("test-e92", "1241",
|
||||
"100", new CreateServerOptions().blockDeviceMapping(ImmutableSet.of(blockDeviceMapping))).toString(),
|
||||
new ParseCreatedServerTest().expected().toString());
|
||||
}
|
||||
|
||||
public void testCreateServerWithDiskConfigAuto() throws Exception {
|
||||
HttpRequest createServer = HttpRequest.builder()
|
||||
.method("POST")
|
||||
|
Loading…
x
Reference in New Issue
Block a user