From a906f9f4ec2bbb58931076bfba118b51c9674c08 Mon Sep 17 00:00:00 2001 From: Andrew Bayer Date: Thu, 20 Jun 2013 16:59:13 -0700 Subject: [PATCH] JCLOUDS-138. Add CloudStackImageExtension support. --- ...CloudStackComputeServiceContextModule.java | 12 ++ .../extensions/CloudStackImageExtension.java | 163 ++++++++++++++ .../CloudStackImageExtensionExpectTest.java | 200 ++++++++++++++++++ .../CloudStackImageExtensionLiveTest.java | 44 ++++ ...createtemplateresponse-imageextension.json | 1 + .../resources/createtemplateresponse.json | 1 + .../listtemplatesresponse-imageextension.json | 1 + ...irtualmachinesresponse-imageextension.json | 2 + .../listvolumesreponse-imageextension.json | 2 + .../listvolumesresponse-imageextension.json | 1 + ...esponse-createtemplate-imageextension.json | 2 + ...nse-stopvirtualmachine-imageextension.json | 2 + ...virtualmachineresponse-imageextension.json | 1 + 13 files changed, 432 insertions(+) create mode 100644 apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/extensions/CloudStackImageExtension.java create mode 100644 apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/extensions/CloudStackImageExtensionExpectTest.java create mode 100644 apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/extensions/CloudStackImageExtensionLiveTest.java create mode 100644 apis/cloudstack/src/test/resources/createtemplateresponse-imageextension.json create mode 100644 apis/cloudstack/src/test/resources/createtemplateresponse.json create mode 100644 apis/cloudstack/src/test/resources/listtemplatesresponse-imageextension.json create mode 100644 apis/cloudstack/src/test/resources/listvirtualmachinesresponse-imageextension.json create mode 100644 apis/cloudstack/src/test/resources/listvolumesreponse-imageextension.json create mode 100644 apis/cloudstack/src/test/resources/listvolumesresponse-imageextension.json create mode 100644 apis/cloudstack/src/test/resources/queryasyncjobresultresponse-createtemplate-imageextension.json create mode 100644 apis/cloudstack/src/test/resources/queryasyncjobresultresponse-stopvirtualmachine-imageextension.json create mode 100644 apis/cloudstack/src/test/resources/stopvirtualmachineresponse-imageextension.json diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/config/CloudStackComputeServiceContextModule.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/config/CloudStackComputeServiceContextModule.java index 8f054cf8f9..92e0b6c4da 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/config/CloudStackComputeServiceContextModule.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/config/CloudStackComputeServiceContextModule.java @@ -31,6 +31,7 @@ import javax.inject.Singleton; import org.jclouds.cloudstack.CloudStackClient; import org.jclouds.cloudstack.compute.CloudStackComputeService; +import org.jclouds.cloudstack.compute.extensions.CloudStackImageExtension; import org.jclouds.cloudstack.compute.functions.OrphanedGroupsByZoneId; import org.jclouds.cloudstack.compute.functions.ServiceOfferingToHardware; import org.jclouds.cloudstack.compute.functions.TemplateToImage; @@ -73,6 +74,7 @@ import org.jclouds.compute.ComputeServiceAdapter; import org.jclouds.compute.config.ComputeServiceAdapterContextModule; import org.jclouds.compute.domain.NodeMetadata; import org.jclouds.compute.domain.OperatingSystem; +import org.jclouds.compute.extensions.ImageExtension; import org.jclouds.compute.options.TemplateOptions; import org.jclouds.domain.Location; import org.jclouds.rest.AuthorizationException; @@ -80,6 +82,7 @@ import org.jclouds.rest.suppliers.MemoizedRetryOnTimeOutButNotOnAuthorizationExc import com.google.common.base.Function; import com.google.common.base.Objects; +import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Supplier; import com.google.common.cache.CacheBuilder; @@ -136,6 +139,9 @@ public class CloudStackComputeServiceContextModule extends bind(new TypeLiteral, Multimap>>() { }).to(OrphanedGroupsByZoneId.class); + bind(new TypeLiteral() { + }).to(CloudStackImageExtension.class); + // to have the compute service adapter override default locations install(new LocationsFromComputeServiceAdapterModule(){}); } @@ -252,4 +258,10 @@ public class CloudStackComputeServiceContextModule extends NetworkType.ADVANCED, new AdvancedNetworkOptionsConverter(), NetworkType.BASIC, new BasicNetworkOptionsConverter()); } + + @Override + protected Optional provideImageExtension(Injector i) { + return Optional.of(i.getInstance(ImageExtension.class)); + } + } diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/extensions/CloudStackImageExtension.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/extensions/CloudStackImageExtension.java new file mode 100644 index 0000000000..c2d27eb7b1 --- /dev/null +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/extensions/CloudStackImageExtension.java @@ -0,0 +1,163 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.cloudstack.compute.extensions; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Iterables.find; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_IMAGE_AVAILABLE; +import static org.jclouds.location.predicates.LocationPredicates.idEquals; + +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicReference; + +import javax.annotation.Resource; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.jclouds.Constants; +import org.jclouds.cloudstack.CloudStackClient; +import org.jclouds.cloudstack.domain.AsyncCreateResponse; +import org.jclouds.cloudstack.domain.Template; +import org.jclouds.cloudstack.domain.TemplateMetadata; +import org.jclouds.cloudstack.domain.VirtualMachine; +import org.jclouds.cloudstack.domain.Volume; +import org.jclouds.cloudstack.options.CreateTemplateOptions; +import org.jclouds.cloudstack.options.ListVolumesOptions; +import org.jclouds.cloudstack.strategy.BlockUntilJobCompletesAndReturnResult; +import org.jclouds.collect.Memoized; +import org.jclouds.compute.domain.CloneImageTemplate; +import org.jclouds.compute.domain.Image; +import org.jclouds.compute.domain.ImageBuilder; +import org.jclouds.compute.domain.ImageTemplate; +import org.jclouds.compute.domain.ImageTemplateBuilder; +import org.jclouds.compute.domain.OperatingSystem; +import org.jclouds.compute.extensions.ImageExtension; +import org.jclouds.compute.reference.ComputeServiceConstants; +import org.jclouds.domain.Location; +import org.jclouds.logging.Logger; + +import com.google.common.base.Predicate; +import com.google.common.base.Supplier; +import com.google.common.collect.Iterables; +import com.google.common.util.concurrent.Atomics; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.UncheckedTimeoutException; + +/** + * CloudStack implementation of {@link ImageExtension} + * + * @author Andrew Bayer + * + */ +@Singleton +public class CloudStackImageExtension implements ImageExtension { + + @Resource + @Named(ComputeServiceConstants.COMPUTE_LOGGER) + protected Logger logger = Logger.NULL; + + private final CloudStackClient client; + private final ListeningExecutorService userExecutor; + private final Supplier> locations; + private final Predicate> imageAvailablePredicate; + private final BlockUntilJobCompletesAndReturnResult blockUntilJobCompletesAndReturnResult; + private final Predicate jobComplete; + + @Inject + public CloudStackImageExtension(CloudStackClient client, + @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor, + @Memoized Supplier> locations, + @Named(TIMEOUT_IMAGE_AVAILABLE) Predicate> imageAvailablePredicate, + BlockUntilJobCompletesAndReturnResult blockUntilJobCompletesAndReturnResult, + Predicate jobComplete) { + this.client = checkNotNull(client, "client"); + this.userExecutor = checkNotNull(userExecutor, "userExecutor"); + this.locations = checkNotNull(locations, "locations"); + this.imageAvailablePredicate = checkNotNull(imageAvailablePredicate, "imageAvailablePredicate"); + this.blockUntilJobCompletesAndReturnResult = checkNotNull(blockUntilJobCompletesAndReturnResult, + "blockUntilJobCompletesAndReturnResult"); + this.jobComplete = checkNotNull(jobComplete, "jobComplete"); + } + + @Override + public ImageTemplate buildImageTemplateFromNode(String name, String id) { + VirtualMachine vm = client.getVirtualMachineClient().getVirtualMachine(id); + if (vm == null) + throw new NoSuchElementException("Cannot find vm with id: " + id); + CloneImageTemplate template = new ImageTemplateBuilder.CloneImageTemplateBuilder().nodeId(id).name(name).build(); + return template; + } + + @Override + public ListenableFuture createImage(ImageTemplate template) { + checkState(template instanceof CloneImageTemplate, + " cloudstack only currently supports creating images through cloning."); + CloneImageTemplate cloneTemplate = (CloneImageTemplate) template; + + VirtualMachine vm = client.getVirtualMachineClient().getVirtualMachine(cloneTemplate.getSourceNodeId()); + String stopJob = client.getVirtualMachineClient().stopVirtualMachine(vm.getId()); + jobComplete.apply(stopJob); + + Set volumes = client.getVolumeClient().listVolumes(ListVolumesOptions.Builder.virtualMachineId(vm.getId())); + Volume volume = Iterables.getOnlyElement(volumes); + + CreateTemplateOptions options = CreateTemplateOptions.Builder.volumeId(volume.getId()); + AsyncCreateResponse templateJob = client.getTemplateClient().createTemplate(TemplateMetadata.builder() + .name(cloneTemplate.getName()) + .osTypeId(vm.getGuestOSId()) + .displayText(cloneTemplate.getName()) + .build(), options); + Template newTemplate = blockUntilJobCompletesAndReturnResult.