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 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