diff --git a/vcloud/core/src/main/java/org/jclouds/vcloud/VCloudAsyncClient.java b/vcloud/core/src/main/java/org/jclouds/vcloud/VCloudAsyncClient.java
index 911a1723d8..9b03cce20f 100644
--- a/vcloud/core/src/main/java/org/jclouds/vcloud/VCloudAsyncClient.java
+++ b/vcloud/core/src/main/java/org/jclouds/vcloud/VCloudAsyncClient.java
@@ -54,6 +54,7 @@ import org.jclouds.rest.annotations.RequestFilters;
import org.jclouds.rest.annotations.XMLResponseParser;
import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404;
import org.jclouds.rest.functions.ReturnVoidOnNotFoundOr404;
+import org.jclouds.vcloud.binders.BindCaptureVAppParamsToXmlPayload;
import org.jclouds.vcloud.binders.BindCloneVAppParamsToXmlPayload;
import org.jclouds.vcloud.binders.BindDeployVAppParamsToXmlPayload;
import org.jclouds.vcloud.binders.BindGuestCustomizationSectionToXmlPayload;
@@ -70,6 +71,7 @@ import org.jclouds.vcloud.endpoints.OrgList;
import org.jclouds.vcloud.filters.SetVCloudTokenCookie;
import org.jclouds.vcloud.functions.OrgNameCatalogNameVAppTemplateNameToEndpoint;
import org.jclouds.vcloud.functions.OrgNameVDCNameResourceEntityNameToEndpoint;
+import org.jclouds.vcloud.options.CaptureVAppOptions;
import org.jclouds.vcloud.options.CloneVAppOptions;
import org.jclouds.vcloud.options.InstantiateVAppTemplateOptions;
import org.jclouds.vcloud.xml.OrgListHandler;
@@ -172,6 +174,20 @@ public interface VCloudAsyncClient extends CommonVCloudAsyncClient {
@MapPayloadParam("newName") @ParamValidators(DnsNameValidator.class) String newName,
CloneVAppOptions... options);
+ /**
+ * @see VCloudClient#captureVAppInVDC
+ */
+ @POST
+ @Path("/action/captureVApp")
+ @Produces("application/vnd.vmware.vcloud.captureVAppParams+xml")
+ @Consumes(VAPPTEMPLATE_XML)
+ @XMLResponseParser(VAppTemplateHandler.class)
+ @MapBinder(BindCaptureVAppParamsToXmlPayload.class)
+ ListenableFuture extends VAppTemplate> captureVAppInVDC(@EndpointParam URI vdc,
+ @MapPayloadParam("vApp") URI toCapture,
+ @MapPayloadParam("templateName") @ParamValidators(DnsNameValidator.class) String templateName,
+ CaptureVAppOptions... options);
+
/**
* @see VCloudClient#findVAppInOrgVDCNamed
*/
diff --git a/vcloud/core/src/main/java/org/jclouds/vcloud/VCloudClient.java b/vcloud/core/src/main/java/org/jclouds/vcloud/VCloudClient.java
index c9612edae5..25fcfd2559 100644
--- a/vcloud/core/src/main/java/org/jclouds/vcloud/VCloudClient.java
+++ b/vcloud/core/src/main/java/org/jclouds/vcloud/VCloudClient.java
@@ -35,6 +35,7 @@ import org.jclouds.vcloud.domain.VApp;
import org.jclouds.vcloud.domain.VAppTemplate;
import org.jclouds.vcloud.domain.Vm;
import org.jclouds.vcloud.domain.ovf.OvfEnvelope;
+import org.jclouds.vcloud.options.CaptureVAppOptions;
import org.jclouds.vcloud.options.CloneVAppOptions;
import org.jclouds.vcloud.options.InstantiateVAppTemplateOptions;
@@ -67,6 +68,20 @@ public interface VCloudClient extends CommonVCloudClient {
Task cloneVAppInVDC(URI vDC, URI toClone, String newName, CloneVAppOptions... options);
+
+ /**
+ * The captureVApp request creates a vApp template from an instantiated vApp.
+ *
Note
+ * Before it can be captured, a vApp must be undeployed
+ *
+ * @param vDC
+ * @param toClone
+ * @param templateName
+ * @param options
+ * @return template in progress
+ */
+ VAppTemplate captureVAppInVDC(URI vDC, URI toClone, String templateName, CaptureVAppOptions... options);
+
VAppTemplate getVAppTemplate(URI vAppTemplate);
OvfEnvelope getOvfEnvelopeForVAppTemplate(URI vAppTemplate);
diff --git a/vcloud/core/src/main/java/org/jclouds/vcloud/binders/BindCaptureVAppParamsToXmlPayload.java b/vcloud/core/src/main/java/org/jclouds/vcloud/binders/BindCaptureVAppParamsToXmlPayload.java
new file mode 100644
index 0000000000..1788994877
--- /dev/null
+++ b/vcloud/core/src/main/java/org/jclouds/vcloud/binders/BindCaptureVAppParamsToXmlPayload.java
@@ -0,0 +1,128 @@
+/**
+ *
+ * Copyright (C) 2010 Cloud Conscious, LLC.
+ *
+ * ====================================================================
+ * Licensed 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.vcloud.binders;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static org.jclouds.vcloud.reference.VCloudConstants.PROPERTY_VCLOUD_XML_NAMESPACE;
+import static org.jclouds.vcloud.reference.VCloudConstants.PROPERTY_VCLOUD_XML_SCHEMA;
+
+import java.util.Map;
+import java.util.Properties;
+
+import javax.inject.Named;
+import javax.inject.Singleton;
+import javax.xml.parsers.FactoryConfigurationError;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerException;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.rest.MapBinder;
+import org.jclouds.rest.binders.BindToStringPayload;
+import org.jclouds.rest.internal.GeneratedHttpRequest;
+import org.jclouds.vcloud.VCloudExpressMediaType;
+import org.jclouds.vcloud.options.CaptureVAppOptions;
+
+import com.google.inject.Inject;
+import com.jamesmurty.utils.XMLBuilder;
+
+/**
+ *
+ * @author Adrian Cole
+ *
+ */
+@Singleton
+public class BindCaptureVAppParamsToXmlPayload implements MapBinder {
+
+ protected final String ns;
+ protected final String schema;
+ private final BindToStringPayload stringBinder;
+
+ @Inject
+ public BindCaptureVAppParamsToXmlPayload(BindToStringPayload stringBinder,
+ @Named(PROPERTY_VCLOUD_XML_NAMESPACE) String ns, @Named(PROPERTY_VCLOUD_XML_SCHEMA) String schema) {
+ this.ns = ns;
+ this.schema = schema;
+ this.stringBinder = stringBinder;
+ }
+
+ @SuppressWarnings("unchecked")
+ public void bindToRequest(HttpRequest request, Map postParams) {
+ checkArgument(checkNotNull(request, "request") instanceof GeneratedHttpRequest,
+ "this binder is only valid for GeneratedHttpRequests!");
+ GeneratedHttpRequest gRequest = (GeneratedHttpRequest) request;
+ checkState(gRequest.getArgs() != null, "args should be initialized at this point");
+ String templateName = checkNotNull(postParams.remove("templateName"), "templateName");
+ String vApp = checkNotNull(postParams.remove("vApp"), "vApp");
+
+ CaptureVAppOptions options = findOptionsInArgsOrNull(gRequest);
+ if (options == null) {
+ options = new CaptureVAppOptions();
+ }
+ try {
+ stringBinder.bindToRequest(request, generateXml(templateName, vApp, options));
+ } catch (ParserConfigurationException e) {
+ throw new RuntimeException(e);
+ } catch (FactoryConfigurationError e) {
+ throw new RuntimeException(e);
+ } catch (TransformerException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ protected String generateXml(String templateName, String vApp, CaptureVAppOptions options)
+ throws ParserConfigurationException, FactoryConfigurationError, TransformerException {
+ XMLBuilder rootBuilder = buildRoot(templateName);
+ if (options.getDescription() != null)
+ rootBuilder.e("Description").text(options.getDescription());
+ rootBuilder.e("Source").a("href", vApp).a("type", VCloudExpressMediaType.VAPP_XML);
+ Properties outputProperties = new Properties();
+ outputProperties.put(javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION, "yes");
+ return rootBuilder.asString(outputProperties);
+ }
+
+ protected XMLBuilder buildRoot(String name) throws ParserConfigurationException, FactoryConfigurationError {
+ XMLBuilder rootBuilder = XMLBuilder.create("CaptureVAppParams").a("name", name).a("xmlns", ns).a("xmlns:xsi",
+ "http://www.w3.org/2001/XMLSchema-instance").a("xsi:schemaLocation", ns + " " + schema);
+ return rootBuilder;
+ }
+
+ protected CaptureVAppOptions findOptionsInArgsOrNull(GeneratedHttpRequest> gRequest) {
+ for (Object arg : gRequest.getArgs()) {
+ if (arg instanceof CaptureVAppOptions) {
+ return (CaptureVAppOptions) arg;
+ } else if (arg instanceof CaptureVAppOptions[]) {
+ CaptureVAppOptions[] options = (CaptureVAppOptions[]) arg;
+ return (options.length > 0) ? options[0] : null;
+ }
+ }
+ return null;
+ }
+
+ public void bindToRequest(HttpRequest request, Object input) {
+ throw new IllegalStateException("CaptureVAppParams is needs parameters");
+ }
+
+ protected String ifNullDefaultTo(String value, String defaultValue) {
+ return value != null ? value : checkNotNull(defaultValue, "defaultValue");
+ }
+}
diff --git a/vcloud/core/src/main/java/org/jclouds/vcloud/options/CaptureVAppOptions.java b/vcloud/core/src/main/java/org/jclouds/vcloud/options/CaptureVAppOptions.java
new file mode 100644
index 0000000000..ed475d5ea0
--- /dev/null
+++ b/vcloud/core/src/main/java/org/jclouds/vcloud/options/CaptureVAppOptions.java
@@ -0,0 +1,54 @@
+/**
+ *
+ * Copyright (C) 2010 Cloud Conscious, LLC.
+ *
+ * ====================================================================
+ * Licensed 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.vcloud.options;
+
+import static com.google.common.base.Preconditions.*;
+
+/**
+ *
+ * @author Adrian Cole
+ *
+ */
+public class CaptureVAppOptions {
+
+ private String description;
+
+ public CaptureVAppOptions withDescription(String description) {
+ checkNotNull(description, "description");
+ this.description = description;
+ return this;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public static class Builder {
+
+ /**
+ * @see CaptureVAppOptions#withDescription(String)
+ */
+ public static CaptureVAppOptions withDescription(String description) {
+ CaptureVAppOptions options = new CaptureVAppOptions();
+ return options.withDescription(description);
+ }
+ }
+
+}
diff --git a/vcloud/core/src/test/java/org/jclouds/vcloud/CaptureVAppLiveTest.java b/vcloud/core/src/test/java/org/jclouds/vcloud/CaptureVAppLiveTest.java
new file mode 100644
index 0000000000..ed21c7c98b
--- /dev/null
+++ b/vcloud/core/src/test/java/org/jclouds/vcloud/CaptureVAppLiveTest.java
@@ -0,0 +1,118 @@
+/**
+ *
+ * Copyright (C) 2010 Cloud Conscious, LLC.
+ *
+ * ====================================================================
+ * Licensed 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.vcloud;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.getOnlyElement;
+
+import java.net.URI;
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+
+import org.jclouds.Constants;
+import org.jclouds.compute.ComputeService;
+import org.jclouds.compute.ComputeServiceContextFactory;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.logging.log4j.config.Log4JLoggingModule;
+import org.jclouds.predicates.RetryablePredicate;
+import org.jclouds.vcloud.domain.Task;
+import org.jclouds.vcloud.domain.VAppTemplate;
+import org.jclouds.vcloud.predicates.TaskSuccess;
+import org.testng.annotations.BeforeGroups;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Module;
+
+/**
+ *
+ * @author Adrian Cole
+ */
+@Test(groups = "live", enabled = true, sequential = true, testName = "vcloud.CaptureVAppLiveTest")
+public class CaptureVAppLiveTest {
+
+ protected String identity;
+ protected String provider;
+ protected String credential;
+ protected ComputeService client;
+ protected String tag = System.getProperty("user.name") + "cap";
+
+ protected void setupCredentials() {
+ provider = "vcloud";
+ identity = checkNotNull(System.getProperty("vcloud.identity"), "vcloud.identity");
+ credential = checkNotNull(System.getProperty("vcloud.credential"), "vcloud.credential");
+ }
+
+ @BeforeGroups(groups = { "live" })
+ public void setupClient() {
+ setupCredentials();
+ Properties props = new Properties();
+ props.setProperty(Constants.PROPERTY_TRUST_ALL_CERTS, "true");
+ props.setProperty(Constants.PROPERTY_RELAX_HOSTNAME, "true");
+ client = new ComputeServiceContextFactory().createContext(provider, identity, credential,
+ ImmutableSet. of(new Log4JLoggingModule()), props).getComputeService();
+ }
+
+ @Test
+ public void testCaptureVApp() throws Exception {
+
+ NodeMetadata node = null;
+ VAppTemplate vappTemplate = null;
+ try {
+
+ node = getOnlyElement(client.runNodesWithTag(tag, 1));
+
+ VCloudClient vcloudApi = VCloudClient.class.cast(client.getContext().getProviderSpecificContext().getApi());
+
+ Predicate taskTester = new RetryablePredicate(new TaskSuccess(vcloudApi), 600, 5, TimeUnit.SECONDS);
+
+ // I have to powerOff first
+ Task task = vcloudApi.powerOffVAppOrVm(URI.create(node.getId()));
+
+ // wait up to ten minutes per above
+ assert taskTester.apply(task.getHref()) : node;
+
+ // having a problem where the api is returning an error telling us to stop!
+
+ // // I have to undeploy first
+ // task = vcloudApi.undeployVAppOrVm(URI.create(node.getId()));
+ //
+ // // wait up to ten minutes per above
+ // assert taskTester.apply(task.getHref()) : node;
+
+ // vdc is equiv to the node's location
+ // vapp uri is the same as the node's id
+ vappTemplate = vcloudApi.captureVAppInVDC(URI.create(node.getLocation().getId()), URI.create(node.getId()),
+ tag);
+
+ task = vappTemplate.getTasks().get(0);
+
+ // wait up to ten minutes per above
+ assert taskTester.apply(task.getHref()) : vappTemplate;
+
+ // TODO implement delete vAppTemplate
+ } finally {
+ if (node != null)
+ client.destroyNode(node.getId());
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/vcloud/core/src/test/java/org/jclouds/vcloud/VCloudAsyncClientTest.java b/vcloud/core/src/test/java/org/jclouds/vcloud/VCloudAsyncClientTest.java
index b7c1dd95c5..494bfdb857 100644
--- a/vcloud/core/src/test/java/org/jclouds/vcloud/VCloudAsyncClientTest.java
+++ b/vcloud/core/src/test/java/org/jclouds/vcloud/VCloudAsyncClientTest.java
@@ -65,6 +65,7 @@ import org.jclouds.vcloud.domain.internal.VDCImpl;
import org.jclouds.vcloud.domain.network.FenceMode;
import org.jclouds.vcloud.domain.network.NetworkConfig;
import org.jclouds.vcloud.filters.SetVCloudTokenCookie;
+import org.jclouds.vcloud.options.CaptureVAppOptions;
import org.jclouds.vcloud.options.CloneVAppOptions;
import org.jclouds.vcloud.options.InstantiateVAppTemplateOptions;
import org.jclouds.vcloud.xml.CatalogHandler;
@@ -215,6 +216,50 @@ public class VCloudAsyncClientTest extends RestClientTest {
checkFilters(request);
}
+
+ public void testCaptureVAppInVDC() throws SecurityException, NoSuchMethodException, IOException {
+ Method method = VCloudAsyncClient.class.getMethod("captureVAppInVDC", URI.class, URI.class, String.class,
+ CaptureVAppOptions[].class);
+ HttpRequest request = processor.createRequest(method, URI
+ .create("https://vcenterprise.bluelock.com/api/v1.0/vdc/1"), URI
+ .create("https://vcenterprise.bluelock.com/api/v1.0/vapp/4181"), "my-template");
+
+ assertRequestLineEquals(request,
+ "POST https://vcenterprise.bluelock.com/api/v1.0/vdc/1/action/captureVApp HTTP/1.1");
+ assertNonPayloadHeadersEqual(request, "Accept: application/vnd.vmware.vcloud.vAppTemplate+xml\n");
+ assertPayloadEquals(request, Utils.toStringAndClose(getClass().getResourceAsStream("/captureVApp-default.xml")),
+ "application/vnd.vmware.vcloud.captureVAppParams+xml", false);
+
+ assertResponseParserClassEquals(method, request, ParseSax.class);
+ assertSaxResponseParserClassEquals(method, VAppTemplateHandler.class);
+ assertExceptionParserClassEquals(method, null);
+
+ checkFilters(request);
+ }
+
+ public void testCaptureVAppInVDCOptions() throws SecurityException, NoSuchMethodException, IOException {
+ Method method = VCloudAsyncClient.class.getMethod("captureVAppInVDC", URI.class, URI.class, String.class,
+ CaptureVAppOptions[].class);
+ HttpRequest request = processor.createRequest(method, URI
+ .create("https://vcenterprise.bluelock.com/api/v1.0/vdc/1"), URI
+ .create("https://vcenterprise.bluelock.com/api/v1.0/vapp/201"), "my-template",
+ new CaptureVAppOptions().withDescription("The description of the new vApp Template"));
+
+ assertRequestLineEquals(request,
+ "POST https://vcenterprise.bluelock.com/api/v1.0/vdc/1/action/captureVApp HTTP/1.1");
+ assertNonPayloadHeadersEqual(request, "Accept: application/vnd.vmware.vcloud.vAppTemplate+xml\n");
+ assertPayloadEquals(request, Utils.toStringAndClose(getClass().getResourceAsStream("/captureVApp.xml")),
+ "application/vnd.vmware.vcloud.captureVAppParams+xml", false);
+
+ assertResponseParserClassEquals(method, request, ParseSax.class);
+ assertSaxResponseParserClassEquals(method, VAppTemplateHandler.class);
+ assertExceptionParserClassEquals(method, null);
+
+ checkFilters(request);
+ }
+
+
+
public void testlistOrgs() throws SecurityException, NoSuchMethodException, IOException {
Method method = VCloudAsyncClient.class.getMethod("listOrgs");
HttpRequest request = processor.createRequest(method);
diff --git a/vcloud/core/src/test/resources/captureVApp-default.xml b/vcloud/core/src/test/resources/captureVApp-default.xml
new file mode 100644
index 0000000000..057087207f
--- /dev/null
+++ b/vcloud/core/src/test/resources/captureVApp-default.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/vcloud/core/src/test/resources/captureVApp.xml b/vcloud/core/src/test/resources/captureVApp.xml
new file mode 100644
index 0000000000..35e62d06c8
--- /dev/null
+++ b/vcloud/core/src/test/resources/captureVApp.xml
@@ -0,0 +1 @@
+The description of the new vApp Template
\ No newline at end of file