diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/domain/Attachment.java b/apis/ec2/src/main/java/org/jclouds/ec2/domain/Attachment.java index a3899edad0..e9caf2c56d 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/domain/Attachment.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/domain/Attachment.java @@ -18,20 +18,20 @@ */ package org.jclouds.ec2.domain; -import static com.google.common.base.Preconditions.checkNotNull; - import java.util.Date; +import static com.google.common.base.Preconditions.checkNotNull; + /** - * + * @author Adrian Cole * @see - * @author Adrian Cole */ public class Attachment implements Comparable { public static enum Status { ATTACHING, ATTACHED, DETACHING, DETACHED, BUSY, UNRECOGNIZED; + public String value() { return name().toLowerCase(); } @@ -50,6 +50,53 @@ public class Attachment implements Comparable { } } + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String region; + private String volumeId; + private String instanceId; + private String device; + private Status status; + private Date attachTime; + + public Builder region(String region) { + this.region = region; + return this; + } + + public Builder volumeId(String volumeId) { + this.volumeId = volumeId; + return this; + } + + public Builder instanceId(String instanceId) { + this.instanceId = instanceId; + return this; + } + + public Builder device(String device) { + this.device = device; + return this; + } + + public Builder status(Status status) { + this.status = status; + return this; + } + + public Builder attachTime(Date attachTime) { + this.attachTime = attachTime; + return this; + } + + public Attachment build() { + return new Attachment(region, volumeId, instanceId, device, status, attachTime); + } + } + private final String region; private final String volumeId; private final String instanceId; @@ -68,7 +115,6 @@ public class Attachment implements Comparable { /** * Snapshots are tied to Regions and can only be used for volumes within the same Region. - * */ public String getRegion() { return region; @@ -167,7 +213,7 @@ public class Attachment implements Comparable { @Override public String toString() { return "Attachment [region=" + region + ", volumeId=" + volumeId + ", instanceId=" + instanceId + ", device=" - + device + ", attachTime=" + attachTime + ", status=" + status + "]"; + + device + ", attachTime=" + attachTime + ", status=" + status + "]"; } @Override diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/domain/Volume.java b/apis/ec2/src/main/java/org/jclouds/ec2/domain/Volume.java index 1221b7423d..a512cdc4bf 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/domain/Volume.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/domain/Volume.java @@ -18,23 +18,22 @@ */ package org.jclouds.ec2.domain; -import static com.google.common.base.Preconditions.checkNotNull; +import com.google.common.base.CaseFormat; +import com.google.common.collect.ImmutableSet; +import org.jclouds.javax.annotation.Nullable; import java.util.Date; import java.util.Set; -import org.jclouds.javax.annotation.Nullable; - -import com.google.common.base.CaseFormat; -import com.google.common.collect.Iterables; -import com.google.common.collect.Sets; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Sets.newHashSet; /** * * @see - * @author Adrian Cole + * @author Adrian Cole, Andrei Savu */ public class Volume implements Comparable { @@ -83,6 +82,71 @@ public class Volume implements Comparable { } } } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String region; + private String id; + private int size; + @Nullable + private String snapshotId; + private String availabilityZone; + private Status status; + private Date createTime; + private Set attachments; + + public Builder region(String region) { + this.region = region; + return this; + } + + public Builder id(String id) { + this.id = id; + return this; + } + + public Builder size(int size) { + this.size = size; + return this; + } + + public Builder snapshotId(String snapshotId) { + this.snapshotId = snapshotId; + return this; + } + + public Builder availabilityZone(String availabilityZone) { + this.availabilityZone = availabilityZone; + return this; + } + + public Builder status(Status status) { + this.status = status; + return this; + } + + public Builder createTime(Date createTime) { + this.createTime = createTime; + return this; + } + + public Builder attachments(Attachment... attachments) { + this.attachments = newHashSet(attachments); + return this; + } + + public Builder attachments(Set attachments) { + this.attachments = ImmutableSet.copyOf(attachments); + return this; + } + + public Volume build() { + return new Volume(region, id, size, snapshotId, availabilityZone, status, createTime, attachments); + } + } private final String region; private final String id; @@ -92,7 +156,7 @@ public class Volume implements Comparable { private final String availabilityZone; private final Status status; private final Date createTime; - private final Set attachments = Sets.newLinkedHashSet(); + private final Set attachments; public Volume(String region, String id, int size, String snapshotId, String availabilityZone, Volume.Status status, Date createTime, Iterable attachments) { @@ -103,7 +167,7 @@ public class Volume implements Comparable { this.availabilityZone = availabilityZone; this.status = status; this.createTime = createTime; - Iterables.addAll(this.attachments, attachments); + this.attachments = ImmutableSet.copyOf(attachments); } /** diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/predicates/VolumeDetached.java b/apis/ec2/src/main/java/org/jclouds/ec2/predicates/VolumeDetached.java index 6a9d113ecd..fe94895be0 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/predicates/VolumeDetached.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/predicates/VolumeDetached.java @@ -1,5 +1,3 @@ -package com.gravitant.cloud.adapters.provision.providers; - /** * Licensed to jclouds, Inc. (jclouds) under one or more * contributor license agreements. See the NOTICE file @@ -19,54 +17,55 @@ package com.gravitant.cloud.adapters.provision.providers; * under the License. */ -import javax.annotation.Resource; -import javax.inject.Singleton; +package org.jclouds.ec2.predicates; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.inject.Inject; +import com.google.inject.Singleton; import org.jclouds.ec2.domain.Attachment; import org.jclouds.ec2.domain.Volume; import org.jclouds.ec2.services.ElasticBlockStoreClient; import org.jclouds.logging.Logger; -import com.google.common.base.Predicate; -import com.google.common.collect.Iterables; -import com.google.common.collect.Sets; -import com.google.inject.Inject; +import javax.annotation.Resource; + +import static com.google.common.collect.Iterables.getLast; /** - * * Tests to see if a volume is detached. - * + * * @author Karthik Arunachalam */ @Singleton public class VolumeDetached implements Predicate { - private final ElasticBlockStoreClient client; - @Resource - protected Logger logger = Logger.NULL; + private final ElasticBlockStoreClient client; + @Resource + protected Logger logger = Logger.NULL; - @Inject - public VolumeDetached(ElasticBlockStoreClient client) { - this.client = client; - } + @Inject + public VolumeDetached(ElasticBlockStoreClient client) { + this.client = client; + } - public boolean apply(Attachment attachment) { - logger.trace("looking for volume %s", attachment.getVolumeId()); - Volume volume = Iterables.getOnlyElement(client.describeVolumesInRegion(attachment - .getRegion(), attachment.getVolumeId())); + public boolean apply(Attachment attachment) { + logger.trace("looking for volume %s", attachment.getVolumeId()); + Volume volume = Iterables.getOnlyElement(client.describeVolumesInRegion(attachment + .getRegion(), attachment.getVolumeId())); - /*If attachment size is 0 volume is detached for sure.*/ - if (volume.getAttachments().size() == 0) { - return true; - } + /*If attachment size is 0 volume is detached for sure.*/ + if (volume.getAttachments().size() == 0) { + return true; + } - /* But if attachment size is > 0, then the attachment could be in any state. - * So we need to check if the status is DETACHED (return true) or not (return false). - */ - Attachment lastAttachment = Sets.newTreeSet(volume.getAttachments()).last(); - logger.trace("%s: looking for status %s: currently: %s", lastAttachment, - Attachment.Status.DETACHED, lastAttachment.getStatus()); - return lastAttachment.getStatus() == Attachment.Status.DETACHED; - } + /* But if attachment size is > 0, then the attachment could be in any state. + * So we need to check if the status is DETACHED (return true) or not (return false). + */ + Attachment lastAttachment = getLast(volume.getAttachments()); + logger.trace("%s: looking for status %s: currently: %s", lastAttachment, + Attachment.Status.DETACHED, lastAttachment.getStatus()); + return lastAttachment.getStatus() == Attachment.Status.DETACHED; + } } diff --git a/apis/ec2/src/test/java/org/jclouds/ec2/predicates/VolumeDetachedTest.java b/apis/ec2/src/test/java/org/jclouds/ec2/predicates/VolumeDetachedTest.java new file mode 100644 index 0000000000..76ea1a07cc --- /dev/null +++ b/apis/ec2/src/test/java/org/jclouds/ec2/predicates/VolumeDetachedTest.java @@ -0,0 +1,115 @@ +/** + * 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.ec2.predicates; + +import org.jclouds.ec2.domain.Attachment; +import org.jclouds.ec2.domain.Volume; +import org.jclouds.ec2.services.ElasticBlockStoreClient; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.util.Date; +import java.util.Set; + +import static com.google.common.collect.Sets.newHashSet; +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; +import static org.jclouds.ec2.domain.Attachment.Status; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +/** + * @author Andrei Savu + */ +@Test(groups = "unit", singleThreaded = true) +public class VolumeDetachedTest { + + private ElasticBlockStoreClient client; + private VolumeDetached volumeDetached; + + @BeforeMethod + public void setUp() { + client = createMock(ElasticBlockStoreClient.class); + volumeDetached = new VolumeDetached(client); + } + + @Test + public void testVolumeWithEmptyListOfAttachments() { + Attachment attachment = newAttachmentWithStatus(Status.ATTACHED); + Set volumes = newHashSet(newVolumeWithAttachments(/* empty */)); + + expect(client.describeVolumesInRegion(attachment.getRegion(), + attachment.getVolumeId())).andReturn(volumes); + replay(client); + + assertTrue(volumeDetached.apply(attachment)); + verify(client); + } + + @DataProvider(name = "notDetachedStatuses") + public Object[][] provideNotDetachedStatuses() { + return new Object[][]{ + {Status.ATTACHED}, + {Status.ATTACHING}, + {Status.BUSY}, + {Status.DETACHING}, + {Status.UNRECOGNIZED} + }; + } + + @Test(dataProvider = "notDetachedStatuses") + public void testWithDifferentStatus(Status attachmentStatus) { + Attachment attachment = newAttachmentWithStatus(attachmentStatus); + Set volumes = newHashSet(newVolumeWithAttachments(attachment)); + + expect(client.describeVolumesInRegion(attachment.getRegion(), + attachment.getVolumeId())).andReturn(volumes); + replay(client); + + assertFalse(volumeDetached.apply(attachment)); + verify(client); + } + + @Test + public void testWithStatusDetached() { + Attachment attachment = newAttachmentWithStatus(Status.DETACHED); + Set volumes = newHashSet(newVolumeWithAttachments(attachment)); + + expect(client.describeVolumesInRegion(attachment.getRegion(), + attachment.getVolumeId())).andReturn(volumes); + replay(client); + + assertTrue(volumeDetached.apply(attachment)); + verify(client); + } + + private Volume newVolumeWithAttachments(Attachment... attachments) { + return Volume.builder().region("us-east-1").attachments(attachments).build(); + } + + private Attachment newAttachmentWithStatus(Status status) { + return Attachment.builder() + .volumeId("1").status(status).region("us-east-1").attachTime(new Date()) + .device("/dev/sda").instanceId("us-east-1/i-1234").build(); + } +}