diff --git a/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/CloudWatch.java b/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/CloudWatch.java index a289a77783..d433926d68 100644 --- a/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/CloudWatch.java +++ b/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/CloudWatch.java @@ -26,7 +26,6 @@ import org.jclouds.cloudwatch.features.MetricApi; import org.jclouds.cloudwatch.options.ListMetricsOptions; import org.jclouds.collect.IterableWithMarker; import org.jclouds.collect.PagedIterables; -import org.jclouds.collect.PagedIterators; import com.google.common.base.Function; import com.google.common.collect.Iterables; @@ -47,7 +46,7 @@ public class CloudWatch { * @return iterable of metrics fitting the criteria */ public static Iterable listMetrics(final MetricApi metricApi, final ListMetricsOptions options) { - return Iterables.concat(PagedIterables.create(PagedIterators.advancing(metricApi.list(options), + return Iterables.concat(PagedIterables.advance(metricApi.list(options), new Function>() { @Override @@ -59,7 +58,7 @@ public class CloudWatch { public String toString() { return "listMetrics(" + options + ")"; } - }))); + })); } /** diff --git a/apis/cloudwatch/src/test/java/org/jclouds/cloudwatch/features/MetricApiExpectTest.java b/apis/cloudwatch/src/test/java/org/jclouds/cloudwatch/features/MetricApiExpectTest.java index 24ca524415..25cae952fe 100644 --- a/apis/cloudwatch/src/test/java/org/jclouds/cloudwatch/features/MetricApiExpectTest.java +++ b/apis/cloudwatch/src/test/java/org/jclouds/cloudwatch/features/MetricApiExpectTest.java @@ -38,8 +38,6 @@ import org.jclouds.http.HttpResponse; import org.jclouds.rest.ResourceNotFoundException; import org.testng.annotations.Test; -import com.google.common.collect.Iterables; - /** * @author Jeremy Whitlock, Adrian Cole */ @@ -118,7 +116,7 @@ public class MetricApiExpectTest extends BaseCloudWatchApiExpectTest { CloudWatchApi apiWhenMetricsExist = requestsSendResponses( listMetrics, listMetricsResponse, listMetrics2, listMetrics2Response); - assertEquals(Iterables.concat(apiWhenMetricsExist.getMetricApiForRegion(null).list()).toString(), + assertEquals(apiWhenMetricsExist.getMetricApiForRegion(null).list().concat().toString(), "[Metric{namespace=AWS/EC2, metricName=CPUUtilization, dimension=[Dimension{name=InstanceId, value=i-689fcf0f}]}, Metric{namespace=AWS/EC2, metricName=CPUUtilization, dimension=[Dimension{name=InstanceId, value=i-689fcf0f}]}]"); } diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/EC2CreateNodesInGroupThenAddToSet.java b/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/EC2CreateNodesInGroupThenAddToSet.java index 465b63dbc5..470b6093ad 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/EC2CreateNodesInGroupThenAddToSet.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/EC2CreateNodesInGroupThenAddToSet.java @@ -133,10 +133,9 @@ public class EC2CreateNodesInGroupThenAddToSet implements CreateNodesInGroupThen public Map> execute(String group, int count, Template template, Set goodNodes, Map badNodes, Multimap customizationResponses) { - Template mutableTemplate = templateBuilderProvider.get().imageId(template.getImage().getId()).fromTemplate(template) - .build(); + Template mutableTemplate = template.clone(); - Iterable ips = allocateElasticIpsInRegion(count, template); + Iterable ips = allocateElasticIpsInRegion(count, mutableTemplate); Iterable started = createKeyPairAndSecurityGroupsAsNeededThenRunInstances(group, count, mutableTemplate); diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/EC2ListNodesStrategy.java b/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/EC2ListNodesStrategy.java index 2c0d1d01dc..a805e47e3c 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/EC2ListNodesStrategy.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/compute/strategy/EC2ListNodesStrategy.java @@ -31,7 +31,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import javax.annotation.Resource; -import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; @@ -51,6 +50,7 @@ import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableSet; +import com.google.inject.Inject; /** * @@ -58,10 +58,15 @@ import com.google.common.collect.ImmutableSet; */ @Singleton public class EC2ListNodesStrategy implements ListNodesStrategy { + @Resource @Named(ComputeServiceConstants.COMPUTE_LOGGER) protected Logger logger = Logger.NULL; + @Inject(optional = true) + @Named(Constants.PROPERTY_REQUEST_TIMEOUT) + protected static Long maxTime; + protected final EC2AsyncClient client; protected final Supplier> regions; protected final Function runningInstanceToNodeMetadata; @@ -100,7 +105,7 @@ public class EC2ListNodesStrategy implements ListNodesStrategy { return castToSpecificTypedFuture(client.getInstanceServices().describeInstancesInRegion(from)); } - }, executor, null, logger, "reservations"); + }, executor, maxTime, logger, "reservations"); return concat(concat(reservations)); } diff --git a/apis/ec2/src/test/java/org/jclouds/ec2/compute/strategy/EC2CreateNodesInGroupThenAddToSetTest.java b/apis/ec2/src/test/java/org/jclouds/ec2/compute/strategy/EC2CreateNodesInGroupThenAddToSetTest.java index 0bf4fe3802..c985dd429d 100644 --- a/apis/ec2/src/test/java/org/jclouds/ec2/compute/strategy/EC2CreateNodesInGroupThenAddToSetTest.java +++ b/apis/ec2/src/test/java/org/jclouds/ec2/compute/strategy/EC2CreateNodesInGroupThenAddToSetTest.java @@ -95,9 +95,7 @@ public class EC2CreateNodesInGroupThenAddToSetTest { strategy.autoAllocateElasticIps = true; // setup expectations - expect(templateBuilder.imageId(region + "/" + imageId)).andReturn(templateBuilder); - expect(templateBuilder.fromTemplate(input.template)).andReturn(templateBuilder); - expect(templateBuilder.build()).andReturn(input.template); + expect(input.template.clone()).andReturn(input.template); expect(strategy.client.getInstanceServices()).andReturn(instanceClient).atLeastOnce(); expect( strategy.createKeyPairAndSecurityGroupsAsNeededAndReturncustomize @@ -106,7 +104,6 @@ public class EC2CreateNodesInGroupThenAddToSetTest { expect(input.template.getLocation()).andReturn(input.location).atLeastOnce(); expect(input.template.getImage()).andReturn(input.image).atLeastOnce(); - expect(input.image.getId()).andReturn(region + "/" + imageId).atLeastOnce(); expect(input.image.getProviderId()).andReturn(imageId).atLeastOnce(); // differences when ip allocation @@ -204,16 +201,13 @@ public class EC2CreateNodesInGroupThenAddToSetTest { "reservationId"); // setup expectations - expect(templateBuilder.imageId(region + "/" + imageId)).andReturn(templateBuilder); - expect(templateBuilder.fromTemplate(input.template)).andReturn(templateBuilder); - expect(templateBuilder.build()).andReturn(input.template); + expect(input.template.clone()).andReturn(input.template); expect(strategy.client.getInstanceServices()).andReturn(instanceClient).atLeastOnce(); expect( strategy.createKeyPairAndSecurityGroupsAsNeededAndReturncustomize .execute(region, input.tag, input.template)).andReturn(ec2Options); expect(input.template.getLocation()).andReturn(input.location).atLeastOnce(); expect(input.template.getImage()).andReturn(input.image).atLeastOnce(); - expect(input.image.getId()).andReturn(region + "/" + imageId).atLeastOnce(); expect(input.image.getProviderId()).andReturn(imageId).atLeastOnce(); expect(instanceClient.runInstancesInRegion(region, zone, imageId, 1, input.count, ec2Options)).andReturn( Reservation.class.cast(reservation)); diff --git a/apis/filesystem/src/main/java/org/jclouds/filesystem/FilesystemAsyncBlobStore.java b/apis/filesystem/src/main/java/org/jclouds/filesystem/FilesystemAsyncBlobStore.java index 81b93f980f..813feb70e5 100644 --- a/apis/filesystem/src/main/java/org/jclouds/filesystem/FilesystemAsyncBlobStore.java +++ b/apis/filesystem/src/main/java/org/jclouds/filesystem/FilesystemAsyncBlobStore.java @@ -402,8 +402,7 @@ public class FilesystemAsyncBlobStore extends BaseAsyncBlobStore { } public static HttpResponseException returnResponseException(int code) { - HttpResponse response = null; - response = HttpResponse.builder().statusCode(code).build(); + HttpResponse response = HttpResponse.builder().statusCode(code).build(); return new HttpResponseException(new HttpCommand() { public int getRedirectCount() { @@ -457,17 +456,22 @@ public class FilesystemAsyncBlobStore extends BaseAsyncBlobStore { String blobKey = blob.getMetadata().getName(); logger.debug("Put blob with key [%s] to container [%s]", blobKey, containerName); - String eTag = getEtag(blob); + if (!storageStrategy.containerExists(containerName)) { + return Futures.immediateFailedFuture(new IllegalStateException("containerName not found: " + containerName)); + } + try { // TODO // must override existing file? - storageStrategy.writePayloadOnFile(containerName, blobKey, blob.getPayload()); + storageStrategy.putBlob(containerName, blob); } catch (IOException e) { logger.error(e, "An error occurred storing the new blob with name [%s] to container [%s].", blobKey, containerName); Throwables.propagate(e); } + + String eTag = getEtag(blob); return immediateFuture(eTag); } @@ -535,7 +539,7 @@ public class FilesystemAsyncBlobStore extends BaseAsyncBlobStore { if (options.getRanges() != null && options.getRanges().size() > 0) { byte[] data; try { - data = toByteArray(blob.getPayload().getInput()); + data = toByteArray(blob.getPayload()); } catch (IOException e) { return immediateFailedFuture(new RuntimeException(e)); } @@ -591,13 +595,6 @@ public class FilesystemAsyncBlobStore extends BaseAsyncBlobStore { } } - private Blob copyBlob(Blob blob) { - Blob returnVal = blobFactory.create(copy(blob.getMetadata())); - returnVal.setPayload(blob.getPayload()); - copyPayloadHeadersToBlob(blob.getPayload(), returnVal); - return returnVal; - } - /** * Calculates the object MD5 and returns it as eTag * @@ -616,6 +613,13 @@ public class FilesystemAsyncBlobStore extends BaseAsyncBlobStore { return eTag; } + private Blob copyBlob(Blob blob) { + Blob returnVal = blobFactory.create(copy(blob.getMetadata())); + returnVal.setPayload(blob.getPayload()); + copyPayloadHeadersToBlob(blob.getPayload(), returnVal); + return returnVal; + } + @Override protected boolean deleteAndVerifyContainerGone(final String container) { storageStrategy.deleteContainer(container); diff --git a/apis/filesystem/src/main/java/org/jclouds/filesystem/strategy/FilesystemStorageStrategy.java b/apis/filesystem/src/main/java/org/jclouds/filesystem/strategy/FilesystemStorageStrategy.java index a0c8662b63..fc648f1092 100644 --- a/apis/filesystem/src/main/java/org/jclouds/filesystem/strategy/FilesystemStorageStrategy.java +++ b/apis/filesystem/src/main/java/org/jclouds/filesystem/strategy/FilesystemStorageStrategy.java @@ -169,11 +169,11 @@ public interface FilesystemStorageStrategy { void removeBlob(String container, String key); /** - * Write a {@link Blob} {@link Payload} into a file - * @param fileName - * @param payload + * Write a {@link Blob} into a file + * @param container + * @param blob * @throws IOException */ - void writePayloadOnFile(String container, String blobKey, Payload payload) throws IOException; + void putBlob(String containerName, Blob blob) throws IOException; } diff --git a/apis/filesystem/src/main/java/org/jclouds/filesystem/strategy/internal/FilesystemStorageStrategyImpl.java b/apis/filesystem/src/main/java/org/jclouds/filesystem/strategy/internal/FilesystemStorageStrategyImpl.java index b3ea8bf275..918f9d21c2 100644 --- a/apis/filesystem/src/main/java/org/jclouds/filesystem/strategy/internal/FilesystemStorageStrategyImpl.java +++ b/apis/filesystem/src/main/java/org/jclouds/filesystem/strategy/internal/FilesystemStorageStrategyImpl.java @@ -204,19 +204,13 @@ public class FilesystemStorageStrategyImpl implements FilesystemStorageStrategy return blobFile; } - /** - * Write a {@link Blob} {@link Payload} into a file - * - * @param container - * @param blobKey - * @param payload - * @throws IOException - */ @Override - public void writePayloadOnFile(String container, String blobKey, Payload payload) throws IOException { - filesystemContainerNameValidator.validate(container); + public void putBlob(final String containerName, final Blob blob) throws IOException { + String blobKey = blob.getMetadata().getName(); + Payload payload = blob.getPayload(); + filesystemContainerNameValidator.validate(containerName); filesystemBlobKeyValidator.validate(blobKey); - File outputFile = getFileForBlobKey(container, blobKey); + File outputFile = getFileForBlobKey(containerName, blobKey); FileOutputStream output = null; try { Files.createParentDirs(outputFile); diff --git a/apis/filesystem/src/test/java/org/jclouds/filesystem/FilesystemAsyncBlobStoreTest.java b/apis/filesystem/src/test/java/org/jclouds/filesystem/FilesystemAsyncBlobStoreTest.java index c8d3b0d844..aa6bbabcc0 100644 --- a/apis/filesystem/src/test/java/org/jclouds/filesystem/FilesystemAsyncBlobStoreTest.java +++ b/apis/filesystem/src/test/java/org/jclouds/filesystem/FilesystemAsyncBlobStoreTest.java @@ -740,6 +740,7 @@ public class FilesystemAsyncBlobStoreTest { * can't be deleted. See http://code.google.com/p/jclouds/issues/detail?id=737 */ final String containerName = "containerWithRanges"; + blobStore.createContainerInLocation(null, containerName); String payload = "abcdefgh"; InputStream is; Blob blob = blobStore.blobBuilder("test").payload(new StringPayload(payload)).build(); diff --git a/apis/filesystem/src/test/java/org/jclouds/filesystem/strategy/internal/FilesystemStorageStrategyImplTest.java b/apis/filesystem/src/test/java/org/jclouds/filesystem/strategy/internal/FilesystemStorageStrategyImplTest.java index dd248ee802..69c6ce1a05 100644 --- a/apis/filesystem/src/test/java/org/jclouds/filesystem/strategy/internal/FilesystemStorageStrategyImplTest.java +++ b/apis/filesystem/src/test/java/org/jclouds/filesystem/strategy/internal/FilesystemStorageStrategyImplTest.java @@ -371,8 +371,10 @@ public class FilesystemStorageStrategyImplTest { blobKey = TestUtils.createRandomBlobKey("writePayload-", ".img"); sourceFile = TestUtils.getImageForBlobPayload(); filePayload = new FilePayload(sourceFile); + Blob blob = storageStrategy.newBlob(blobKey); + blob.setPayload(filePayload); // write files - storageStrategy.writePayloadOnFile(CONTAINER_NAME, blobKey, filePayload); + storageStrategy.putBlob(CONTAINER_NAME, blob); // verify that the files is equal File blobFullPath = new File(TARGET_CONTAINER_NAME, blobKey); InputSupplier expectedInput = diff --git a/blobstore/src/main/java/org/jclouds/blobstore/TransientAsyncBlobStore.java b/blobstore/src/main/java/org/jclouds/blobstore/TransientAsyncBlobStore.java index e8fcd51b1b..4e6074a6a6 100644 --- a/blobstore/src/main/java/org/jclouds/blobstore/TransientAsyncBlobStore.java +++ b/blobstore/src/main/java/org/jclouds/blobstore/TransientAsyncBlobStore.java @@ -115,33 +115,33 @@ public class TransientAsyncBlobStore extends BaseAsyncBlobStore { protected final DateService dateService; protected final Crypto crypto; - protected final Provider uriBuilders; protected final HttpGetOptionsListToGetOptions httpGetOptionsConverter; + protected final ContentMetadataCodec contentMetadataCodec; protected final IfDirectoryReturnNameStrategy ifDirectoryReturnName; protected final Factory blobFactory; protected final TransientStorageStrategy storageStrategy; - protected final ContentMetadataCodec contentMetadataCodec; + protected final Provider uriBuilders; @Inject protected TransientAsyncBlobStore(BlobStoreContext context, DateService dateService, Crypto crypto, HttpGetOptionsListToGetOptions httpGetOptionsConverter, + ContentMetadataCodec contentMetadataCodec, IfDirectoryReturnNameStrategy ifDirectoryReturnName, BlobUtils blobUtils, @Named(Constants.PROPERTY_USER_THREADS) ExecutorService service, Supplier defaultLocation, @Memoized Supplier> locations, - Factory blobFactory, Provider uriBuilders, - ContentMetadataCodec contentMetadataCodec) { + Factory blobFactory, Provider uriBuilders) { super(context, blobUtils, service, defaultLocation, locations); this.blobFactory = blobFactory; this.dateService = dateService; this.crypto = crypto; - this.uriBuilders = uriBuilders; this.httpGetOptionsConverter = httpGetOptionsConverter; + this.contentMetadataCodec = contentMetadataCodec; this.ifDirectoryReturnName = ifDirectoryReturnName; this.storageStrategy = new TransientStorageStrategy(defaultLocation); - this.contentMetadataCodec = contentMetadataCodec; + this.uriBuilders = uriBuilders; } /** @@ -472,7 +472,8 @@ public class TransientAsyncBlobStore extends BaseAsyncBlobStore { storageStrategy.putBlob(containerName, blob); - return immediateFuture(Iterables.getOnlyElement(blob.getAllHeaders().get(HttpHeaders.ETAG))); + String eTag = getEtag(blob); + return immediateFuture(eTag); } private Blob createUpdatedCopyOfBlobInContainer(String containerName, Blob in) { @@ -579,7 +580,7 @@ public class TransientAsyncBlobStore extends BaseAsyncBlobStore { if (options.getRanges() != null && options.getRanges().size() > 0) { byte[] data; try { - data = toByteArray(blob.getPayload().getInput()); + data = toByteArray(blob.getPayload()); } catch (IOException e) { return immediateFailedFuture(new RuntimeException(e)); } @@ -635,6 +636,24 @@ public class TransientAsyncBlobStore extends BaseAsyncBlobStore { } } + /** + * Calculates the object MD5 and returns it as eTag + * + * @param object + * @return + */ + private String getEtag(Blob object) { + try { + Payloads.calculateMD5(object, crypto.md5()); + } catch (IOException ex) { + logger.error(ex, "An error occurred calculating MD5 for object with name %s.", object.getMetadata().getName()); + Throwables.propagate(ex); + } + + String eTag = CryptoStreams.hex(object.getPayload().getContentMetadata().getContentMD5()); + return eTag; + } + private Blob copyBlob(Blob blob) { Blob returnVal = blobFactory.create(copy(blob.getMetadata())); returnVal.setPayload(blob.getPayload()); diff --git a/core/src/main/java/org/jclouds/collect/PagedIterable.java b/core/src/main/java/org/jclouds/collect/PagedIterable.java index cdffb6c455..d5cf683a64 100644 --- a/core/src/main/java/org/jclouds/collect/PagedIterable.java +++ b/core/src/main/java/org/jclouds/collect/PagedIterable.java @@ -18,17 +18,39 @@ */ package org.jclouds.collect; +import java.util.Iterator; + import com.google.common.annotations.Beta; import com.google.common.collect.FluentIterable; +import com.google.common.collect.Iterators; +import com.google.common.collect.UnmodifiableIterator; /** + * Extends {@link FluentIterable} allowing you to lazily advance through + * sequence of pages in a resultset. Typically used in apis that return only a + * certain number of records at a time. + * + * Simplest usage is to employ the {@link #concat} convenience function, and one + * of the methods from {@link FluentIterable}. + * + *
+ *    // pull in new pages until it we see something interesting.
+ *     Optional firstInterestingBlob = blobstore
+ *         .list(// options //)
+ *         .concat()
+ *         .firstMatch(isInterestingBlob());
+ * 
+ * + * For those seeking manual control of page advances, don't use concat, and + * instead look at the value of {@link IterableWithMarker#nextToken}. + * *
  * PagedIterator blobs = blobstore.list(...).iterator();
  * while (blobs.hasNext()) {
- *     FluentIterable page = blobs.next();
+ *     IterableWithMarker page = blobs.next();
  *     ProcessedResults results = process(page);
- *     if (results.shouldBeBookmarked() && blobs.nextMarker().isPresent()) {
- *         saveBookmark(blobs.nextMarker().get());
+ *     if (results.shouldBeBookmarked() && page.nextMarker().isPresent()) {
+ *         saveBookmark(page.nextMarker().get());
  *     }
  * }
  * 
@@ -36,9 +58,39 @@ import com.google.common.collect.FluentIterable; * @author Adrian Cole */ @Beta -public abstract class PagedIterable extends FluentIterable> { +public abstract class PagedIterable extends FluentIterable> { - @Override - public abstract PagedIterator iterator(); + /** + * Combines all the pages into a single unmodifiable iterable. ex. + * + *
+    * FluentIterable blobs = blobstore.list(...).concat();
+    * for (StorageMetadata blob : blobs) {
+    *     process(blob);
+    * }
+    * 
+ * + * @see Iterators#concat + */ + public FluentIterable concat() { + final Iterator> iterator = iterator(); + final UnmodifiableIterator> unmodifiable = new UnmodifiableIterator>() { + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public Iterator next() { + return iterator.next().iterator(); + } + }; + return new FluentIterable() { + @Override + public Iterator iterator() { + return Iterators.concat(unmodifiable); + } + }; + } } \ No newline at end of file diff --git a/core/src/main/java/org/jclouds/collect/PagedIterables.java b/core/src/main/java/org/jclouds/collect/PagedIterables.java index 5267f57416..b98c03865d 100644 --- a/core/src/main/java/org/jclouds/collect/PagedIterables.java +++ b/core/src/main/java/org/jclouds/collect/PagedIterables.java @@ -18,7 +18,15 @@ */ package org.jclouds.collect; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Iterator; + import com.google.common.annotations.Beta; +import com.google.common.base.Function; +import com.google.common.base.Objects; +import com.google.common.collect.AbstractIterator; +import com.google.common.collect.ImmutableSet; /** * Utilities for using {@link PagedIterable}s. @@ -27,23 +35,120 @@ import com.google.common.annotations.Beta; */ @Beta public class PagedIterables { - /** + * @param only + * the only page of data * - * @param iterator - * how to advance pages - * - * @return iterable current data which continues if the user iterates beyond the first page + * @return iterable with only the one page */ - public static PagedIterable create(final PagedIterator iterator) { + public static PagedIterable of(final IterableWithMarker only) { return new PagedIterable() { @Override - public PagedIterator iterator() { - return iterator; + public Iterator> iterator() { + return ImmutableSet.of(only).iterator(); } }; } + /** + * + * + * @param initial + * the initial set current data + * @param markerToNext + * produces the next set based on the marker + * + * @return iterable current data which continues if the user iterates beyond + * the first page + */ + public static PagedIterable advance(final IterableWithMarker initial, + final Function> markerToNext) { + return new PagedIterable() { + + @Override + public Iterator> iterator() { + return advancingIterator(initial, markerToNext); + } + + }; + } + + private static class AdvancingIterator extends AbstractIterator> { + + private final Function> markerToNext; + private transient IterableWithMarker current; + private transient boolean unread = true; + + private AdvancingIterator(IterableWithMarker initial, Function> markerToNext) { + this.current = checkNotNull(initial, "initial iterable"); + this.markerToNext = checkNotNull(markerToNext, "marker to next iterable"); + } + + /** + * {@inheritDoc} + */ + @Override + protected IterableWithMarker computeNext() { + if (unread) + try { + return current; + } finally { + unread = false; + } + else if (current.nextMarker().isPresent()) + return current = markerToNext.apply(current.nextMarker().get()); + else + return endOfData(); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(current, unread); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + AdvancingIterator other = AdvancingIterator.class.cast(obj); + return Objects.equal(this.current, other.current) && Objects.equal(this.unread, other.unread); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return Objects.toStringHelper("").add("current", current).add("unread", unread).toString(); + } + } + + /** + * + * @param initial + * the initial set current data + * @param markerToNext + * produces the next set based on the marker + * + * @return iterable current data which continues if the user iterates beyond + * the first page + */ + public static Iterator> advancingIterator(IterableWithMarker initial, + Function> markerToNext) { + if (!initial.nextMarker().isPresent()) { + return ImmutableSet.of(initial).iterator(); + } + return new AdvancingIterator(initial, markerToNext); + } + } diff --git a/core/src/main/java/org/jclouds/collect/PagedIterator.java b/core/src/main/java/org/jclouds/collect/PagedIterator.java deleted file mode 100644 index 9d47c6d1fb..0000000000 --- a/core/src/main/java/org/jclouds/collect/PagedIterator.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * 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.collect; - -import com.google.common.annotations.Beta; -import com.google.common.base.Optional; -import com.google.common.collect.AbstractIterator; - -/** - * - * @author Adrian Cole - */ -@Beta -public abstract class PagedIterator extends AbstractIterator> { - - /** - * If there is a next marker, then the set is incomplete and you should issue another command to - * retrieve the rest, setting the option {@code marker} to this value - * - * @return next marker, or absent if list is complete - */ - public abstract Optional nextMarker(); - -} \ No newline at end of file diff --git a/core/src/main/java/org/jclouds/collect/PagedIterators.java b/core/src/main/java/org/jclouds/collect/PagedIterators.java deleted file mode 100644 index 4ff50e9bd5..0000000000 --- a/core/src/main/java/org/jclouds/collect/PagedIterators.java +++ /dev/null @@ -1,184 +0,0 @@ -/** - * 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 current 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.collect; - -import static com.google.common.base.Preconditions.checkNotNull; - -import com.google.common.annotations.Beta; -import com.google.common.base.Function; -import com.google.common.base.Objects; -import com.google.common.base.Optional; - -/** - * Utilities for using {@link PagedIterator}s. - * - * @author Adrian Cole - */ -@Beta -public class PagedIterators { - - private static class AdvancingPagedIterator extends PagedIterator { - - private final Function> markerToNext; - private transient IterableWithMarker current; - private transient boolean unread = true; - - private AdvancingPagedIterator(IterableWithMarker initial, Function> markerToNext) { - this.current = checkNotNull(initial, "initial iterable"); - this.markerToNext = checkNotNull(markerToNext, "marker to next iterable"); - } - - /** - * {@inheritDoc} - */ - @Override - protected IterableWithMarker computeNext() { - if (unread) - try { - return current; - } finally { - unread = false; - } - else if (nextMarker().isPresent()) - return current = markerToNext.apply(nextMarker().get()); - else - return endOfData(); - } - - @Override - public Optional nextMarker() { - return current.nextMarker(); - } - - /** - * {@inheritDoc} - */ - @Override - public int hashCode() { - return Objects.hashCode(current, unread); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null || getClass() != obj.getClass()) - return false; - AdvancingPagedIterator other = AdvancingPagedIterator.class.cast(obj); - return Objects.equal(this.current, other.current) && Objects.equal(this.unread, other.unread); - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() { - return Objects.toStringHelper("").add("current", current).add("unread", unread).toString(); - } - } - - /** - * - * @param initial - * the initial set current data - * @param markerToNext - * produces the next set based on the marker - * - * @return iterable current data which continues if the user iterates beyond the first page - */ - public static PagedIterator advancing(IterableWithMarker initial, - Function> markerToNext) { - if (!initial.nextMarker().isPresent()) { - return of(initial); - } - return new AdvancingPagedIterator(initial, markerToNext); - } - - /** - * - * @param initial - * the initial set current data - * @return iterable current data which only contains the single element - */ - public static PagedIterator of(IterableWithMarker initial) { - return new OnlyElementIterator(initial); - } - - private static class OnlyElementIterator extends PagedIterator { - - private transient IterableWithMarker onlyElement; - private transient boolean unread = true; - - private OnlyElementIterator(IterableWithMarker onlyElement) { - this.onlyElement = checkNotNull(onlyElement, "onlyElement"); - } - - /** - * {@inheritDoc} - */ - @Override - protected IterableWithMarker computeNext() { - if (unread) - try { - return onlyElement; - } finally { - unread = false; - } - else - return endOfData(); - } - - @Override - public Optional nextMarker() { - return onlyElement.nextMarker(); - } - - /** - * {@inheritDoc} - */ - @Override - public int hashCode() { - return Objects.hashCode(onlyElement); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null || getClass() != obj.getClass()) - return false; - OnlyElementIterator other = OnlyElementIterator.class.cast(obj); - return Objects.equal(this.onlyElement, other.onlyElement) && Objects.equal(this.unread, other.unread); - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() { - return Objects.toStringHelper("").add("onlyElement", onlyElement).add("unread", unread).toString(); - } - } -} diff --git a/core/src/main/java/org/jclouds/collect/internal/CallerArg0ToPagedIterable.java b/core/src/main/java/org/jclouds/collect/internal/CallerArg0ToPagedIterable.java index c93738fc2b..a1ddedb14b 100644 --- a/core/src/main/java/org/jclouds/collect/internal/CallerArg0ToPagedIterable.java +++ b/core/src/main/java/org/jclouds/collect/internal/CallerArg0ToPagedIterable.java @@ -40,7 +40,6 @@ package org.jclouds.collect.internal; import org.jclouds.collect.IterableWithMarker; import org.jclouds.collect.PagedIterable; import org.jclouds.collect.PagedIterables; -import org.jclouds.collect.PagedIterators; import org.jclouds.http.HttpRequest; import org.jclouds.rest.InvocationContext; import org.jclouds.rest.internal.GeneratedHttpRequest; @@ -61,7 +60,7 @@ public abstract class CallerArg0ToPagedIterable apply(IterableWithMarker input) { if (input.nextMarker() == null) - return PagedIterables.create(PagedIterators.of(input)); + return PagedIterables.of(input); Optional arg0Option = Optional.absent(); if (request.getCaller().get().getArgs() != null && request.getCaller().get().getArgs().length > 0) { @@ -70,7 +69,7 @@ public abstract class CallerArg0ToPagedIterable> markerToNextForCallingArg0(String arg0); diff --git a/core/src/main/java/org/jclouds/concurrent/FutureIterables.java b/core/src/main/java/org/jclouds/concurrent/FutureIterables.java index 2655df9dff..119f5807a5 100644 --- a/core/src/main/java/org/jclouds/concurrent/FutureIterables.java +++ b/core/src/main/java/org/jclouds/concurrent/FutureIterables.java @@ -137,8 +137,9 @@ public class FutureIterables { errors.incrementAndGet(); logException(logger, logPrefix, total, complete.get(), errors.get(), start, e); errorMap.put(future.getKey(), e); + } finally { + doneSignal.countDown(); } - doneSignal.countDown(); } @Override @@ -148,10 +149,11 @@ public class FutureIterables { }, exec); } try { - if (maxTime != null) + if (maxTime != null) { doneSignal.await(maxTime, TimeUnit.MILLISECONDS); - else + } else { doneSignal.await(); + } if (errors.get() > 0) { String message = message(logPrefix, total, complete.get(), errors.get(), start); RuntimeException exception = new RuntimeException(message); diff --git a/core/src/main/java/org/jclouds/crypto/CryptoStreams.java b/core/src/main/java/org/jclouds/crypto/CryptoStreams.java index 59c25a6012..5111fcb2d1 100644 --- a/core/src/main/java/org/jclouds/crypto/CryptoStreams.java +++ b/core/src/main/java/org/jclouds/crypto/CryptoStreams.java @@ -49,20 +49,21 @@ import com.google.common.io.InputSupplier; @Beta public class CryptoStreams { - public static String hex(byte[] in) { - byte[] hex = new byte[2 * in.length]; + private static char[] hex(byte[] in, int offset, int len) { + char[] hex = new char[2 * len]; int index = 0; - for (byte b : in) { + for (int i = offset; i < offset + len; ++i) { + byte b = in[i]; int v = b & 0xFF; hex[index++] = HEX_CHAR_TABLE[v >>> 4]; hex[index++] = HEX_CHAR_TABLE[v & 0xF]; } - try { - return new String(hex, "ASCII"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } + return hex; + } + + public static String hex(byte[] in) { + return new String(hex(in, 0, in.length)); } public static byte[] hex(String s) { @@ -295,9 +296,10 @@ public class CryptoStreams { }); } - final static byte[] HEX_CHAR_TABLE = { (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', - (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', - (byte) 'f' }; + private final static char[] HEX_CHAR_TABLE = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', + }; /** * Computes and returns the hex value for a supplied input stream. @@ -313,16 +315,7 @@ public class CryptoStreams { final StringBuilder out = new StringBuilder(); return com.google.common.io.ByteStreams.readBytes(supplier, new ByteProcessor() { public boolean processBytes(byte[] buf, int off, int len) { - char[] hex = new char[2 * len]; - int index = 0; - - for (int i = off; i < off + len; i++) { - byte b = buf[i]; - int v = b & 0xFF; - hex[index++] = (char) HEX_CHAR_TABLE[v >>> 4]; - hex[index++] = (char) HEX_CHAR_TABLE[v & 0xF]; - } - out.append(hex); + out.append(hex(buf, off, len)); return true; } diff --git a/core/src/test/java/org/jclouds/collect/PagedIterablesTest.java b/core/src/test/java/org/jclouds/collect/PagedIterablesTest.java index 73d8ad158b..9ce207dc6b 100644 --- a/core/src/test/java/org/jclouds/collect/PagedIterablesTest.java +++ b/core/src/test/java/org/jclouds/collect/PagedIterablesTest.java @@ -9,7 +9,6 @@ import org.testng.annotations.Test; import com.google.common.base.Function; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; /** * Tests behavior of {@code IterableWithMarkers}. @@ -28,7 +27,7 @@ public class PagedIterablesTest { EasyMock.replay(markerToNext); - PagedIterable iterable = PagedIterables.create(PagedIterators.advancing(initial, markerToNext)); + PagedIterable iterable = PagedIterables.advance(initial, markerToNext); Assert.assertSame(iterable.get(0), initial); @@ -49,9 +48,9 @@ public class PagedIterablesTest { EasyMock.replay(markerToNext); - PagedIterable iterable = PagedIterables.create(PagedIterators.advancing(initial, markerToNext)); + PagedIterable iterable = PagedIterables.advance(initial, markerToNext); - Assert.assertEquals(ImmutableSet.copyOf(Iterables.concat(iterable)), + Assert.assertEquals(iterable.concat().toImmutableSet(), ImmutableSet.of("foo", "bar", "boo", "baz", "ham", "cheeze")); EasyMock.verify(markerToNext); diff --git a/core/src/test/java/org/jclouds/collect/PagedIteratorsTest.java b/core/src/test/java/org/jclouds/collect/PagedIteratorsTest.java deleted file mode 100644 index c1348c460f..0000000000 --- a/core/src/test/java/org/jclouds/collect/PagedIteratorsTest.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.jclouds.collect; - -import static org.easymock.EasyMock.createMock; -import static org.easymock.EasyMock.expect; - -import org.easymock.EasyMock; -import org.testng.Assert; -import org.testng.annotations.Test; - -import com.google.common.base.Function; -import com.google.common.base.Optional; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; - -/** - * Tests behavior of {@code IterableWithMarkers}. - * - * @author Adrian Cole - */ -@Test(testName = "PagedIteratorsTest") -public class PagedIteratorsTest { - - @SuppressWarnings("unchecked") - @Test - public void testSinglePageResultReturnsSame() { - - IterableWithMarker initial = IterableWithMarkers.from(ImmutableSet.of("foo", "bar")); - Function> markerToNext = createMock(Function.class); - - EasyMock.replay(markerToNext); - - Assert.assertSame(PagedIterators.advancing(initial, markerToNext).next(), initial); - - EasyMock.verify(markerToNext); - } - - @SuppressWarnings("unchecked") - @Test - public void testMultiPage2Pages() { - - IterableWithMarker initial = IterableWithMarkers.from(ImmutableSet.of("foo", "bar"), "MARKER1"); - Function> markerToNext = createMock(Function.class); - - expect(markerToNext.apply("MARKER1")).andReturn(IterableWithMarkers.from(ImmutableSet.of("boo", "baz"), null)); - - EasyMock.replay(markerToNext); - - Assert.assertEquals(ImmutableSet.copyOf(Iterables.concat(ImmutableSet.copyOf(PagedIterators.advancing(initial, - markerToNext)))), ImmutableSet.of("foo", "bar", "boo", "baz")); - - EasyMock.verify(markerToNext); - - } - - @SuppressWarnings("unchecked") - @Test - public void testMultiPage3Pages() { - - IterableWithMarker initial = IterableWithMarkers.from(ImmutableSet.of("foo", "bar"), "MARKER1"); - Function> markerToNext = createMock(Function.class); - - expect(markerToNext.apply("MARKER1")).andReturn( - IterableWithMarkers.from(ImmutableSet.of("boo", "baz"), "MARKER2")); - - expect(markerToNext.apply("MARKER2")).andReturn(IterableWithMarkers.from(ImmutableSet.of("ham", "cheeze"), null)); - - EasyMock.replay(markerToNext); - - Assert.assertEquals(ImmutableSet.copyOf(Iterables.concat(ImmutableSet.copyOf(PagedIterators.advancing(initial, - markerToNext)))), ImmutableSet.of("foo", "bar", "boo", "baz", "ham", "cheeze")); - - EasyMock.verify(markerToNext); - - } - - @SuppressWarnings("unchecked") - @Test - public void testMultiPage3PagesNextMarkerSetCorrectly() { - - IterableWithMarker initial = IterableWithMarkers.from(ImmutableSet.of("foo", "bar"), "MARKER1"); - Function> markerToNext = createMock(Function.class); - IterableWithMarker second = IterableWithMarkers.from(ImmutableSet.of("boo", "baz"), "MARKER2"); - expect(markerToNext.apply("MARKER1")).andReturn(second); - IterableWithMarker third = IterableWithMarkers.from(ImmutableSet.of("ham", "cheeze"), null); - expect(markerToNext.apply("MARKER2")).andReturn(third); - - EasyMock.replay(markerToNext); - PagedIterator iterator = PagedIterators.advancing(initial, markerToNext); - - Assert.assertEquals(iterator.hasNext(), true); - Assert.assertEquals(iterator.nextMarker(), Optional.of("MARKER1")); - Assert.assertEquals(iterator.next(), initial); - Assert.assertEquals(iterator.hasNext(), true); - Assert.assertEquals(iterator.nextMarker(), Optional.of("MARKER2")); - Assert.assertEquals(iterator.next(), second); - Assert.assertEquals(iterator.hasNext(), true); - Assert.assertEquals(iterator.nextMarker(), Optional.absent()); - Assert.assertEquals(iterator.next(), third); - Assert.assertEquals(iterator.hasNext(), false); - Assert.assertEquals(iterator.nextMarker(), Optional.absent()); - EasyMock.verify(markerToNext); - - } - -} diff --git a/core/src/test/java/org/jclouds/rest/internal/BaseRestApiExpectTest.java b/core/src/test/java/org/jclouds/rest/internal/BaseRestApiExpectTest.java index 3fd4ef6731..b1ad1f0eab 100644 --- a/core/src/test/java/org/jclouds/rest/internal/BaseRestApiExpectTest.java +++ b/core/src/test/java/org/jclouds/rest/internal/BaseRestApiExpectTest.java @@ -80,7 +80,6 @@ import com.google.common.annotations.Beta; import com.google.common.base.Function; import com.google.common.base.Objects; import com.google.common.base.Throwables; -import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -118,7 +117,6 @@ import com.google.inject.name.Names; * * @author Adrian Cole */ -@Test(groups = "unit") @Beta public abstract class BaseRestApiExpectTest { @@ -441,13 +439,14 @@ public abstract class BaseRestApiExpectTest { public S requestsSendResponses(final Map requestToResponse, Module module, Properties props) { return createClient(new Function() { - ImmutableBiMap bimap = ImmutableBiMap.copyOf(requestToResponse); - + @Override public HttpResponse apply(HttpRequest input) { + HttpRequest matchedRequest = null; HttpResponse response = null; for (HttpRequest request : requestToResponse.keySet()) { if (httpRequestsAreEqual(input, request)) { + matchedRequest = request; response = requestToResponse.get(request); } } @@ -468,7 +467,7 @@ public abstract class BaseRestApiExpectTest { } else if (compareHttpRequestAsType(input) == HttpRequestComparisonType.DEFAULT) { // in case hashCode/equals doesn't do a full content check - assertEquals(renderRequest(input), renderRequest(bimap.inverse().get(response))); + assertEquals(renderRequest(input), renderRequest(matchedRequest)); } return response; diff --git a/labs/elb/src/main/java/org/jclouds/elb/loadbalancer/strategy/ELBListLoadBalancersStrategy.java b/labs/elb/src/main/java/org/jclouds/elb/loadbalancer/strategy/ELBListLoadBalancersStrategy.java index ec11c1a7eb..87ef4b6efa 100644 --- a/labs/elb/src/main/java/org/jclouds/elb/loadbalancer/strategy/ELBListLoadBalancersStrategy.java +++ b/labs/elb/src/main/java/org/jclouds/elb/loadbalancer/strategy/ELBListLoadBalancersStrategy.java @@ -46,7 +46,6 @@ import org.jclouds.logging.Logger; import com.google.common.base.Function; import com.google.common.base.Supplier; -import com.google.common.collect.Iterables; import com.google.common.util.concurrent.ListenableFuture; /** @@ -88,14 +87,13 @@ public class ELBListLoadBalancersStrategy implements ListLoadBalancersStrategy { @Override public Iterable apply(PagedIterable input) { - return Iterables.transform(Iterables.concat(input), - new Function() { - - @Override - public LoadBalancerInRegion apply(LoadBalancer lb) { - return new LoadBalancerInRegion(lb, from); - } - }); + return input.concat() + .transform(new Function() { + @Override + public LoadBalancerInRegion apply(LoadBalancer lb) { + return new LoadBalancerInRegion(lb, from); + } + }); } }, executor); diff --git a/labs/elb/src/test/java/org/jclouds/elb/features/LoadBalancerApiExpectTest.java b/labs/elb/src/test/java/org/jclouds/elb/features/LoadBalancerApiExpectTest.java index 3d4a98cf2a..ab4ddb4778 100644 --- a/labs/elb/src/test/java/org/jclouds/elb/features/LoadBalancerApiExpectTest.java +++ b/labs/elb/src/test/java/org/jclouds/elb/features/LoadBalancerApiExpectTest.java @@ -145,7 +145,7 @@ public class LoadBalancerApiExpectTest extends BaseELBApiExpectTest { LoadBalancer lb1 = new GetLoadBalancerResponseTest().expected().toBuilder().name("my-load-balancer-1").build(); LoadBalancer lb2 = new GetLoadBalancerResponseTest().expected(); - assertEquals(ImmutableSet.copyOf(Iterables.concat(apiWhenExist.getLoadBalancerApi().list())), ImmutableSet.of(lb1, lb2)); + assertEquals(apiWhenExist.getLoadBalancerApi().list().concat().toImmutableSet(), ImmutableSet.of(lb1, lb2)); } public void testList2PagesWhenResponseIs2xxInEU() throws Exception { diff --git a/labs/elb/src/test/java/org/jclouds/elb/loadbalancer/ELBLoadBalancerServiceLiveTest.java b/labs/elb/src/test/java/org/jclouds/elb/loadbalancer/ELBLoadBalancerServiceLiveTest.java index e47cc479cb..b4172e83f8 100644 --- a/labs/elb/src/test/java/org/jclouds/elb/loadbalancer/ELBLoadBalancerServiceLiveTest.java +++ b/labs/elb/src/test/java/org/jclouds/elb/loadbalancer/ELBLoadBalancerServiceLiveTest.java @@ -20,7 +20,6 @@ package org.jclouds.elb.loadbalancer; import static org.testng.Assert.assertEquals; -import org.jclouds.collect.PagedIterable; import org.jclouds.compute.domain.NodeMetadata; import org.jclouds.elb.ELBApi; import org.jclouds.elb.ELBAsyncApi; @@ -30,9 +29,9 @@ import org.jclouds.rest.RestContext; import org.jclouds.sshj.config.SshjSshClientModule; import org.testng.annotations.Test; +import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet.Builder; -import com.google.common.collect.Iterables; /** * @@ -61,8 +60,8 @@ public class ELBLoadBalancerServiceLiveTest extends BaseLoadBalancerServiceLiveT instanceIds.add(node.getProviderId()); } - PagedIterable elbs = elbApi.getLoadBalancerApi().list(); - for (LoadBalancer elb : Iterables.concat(elbs)) { + FluentIterable elbs = elbApi.getLoadBalancerApi().list().concat(); + for (LoadBalancer elb : elbs) { if (elb.getName().equals(group)) assertEquals(elb.getInstanceIds(), instanceIds.build()); } diff --git a/labs/iam/src/test/java/org/jclouds/iam/features/UserApiExpectTest.java b/labs/iam/src/test/java/org/jclouds/iam/features/UserApiExpectTest.java index ed554fd836..15000246aa 100644 --- a/labs/iam/src/test/java/org/jclouds/iam/features/UserApiExpectTest.java +++ b/labs/iam/src/test/java/org/jclouds/iam/features/UserApiExpectTest.java @@ -149,7 +149,7 @@ public class UserApiExpectTest extends BaseIAMApiExpectTest { IAMApi apiWhenExist = requestsSendResponses(list, listResponse, list2, list2Response); - assertEquals(ImmutableList.copyOf(Iterables.concat(apiWhenExist.getUserApi().list())), + assertEquals(apiWhenExist.getUserApi().list().concat().toImmutableList(), ImmutableList.copyOf(Iterables.concat(new ListUsersResponseTest().expected(), new ListUsersResponseTest().expected()))); } diff --git a/labs/pom.xml b/labs/pom.xml index c5a4f19e2b..6c5b1477d7 100644 --- a/labs/pom.xml +++ b/labs/pom.xml @@ -55,5 +55,6 @@ nodepool rds aws-rds + smartos-ssh diff --git a/labs/rds/src/test/java/org/jclouds/rds/features/InstanceApiExpectTest.java b/labs/rds/src/test/java/org/jclouds/rds/features/InstanceApiExpectTest.java index 7a42aab7eb..18ef406a7f 100644 --- a/labs/rds/src/test/java/org/jclouds/rds/features/InstanceApiExpectTest.java +++ b/labs/rds/src/test/java/org/jclouds/rds/features/InstanceApiExpectTest.java @@ -140,7 +140,7 @@ public class InstanceApiExpectTest extends BaseRDSApiExpectTest { RDSApi apiWhenExist = requestsSendResponses( list, listResponse, list2, list2Response); - assertEquals(ImmutableList.copyOf(Iterables.concat(apiWhenExist.getInstanceApi().list())), + assertEquals(apiWhenExist.getInstanceApi().list().concat().toImmutableList(), ImmutableList.copyOf(Iterables.concat(new DescribeDBInstancesResponseTest().expected(), new DescribeDBInstancesResponseTest().expected()))); } diff --git a/labs/rds/src/test/java/org/jclouds/rds/features/SecurityGroupApiExpectTest.java b/labs/rds/src/test/java/org/jclouds/rds/features/SecurityGroupApiExpectTest.java index 5ae1882781..8a04e2b528 100644 --- a/labs/rds/src/test/java/org/jclouds/rds/features/SecurityGroupApiExpectTest.java +++ b/labs/rds/src/test/java/org/jclouds/rds/features/SecurityGroupApiExpectTest.java @@ -140,7 +140,7 @@ public class SecurityGroupApiExpectTest extends BaseRDSApiExpectTest { RDSApi apiWhenExist = requestsSendResponses( list, listResponse, list2, list2Response); - assertEquals(ImmutableList.copyOf(Iterables.concat(apiWhenExist.getSecurityGroupApi().list())), + assertEquals(apiWhenExist.getSecurityGroupApi().list().concat().toImmutableList(), ImmutableList.copyOf(Iterables.concat(new DescribeDBSecurityGroupsResponseTest().expected(), new DescribeDBSecurityGroupsResponseTest().expected()))); } diff --git a/labs/rds/src/test/java/org/jclouds/rds/features/SubnetGroupApiExpectTest.java b/labs/rds/src/test/java/org/jclouds/rds/features/SubnetGroupApiExpectTest.java index 18104519ba..64ab2b740b 100644 --- a/labs/rds/src/test/java/org/jclouds/rds/features/SubnetGroupApiExpectTest.java +++ b/labs/rds/src/test/java/org/jclouds/rds/features/SubnetGroupApiExpectTest.java @@ -141,7 +141,7 @@ public class SubnetGroupApiExpectTest extends BaseRDSApiExpectTest { RDSApi apiWhenExist = requestsSendResponses( list, listResponse, list2, list2Response); - assertEquals(ImmutableList.copyOf(Iterables.concat(apiWhenExist.getSubnetGroupApi().list())), + assertEquals(apiWhenExist.getSubnetGroupApi().list().concat().toImmutableList(), ImmutableList.copyOf(Iterables.concat(new DescribeDBSubnetGroupsResponseTest().expected(), new DescribeDBSubnetGroupsResponseTest().expected()))); } diff --git a/labs/smartos-ssh/README.md b/labs/smartos-ssh/README.md new file mode 100644 index 0000000000..7ca7d43352 --- /dev/null +++ b/labs/smartos-ssh/README.md @@ -0,0 +1,20 @@ + +This is designed for interacting with a Joyent SmartOS host, in order to be able to leverage +the lightweight VM support (nee Solaris Zones). + +It is planned to support KVM VMs in the future. + +-------------- + +#Setup + +Have a SmartOS installation, that you know either the root password for, or a secondary account with sufficient +permissions to be able to run the vm tools (vmadm, dsadm). + +That's it! + +-------------- + +#Notes: + +- This is a work in progress, so please report any bugs that you find. diff --git a/labs/smartos-ssh/pom.xml b/labs/smartos-ssh/pom.xml new file mode 100644 index 0000000000..c93ed79778 --- /dev/null +++ b/labs/smartos-ssh/pom.xml @@ -0,0 +1,121 @@ + + + + 4.0.0 + + org.jclouds + jclouds-project + 1.5.0-SNAPSHOT + ../../project/pom.xml + + org.jclouds.labs + smartos-ssh + smartos ssh api + jclouds components to access SmartOS over SSH + bundle + + + https://api.joyentcloud.com + ~6.5 + + FIXME_IDENTITY + FIXME_CREDENTIALS + org.jclouds.joyent.cloudapi.v6_5*;version="${project.version}" + + org.jclouds.rest.internal;version="${project.version}", + org.jclouds*;version="${project.version}", + * + + + + + org.jclouds + jclouds-compute + ${project.version} + + + org.jclouds + jclouds-compute + ${project.version} + test-jar + test + + + org.jclouds + jclouds-core + ${project.version} + test-jar + test + + + org.jclouds.driver + jclouds-slf4j + ${project.version} + test + + + org.jclouds.driver + jclouds-sshj + ${project.version} + test + + + ch.qos.logback + logback-classic + 1.0.0 + test + + + + + + live + + + + org.apache.maven.plugins + maven-surefire-plugin + + + integration + integration-test + + test + + + + ${test.smartos-ssh.endpoint} + ${test.smartos-ssh.api-version} + ${test.smartos-ssh.build-version} + ${test.smartos-ssh.identity} + ${test.smartos-ssh.credential} + + + + + + + + + + + diff --git a/labs/smartos-ssh/src/main/java/org/jclouds/smartos/SmartOSApiMetadata.java b/labs/smartos-ssh/src/main/java/org/jclouds/smartos/SmartOSApiMetadata.java new file mode 100644 index 0000000000..af47bb55b8 --- /dev/null +++ b/labs/smartos-ssh/src/main/java/org/jclouds/smartos/SmartOSApiMetadata.java @@ -0,0 +1,57 @@ +package org.jclouds.smartos; + +import java.net.URI; + +import org.jclouds.apis.ApiMetadata; +import org.jclouds.apis.internal.BaseApiMetadata; +import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.smartos.compute.config.SmartOSComputeServiceContextModule; + +/** + * Implementation of {@link ApiMetadata} for SmartOS + * + * @author Nigel Magnay + */ +public class SmartOSApiMetadata extends BaseApiMetadata { + + /** The serialVersionUID */ + private static final long serialVersionUID = 3606170564482119304L; + + public static Builder builder() { + return new Builder(); + } + + @Override + public Builder toBuilder() { + return Builder.class.cast(builder().fromApiMetadata(this)); + } + + public SmartOSApiMetadata() { + super(builder()); + } + + protected SmartOSApiMetadata(Builder builder) { + super(builder); + } + + public static class Builder extends BaseApiMetadata.Builder { + + protected Builder(){ + id("smartos-ssh") + .name("SmartOS SSH API") + .identityName("Username") + .defaultIdentity("root") + .defaultCredential("smartos") + .defaultEndpoint("http://localhost") + .documentation(URI.create("http://http://wiki.smartos.org/display/DOC/How+to+create+a+Virtual+Machine+in+SmartOS")) + .view(ComputeServiceContext.class) + .defaultModule(SmartOSComputeServiceContextModule.class); + } + + @Override + public SmartOSApiMetadata build() { + return new SmartOSApiMetadata(this); + } + + } +} \ No newline at end of file diff --git a/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/config/SmartOSComputeServiceContextModule.java b/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/config/SmartOSComputeServiceContextModule.java new file mode 100644 index 0000000000..a5fe6c6805 --- /dev/null +++ b/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/config/SmartOSComputeServiceContextModule.java @@ -0,0 +1,62 @@ +/** + * 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.smartos.compute.config; + +import com.google.common.base.Function; +import com.google.inject.TypeLiteral; +import org.jclouds.smartos.compute.domain.DataSet; +import org.jclouds.smartos.compute.domain.SmartOSHost; +import org.jclouds.smartos.compute.domain.VM; +import org.jclouds.smartos.compute.domain.VmSpecification; +import org.jclouds.compute.ComputeServiceAdapter; +import org.jclouds.compute.config.ComputeServiceAdapterContextModule; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.domain.Location; + +import org.jclouds.smartos.compute.functions.DataSetToImage; +import org.jclouds.smartos.compute.functions.DatacenterToLocation; +import org.jclouds.smartos.compute.functions.VMToNodeMetadata; +import org.jclouds.smartos.compute.functions.VmSpecificationToHardware; +import org.jclouds.smartos.compute.strategy.SmartOSComputeServiceAdapter; + +/** + * + * @author Nigel Magnay + */ +public class SmartOSComputeServiceContextModule extends + ComputeServiceAdapterContextModule { + + @Override + protected void configure() { + super.configure(); + bind(new TypeLiteral>() { + }).to(SmartOSComputeServiceAdapter.class); + bind(new TypeLiteral>() { + }).to(VMToNodeMetadata.class); + bind(new TypeLiteral>() { + }).to(DataSetToImage.class); + bind(new TypeLiteral>() { + }).to(VmSpecificationToHardware.class); + bind(new TypeLiteral>() { + }).to(DatacenterToLocation.class); + // to have the compute service adapter override default locations + //install(new LocationsFromComputeServiceAdapterModule(){}); + + } +} \ No newline at end of file diff --git a/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/domain/DataSet.java b/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/domain/DataSet.java new file mode 100644 index 0000000000..0b766af966 --- /dev/null +++ b/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/domain/DataSet.java @@ -0,0 +1,141 @@ +package org.jclouds.smartos.compute.domain; + +import com.google.common.base.Objects; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.UUID; + +/** + * Dataset is a pre-built image ready to be cloned. + */ +public class DataSet { + private final UUID uuid; + private final String os; + private final String published; + private final String urn; + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return builder().fromDataSet(this); + } + + public static class Builder { + + public UUID uuid; + public String os; + public String published; + public String urn; + + public Builder uuid(UUID uuid) { + this.uuid = uuid; + return this; + } + + public Builder uuid(String uuid) { + this.uuid = UUID.fromString(uuid); + return this; + } + + public Builder os(String os) { + this.os = os; + return this; + } + + public Builder published(String published) { + this.published = published; + return this; + } + + public Builder urn(String urn) { + this.urn = urn; + return this; + } + + public Builder fromDsadmString(String string) { + String [] sections = string.split(" "); + + uuid ( sections[0] ); + os ( sections[1] ); + published ( sections[2] ); + urn ( sections[3] ); + + return this; + } + + + public DataSet build() { + return new DataSet(uuid, os, published, urn); + } + + public Builder fromDataSet(DataSet in) { + return uuid(in.getUuid()) + .os(in.getOs()) + .published(in.getPublished()) + .urn(in.getUrn()); + } + } + + protected DataSet(UUID uuid, String os, String published, String urn) { + this.uuid = uuid; + this.os = os; + this.published = published; + this.urn = urn; + } + + public UUID getUuid() { + return uuid; + } + + public String getOs() { + return os; + } + + public String getPublished() { + return published; + } + + public String getUrn() { + return urn; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + // UUID is primary key + return uuid.hashCode(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + return uuid.equals(((DataSet)obj).uuid); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return Objects.toStringHelper(this).omitNullValues() + .add("uuid", uuid) + .add("os", os) + .add("published", published) + .add("urn", urn).toString(); + } + +} diff --git a/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/domain/SmartOSHost.java b/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/domain/SmartOSHost.java new file mode 100644 index 0000000000..7feb88a9ea --- /dev/null +++ b/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/domain/SmartOSHost.java @@ -0,0 +1,296 @@ +package org.jclouds.smartos.compute.domain; + +import com.google.common.base.Splitter; +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.net.HostAndPort; +import org.jclouds.domain.LoginCredentials; +import org.jclouds.location.Provider; +import org.jclouds.rest.annotations.Credential; +import org.jclouds.rest.annotations.Identity; +import org.jclouds.ssh.SshClient; + +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.net.URI; +import java.util.Map; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A host machine that runs smartOS + */ +@Singleton +public class SmartOSHost { + protected final String hostname; + protected final String username; + protected final String password; + + protected SshClient.Factory sshClientFactory; + + private SshClient _connection; + + public static class HostException extends RuntimeException { + public HostException(String s, Throwable throwable) { + super(s, throwable); + } + + public HostException(String s) { + super(s); + } + } + + public static class NoResponseException extends Exception { + public NoResponseException() { + } + } + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return builder().fromSmartOSHost(this); + } + + public static class Builder { + protected String hostname; + protected String username; + protected String password; + protected SshClient.Factory sshFactory; + + public Builder hostname(String hostname) { + this.hostname = hostname; + return this; + } + + public Builder username(String username) { + this.username = username; + return this; + } + + public Builder password(String password) { + this.password = password; + return this; + } + + public Builder sshFactory(SshClient.Factory sshFactory) { + this.sshFactory = sshFactory; + return this; + } + + public SmartOSHost build() { + return new SmartOSHost(hostname, username, password, sshFactory); + } + + public Builder fromSmartOSHost(SmartOSHost in) { + return this.hostname ( in.getHostname() ) + .username ( in.getHostname() ) + .password ( in.getPassword() ) + .sshFactory( in.getSshClientFactory() ); + } + } + + @Inject + protected SmartOSHost(@Provider Supplier provider, + @Nullable @Identity String identity, + @Nullable @Credential String credential, + SshClient.Factory sshFactory) { + + this.hostname = provider.get().getHost(); + this.username = identity; + this.password = credential; + this.sshClientFactory = sshFactory; + } + + protected SmartOSHost(String hostname, String username, String password, SshClient.Factory sshClientFactory) { + this.hostname = hostname; + this.username = username; + this.password = password; + this.sshClientFactory = sshClientFactory; + } + + public String getDescription() { + return "SmartOS@" + hostname; + } + + public String getHostname() { + return hostname; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public SshClient.Factory getSshClientFactory() { + return sshClientFactory; + } + + protected SshClient getConnection() { + if (_connection == null) { + + LoginCredentials credentials = new LoginCredentials.Builder() + .user(username) + .password(password) + .build(); + + _connection = getSshClientFactory().create( + HostAndPort.fromParts(hostname, 22), + credentials + ); + + _connection.connect(); + + } + return _connection; + } + + + public String exec(String cmd) { + return getConnection().exec(cmd).getOutput(); + } + + public String vmList() { + return exec("vmadm list -p"); + } + + + public Map getVMIpAddresses(UUID vmId) + { + ImmutableMap.Builder netMapBuilder = ImmutableMap.builder(); + + String response = getConnection().exec("zlogin " + vmId.toString() + " ifconfig -a4").getOutput(); + + if( response.length() == 0) + return ImmutableMap.of(); + + Iterable strings = Splitter.on("\n").split(response); + Pattern inetMatcher = Pattern.compile("inet [0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}"); + + String iface = ""; + for(String line : strings ) + { + if( line.length() > 0 && Character.isLetterOrDigit(line.charAt(0)) ) + { + iface = line.substring(0, line.indexOf(":") ); + } + else + { + Matcher matcher = inetMatcher.matcher(line); + if( matcher.find() ) + netMapBuilder.put(iface, matcher.group().substring(5)); + } + } + + return netMapBuilder.build(); + + } + + /** + * What remotely available images are there in the cloud? + * + * @return Collection of datasets + */ + public Iterable getAvailableImages() { + return toSpecList(exec("dsadm avail")); + } + + public Iterable getLocalDatasets() { + return toSpecList(exec("dsadm list")); + } + + public Iterable getVMs() { + return toVMList(exec("vmadm list -p")); + } + + public VM createVM(VmSpecification specification) { + + String response = getConnection().exec("(cat < toVMList(String string) { + try { + BufferedReader r = new BufferedReader(new StringReader(string)); + String line; + ImmutableList.Builder resultBuilder = ImmutableList.builder(); + while ((line = r.readLine()) != null) { + VM vm = VM.builder().host(this).fromVmadmString(line).build(); + + resultBuilder.add(vm); + } + return resultBuilder.build(); + } catch (IOException e) { + throw new HostException("Error parsing response when building VM list", e); + } + } + + public VM getVM(UUID serverId) { + for (VM vm : getVMs()) + if (vm.uuid.equals(serverId)) + return vm; + return null; + } + + public DataSet getDataSet(UUID imageId) { + for (DataSet ds : getLocalDatasets()) { + if (ds.getUuid().equals(imageId)) + return ds; + } + return null; + } + + public void destroyHost(UUID uuid) { + exec("vmadm delete " + uuid.toString()); + } + + public void rebootHost(UUID uuid) { + exec("vmadm reboot " + uuid.toString()); + } + + public void stopHost(UUID uuid) { + exec("vmadm stop -p"); + } + + public void startHost(UUID uuid) { + exec("vmadm start " + uuid.toString()); + } +} diff --git a/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/domain/VM.java b/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/domain/VM.java new file mode 100644 index 0000000000..ec9e6e9007 --- /dev/null +++ b/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/domain/VM.java @@ -0,0 +1,222 @@ +package org.jclouds.smartos.compute.domain; + +import com.google.common.base.Objects; +import com.google.common.base.Optional; + +import java.io.IOException; +import java.util.Collection; +import java.util.Map; +import java.util.UUID; + +/** + * Representing a Virtual Machine (Zone / KVM ) + **/ +public class VM { + + public enum State + { + RUNNING, + STOPPED, + INCOMPLETE + } + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return builder().fromVM(this); + } + + public static class Builder { + + protected SmartOSHost host; + protected UUID uuid; + protected String type; + protected String ram; + protected State state = State.STOPPED; + protected String alias; + + public Builder uuid(UUID uuid) { + this.uuid = uuid; + return this; + } + + public Builder uuid(String uuid) { + this.uuid = UUID.fromString(uuid); + return this; + } + + public Builder host(SmartOSHost host) { + this.host = host; + return this; + } + + public Builder type(String type) { + this.type = type; + return this; + } + + public Builder ram(String ram) { + this.ram = ram; + return this; + } + + public Builder state(String state) { + this.state = State.valueOf(state.toUpperCase()); + return this; + } + + public Builder state(State state) { + this.state = state; + return this; + } + + public Builder alias(String alias) { + this.alias = alias; + return this; + } + + public Builder fromVmadmString(String string) { + String[] sections = string.split(":"); + + uuid(sections[0]); + + type(sections[1]); + ram(sections[2]); + state(sections[3]); + + if (sections.length > 4) + alias(sections[4]); + + return this; + } + + + public VM build() { + return new VM(host,uuid,type,ram,state,alias); + } + + public Builder fromVM(VM in) { + return host(in.getHost()) + .uuid(in.getUuid()) + .type(in.getType()) + .ram(in.getRam()) + .state(in.getState()) + .alias(in.getAlias()); + } + } + + + protected SmartOSHost host; + protected final UUID uuid; + protected String type; + protected String ram; + protected State state; + protected String alias; + + + public VM(SmartOSHost host, UUID uuid, String type, String ram, State state, String alias) { + this.host = host; + this.uuid = uuid; + this.type = type; + this.ram = ram; + this.state = state; + this.alias = alias; + } + + public State getState() { + return state; + } + + public void destroy() { + host.destroyHost(uuid); + } + + public void reboot() { + host.rebootHost(uuid); + } + + public void stop() { + host.stopHost(uuid); + } + + public void start() { + host.startHost(uuid); + } + + public Optional getPublicAddress() throws InterruptedException { + Map ipAddresses; + + for( int i=0; i<30; i++ ) + { + ipAddresses = host.getVMIpAddresses(uuid); + if( ipAddresses.isEmpty() ) + { + // Got some + String ip = ipAddresses.get("net0"); + if( ip != null && !ip.equals("0.0.0.0")) + return Optional.of(ip); + } + + Thread.sleep(1000); + } + + return Optional.absent(); + } + + public SmartOSHost getHost() { + return host; + } + + public UUID getUuid() { + return uuid; + } + + public String getType() { + return type; + } + + public String getRam() { + return ram; + } + + public String getAlias() { + return alias; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + // UUID is primary key + return uuid.hashCode(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + return uuid.equals(((DataSet)obj).getUuid()); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return Objects.toStringHelper(this).omitNullValues() + .add("uuid", uuid) + .add("type", type) + .add("ram", ram) + .add("alias", alias).toString(); + } +} diff --git a/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/domain/VmNIC.java b/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/domain/VmNIC.java new file mode 100644 index 0000000000..e8caa3a9bf --- /dev/null +++ b/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/domain/VmNIC.java @@ -0,0 +1,110 @@ +package org.jclouds.smartos.compute.domain; + +import com.google.common.base.Objects; +import com.google.gson.annotations.SerializedName; +import com.google.inject.name.Named; + +import java.util.UUID; + + +/** + * Specification of a network card. + */ +public class VmNIC { + + @SerializedName("nic_tag") + protected final String tag; + protected final String ip; + protected final String netmask; + protected final String gateway; + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return builder().fromVmNIC(this); + } + + public static class Builder { + + public String tag = "admin"; + public String ip; + public String netmask; + public String gateway; + + + public Builder simpleDCHPNic() { + tag = "admin"; + ip = "dhcp"; + return this; + } + + public Builder tag(String tag) { + this.tag = tag; + return this; + } + + public Builder ip(String ip) { + this.ip = ip; + return this; + } + + public Builder netmask(String netmask) { + this.netmask = netmask; + return this; + } + + public Builder gateway(String gateway) { + this.gateway = gateway; + return this; + } + + public VmNIC build() { + return new VmNIC(tag,ip,netmask,gateway); + } + + public Builder fromVmNIC(VmNIC in) { + return tag ( in.getTag()) + .ip ( in.getIp() ) + .netmask( in.getNetmask() ) + .gateway( in.getGateway() ); + } + } + + + public VmNIC(String tag, String ip, String netmask, String gateway) { + this.tag = tag; + this.ip = ip; + this.netmask = netmask; + this.gateway = gateway; + } + + public String getTag() { + return tag; + } + + public String getIp() { + return ip; + } + + public String getNetmask() { + return netmask; + } + + public String getGateway() { + return gateway; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return Objects.toStringHelper(this).omitNullValues() + .add("tag", tag) + .add("ip", ip) + .add("netmask", netmask) + .add("gateway", gateway).toString(); + } +} diff --git a/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/domain/VmSpecification.java b/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/domain/VmSpecification.java new file mode 100644 index 0000000000..e793816fc7 --- /dev/null +++ b/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/domain/VmSpecification.java @@ -0,0 +1,142 @@ +package org.jclouds.smartos.compute.domain; + + +import com.google.common.collect.ImmutableList; +import com.google.gson.*; +import com.google.gson.annotations.SerializedName; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Specification of a VM to build, based on a dataset. + */ +public class VmSpecification { + protected final String alias; + protected final String brand; + + @SerializedName("dataset_uuid") + protected final DataSet dataset; + protected final String dnsDomain; + protected final String quota; + + protected final List nics; + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return builder().fromVmSpecification(this); + } + + public static class Builder { + + protected String alias; + protected String brand = "joyent"; + protected DataSet dataset; + protected String dnsDomain = "local"; + protected String quota = "10"; + + protected List nics = new ArrayList(); + + public Builder alias(String alias) { + this.alias = alias; + return this; + } + + public Builder brand(String brand) { + this.brand = brand; + return this; + } + + public Builder dataset(DataSet dataset) { + this.dataset = dataset; + return this; + } + + public Builder dnsDomain(String dnsDomain) { + this.dnsDomain = dnsDomain; + return this; + } + + public Builder quota(String quota) { + this.quota = quota; + return this; + } + + public Builder nics(Collection nic) { + this.nics.addAll(nics); + return this; + } + + public Builder nic(VmNIC nic) { + this.nics.add(nic); + return this; + } + + public VmSpecification build() { + return new VmSpecification(alias, brand, dataset, dnsDomain, quota, nics); + } + + public Builder fromVmSpecification(VmSpecification in) { + return alias (in.getAlias()) + .brand (in.getBrand()) + .dataset (in.getDataset()) + .dnsDomain(in.getDnsDomain()) + .quota (in.getQuota()) + .nics(in.getNics()); + } + } + + protected VmSpecification(String alias, String brand, DataSet dataset, String dnsDomain, String quota, List nics) { + this.alias = alias; + this.brand = brand; + this.dataset = dataset; + this.dnsDomain = dnsDomain; + this.quota = quota; + this.nics = nics; + } + + public String getAlias() { + return alias; + } + + public String getBrand() { + return brand; + } + + public DataSet getDataset() { + return dataset; + } + + public String getDnsDomain() { + return dnsDomain; + } + + public String getQuota() { + return quota; + } + + public List getNics() { + return ImmutableList.copyOf(nics); + } + + public String toJSONSpecification() { + GsonBuilder gson = new GsonBuilder(); + gson.registerTypeAdapter(DataSet.class, new FlattenDataset() ); + Gson g = gson.create(); + + return g.toJson(this); + } + + public class FlattenDataset implements JsonSerializer + { + @Override + public JsonElement serialize(DataSet vmSpecification, Type type, JsonSerializationContext jsonSerializationContext) { + return new JsonPrimitive(dataset.getUuid().toString()); + } + } +} diff --git a/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/functions/DataSetToImage.java b/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/functions/DataSetToImage.java new file mode 100644 index 0000000000..ee90c86d89 --- /dev/null +++ b/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/functions/DataSetToImage.java @@ -0,0 +1,63 @@ +/** + * 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.smartos.compute.functions; + +import javax.annotation.Resource; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.jclouds.smartos.compute.domain.DataSet; +import org.jclouds.compute.domain.Image; +import org.jclouds.compute.domain.ImageBuilder; +import org.jclouds.compute.domain.OperatingSystem; +import org.jclouds.compute.domain.OsFamily; +import org.jclouds.compute.reference.ComputeServiceConstants; +import org.jclouds.logging.Logger; + +import com.google.common.base.Function; + +/** + * @author Nigel Magnay + */ +@Singleton +public class DataSetToImage implements Function { + @Resource + @Named(ComputeServiceConstants.COMPUTE_LOGGER) + protected Logger logger = Logger.NULL; + + @Override + public Image apply(DataSet from) { + + ImageBuilder builder = new ImageBuilder(); + builder.ids(from.getUuid() + ""); + builder.name(from.getUrn()); + builder.description(from.getUrn()); + builder.status(Image.Status.AVAILABLE); + + OsFamily family; + try { + family = OsFamily.SOLARIS; + builder.operatingSystem(new OperatingSystem.Builder().name(from.getUrn()).description(from.getUrn()).family(family).build()); + } catch (IllegalArgumentException e) { + logger.debug("<< didn't match os(%s)", from); + } + return builder.build(); + } + +} diff --git a/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/functions/DatacenterToLocation.java b/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/functions/DatacenterToLocation.java new file mode 100644 index 0000000000..ea0984c7f8 --- /dev/null +++ b/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/functions/DatacenterToLocation.java @@ -0,0 +1,53 @@ +/** + * 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.smartos.compute.functions; + +import com.google.common.base.Function; +import com.google.common.base.Supplier; +import org.jclouds.smartos.compute.domain.SmartOSHost; +import org.jclouds.domain.Location; +import org.jclouds.domain.LocationBuilder; +import org.jclouds.domain.LocationScope; + +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * @author Nigel Magnay + */ +@Singleton +public class DatacenterToLocation implements Function { + private final Provider> provider; + + // allow us to lazy discover the provider of a resource + @Inject + public DatacenterToLocation(Provider> provider) { + this.provider = checkNotNull(provider, "provider"); + } + + @Override + public Location apply(SmartOSHost from) { + return new LocationBuilder().scope(LocationScope.ZONE).id(from.getHostname() + "").description(from.getDescription()).parent( + provider.get().get()).build(); + } + +} diff --git a/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/functions/VMToNodeMetadata.java b/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/functions/VMToNodeMetadata.java new file mode 100644 index 0000000000..2780c2df1f --- /dev/null +++ b/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/functions/VMToNodeMetadata.java @@ -0,0 +1,150 @@ +/** + * 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.smartos.compute.functions; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.sun.corba.se.spi.activation.Server; +import org.jclouds.smartos.compute.domain.VM; +import org.jclouds.collect.FindResourceInSet; +import org.jclouds.collect.Memoized; +import org.jclouds.compute.domain.*; +import org.jclouds.compute.functions.GroupNamingConvention; +import org.jclouds.domain.Credentials; +import org.jclouds.domain.Location; +import org.jclouds.domain.LoginCredentials; +import org.omg.PortableInterceptor.ACTIVE; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.IOException; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * @author Nigel Magnay + */ +@Singleton +public class VMToNodeMetadata implements Function { + + public static final Map serverStatusToNodeStatus = ImmutableMap + . builder() + .put(VM.State.RUNNING, NodeMetadata.Status.RUNNING)// + .put(VM.State.STOPPED, NodeMetadata.Status.SUSPENDED)// + .put(VM.State.INCOMPLETE, NodeMetadata.Status.PENDING)// + .build(); + + private final FindHardwareForServer findHardwareForServer; + private final FindLocationForServer findLocationForServer; + private final FindImageForServer findImageForServer; + private final Map credentialStore; + private final GroupNamingConvention nodeNamingConvention; + + @Inject + VMToNodeMetadata(Map credentialStore, FindHardwareForServer findHardwareForServer, + FindLocationForServer findLocationForServer, FindImageForServer findImageForServer, + GroupNamingConvention.Factory namingConvention) { + this.nodeNamingConvention = checkNotNull(namingConvention, "namingConvention").createWithoutPrefix(); + this.credentialStore = checkNotNull(credentialStore, "credentialStore"); + this.findHardwareForServer = checkNotNull(findHardwareForServer, "findHardwareForServer"); + this.findLocationForServer = checkNotNull(findLocationForServer, "findLocationForServer"); + this.findImageForServer = checkNotNull(findImageForServer, "findImageForServer"); + } + + @Override + public NodeMetadata apply(VM from) { + // convert the result object to a jclouds NodeMetadata + NodeMetadataBuilder builder = new NodeMetadataBuilder(); + builder.ids(from.getUuid() + ""); + builder.name(from.getAlias()); + builder.location(findLocationForServer.apply(from)); + builder.group(nodeNamingConvention.groupInUniqueNameOrNull(from.getType())); + builder.imageId(from.getType() + ""); + Image image = findImageForServer.apply(from); + if (image != null) + builder.operatingSystem(image.getOperatingSystem()); + builder.hardware(findHardwareForServer.apply(from)); + builder.status(serverStatusToNodeStatus.get(from.getState())); + try { + if( from.getState() == VM.State.RUNNING ) + { + Optional ip = from.getPublicAddress(); + if( ip.isPresent() ) { + builder.publicAddresses(ImmutableSet. of(ip.get())); + builder.privateAddresses(ImmutableSet. of(ip.get())); + } + } + } + catch(Exception ex) + { + // None? + } + //builder.privateAddresses(ImmutableSet. of(from.privateAddress)); + builder.credentials(LoginCredentials.fromCredentials(credentialStore.get(from.getUuid() + ""))); + return builder.build(); + } + + @Singleton + public static class FindHardwareForServer extends FindResourceInSet { + + @Inject + public FindHardwareForServer(@Memoized Supplier> hardware) { + super(hardware); + } + + @Override + public boolean matches(VM from, Hardware input) { + return input.getProviderId().equals(from.getUuid() + ""); + } + } + + @Singleton + public static class FindImageForServer extends FindResourceInSet { + + @Inject + public FindImageForServer(@Memoized Supplier> hardware) { + super(hardware); + } + + @Override + public boolean matches(VM from, Image input) { + return input.getProviderId().equals(from.getUuid() + ""); + } + } + + @Singleton + public static class FindLocationForServer extends FindResourceInSet { + + @Inject + public FindLocationForServer(@Memoized Supplier> hardware) { + super(hardware); + } + + @Override + public boolean matches(VM from, Location input) { + return input.getId().equals(from.getUuid() + ""); + } + } +} diff --git a/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/functions/VmSpecificationToHardware.java b/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/functions/VmSpecificationToHardware.java new file mode 100644 index 0000000000..e789367af1 --- /dev/null +++ b/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/functions/VmSpecificationToHardware.java @@ -0,0 +1,48 @@ +/** + * 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.smartos.compute.functions; + +import javax.inject.Singleton; + +import org.jclouds.smartos.compute.domain.VmSpecification; +import org.jclouds.compute.domain.Hardware; +import org.jclouds.compute.domain.HardwareBuilder; +import org.jclouds.compute.domain.Processor; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; + +/** + * @author Nigel Magnay + */ +@Singleton +public class VmSpecificationToHardware implements Function { + + @Override + public Hardware apply(VmSpecification from) { + HardwareBuilder builder = new HardwareBuilder(); + builder.ids("AnID"); + builder.name(from.getAlias()); + builder.processors(ImmutableList.of(new Processor(1, 1.0))); + builder.ram(256); + //builder.volumes(ImmutableList. of(new VolumeImpl(from.disk, true, false))); + return builder.build(); + } + +} diff --git a/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/strategy/SmartOSComputeServiceAdapter.java b/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/strategy/SmartOSComputeServiceAdapter.java new file mode 100644 index 0000000000..094ad8cffe --- /dev/null +++ b/labs/smartos-ssh/src/main/java/org/jclouds/smartos/compute/strategy/SmartOSComputeServiceAdapter.java @@ -0,0 +1,128 @@ +/** + * 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.smartos.compute.strategy; + +import static com.google.common.base.Preconditions.checkNotNull; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.jclouds.smartos.compute.domain.*; +import org.jclouds.compute.ComputeService; +import org.jclouds.compute.ComputeServiceAdapter; +import org.jclouds.compute.domain.Template; +import org.jclouds.domain.LoginCredentials; + +import com.google.common.collect.ImmutableSet; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * defines the connection between the {@link org.jclouds.smartos.compute.domain.SmartOSHost} implementation and the jclouds + * {@link ComputeService} + * + */ +@Singleton +public class SmartOSComputeServiceAdapter implements ComputeServiceAdapter { + private final SmartOSHost host; + + @Inject + public SmartOSComputeServiceAdapter(SmartOSHost host) { + this.host = checkNotNull(host, "host"); + } + + private SmartOSHost getHost() { + return host; + } + + @Override + public NodeAndInitialCredentials createNodeWithGroupEncodedIntoName(String tag, String name, Template template) { + VmSpecification specification = VmSpecification.builder() + .alias(name) + .dataset(getHost().getDataSet(UUID.fromString(template.getImage().getProviderId()))) + .nic(VmNIC.builder().simpleDCHPNic().build()) + .build(); + + VM from = getHost().createVM(specification); + + return new NodeAndInitialCredentials(from, from.getUuid() + "", LoginCredentials.builder().user("smartos") + .password("smartos").build()); + } + + @Override + public Iterable listHardwareProfiles() { + List specificationList = new ArrayList(); + + VmSpecification vs = VmSpecification.builder() + .alias("Standard Joyent VM") + .nic(VmNIC.builder().simpleDCHPNic().build()) + .build(); + + specificationList.add(vs); + + return specificationList; + } + + @Override + public Iterable listImages() { + return getHost().getLocalDatasets(); + } + + @Override + public DataSet getImage(String id) { + return getHost().getDataSet(UUID.fromString(id)); + } + + @Override + public Iterable listNodes() { + return getHost().getVMs(); + } + + @Override + public Iterable listLocations() { + return ImmutableSet.of(); + } + + @Override + public VM getNode(String id) { + return getHost().getVM(UUID.fromString(id)); + } + + @Override + public void destroyNode(String id) { + getHost().getVM(UUID.fromString(id)).destroy(); + } + + @Override + public void rebootNode(String id) { + getHost().getVM(UUID.fromString(id)).reboot(); + } + + @Override + public void resumeNode(String id) { + getHost().getVM(UUID.fromString(id)).start(); + } + + @Override + public void suspendNode(String id) { + getHost().getVM(UUID.fromString(id)).stop(); + } +} \ No newline at end of file diff --git a/labs/smartos-ssh/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata b/labs/smartos-ssh/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata new file mode 100644 index 0000000000..04d5659a80 --- /dev/null +++ b/labs/smartos-ssh/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata @@ -0,0 +1 @@ +org.jclouds.smartos.SmartOSApiMetadata \ No newline at end of file diff --git a/labs/smartos-ssh/src/test/java/org/jclouds/smartos/compute/SmartOSManagerComputeServiceContextBuilderTest.java b/labs/smartos-ssh/src/test/java/org/jclouds/smartos/compute/SmartOSManagerComputeServiceContextBuilderTest.java new file mode 100644 index 0000000000..fe91d429d6 --- /dev/null +++ b/labs/smartos-ssh/src/test/java/org/jclouds/smartos/compute/SmartOSManagerComputeServiceContextBuilderTest.java @@ -0,0 +1,88 @@ +/** + * 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.smartos.compute; + +import static org.testng.Assert.assertEquals; + +import java.util.Properties; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.Module; +import org.jclouds.ContextBuilder; +import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.internal.ContextImpl; +import org.jclouds.rest.internal.BaseRestClientTest; +import org.jclouds.smartos.SmartOSApiMetadata; +import org.jclouds.sshj.config.SshjSshClientModule; +import org.testng.annotations.Test; + +/** + * + * @author Adrian Cole + * + */ +@Test(groups = "unit", testName = "ServerManagerContextBuilderTest") +public class SmartOSManagerComputeServiceContextBuilderTest { + + + @Test + public void testCanBuildWithApiMetadata() { + ComputeServiceContext context = ContextBuilder.newBuilder( + new SmartOSApiMetadata()) + .modules(ImmutableSet.of(getSshModule())) + .build(ComputeServiceContext.class); + context.close(); + } + + @Test + public void testCanBuildById() { + ComputeServiceContext context = ContextBuilder.newBuilder("smartos-ssh") + .modules(ImmutableSet.of(getSshModule())) + .build(ComputeServiceContext.class); + context.close(); + } + + @Test + public void testCanBuildWithOverridingProperties() { + Properties overrides = new Properties(); + overrides.setProperty("smartos-ssh.endpoint", "http://host"); + overrides.setProperty("smartos-ssh.api-version", "1"); + + ComputeServiceContext context = ContextBuilder.newBuilder("smartos-ssh") + .modules(ImmutableSet.of(getSshModule())) + .overrides(overrides).build(ComputeServiceContext.class); + + context.close(); + } + + @Test + public void testUnwrapIsCorrectType() { + ComputeServiceContext context = ContextBuilder.newBuilder("smartos-ssh") + .modules(ImmutableSet.of(getSshModule())) + .build(ComputeServiceContext.class); + + assertEquals(context.unwrap().getClass(), ContextImpl.class); + + context.close(); + } + + protected Module getSshModule() { + return new SshjSshClientModule(); + } +} diff --git a/labs/smartos-ssh/src/test/java/org/jclouds/smartos/compute/SmartOSManagerComputeServiceLiveTest.java b/labs/smartos-ssh/src/test/java/org/jclouds/smartos/compute/SmartOSManagerComputeServiceLiveTest.java new file mode 100644 index 0000000000..df29de6968 --- /dev/null +++ b/labs/smartos-ssh/src/test/java/org/jclouds/smartos/compute/SmartOSManagerComputeServiceLiveTest.java @@ -0,0 +1,60 @@ +/** + * 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.smartos.compute; + +import static org.jclouds.compute.util.ComputeServiceUtils.getCores; +import static org.testng.Assert.assertEquals; + +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.OsFamily; +import org.jclouds.compute.domain.Template; +import org.jclouds.compute.internal.BaseComputeServiceLiveTest; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMap; + +/** + * @author Adrian Cole + */ +@Test(groups = "live", enabled = true, singleThreaded = true) +public class SmartOSManagerComputeServiceLiveTest extends BaseComputeServiceLiveTest { + + public SmartOSManagerComputeServiceLiveTest() { + provider = "smartos-ssh"; + } + + @Test + public void testTemplateBuilder() { + Template defaultTemplate = client.templateBuilder().build(); + assertEquals(defaultTemplate.getImage().getOperatingSystem().is64Bit(), true); + assertEquals(defaultTemplate.getImage().getOperatingSystem().getVersion(), "5.3"); + assertEquals(defaultTemplate.getImage().getOperatingSystem().getFamily(), OsFamily.CENTOS); + assertEquals(defaultTemplate.getLocation().getId(), "1"); + assertEquals(getCores(defaultTemplate.getHardware()), 0.5d); + } + + + // smartos-ssh does not support metadata + @Override + protected void checkUserMetadataInNodeEquals(NodeMetadata node, ImmutableMap userMetadata) { + assert node.getUserMetadata().equals(ImmutableMap. of()) : String.format( + "node userMetadata did not match %s %s", userMetadata, node); + } + +} diff --git a/labs/smartos-ssh/src/test/java/org/jclouds/smartos/compute/domain/DataSetTest.java b/labs/smartos-ssh/src/test/java/org/jclouds/smartos/compute/domain/DataSetTest.java new file mode 100644 index 0000000000..a64e92ca5a --- /dev/null +++ b/labs/smartos-ssh/src/test/java/org/jclouds/smartos/compute/domain/DataSetTest.java @@ -0,0 +1,36 @@ +package org.jclouds.smartos.compute.domain; + +import com.google.common.collect.ImmutableList; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.io.*; +import java.util.List; +import java.util.UUID; + +@Test(groups = "unit", testName = "DataSetTest") +public class DataSetTest { + + @Test + public void testParse() throws IOException { + // Response from console from a 'dsadm list' + InputStream is = getClass().getResourceAsStream("dsadm-list-response.txt"); + + BufferedReader r = new BufferedReader(new InputStreamReader(is)); + String line = r.readLine(); // skip line + ImmutableList.Builder resultBuilder = ImmutableList.builder(); + while ((line = r.readLine()) != null) { + DataSet ds = DataSet.builder().fromDsadmString(line).build(); + + resultBuilder.add(ds); + } + List dataSetList = resultBuilder.build(); + + Assert.assertEquals(10, dataSetList.size()); + + Assert.assertEquals(UUID.fromString("c0ffee88-883e-47cf-80d1-ad71cc872180"), dataSetList.get(0).getUuid()); + Assert.assertEquals("nrm:nrm:realtime-jenkins:1.7", dataSetList.get(0).getUrn()); + + + } +} diff --git a/labs/smartos-ssh/src/test/java/org/jclouds/smartos/compute/domain/VMTest.java b/labs/smartos-ssh/src/test/java/org/jclouds/smartos/compute/domain/VMTest.java new file mode 100644 index 0000000000..3397c48b39 --- /dev/null +++ b/labs/smartos-ssh/src/test/java/org/jclouds/smartos/compute/domain/VMTest.java @@ -0,0 +1,38 @@ +package org.jclouds.smartos.compute.domain; + +import com.google.common.collect.ImmutableList; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.List; +import java.util.UUID; + +@Test(groups = "unit", testName = "VMTest") +public class VMTest { + + @Test + public void testParse() throws IOException { + // Response from console from a 'vmadm list -p' + InputStream is = getClass().getResourceAsStream("vmadm-list-response.txt"); + + BufferedReader r = new BufferedReader(new InputStreamReader(is)); + String line = null; + ImmutableList.Builder resultBuilder = ImmutableList.builder(); + while ((line = r.readLine()) != null) { + VM vm = VM.builder().fromVmadmString(line).build(); + + resultBuilder.add(vm); + } + List vmList = resultBuilder.build(); + + Assert.assertEquals(2, vmList.size()); + + Assert.assertEquals(UUID.fromString("60bd2ae5-4e4d-4952-88f9-1b850259d914"), vmList.get(0).getUuid()); + Assert.assertEquals(VM.State.STOPPED, vmList.get(0).getState()); + + } +} \ No newline at end of file diff --git a/labs/smartos-ssh/src/test/resources/log4j.xml b/labs/smartos-ssh/src/test/resources/log4j.xml new file mode 100644 index 0000000000..b14cdd75a3 --- /dev/null +++ b/labs/smartos-ssh/src/test/resources/log4j.xml @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/labs/smartos-ssh/src/test/resources/org/jclouds/smartos/compute/domain/dsadm-list-response.txt b/labs/smartos-ssh/src/test/resources/org/jclouds/smartos/compute/domain/dsadm-list-response.txt new file mode 100644 index 0000000000..3f7cc7302a --- /dev/null +++ b/labs/smartos-ssh/src/test/resources/org/jclouds/smartos/compute/domain/dsadm-list-response.txt @@ -0,0 +1,11 @@ +UUID OS PUBLISHED URN +c0ffee88-883e-47cf-80d1-ad71cc872180 smartos 2012-05-02 nrm:nrm:realtime-jenkins:1.7 +c0ffee99-883e-47cf-80d1-ad71cc872180 smartos 2012-05-02 nrm:nrm:realtime-jenkins:1.7 +bb5aa3d2-cc19-11e1-99e7-a752418da63a smartos 2012-05-02 nrm:nrm:jenkins-slave:1.0 +c0ffee32-883e-47cf-80d1-ad71cc872180 smartos 2012-05-02 nrm:nrm:realtime-jenkins:1.6.3 +467ca742-4873-11e1-80ea-37290b38d2eb smartos 2012-02-14 sdc:sdc:smartos64:1.5.3 +56108678-1183-11e1-83c3-ff3185a5b47f linux 2011-11-18 sdc:sdc:ubuntu10.04:0.1.0 +f9e4be48-9466-11e1-bc41-9f993f5dff36 smartos 2012-05-02 sdc:sdc:smartos64:1.6.3 +c0ffee64-883e-47cf-80d1-ad71cc872180 smartos 2012-05-02 nrm:nrm:realtime-jenkins:1.6 +a9380908-ea0e-11e0-aeee-4ba794c83c33 smartos 2011-09-28 sdc:sdc:percona:1.0.7 +c0ffee00-883e-47cf-80d1-ad71cc872180 smartos 2012-05-02 sdc:sdc:realtime-jenkins:1.6.3 diff --git a/labs/smartos-ssh/src/test/resources/org/jclouds/smartos/compute/domain/vmadm-list-response.txt b/labs/smartos-ssh/src/test/resources/org/jclouds/smartos/compute/domain/vmadm-list-response.txt new file mode 100644 index 0000000000..442d0d93ca --- /dev/null +++ b/labs/smartos-ssh/src/test/resources/org/jclouds/smartos/compute/domain/vmadm-list-response.txt @@ -0,0 +1,2 @@ +60bd2ae5-4e4d-4952-88f9-1b850259d914:OS:256:stopped: +a8799014-7680-481f-b7de-76b501dbd803:OS:256:running:instance1-3b5 diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/strategy/AWSEC2ListNodesStrategy.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/strategy/AWSEC2ListNodesStrategy.java index 240e616079..7316c750be 100644 --- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/strategy/AWSEC2ListNodesStrategy.java +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/strategy/AWSEC2ListNodesStrategy.java @@ -29,7 +29,6 @@ import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; -import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; @@ -45,6 +44,7 @@ import org.jclouds.location.Region; import com.google.common.base.Function; import com.google.common.base.Supplier; +import com.google.inject.Inject; /** * @@ -76,7 +76,7 @@ public class AWSEC2ListNodesStrategy extends EC2ListNodesStrategy { .describeSpotInstanceRequestsInRegion(from); } - }, executor, null, logger, "reservations")), spotConverter), notNull()); + }, executor, maxTime, logger, "reservations")), spotConverter), notNull()); return concat(super.pollRunningInstances(), spots); }