From 2b36a75f9fb44e84ff82ae51438347b43db225a5 Mon Sep 17 00:00:00 2001 From: Rita Zhang Date: Mon, 16 May 2016 18:55:01 -0700 Subject: [PATCH] JCLOUDS-664 Azurecompute-arm image capture userdata keyvault --- azurecompute-arm/pom.xml | 1 + .../arm/AzureComputeProviderMetadata.java | 8 +- .../compute/AzureComputeServiceAdapter.java | 103 +++--- .../AzureComputeServiceContextModule.java | 144 ++++++++- .../AzureComputeImageExtension.java | 142 +++++++++ .../functions/DeploymentToNodeMetadata.java | 56 ++-- .../functions/VMHardwareToHardware.java | 23 +- .../arm/compute/functions/VMImageToImage.java | 29 +- .../compute/options/AzureTemplateOptions.java | 223 +++++++++++++ ...faultLoginCredentialsForImageStrategy.java | 43 +++ .../CreateResourceGroupThenCreateNodes.java | 62 +++- .../arm/config/AzureComputeProperties.java | 6 + .../arm/domain/DeploymentTemplate.java | 27 +- .../arm/domain/KeyVaultReference.java | 46 +++ .../NetworkInterfaceCardProperties.java | 12 +- .../azurecompute/arm/domain/OSDisk.java | 29 +- .../arm/domain/StorageProfile.java | 1 + .../arm/domain/TemplateParameterType.java | 34 ++ .../azurecompute/arm/domain/VMDeployment.java | 5 + .../azurecompute/arm/domain/VMHardware.java | 25 +- .../azurecompute/arm/domain/VMImage.java | 21 +- .../azurecompute/arm/features/JobApi.java | 15 + .../arm/features/VirtualMachineApi.java | 25 ++ .../arm/functions/CleanupResources.java | 96 ++++-- .../arm/functions/StatusCodeParser.java | 38 +++ .../handlers/AzureComputeErrorHandler.java | 7 +- .../arm/util/DeploymentTemplateBuilder.java | 300 ++++++++++++------ .../AzureComputeServiceContextLiveTest.java | 284 ----------------- .../compute/AzureComputeServiceLiveTest.java | 64 +++- .../AzureComputeImageExtensionLiveTest.java | 89 ++++++ .../arm/features/DeploymentApiLiveTest.java | 3 +- .../DeploymentTemplateBuilderTest.java | 80 +++-- .../arm/features/JobApiMockTest.java | 23 ++ .../NetworkInterfaceCardApiMockTest.java | 3 +- .../TemplateToDeploymentTemplateLiveTest.java | 89 +++++- .../features/VirtualMachineApiLiveTest.java | 103 ++++-- .../features/VirtualMachineApiMockTest.java | 32 +- .../AbstractAzureComputeApiLiveTest.java | 15 + .../internal/BaseAzureComputeApiLiveTest.java | 10 +- .../src/test/resources/logback.xml | 82 +++++ .../test/resources/resourceDefinition.json | 22 ++ 41 files changed, 1787 insertions(+), 633 deletions(-) create mode 100644 azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeImageExtension.java create mode 100644 azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/options/AzureTemplateOptions.java create mode 100644 azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/strategy/AzurePopulateDefaultLoginCredentialsForImageStrategy.java create mode 100644 azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/KeyVaultReference.java create mode 100644 azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/TemplateParameterType.java create mode 100644 azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/functions/StatusCodeParser.java delete mode 100644 azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/AzureComputeServiceContextLiveTest.java create mode 100644 azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeImageExtensionLiveTest.java create mode 100644 azurecompute-arm/src/test/resources/logback.xml create mode 100644 azurecompute-arm/src/test/resources/resourceDefinition.json diff --git a/azurecompute-arm/pom.xml b/azurecompute-arm/pom.xml index 33251fded..5a41e44f2 100644 --- a/azurecompute-arm/pom.xml +++ b/azurecompute-arm/pom.xml @@ -156,6 +156,7 @@ test + 1 ${test.azurecompute-arm.endpoint} ${test.azurecompute-arm.api-version} diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/AzureComputeProviderMetadata.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/AzureComputeProviderMetadata.java index 4bbc5080a..460df6727 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/AzureComputeProviderMetadata.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/AzureComputeProviderMetadata.java @@ -24,6 +24,9 @@ import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.OPERATION_POLL_MAX_PERIOD; import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.TCP_RULE_FORMAT; import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.TCP_RULE_REGEXP; +import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.DEFAULT_VNET_ADDRESS_SPACE_PREFIX; +import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.DEFAULT_SUBNET_ADDRESS_PREFIX; +import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.DEFAULT_DATADISKSIZE; import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.DEFAULT_IMAGE_LOGIN; @@ -70,7 +73,10 @@ public static Properties defaultProperties() { properties.put(RESOURCE, "https://management.azure.com/"); properties.put(CREDENTIAL_TYPE, CLIENT_CREDENTIALS_SECRET.toString()); properties.put(RESOURCE_GROUP_NAME, "jcloudsgroup"); - properties.put(IMAGE_PUBLISHERS, "Microsoft.WindowsAzure.Compute, MicrosoftWindowsServer, Canonical"); + properties.put(DEFAULT_VNET_ADDRESS_SPACE_PREFIX, "10.0.0.0/16"); + properties.put(DEFAULT_SUBNET_ADDRESS_PREFIX, "10.0.0.0/24"); + properties.put(DEFAULT_DATADISKSIZE, "100"); + properties.put(IMAGE_PUBLISHERS, "Canonical,RedHat"); properties.put(DEFAULT_IMAGE_LOGIN, "jclouds:Password1!"); properties.put(TIMEOUT_NODE_TERMINATED, 60 * 10 * 1000); return properties; diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/AzureComputeServiceAdapter.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/AzureComputeServiceAdapter.java index 9a1d221a3..3d87d6d1f 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/AzureComputeServiceAdapter.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/AzureComputeServiceAdapter.java @@ -18,9 +18,11 @@ import static java.lang.String.format; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.jclouds.azurecompute.arm.compute.extensions.AzureComputeImageExtension.CUSTOM_IMAGE_PREFIX; import static org.jclouds.util.Predicates2.retry; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -40,15 +42,14 @@ import org.jclouds.azurecompute.arm.domain.Deployment; import org.jclouds.azurecompute.arm.domain.DeploymentBody; import org.jclouds.azurecompute.arm.domain.DeploymentProperties; +import org.jclouds.azurecompute.arm.domain.ResourceProviderMetaData; import org.jclouds.azurecompute.arm.domain.VMImage; import org.jclouds.azurecompute.arm.domain.VMHardware; import org.jclouds.azurecompute.arm.domain.Location; import org.jclouds.azurecompute.arm.domain.Offer; import org.jclouds.azurecompute.arm.domain.PublicIPAddress; -import org.jclouds.azurecompute.arm.domain.ResourceProviderMetaData; import org.jclouds.azurecompute.arm.domain.SKU; import org.jclouds.azurecompute.arm.domain.VMDeployment; -import org.jclouds.azurecompute.arm.domain.VMSize; import org.jclouds.azurecompute.arm.domain.VirtualMachine; import org.jclouds.azurecompute.arm.features.DeploymentApi; import org.jclouds.azurecompute.arm.features.OSImageApi; @@ -60,6 +61,8 @@ import org.jclouds.domain.LoginCredentials; import org.jclouds.logging.Logger; import org.jclouds.azurecompute.arm.functions.CleanupResources; +import org.jclouds.azurecompute.arm.domain.VMSize; +import org.jclouds.azurecompute.arm.domain.Version; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; @@ -118,15 +121,13 @@ public NodeAndInitialCredentials createNodeWithGroupEncodedIntoNam logger.debug("Deployment created with name: %s group: %s", name, group); - final Set deployments = Sets.newHashSet(); - final DeploymentApi deploymentApi = api.getDeploymentApi(group); + final DeploymentApi deploymentApi = api.getDeploymentApi(azureGroup); if (!retry(new Predicate() { @Override public boolean apply(final String name) { - Deployment deployment = deploymentApi.create(name, deploymentTemplate); if (deployment != null) { @@ -149,8 +150,18 @@ public boolean apply(final String name) { final VMDeployment deployment = deployments.iterator().next(); - return new NodeAndInitialCredentials(deployment, name, - LoginCredentials.builder().user(loginUser).identity(loginUser).password(loginPassword).authenticateSudo(true).build()); + NodeAndInitialCredentials credential = null; + + if (template.getOptions().getPublicKey() != null){ + String privateKey = template.getOptions().getPrivateKey(); + credential = new NodeAndInitialCredentials(deployment, name, + LoginCredentials.builder().user(loginUser).privateKey(privateKey).authenticateSudo(true).build()); + } else { + credential = new NodeAndInitialCredentials(deployment, name, + LoginCredentials.builder().user(loginUser).password(loginPassword).authenticateSudo(true).build()); + } + + return credential; } @Override @@ -166,17 +177,17 @@ public Iterable listHardwareProfiles() { Iterable vmSizes = api.getVMSizeApi(location.name()).list(); for (VMSize vmSize : vmSizes){ - VMHardware hwProfile = new VMHardware(); - hwProfile.name = vmSize.name(); - hwProfile.numberOfCores = vmSize.numberOfCores(); - hwProfile.osDiskSizeInMB = vmSize.osDiskSizeInMB(); - hwProfile.resourceDiskSizeInMB = vmSize.resourceDiskSizeInMB(); - hwProfile.memoryInMB = vmSize.memoryInMB(); - hwProfile.maxDataDiskCount = vmSize.maxDataDiskCount(); - hwProfile.location = location.name(); + VMHardware hwProfile = VMHardware.create( + vmSize.name(), + vmSize.numberOfCores(), + vmSize.osDiskSizeInMB(), + vmSize.resourceDiskSizeInMB(), + vmSize.memoryInMB(), + vmSize.maxDataDiskCount(), + location.name(), + false); hwProfiles.add(hwProfile); } - } checkAndSetHwAvailability(hwProfiles, Sets.newHashSet(locationIds)); @@ -186,32 +197,33 @@ public Iterable listHardwareProfiles() { private void checkAndSetHwAvailability(List hwProfiles, Collection locations) { Multimap hwMap = ArrayListMultimap.create(); for (VMHardware hw : hwProfiles) { - hwMap.put(hw.name, hw.location); + hwMap.put(hw.name(), hw.location()); } - for (VMHardware hw : hwProfiles) { - hw.globallyAvailable = hwMap.get(hw.name).containsAll(locations); - } + /// TODO + // for (VMHardware hw : hwProfiles) { + // hw.globallyAvailable() = hwMap.get(hw.name()).containsAll(locations); + // } } private void getImagesFromPublisher(String publisherName, List osImagesRef, String location) { - OSImageApi osImageApi = api.getOSImageApi(location); + OSImageApi osImageApi = api.getOSImageApi(location); Iterable offerList = osImageApi.listOffers(publisherName); for (Offer offer : offerList) { Iterable skuList = osImageApi.listSKUs(publisherName, offer.name()); for (SKU sku : skuList) { - VMImage vmImage = new VMImage(); - vmImage.publisher = publisherName; - vmImage.offer = offer.name(); - vmImage.sku = sku.name(); - vmImage.location = location; - osImagesRef.add(vmImage); + Iterable versionList = osImageApi.listVersions(publisherName, offer.name(), sku.name()); + for (Version version : versionList) { + VMImage vmImage = VMImage.create(publisherName, offer.name(), sku.name(), version.name(), location, false); + osImagesRef.add(vmImage); + } } } + } private List listImagesByLocation(String location) { @@ -241,17 +253,23 @@ private void checkAndSetImageAvailability(List images, Collection map = ArrayListMultimap.create(); for (VMImage image : images) { - map.put( image.offer + "/" + image.sku, image.location); - } - - for (VMImage image : images) { - image.globallyAvailable = map.get(image.offer + "/" + image.sku).containsAll(locations); + map.put( image.offer() + "/" + image.sku(), image.location()); } + ///TODO + // for (VMImage image : images) { + // image.globallyAvailable() = map.get(image.offer() + "/" + image.sku()).containsAll(locations); + // } } @Override public VMImage getImage(final String id) { String[] fields = VMImageToImage.decodeFieldsFromUniqueId(id); + if (fields[2].startsWith(CUSTOM_IMAGE_PREFIX)) { + String storage = fields[2].substring(CUSTOM_IMAGE_PREFIX.length()); + String vhd = fields[3]; + VMImage ref = VMImage.create(CUSTOM_IMAGE_PREFIX + azureGroup, CUSTOM_IMAGE_PREFIX + storage, vhd, null, fields[0], false); + return ref; + } Iterable images = listImages(); @@ -266,6 +284,7 @@ public VMImage getImage(final String id) { @Override public Iterable listLocations() { + List locations = api.getLocationApi().list(); List resources = api.getResourceProviderApi().get("Microsoft.Compute"); @@ -286,7 +305,7 @@ public boolean apply(Location input) { } }); - return result; + return result; } private String getResourceGroupFromId(String id) { @@ -309,9 +328,15 @@ public VMDeployment getNode(final String id) { vmDeployment.deployment = deployment; List list = getIPAddresses(deployment); vmDeployment.ipAddressList = list; - VirtualMachine vm = api.getVirtualMachineApi(azureGroup).get(id); vmDeployment.virtualMachine = vm; + vmDeployment.vm = api.getVirtualMachineApi(azureGroup).getInstanceDetails(id); + if (vm != null && vm.tags() != null) { + vmDeployment.userMetaData = vm.tags(); + String tagString = vmDeployment.userMetaData.get("tags"); + List tags = Arrays.asList(tagString.split(",")); + vmDeployment.tags = tags; + } return vmDeployment; } @@ -372,9 +397,15 @@ public Iterable listNodes() { List list = getIPAddresses(d); vmDeployment.ipAddressList = list; - VirtualMachine virtualMachine = vmApi.get(d.name()); - vmDeployment.virtualMachine = virtualMachine; + VirtualMachine vm = vmApi.get(d.name()); + vmDeployment.virtualMachine = vm; + if (vm != null && vm.tags() != null) { + vmDeployment.userMetaData = vm.tags(); + String tagString = vmDeployment.userMetaData.get("tags"); + List tags = Arrays.asList(tagString.split(",")); + vmDeployment.tags = tags; + } vmDeployments.add(vmDeployment); } return vmDeployments; diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/config/AzureComputeServiceContextModule.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/config/AzureComputeServiceContextModule.java index 9844be42b..7df8111af 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/config/AzureComputeServiceContextModule.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/config/AzureComputeServiceContextModule.java @@ -16,34 +16,56 @@ */ package org.jclouds.azurecompute.arm.compute.config; +import javax.annotation.Resource; import javax.inject.Named; import javax.inject.Singleton; + import com.google.inject.Provides; import org.jclouds.azurecompute.arm.compute.AzureComputeServiceAdapter; +import org.jclouds.azurecompute.arm.compute.extensions.AzureComputeImageExtension; import org.jclouds.azurecompute.arm.compute.functions.VMImageToImage; import org.jclouds.azurecompute.arm.compute.functions.DeploymentToNodeMetadata; import org.jclouds.azurecompute.arm.compute.functions.VMHardwareToHardware; import org.jclouds.azurecompute.arm.compute.functions.LocationToLocation; +import org.jclouds.azurecompute.arm.compute.strategy.AzurePopulateDefaultLoginCredentialsForImageStrategy; +import org.jclouds.azurecompute.arm.compute.options.AzureTemplateOptions; +import org.jclouds.azurecompute.arm.domain.ResourceDefinition; import org.jclouds.azurecompute.arm.domain.VMDeployment; import org.jclouds.azurecompute.arm.domain.VMHardware; import org.jclouds.azurecompute.arm.domain.VMImage; import org.jclouds.azurecompute.arm.domain.Location; import org.jclouds.azurecompute.arm.compute.strategy.CreateResourceGroupThenCreateNodes; import org.jclouds.azurecompute.arm.AzureComputeApi; +import org.jclouds.azurecompute.arm.domain.VirtualMachineInstance; import org.jclouds.azurecompute.arm.functions.ParseJobStatus; -import org.jclouds.azurecompute.arm.compute.AzureComputeService; - +import org.jclouds.compute.options.TemplateOptions; import org.jclouds.compute.ComputeServiceAdapter; import org.jclouds.compute.config.ComputeServiceAdapterContextModule; import org.jclouds.compute.domain.Hardware; import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.reference.ComputeServiceConstants; import org.jclouds.compute.strategy.CreateNodesInGroupThenAddToSet; import org.jclouds.compute.reference.ComputeServiceConstants.Timeouts; import org.jclouds.compute.reference.ComputeServiceConstants.PollPeriod; -import org.jclouds.compute.ComputeService; + +import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.OPERATION_TIMEOUT; +import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.OPERATION_POLL_INITIAL_PERIOD; +import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.OPERATION_POLL_MAX_PERIOD; +import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.TCP_RULE_FORMAT; +import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.TCP_RULE_REGEXP; +import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.RESOURCE_GROUP_NAME; +import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.IMAGE_PUBLISHERS; +import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.DEFAULT_IMAGE_LOGIN; +import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.DEFAULT_VNET_ADDRESS_SPACE_PREFIX; +import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.DEFAULT_SUBNET_ADDRESS_PREFIX; +import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.TIMEOUT_RESOURCE_DELETED; +import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.DEFAULT_DATADISKSIZE; + +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED; import static org.jclouds.util.Predicates2.retry; import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_IMAGE_AVAILABLE; import com.google.common.base.Function; import com.google.inject.Inject; @@ -52,21 +74,22 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.VisibleForTesting; import java.net.URI; +import java.util.List; -import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.IMAGE_PUBLISHERS; -import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.RESOURCE_GROUP_NAME; -import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.OPERATION_TIMEOUT; -import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.OPERATION_POLL_INITIAL_PERIOD; -import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.OPERATION_POLL_MAX_PERIOD; -import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.TCP_RULE_FORMAT; -import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.TCP_RULE_REGEXP; -import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.DEFAULT_IMAGE_LOGIN; -import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.TIMEOUT_RESOURCE_DELETED; +import org.jclouds.compute.extensions.ImageExtension; +import org.jclouds.compute.strategy.PopulateDefaultLoginCredentialsForImageStrategy; +import org.jclouds.azurecompute.arm.compute.AzureComputeService; +import org.jclouds.compute.ComputeService; +import org.jclouds.logging.Logger; public class AzureComputeServiceContextModule extends ComputeServiceAdapterContextModule { + @Resource + @Named(ComputeServiceConstants.COMPUTE_LOGGER) + protected Logger logger = Logger.NULL; + @Override protected void configure() { super.configure(); @@ -84,7 +107,12 @@ protected void configure() { install(new LocationsFromComputeServiceAdapterModule() { }); + bind(TemplateOptions.class).to(AzureTemplateOptions.class); + bind(PopulateDefaultLoginCredentialsForImageStrategy.class).to(AzurePopulateDefaultLoginCredentialsForImageStrategy.class); + //bind(TemplateOptionsToStatement.class).to(TemplateOptionsToStatementWithoutPublicKey.class); bind(CreateNodesInGroupThenAddToSet.class).to(CreateResourceGroupThenCreateNodes.class); + bind(new TypeLiteral() { + }).to(AzureComputeImageExtension.class); } @Singleton @@ -122,6 +150,18 @@ public static class AzureComputeConstants { @Inject private String azureDefaultImageLoginProperty; + @Named(DEFAULT_VNET_ADDRESS_SPACE_PREFIX) + @Inject + private String azureDefaultVnetAddressPrefixProperty; + + @Named(DEFAULT_SUBNET_ADDRESS_PREFIX) + @Inject + private String azureDefaultSubnetAddressPrefixProperty; + + @Named(DEFAULT_DATADISKSIZE) + @Inject + private String azureDefaultDataDiskSizeProperty; + public Long operationTimeout() { return Long.parseLong(operationTimeoutProperty); } @@ -138,6 +178,18 @@ public String azureDefaultImageLogin() { return azureDefaultImageLoginProperty; } + public String azureDefaultVnetAddressPrefixProperty() { + return azureDefaultVnetAddressPrefixProperty; + } + + public String azureDefaultSubnetAddressPrefixProperty() { + return azureDefaultSubnetAddressPrefixProperty; + } + + public String azureDefaultDataDiskSizeProperty() { + return azureDefaultDataDiskSizeProperty; + } + public Integer operationPollInitialPeriod() { return Integer.parseInt(operationPollInitialPeriodProperty); } @@ -158,19 +210,39 @@ public String tcpRuleRegexp() { @Provides @Named(TIMEOUT_NODE_TERMINATED) protected Predicate provideNodeTerminatedPredicate(final AzureComputeApi api, Timeouts timeouts, - PollPeriod pollPeriod) { + PollPeriod pollPeriod) { return retry(new ActionDonePredicate(api), timeouts.nodeTerminated, pollPeriod.pollInitialPeriod, pollPeriod.pollMaxPeriod); } + @Provides + @Named(TIMEOUT_IMAGE_AVAILABLE) + protected Predicate provideImageAvailablePredicate(final AzureComputeApi api, Timeouts timeouts, + PollPeriod pollPeriod) { + return retry(new ImageDonePredicate(api), timeouts.imageAvailable, pollPeriod.pollInitialPeriod, + pollPeriod.pollMaxPeriod); + } + @Provides @Named(TIMEOUT_RESOURCE_DELETED) protected Predicate provideResourceDeletedPredicate(final AzureComputeApi api, Timeouts timeouts, - PollPeriod pollPeriod) { + PollPeriod pollPeriod) { return retry(new ActionDonePredicate(api), timeouts.nodeTerminated, pollPeriod.pollInitialPeriod, pollPeriod.pollMaxPeriod); } + @Provides + @Named(TIMEOUT_NODE_SUSPENDED) + protected Predicate provideNodeSuspendedPredicate(final AzureComputeApi api, + final AzureComputeServiceContextModule.AzureComputeConstants azureComputeConstants, + Timeouts timeouts, + PollPeriod pollPeriod) { + + String azureGroup = azureComputeConstants.azureResourceGroup(); + return retry(new NodeSuspendedPredicate(api, azureGroup), timeouts.nodeSuspended, pollPeriod.pollInitialPeriod, + pollPeriod.pollMaxPeriod); + } + @VisibleForTesting static class ActionDonePredicate implements Predicate { @@ -183,9 +255,51 @@ public ActionDonePredicate(AzureComputeApi api) { @Override public boolean apply(URI uri) { checkNotNull(uri, "uri cannot be null"); - return ParseJobStatus.JobStatus.DONE == api.getJobApi().jobStatus(uri); + return (ParseJobStatus.JobStatus.DONE == api.getJobApi().jobStatus(uri)) || (ParseJobStatus.JobStatus.NO_CONTENT == api.getJobApi().jobStatus(uri)); } } + @VisibleForTesting + static class ImageDonePredicate implements Predicate { + + private final AzureComputeApi api; + + public ImageDonePredicate(AzureComputeApi api) { + this.api = checkNotNull(api, "api must not be null"); + } + + @Override + public boolean apply(URI uri) { + checkNotNull(uri, "uri cannot be null"); + List definitions = api.getJobApi().captureStatus(uri); + return definitions != null; + } + } + + @VisibleForTesting + static class NodeSuspendedPredicate implements Predicate { + + private final AzureComputeApi api; + private final String azureGroup; + + public NodeSuspendedPredicate(AzureComputeApi api, String azureGroup) { + this.api = checkNotNull(api, "api must not be null"); + this.azureGroup = checkNotNull(azureGroup, "azuregroup must not be null"); + } + + @Override + public boolean apply(String name) { + checkNotNull(name, "name cannot be null"); + String status = ""; + List statuses = api.getVirtualMachineApi(this.azureGroup).getInstanceDetails(name).statuses(); + for (int c = 0; c < statuses.size(); c++) { + if (statuses.get(c).code().substring(0, 10).equals("PowerState")) { + status = statuses.get(c).displayStatus(); + break; + } + } + return status.equals("VM stopped"); + } + } } diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeImageExtension.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeImageExtension.java new file mode 100644 index 000000000..626f51152 --- /dev/null +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeImageExtension.java @@ -0,0 +1,142 @@ +/* + * 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.azurecompute.arm.compute.extensions; + +import com.google.common.base.Predicate; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.gson.internal.LinkedTreeMap; +import com.google.inject.Inject; +import com.google.inject.name.Named; +import org.jclouds.Constants; +import org.jclouds.azurecompute.arm.AzureComputeApi; +import org.jclouds.azurecompute.arm.compute.config.AzureComputeServiceContextModule; +import org.jclouds.azurecompute.arm.compute.functions.VMImageToImage; +import org.jclouds.azurecompute.arm.domain.ResourceDefinition; +import org.jclouds.azurecompute.arm.domain.VMImage; +import org.jclouds.azurecompute.arm.domain.VirtualMachine; +import org.jclouds.compute.domain.CloneImageTemplate; +import org.jclouds.compute.domain.Image; +import org.jclouds.compute.domain.ImageTemplate; +import org.jclouds.compute.domain.ImageTemplateBuilder; +import org.jclouds.compute.extensions.ImageExtension; +import org.jclouds.azurecompute.arm.compute.config.AzureComputeServiceContextModule.AzureComputeConstants; + +import static java.lang.String.format; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_IMAGE_AVAILABLE; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED; + +import com.google.common.util.concurrent.UncheckedTimeoutException; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; + + +public class AzureComputeImageExtension implements ImageExtension { + private final AzureComputeApi api; + private final Predicate imageAvailablePredicate; + private final Predicate nodeSuspendedPredicate; + private final AzureComputeConstants azureComputeConstants; + private final ListeningExecutorService userExecutor; + private final String group; + private final VMImageToImage imageReferenceToImage; + public static final String CONTAINER_NAME = "vhdsnew"; + public static final String CUSTOM_IMAGE_PREFIX = "#"; + + @Inject + AzureComputeImageExtension(AzureComputeApi api, + @Named(TIMEOUT_IMAGE_AVAILABLE) Predicate imageAvailablePredicate, + @Named(TIMEOUT_NODE_SUSPENDED) Predicate nodeSuspendedPredicate, + final AzureComputeServiceContextModule.AzureComputeConstants azureComputeConstants, + @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor, + VMImageToImage imageReferenceToImage) { + this.userExecutor = userExecutor; + this.group = azureComputeConstants.azureResourceGroup(); + this.imageReferenceToImage = imageReferenceToImage; + this.api = api; + this.imageAvailablePredicate = imageAvailablePredicate; + this.nodeSuspendedPredicate = nodeSuspendedPredicate; + this.azureComputeConstants = azureComputeConstants; + } + + @Override + public ImageTemplate buildImageTemplateFromNode(String name, String id) { + String imageName = name.toLowerCase(); + return new ImageTemplateBuilder.CloneImageTemplateBuilder().nodeId(id).name(imageName).build(); + } + + @Override + public ListenableFuture createImage(ImageTemplate template) { + final CloneImageTemplate cloneTemplate = (CloneImageTemplate) template; + final String id = cloneTemplate.getSourceNodeId(); + final String storageAccountName = id.replaceAll("[^A-Za-z0-9 ]", "") + "stor"; + + // VM needs to be stopped before it can be generalized + String status = ""; + api.getVirtualMachineApi(group).stop(id); + //Poll until resource is ready to be used + if (nodeSuspendedPredicate.apply(id)) { + return userExecutor.submit(new Callable() { + @Override + public Image call() throws Exception { + api.getVirtualMachineApi(group).generalize(id); + + final String[] disks = new String[2]; + URI uri = api.getVirtualMachineApi(group).capture(id, cloneTemplate.getName(), CONTAINER_NAME); + if (uri != null) { + if (imageAvailablePredicate.apply(uri)) { + List definitions = api.getJobApi().captureStatus(uri); + if (definitions != null) { + for (ResourceDefinition definition : definitions) { + LinkedTreeMap properties = (LinkedTreeMap) definition.properties(); + Object storageObject = properties.get("storageProfile"); + LinkedTreeMap properties2 = (LinkedTreeMap) storageObject; + Object osDiskObject = properties2.get("osDisk"); + LinkedTreeMap osProperties = (LinkedTreeMap) osDiskObject; + Object dataDisksObject = properties2.get("dataDisks"); + ArrayList dataProperties = (ArrayList) dataDisksObject; + LinkedTreeMap datadiskObject = (LinkedTreeMap) dataProperties.get(0); + + disks[0] = osProperties.get("name"); + disks[1] = datadiskObject.get("name"); + + VirtualMachine vm = api.getVirtualMachineApi(group).get(id); + String location = vm.location(); + final VMImage ref = VMImage.create(CUSTOM_IMAGE_PREFIX + group, CUSTOM_IMAGE_PREFIX + storageAccountName, disks[0], disks[1], location, false); + return imageReferenceToImage.apply(ref); + } + } + } + } + throw new UncheckedTimeoutException("Image was not created within the time limit: " + + cloneTemplate.getName()); + } + }); + } else { + final String illegalStateExceptionMessage = format("Node %s was not suspended within %sms.", + id, azureComputeConstants.operationTimeout()); + throw new IllegalStateException(illegalStateExceptionMessage); + } + } + + @Override + public boolean deleteImage(String id) { + return false; + } +} diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/DeploymentToNodeMetadata.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/DeploymentToNodeMetadata.java index bccc63cb5..40e09b724 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/DeploymentToNodeMetadata.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/DeploymentToNodeMetadata.java @@ -48,6 +48,7 @@ public class DeploymentToNodeMetadata implements Function { + public static final String JCLOUDS_DEFAULT_USERNAME = "root"; public static final String AZURE_LOGIN_USERNAME = DeploymentTemplateBuilder.getLoginUserUsername(); public static final String AZURE_LOGIN_PASSWORD = DeploymentTemplateBuilder.getLoginPassword(); @@ -116,6 +117,10 @@ public NodeMetadata apply(final VMDeployment from) { builder.name(deployment.name()); String group = this.nodeNamingConvention.extractGroup(deployment.name()); builder.group(group); + if (from.tags != null) + builder.tags(from.tags); + if (from.userMetaData != null) + builder.userMetadata(from.userMetaData); NodeMetadata.Status status = STATUS_TO_NODESTATUS.get(provisioningStateFromString(deployment.properties().provisioningState())); if (status == NodeMetadata.Status.RUNNING && from.vm != null && from.vm.statuses() != null) { @@ -134,9 +139,25 @@ public NodeMetadata apply(final VMDeployment from) { builder.status(status); + if (from.vm != null) { + builder.hostname(deployment.name() + "pc"); + } + Credentials credentials = credentialStore.get("node#" + from.deployment.name()); - if (credentials == null) { - credentials = new Credentials(AZURE_LOGIN_USERNAME, AZURE_LOGIN_PASSWORD); + if (credentials != null && credentials.identity.equals(JCLOUDS_DEFAULT_USERNAME)) { + credentials = new Credentials(AZURE_LOGIN_USERNAME, credentials.credential); + } + else if (credentials == null) { + String username = AZURE_LOGIN_USERNAME; + String password = AZURE_LOGIN_PASSWORD; + if (username == null) { + username = "jclouds"; + } + if (password == null) { + password = "Password1!"; + } + + credentials = new Credentials(username, password); } builder.credentials(LoginCredentials.fromCredentials(credentials)); @@ -149,7 +170,6 @@ public NodeMetadata apply(final VMDeployment from) { publicIpAddresses.add(ip.properties().ipAddress()); break; } - } if (publicIpAddresses.size() > 0) builder.publicAddresses(publicIpAddresses); @@ -171,13 +191,12 @@ public NodeMetadata apply(final VMDeployment from) { ImageReference imageReference = from.virtualMachine.properties().storageProfile().imageReference(); - VMImage vmImage = new VMImage(); - vmImage.publisher = imageReference.publisher(); - vmImage.offer = imageReference.offer(); - vmImage.sku = imageReference.sku(); - vmImage.location = locationName; - Image image = vmImageToImage.apply(vmImage); - builder.imageId(image.getId()); + if (imageReference != null) { + VMImage vmImage = VMImage.create(imageReference.publisher(), imageReference.offer(), imageReference.sku(), + imageReference.version(), locationName, false); + Image image = vmImageToImage.apply(vmImage); + builder.imageId(image.getId()); + } VMSize myVMSize = null; String vmSizeName = from.virtualMachine.properties().hardwareProfile().vmSize(); @@ -189,14 +208,15 @@ public NodeMetadata apply(final VMDeployment from) { } } - VMHardware hwProfile = new VMHardware(); - hwProfile.name = myVMSize.name(); - hwProfile.numberOfCores = myVMSize.numberOfCores(); - hwProfile.osDiskSizeInMB = myVMSize.osDiskSizeInMB(); - hwProfile.resourceDiskSizeInMB = myVMSize.resourceDiskSizeInMB(); - hwProfile.memoryInMB = myVMSize.memoryInMB(); - hwProfile.maxDataDiskCount = myVMSize.maxDataDiskCount(); - hwProfile.location = locationName; + VMHardware hwProfile = VMHardware.create( + myVMSize.name(), + myVMSize.numberOfCores(), + myVMSize.osDiskSizeInMB(), + myVMSize.resourceDiskSizeInMB(), + myVMSize.memoryInMB(), + myVMSize.maxDataDiskCount(), + locationName, + false); Hardware hardware = vmHardwareToHardware.apply(hwProfile); builder.hardware(hardware); diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/VMHardwareToHardware.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/VMHardwareToHardware.java index 51a6e5e8a..5303e2538 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/VMHardwareToHardware.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/VMHardwareToHardware.java @@ -47,30 +47,31 @@ public class VMHardwareToHardware implements Function { @Override public Hardware apply(VMHardware from) { final HardwareBuilder builder = new HardwareBuilder() - .name(from.name) - .id(from.name) - .processors(ImmutableList.of(new Processor(from.numberOfCores, 2))) - .ram(from.memoryInMB) - .location(from.globallyAvailable ? null : FluentIterable.from(locations.get()) - .firstMatch(LocationPredicates.idEquals(from.location)) + .name(from.name()) + .providerId(from.name()) + .id(from.name()) + .processors(ImmutableList.of(new Processor(from.numberOfCores(), 2))) + .ram(from.memoryInMB()) + .location(from.globallyAvailable() ? null : FluentIterable.from(locations.get()) + .firstMatch(LocationPredicates.idEquals(from.location())) .get()); // No id or providerId from Azure - if (from.resourceDiskSizeInMB != null) { + if (from.resourceDiskSizeInMB() != null) { builder.volume(new VolumeBuilder() - .size(Float.valueOf(from.resourceDiskSizeInMB)) + .size(Float.valueOf(from.resourceDiskSizeInMB())) .type(Volume.Type.LOCAL) .build()); } - if (from.osDiskSizeInMB != null) { + if (from.osDiskSizeInMB() != null) { builder.volume(new VolumeBuilder() - .size(Float.valueOf(from.osDiskSizeInMB)) + .size(Float.valueOf(from.osDiskSizeInMB())) .type(Volume.Type.LOCAL) .build()); } ImmutableMap.Builder metadata = ImmutableMap.builder(); - metadata.put("maxDataDiskCount", String.valueOf(from.maxDataDiskCount)); + metadata.put("maxDataDiskCount", String.valueOf(from.maxDataDiskCount())); builder.userMetadata(metadata.build()); return builder.build(); diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/VMImageToImage.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/VMImageToImage.java index 65a3d4b14..75bcc0e03 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/VMImageToImage.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/VMImageToImage.java @@ -17,6 +17,8 @@ package org.jclouds.azurecompute.arm.compute.functions; import static com.google.common.base.Preconditions.checkNotNull; +import static org.jclouds.azurecompute.arm.compute.functions.DeploymentToNodeMetadata.AZURE_LOGIN_PASSWORD; +import static org.jclouds.azurecompute.arm.compute.functions.DeploymentToNodeMetadata.AZURE_LOGIN_USERNAME; import com.google.common.base.Supplier; import com.google.common.collect.FluentIterable; @@ -29,7 +31,9 @@ import com.google.common.base.Function; import com.google.inject.Inject; +import org.jclouds.domain.Credentials; import org.jclouds.domain.Location; +import org.jclouds.domain.LoginCredentials; import org.jclouds.location.predicates.LocationPredicates; import java.util.Set; @@ -59,7 +63,7 @@ public class VMImageToImage implements Function { private final Supplier> locations; public static String encodeFieldsToUniqueId(VMImage imageReference){ - return (imageReference.globallyAvailable ? "global" : imageReference.location) + "/" + imageReference.publisher + "/" + imageReference.offer + "/" + imageReference.sku; + return (imageReference.globallyAvailable() ? "global" : imageReference.location()) + "/" + imageReference.publisher() + "/" + imageReference.offer() + "/" + imageReference.sku(); } public static String[] decodeFieldsFromUniqueId(final String id) { @@ -74,18 +78,19 @@ public static String[] decodeFieldsFromUniqueId(final String id) { @Override public Image apply(final VMImage image) { + Credentials credentials = new Credentials(AZURE_LOGIN_USERNAME, AZURE_LOGIN_PASSWORD); final ImageBuilder builder = new ImageBuilder() - .name(image.offer) - .description(image.sku) + .name(image.offer()) + .description(image.sku()) .status(Image.Status.AVAILABLE) - .version(image.sku) + .version(image.sku()) .id(encodeFieldsToUniqueId(image)) - .providerId(image.publisher) - .location(image.globallyAvailable ? null : FluentIterable.from(locations.get()) - .firstMatch(LocationPredicates.idEquals(image.location)) + .defaultCredentials(LoginCredentials.fromCredentials(credentials)) + .providerId(image.publisher()) + .location(image.globallyAvailable() ? null : FluentIterable.from(locations.get()) + .firstMatch(LocationPredicates.idEquals(image.location())) .get()); - final OperatingSystem.Builder osBuilder = osFamily().apply(image); return builder.operatingSystem(osBuilder.build()).build(); } @@ -94,8 +99,8 @@ public static Function osFamily() { return new Function() { @Override public OperatingSystem.Builder apply(final VMImage image) { - checkNotNull(image.offer, "offer"); - final String label = image.offer; + checkNotNull(image.offer(), "offer"); + final String label = image.offer(); OsFamily family = OsFamily.UNRECOGNIZED; if (label.contains(CENTOS)) { @@ -116,8 +121,8 @@ public OperatingSystem.Builder apply(final VMImage image) { return OperatingSystem.builder(). family(family). is64Bit(true). - description(image.sku). - version(image.sku); + description(image.sku()). + version(image.sku()); } }; } diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/options/AzureTemplateOptions.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/options/AzureTemplateOptions.java new file mode 100644 index 000000000..c5267b1db --- /dev/null +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/options/AzureTemplateOptions.java @@ -0,0 +1,223 @@ +/* + * 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.azurecompute.arm.compute.options; + +import static com.google.common.base.Objects.equal; +import org.jclouds.compute.options.TemplateOptions; +import com.google.common.base.Objects; + +/** + * Azure ARM custom options + */ +public class AzureTemplateOptions extends TemplateOptions implements Cloneable { + + + private String customData; + private String virtualNetworkAddressPrefix; + private String subnetAddressPrefix; + private String DNSLabelPrefix; + private String keyVaultIdAndSecret; + + + /** + * Custom options for the Azure ARM API + */ + public AzureTemplateOptions customData(String customData) { + this.customData = customData; + return this; + } + private String virtualNetworkName; + private String subnetId; + + /** + * Sets the CIDR block for virtual network + */ + public AzureTemplateOptions virtualNetworkAddressPrefix(String virtualNetworkAddressPrefix) { + this.virtualNetworkAddressPrefix = virtualNetworkAddressPrefix; + return this; + } + + /** + * Sets the CIDR block for subnet within virtual network + */ + public AzureTemplateOptions subnetAddressPrefix(String subnetAddressPrefix) { + this.subnetAddressPrefix = subnetAddressPrefix; + return this; + } + + /** + * Sets the DNS label prefix for public IP address. label.location.cloudapp.azure.com + */ + public AzureTemplateOptions DNSLabelPrefix(String DNSLabelPrefix) { + this.DNSLabelPrefix = DNSLabelPrefix; + return this; + } + + /** + * Sets the KeyVault id and secret separated with ":" + */ + public AzureTemplateOptions keyVaultIdAndSecret(String keyVaultIdAndSecret) { + this.keyVaultIdAndSecret = keyVaultIdAndSecret; + return this; + } + + public String getCustomData() { return customData; } + public String getVirtualNetworkAddressPrefix() { return virtualNetworkAddressPrefix; } + public String getSubnetAddressPrefix() { return subnetAddressPrefix; } + public String getDNSLabelPrefix() { return DNSLabelPrefix; } + public String getKeyVaultIdAndSecret() { return keyVaultIdAndSecret; } + public String getVirtualNetworkName() { return virtualNetworkName; } + public String getSubnetId() { return subnetId; } + + + /** + * Sets the virtual network name + */ + public AzureTemplateOptions virtualNetworkName(String virtualNetworkName) { + this.virtualNetworkName = virtualNetworkName; + return this; + } + + /** + * Sets the subnet name + */ + public AzureTemplateOptions subnetId(String subnetId) { + this.subnetId = subnetId; + return this; + } + + @Override + public AzureTemplateOptions clone() { + AzureTemplateOptions options = new AzureTemplateOptions(); + copyTo(options); + return options; + } + + @Override + public void copyTo(TemplateOptions to) { + super.copyTo(to); + if (to instanceof AzureTemplateOptions) { + AzureTemplateOptions eTo = AzureTemplateOptions.class.cast(to); + eTo.customData(customData); + eTo.virtualNetworkAddressPrefix(virtualNetworkAddressPrefix); + eTo.subnetAddressPrefix(subnetAddressPrefix); + eTo.DNSLabelPrefix(DNSLabelPrefix); + eTo.keyVaultIdAndSecret(keyVaultIdAndSecret); + eTo.virtualNetworkName(virtualNetworkName); + eTo.subnetId(subnetId); + } + } + + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), virtualNetworkAddressPrefix, subnetAddressPrefix, DNSLabelPrefix, customData, keyVaultIdAndSecret, virtualNetworkName, subnetId); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + AzureTemplateOptions other = (AzureTemplateOptions) obj; + return super.equals(other) + && equal(this.customData, other.customData) + && equal(this.virtualNetworkAddressPrefix, other.virtualNetworkAddressPrefix) + && equal(this.subnetAddressPrefix, other.subnetAddressPrefix) + && equal(this.DNSLabelPrefix, other.DNSLabelPrefix) + && equal(this.keyVaultIdAndSecret, other.keyVaultIdAndSecret) + && equal(this.virtualNetworkName, other.virtualNetworkName) + && equal(this.subnetId, other.subnetId); + } + + @Override + public Objects.ToStringHelper string() { + Objects.ToStringHelper toString = super.string().omitNullValues(); + toString.add("customData", customData); + toString.add("virtualNetworkAddressPrefix", virtualNetworkAddressPrefix); + toString.add("subnetAddressPrefix", subnetAddressPrefix); + toString.add("DNSLabelPrefix", DNSLabelPrefix); + toString.add("keyVaultIdAndSecret", keyVaultIdAndSecret); + toString.add("virtualNetworkName", virtualNetworkName); + toString.add("subnetId", subnetId); + return toString; + } + + public static class Builder { + + /** + * @see AzureTemplateOptions#customData + */ + public static AzureTemplateOptions customData(String customData) { + AzureTemplateOptions options = new AzureTemplateOptions(); + return options.customData(customData); + } + + /** + * @see AzureTemplateOptions#virtualNetworkAddressPrefix + */ + public static AzureTemplateOptions virtualNetworkAddressPrefix(String virtualNetworkAddressPrefix) { + AzureTemplateOptions options = new AzureTemplateOptions(); + return options.virtualNetworkAddressPrefix(virtualNetworkAddressPrefix); + } + + /** + * @see AzureTemplateOptions#subnetAddressPrefix + */ + public static AzureTemplateOptions subnetAddressPrefix(String subnetAddressPrefix) { + AzureTemplateOptions options = new AzureTemplateOptions(); + return options.subnetAddressPrefix(subnetAddressPrefix); + } + + /** + * @see AzureTemplateOptions#DNSLabelPrefix + */ + public static AzureTemplateOptions DNSLabelPrefix(String DNSLabelPrefix) { + AzureTemplateOptions options = new AzureTemplateOptions(); + return options.DNSLabelPrefix(DNSLabelPrefix); + } + + /** + * @see AzureTemplateOptions#keyVaultIdAndSecret + */ + public static AzureTemplateOptions keyVaultIdAndSecret(String keyVaultIdAndSecret) { + AzureTemplateOptions options = new AzureTemplateOptions(); + return options.keyVaultIdAndSecret(keyVaultIdAndSecret); + } + + /** + * @see AzureTemplateOptions#virtualNetworkName + */ + public static AzureTemplateOptions virtualNetworkName(String virtualNetworkName) { + AzureTemplateOptions options = new AzureTemplateOptions(); + return options.virtualNetworkName(virtualNetworkName); + } + + /** + * @see AzureTemplateOptions#subnetId + */ + public static AzureTemplateOptions subnetId(String subnetId) { + AzureTemplateOptions options = new AzureTemplateOptions(); + return options.subnetId(subnetId); + } + } +} diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/strategy/AzurePopulateDefaultLoginCredentialsForImageStrategy.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/strategy/AzurePopulateDefaultLoginCredentialsForImageStrategy.java new file mode 100644 index 000000000..55d1a3ce3 --- /dev/null +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/strategy/AzurePopulateDefaultLoginCredentialsForImageStrategy.java @@ -0,0 +1,43 @@ +/* + * 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.azurecompute.arm.compute.strategy; + +import org.jclouds.compute.domain.internal.ImageImpl; +import org.jclouds.compute.strategy.PopulateDefaultLoginCredentialsForImageStrategy; +import org.jclouds.domain.Credentials; +import org.jclouds.domain.LoginCredentials; + +import static org.jclouds.azurecompute.arm.compute.functions.DeploymentToNodeMetadata.AZURE_LOGIN_PASSWORD; +import static org.jclouds.azurecompute.arm.compute.functions.DeploymentToNodeMetadata.AZURE_LOGIN_USERNAME; + +public class AzurePopulateDefaultLoginCredentialsForImageStrategy implements PopulateDefaultLoginCredentialsForImageStrategy { + @Override + public LoginCredentials apply(Object o) { + ImageImpl node = (ImageImpl)o; + String username = AZURE_LOGIN_USERNAME; + String password = AZURE_LOGIN_PASSWORD; + if (username == null) { + username = "jclouds"; + } + if (password == null) { + password = "Password1!"; + } + Credentials creds = new Credentials(username, password); + LoginCredentials credentials = LoginCredentials.fromCredentials(creds); + return credentials; + } +} diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/strategy/CreateResourceGroupThenCreateNodes.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/strategy/CreateResourceGroupThenCreateNodes.java index 6900f1799..468b87c6f 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/strategy/CreateResourceGroupThenCreateNodes.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/strategy/CreateResourceGroupThenCreateNodes.java @@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkNotNull; +import java.util.Arrays; import java.util.Map; import java.util.Set; @@ -28,8 +29,13 @@ import com.google.common.collect.ImmutableMap; import org.jclouds.Constants; +import org.jclouds.azurecompute.arm.compute.config.AzureComputeServiceContextModule; +import org.jclouds.azurecompute.arm.compute.options.AzureTemplateOptions; import org.jclouds.azurecompute.arm.domain.ResourceGroup; +import org.jclouds.azurecompute.arm.domain.Subnet; import org.jclouds.azurecompute.arm.features.ResourceGroupApi; +import org.jclouds.azurecompute.arm.features.SubnetApi; +import org.jclouds.azurecompute.arm.features.VirtualNetworkApi; import org.jclouds.compute.config.CustomizationResponse; import org.jclouds.compute.domain.NodeMetadata; import org.jclouds.compute.domain.Template; @@ -40,7 +46,7 @@ import org.jclouds.compute.strategy.ListNodesStrategy; import org.jclouds.compute.strategy.impl.CreateNodesWithGroupEncodedIntoNameThenAddToSet; import org.jclouds.azurecompute.arm.AzureComputeApi; - +import org.jclouds.azurecompute.arm.domain.VirtualNetwork; import org.jclouds.logging.Logger; import com.google.common.collect.Multimap; import com.google.common.util.concurrent.ListenableFuture; @@ -54,6 +60,7 @@ public class CreateResourceGroupThenCreateNodes extends CreateNodesWithGroupEnco protected Logger logger = Logger.NULL; private final AzureComputeApi api; + private final AzureComputeServiceContextModule.AzureComputeConstants azureComputeConstants; @Inject protected CreateResourceGroupThenCreateNodes( @@ -62,11 +69,12 @@ protected CreateResourceGroupThenCreateNodes( GroupNamingConvention.Factory namingConvention, @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor, CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory, - AzureComputeApi api) { + AzureComputeApi api, AzureComputeServiceContextModule.AzureComputeConstants azureComputeConstants) { super(addNodeWithGroupStrategy, listNodesStrategy, namingConvention, userExecutor, customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory); this.api = checkNotNull(api, "api cannot be null"); checkNotNull(userExecutor, "userExecutor cannot be null"); + this.azureComputeConstants = azureComputeConstants; } @Override @@ -74,23 +82,59 @@ public Map> execute(String group, int count, Template Set goodNodes, Map badNodes, Multimap customizationResponses) { + String azureGroupName = this.azureComputeConstants.azureResourceGroup(); + + AzureTemplateOptions options = template.getOptions().as(AzureTemplateOptions.class); + // create resource group for jclouds group if it does not already exist ResourceGroupApi resourceGroupApi = api.getResourceGroupApi(); - ResourceGroup resourceGroup = resourceGroupApi.get(group); + ResourceGroup resourceGroup = resourceGroupApi.get(azureGroupName); final String location = template.getLocation().getId(); - final String resourceGroupName; if (resourceGroup == null){ - final Map tags = ImmutableMap.of("description", "jClouds managed VMs"); - resourceGroupName = resourceGroupApi.create(group, location, tags).name(); - } else { - resourceGroupName = resourceGroup.name(); + resourceGroupApi.create(azureGroupName, location, tags).name(); } - Map> responses = super.execute(resourceGroupName, count, template, goodNodes, badNodes, + String vnetName = azureGroupName + "virtualnetwork"; + String subnetName = azureGroupName + "subnet"; + + if (options.getVirtualNetworkName() != null) { + vnetName = options.getVirtualNetworkName(); + } + + this.getOrCreateVirtualNetworkWithSubnet(vnetName, subnetName, location, options, azureGroupName); + + + Map> responses = super.execute(group, count, template, goodNodes, badNodes, customizationResponses); return responses; } + protected synchronized void getOrCreateVirtualNetworkWithSubnet( + final String virtualNetworkName, final String subnetName, final String location, + AzureTemplateOptions options, final String azureGroupName) { + + //Subnets belong to a virtual network so that needs to be created first + VirtualNetworkApi vnApi = api.getVirtualNetworkApi(azureGroupName); + VirtualNetwork vn = vnApi.get(virtualNetworkName); + + if (vn == null) { + VirtualNetwork.VirtualNetworkProperties virtualNetworkProperties = VirtualNetwork.VirtualNetworkProperties.builder() + .addressSpace(VirtualNetwork.AddressSpace.create(Arrays.asList(this.azureComputeConstants.azureDefaultVnetAddressPrefixProperty()))) + .subnets( + Arrays.asList( + Subnet.create(subnetName, null, null, + Subnet.SubnetProperties.builder().addressPrefix(this.azureComputeConstants.azureDefaultSubnetAddressPrefixProperty()).build()))) + .build(); + vn = vnApi.createOrUpdate(virtualNetworkName, location, virtualNetworkProperties); + } + + SubnetApi subnetApi = api.getSubnetApi(azureGroupName, virtualNetworkName); + Subnet subnet = subnetApi.get(subnetName); + + options.virtualNetworkName(virtualNetworkName); + options.subnetId(subnet.id()); + + } } diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/AzureComputeProperties.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/AzureComputeProperties.java index 48d628774..e5ef5cd56 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/AzureComputeProperties.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/AzureComputeProperties.java @@ -41,4 +41,10 @@ public class AzureComputeProperties { public static final String TIMEOUT_RESOURCE_DELETED = "jclouds.azurecompute.arm.timeout.resourcedeleted"; + public static final String DEFAULT_VNET_ADDRESS_SPACE_PREFIX = "jclouds.azurecompute.arm.vnet.addressprefix"; + + public static final String DEFAULT_SUBNET_ADDRESS_PREFIX = "jclouds.azurecompute.arm.subnet.addressprefix"; + + public static final String DEFAULT_DATADISKSIZE = "jclouds.azurecompute.arm.datadisksize"; + } diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/DeploymentTemplate.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/DeploymentTemplate.java index 848000dda..5221e053d 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/DeploymentTemplate.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/DeploymentTemplate.java @@ -31,8 +31,25 @@ public abstract class DeploymentTemplate { //Empty placeholders as we want to generate the empty JSON object @AutoValue public abstract static class Parameters { - public static Parameters create() { - return new AutoValue_DeploymentTemplate_Parameters(); + + @Nullable + public abstract KeyVaultReference publicKeyFromAzureKeyVault(); + + public static Parameters create(KeyVaultReference reference) + { + return new AutoValue_DeploymentTemplate_Parameters(reference); + } + } + + @AutoValue + public abstract static class TemplateParameters { + + @Nullable + public abstract TemplateParameterType publicKeyFromAzureKeyVault(); + + public static TemplateParameters create(TemplateParameterType publicKeyFromAzureKeyVault) + { + return new AutoValue_DeploymentTemplate_TemplateParameters(publicKeyFromAzureKeyVault); } } @@ -40,7 +57,7 @@ public static Parameters create() { public abstract String contentVersion(); - public abstract Parameters parameters(); + public abstract TemplateParameters parameters(); public abstract Map variables(); @@ -52,7 +69,7 @@ public static Parameters create() { @SerializedNames({"$schema", "contentVersion", "parameters", "variables", "resources" , "outputs"}) public static DeploymentTemplate create(final String schema, final String contentVersion, - final Parameters parameters, + final TemplateParameters parameters, final Map variables, final List resources, final List outputs) { @@ -83,7 +100,7 @@ public abstract static class Builder { public abstract Builder contentVersion(String type); - public abstract Builder parameters(Parameters parameters); + public abstract Builder parameters(TemplateParameters parameters); public abstract Builder variables(Map variables); diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/KeyVaultReference.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/KeyVaultReference.java new file mode 100644 index 000000000..2eb2f87e9 --- /dev/null +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/KeyVaultReference.java @@ -0,0 +1,46 @@ +/* + * 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.azurecompute.arm.domain; + +import com.google.auto.value.AutoValue; +import org.jclouds.json.SerializedNames; + +// Simple helper class to serialize / deserialize keyvault reference. + +@AutoValue +public abstract class KeyVaultReference { + + @AutoValue + public abstract static class Reference { + + public abstract IdReference keyVault(); + + public abstract String secretName(); + + @SerializedNames({"keyVault", "secretName"}) + public static Reference create(final IdReference keyVault, final String secretName) { + return new AutoValue_KeyVaultReference_Reference(keyVault, secretName); + } + } + + public abstract Reference reference(); + + @SerializedNames({"reference"}) + public static KeyVaultReference create(final Reference reference) { + return new AutoValue_KeyVaultReference(reference); + } +} diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/NetworkInterfaceCardProperties.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/NetworkInterfaceCardProperties.java index e6f2de7da..8b19493dd 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/NetworkInterfaceCardProperties.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/NetworkInterfaceCardProperties.java @@ -38,13 +38,17 @@ public abstract class NetworkInterfaceCardProperties { @Nullable public abstract List ipConfigurations(); - @SerializedNames({"provisioningState", "resourceGuid", "enableIPForwarding", "ipConfigurations"}) - public static NetworkInterfaceCardProperties create(final String provisioningState, final String resourceGuid, final Boolean enableIPForwarding, final List ipConfigurations) { + @Nullable + public abstract IdReference networkSecurityGroup(); + + @SerializedNames({"provisioningState", "resourceGuid", "enableIPForwarding", "ipConfigurations", "networkSecurityGroup"}) + public static NetworkInterfaceCardProperties create(final String provisioningState, final String resourceGuid, final Boolean enableIPForwarding, final List ipConfigurations, final IdReference networkSecurityGroup) { NetworkInterfaceCardProperties.Builder builder = NetworkInterfaceCardProperties.builder() .provisioningState(provisioningState) .resourceGuid(resourceGuid) .enableIPForwarding(enableIPForwarding) - .ipConfigurations(ipConfigurations == null ? null : ImmutableList.copyOf(ipConfigurations)); + .ipConfigurations(ipConfigurations == null ? null : ImmutableList.copyOf(ipConfigurations)) + .networkSecurityGroup(networkSecurityGroup); return builder.build(); } @@ -66,6 +70,8 @@ public abstract static class Builder { abstract List ipConfigurations(); + public abstract Builder networkSecurityGroup(IdReference networkSecurityGroup); + abstract NetworkInterfaceCardProperties autoBuild(); public NetworkInterfaceCardProperties build() { diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/OSDisk.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/OSDisk.java index a9f7349ce..0be43bfc4 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/OSDisk.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/OSDisk.java @@ -52,16 +52,23 @@ public abstract class OSDisk { @Nullable public abstract String createOption(); - @SerializedNames({"osType", "name", "vhd", "caching", "createOption"}) + /** + * The url of the custom image + */ + @Nullable + public abstract VHD image(); + + @SerializedNames({"osType", "name", "vhd", "caching", "createOption", "image"}) public static OSDisk create(final String osType, final String name, final VHD vhd, - final String caching, final String createOption) { + final String caching, final String createOption, final VHD image) { return builder() - .osType(osType) - .name(name) - .vhd(vhd) - .caching(caching) - .createOption(createOption) - .build(); + .osType(osType) + .name(name) + .vhd(vhd) + .caching(caching) + .createOption(createOption) + .image(image) + .build(); } public static Builder builder() { @@ -71,15 +78,11 @@ public static Builder builder() { @AutoValue.Builder public abstract static class Builder { public abstract Builder osType(String osType); - public abstract Builder name(String name); - public abstract Builder caching(String caching); - public abstract Builder createOption(String createOption); - public abstract Builder vhd(VHD vhd); - + public abstract Builder image(VHD image); public abstract OSDisk build(); } } diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/StorageProfile.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/StorageProfile.java index 7c693ef2e..bcb62ee21 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/StorageProfile.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/StorageProfile.java @@ -29,6 +29,7 @@ public abstract class StorageProfile { /** * The image reference of the storage profile */ + @Nullable public abstract ImageReference imageReference(); /** diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/TemplateParameterType.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/TemplateParameterType.java new file mode 100644 index 000000000..d0ccc7168 --- /dev/null +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/TemplateParameterType.java @@ -0,0 +1,34 @@ +/* + * 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.azurecompute.arm.domain; + +import com.google.auto.value.AutoValue; +import org.jclouds.javax.annotation.Nullable; +import org.jclouds.json.SerializedNames; + +// Simple helper class to serialize / deserialize id reference. + +@AutoValue +public abstract class TemplateParameterType { + @Nullable + public abstract String type(); + + @SerializedNames({"type"}) + public static TemplateParameterType create(final String type) { + return new AutoValue_TemplateParameterType(type); + } +} diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/VMDeployment.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/VMDeployment.java index 6909a7b49..c66394402 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/VMDeployment.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/VMDeployment.java @@ -18,6 +18,7 @@ import java.util.List; +import java.util.Map; public class VMDeployment { @@ -28,4 +29,8 @@ public class VMDeployment { public VirtualMachineInstance vm; public VirtualMachine virtualMachine; + + public Map userMetaData; + + public Iterable tags; } diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/VMHardware.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/VMHardware.java index d338327dc..f0aa77e94 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/VMHardware.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/VMHardware.java @@ -17,6 +17,7 @@ package org.jclouds.azurecompute.arm.domain; import com.google.auto.value.AutoValue; +import org.jclouds.json.SerializedNames; /** * A VM Size that is available in a region for a given subscription. @@ -24,45 +25,51 @@ * @see api */ @AutoValue -public class VMHardware { +public abstract class VMHardware { /** * The name of the VM size. */ - public String name; + public abstract String name(); /** * The number of cores that are available in the VM size. */ - public Integer numberOfCores; + public abstract Integer numberOfCores(); /** * Specifies the size in MB of the OS Disk. */ - public Integer osDiskSizeInMB; + public abstract Integer osDiskSizeInMB(); /** * The size of the resource disk. */ - public Integer resourceDiskSizeInMB; + public abstract Integer resourceDiskSizeInMB(); /** * Specifies the available RAM in MB. */ - public Integer memoryInMB; + public abstract Integer memoryInMB(); /** * Specifies the maximum number of data disks that can be attached to the VM size. */ - public Integer maxDataDiskCount; + public abstract Integer maxDataDiskCount(); /** * Specifies the location of the HW resource */ - public String location; + public abstract String location(); /** * Specifies if this HW is globally available */ - public boolean globallyAvailable; + public abstract boolean globallyAvailable(); + + @SerializedNames({ "name", "numberOfCores", "osDiskSizeInMB", "resourceDiskSizeInMB", "memoryInMB", "maxDataDiskCount", "location", "globallyAvailable"}) + public static VMHardware create(String name, Integer numberOfCores, Integer osDiskSizeInMB, Integer resourceDiskSizeInMB, Integer memoryInMB, Integer maxDataDiskCount, String location, boolean globallyAvailable) { + + return new AutoValue_VMHardware(name, numberOfCores, osDiskSizeInMB, resourceDiskSizeInMB, memoryInMB, maxDataDiskCount, location, globallyAvailable); + } } diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/VMImage.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/VMImage.java index ccfb05aef..2d4fc919d 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/VMImage.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/VMImage.java @@ -17,37 +17,44 @@ package org.jclouds.azurecompute.arm.domain; import com.google.auto.value.AutoValue; +import org.jclouds.json.SerializedNames; @AutoValue -public class VMImage { +public abstract class VMImage { /** * The publisher of the image reference. */ - public String publisher; + public abstract String publisher(); /** * The offer of the image reference. */ - public String offer; + public abstract String offer(); /** * The sku of the image reference. */ - public String sku; + public abstract String sku(); /** * The version of the image reference. */ - public String version; + public abstract String version(); /** * The location from where Image was fetched */ - public String location; + public abstract String location(); /** * Specifies if this image is globally available */ - public boolean globallyAvailable; + public abstract boolean globallyAvailable(); + + @SerializedNames({ "publisher", "offer", "sku", "version", "location", "globallyAvailable"}) + public static VMImage create(String publisher, String offer, String sku, String version, String location, boolean globallyAvailable) { + + return new AutoValue_VMImage(publisher, offer, sku, version, location, globallyAvailable); + } } diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/features/JobApi.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/features/JobApi.java index 7dd75a962..f2858d9ca 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/features/JobApi.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/features/JobApi.java @@ -17,15 +17,21 @@ package org.jclouds.azurecompute.arm.features; import java.io.Closeable; import java.net.URI; +import java.util.List; import javax.ws.rs.Consumes; import javax.ws.rs.core.MediaType; import javax.ws.rs.GET; + +import org.jclouds.Fallbacks; +import org.jclouds.azurecompute.arm.domain.ResourceDefinition; import org.jclouds.oauth.v2.filters.OAuthFilter; import org.jclouds.rest.annotations.EndpointParam; +import org.jclouds.rest.annotations.Fallback; import org.jclouds.rest.annotations.RequestFilters; import org.jclouds.rest.annotations.ResponseParser; import org.jclouds.azurecompute.arm.functions.ParseJobStatus; import org.jclouds.azurecompute.arm.functions.ParseJobStatus.JobStatus; +import org.jclouds.rest.annotations.SelectJson; /** * The Azure Resource Manager API checks for job status and progress. @@ -37,5 +43,14 @@ public interface JobApi extends Closeable{ @GET @ResponseParser(ParseJobStatus.class) JobStatus jobStatus(@EndpointParam URI jobURI); + + /** + * Get status of captured custom image after capture call + */ + @GET + @Fallback(Fallbacks.EmptyListOnNotFoundOr404.class) + @SelectJson("resources") + List captureStatus(@EndpointParam URI jobURI); + } diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/features/VirtualMachineApi.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/features/VirtualMachineApi.java index 468906485..14f3c7033 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/features/VirtualMachineApi.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/features/VirtualMachineApi.java @@ -131,5 +131,30 @@ VirtualMachine create(@PathParam("vmname") String vmname, @Path("/{name}/powerOff") void stop(@PathParam("name") String name); + /** + * Generalize the virtual machine + */ + @Named("generalize") + @POST + @Path("/{name}/generalize") + void generalize(@PathParam("name") String name); + + /** + * Capture the virtual machine image + * destinationContainerName: the name of the folder created under the "system" container in the storage account + * Folder structure: Microsoft.Computer > Images > destinationContainerName + * Within the folder, there will be 1 page blob for the osDisk vhd and 1 block blob for the vmTemplate json file + */ + @Named("capture") + @POST + @Payload("%7B\"vhdPrefix\":\"{vhdPrefix}\",\"destinationContainerName\":\"{destinationContainerName}\",\"overwriteVhds\":\"true\"%7D") + @MapBinder(BindToJsonPayload.class) + @Path("/{name}/capture") + @ResponseParser(URIParser.class) + @Fallback(Fallbacks.NullOnNotFoundOr404.class) + URI capture(@PathParam("name") String name, + @PayloadParam("vhdPrefix") String vhdPrefix, + @PayloadParam("destinationContainerName") String destinationContainerName); + } diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/functions/CleanupResources.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/functions/CleanupResources.java index 2b6a18ee3..1646aec02 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/functions/CleanupResources.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/functions/CleanupResources.java @@ -26,8 +26,15 @@ import com.google.common.base.Predicate; import org.jclouds.azurecompute.arm.AzureComputeApi; +import org.jclouds.azurecompute.arm.compute.config.AzureComputeServiceContextModule; +import org.jclouds.azurecompute.arm.domain.Deployment; +import org.jclouds.azurecompute.arm.domain.NetworkInterfaceCard; +import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroup; +import org.jclouds.azurecompute.arm.domain.PublicIPAddress; +import org.jclouds.azurecompute.arm.domain.VirtualMachine; import org.jclouds.compute.reference.ComputeServiceConstants; import org.jclouds.logging.Logger; +import org.jclouds.azurecompute.arm.domain.StorageService; import com.google.common.base.Function; @@ -36,6 +43,7 @@ @Singleton public class CleanupResources implements Function { + private final AzureComputeServiceContextModule.AzureComputeConstants azureComputeConstants; @Resource @Named(ComputeServiceConstants.COMPUTE_LOGGER) protected Logger logger = Logger.NULL; @@ -46,9 +54,10 @@ public class CleanupResources implements Function { @Inject public CleanupResources(AzureComputeApi azureComputeApi, + AzureComputeServiceContextModule.AzureComputeConstants azureComputeConstants, @Named(TIMEOUT_NODE_TERMINATED) Predicate nodeTerminated, @Named(TIMEOUT_RESOURCE_DELETED) Predicate resourceDeleted) { - + this.azureComputeConstants = azureComputeConstants; this.api = azureComputeApi; this.nodeTerminated = nodeTerminated; this.resourceDeleted = resourceDeleted; @@ -58,36 +67,67 @@ public CleanupResources(AzureComputeApi azureComputeApi, public Boolean apply(String id) { logger.debug("Destroying %s ...", id); - String storageAccountName = id.replaceAll("[^A-Za-z0-9 ]", "") + "storage"; - int index = id.lastIndexOf("-"); - String group = id.substring(0, index); - - // Delete VM - URI uri = api.getVirtualMachineApi(group).delete(id); - if (uri != null){ - boolean jobDone = nodeTerminated.apply(uri); - - if (jobDone) { - // Delete storage account - api.getStorageAccountApi(group).delete(storageAccountName); - - // Delete NIC - uri = api.getNetworkInterfaceCardApi(group).delete(id + "nic"); - if (uri != null){ - jobDone = resourceDeleted.apply(uri); - if (jobDone) { - - // Delete deployment + String storageAccountName = id.replaceAll("[^A-Za-z0-9 ]", "") + "stor"; + String group = azureComputeConstants.azureResourceGroup(); + + VirtualMachine vm = api.getVirtualMachineApi(group).get(id); + if (vm != null) { + URI uri = api.getVirtualMachineApi(group).delete(id); + if (uri != null) { + boolean jobDone = nodeTerminated.apply(uri); + boolean storageAcctDeleteStatus = false; + boolean deploymentDeleteStatus = false; + + if (jobDone) { + StorageService ss = api.getStorageAccountApi(group).get(storageAccountName); + if (ss != null) { + storageAcctDeleteStatus = api.getStorageAccountApi(group).delete(storageAccountName); + } else { + storageAcctDeleteStatus = true; + } + Deployment deployment = api.getDeploymentApi(group).get(id); + if (deployment != null) { uri = api.getDeploymentApi(group).delete(id); jobDone = resourceDeleted.apply(uri); if (jobDone) { - // Delete public ip - boolean ipDeleteStatus = api.getPublicIPAddressApi(group).delete(id + "publicip"); - - // Delete Virtual network - boolean vnetDeleteStatus = api.getVirtualNetworkApi(group).delete(group + "virtualnetwork"); - return ipDeleteStatus && vnetDeleteStatus; - + deploymentDeleteStatus = true; + } + } else { + deploymentDeleteStatus = true; + } + NetworkInterfaceCard nic = api.getNetworkInterfaceCardApi(group).get(id + "nic"); + if (nic != null) { + uri = api.getNetworkInterfaceCardApi(group).delete(id + "nic"); + if (uri != null) { + jobDone = resourceDeleted.apply(uri); + if (jobDone) { + boolean ipDeleteStatus = false; + PublicIPAddress ip = api.getPublicIPAddressApi(group).get(id + "publicip"); + if (ip != null) { + ipDeleteStatus = api.getPublicIPAddressApi(group).delete(id + "publicip"); + } else { + ipDeleteStatus = true; + } + + // Get NSG + boolean nsgDeleteStatus = false; + NetworkSecurityGroup nsg = api.getNetworkSecurityGroupApi(group).get(id + "nsg"); + if (nsg != null) { + uri = api.getNetworkSecurityGroupApi(group).delete(id + "nsg"); + jobDone = resourceDeleted.apply(uri); + if (jobDone) { + nsgDeleteStatus = true; + + } + } + else { + nsgDeleteStatus = true; + } + + return deploymentDeleteStatus && storageAcctDeleteStatus && ipDeleteStatus && nsgDeleteStatus; + } else { + return false; + } } else { return false; } diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/functions/StatusCodeParser.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/functions/StatusCodeParser.java new file mode 100644 index 000000000..4c14ec298 --- /dev/null +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/functions/StatusCodeParser.java @@ -0,0 +1,38 @@ +/* + * 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.azurecompute.arm.functions; +import com.google.common.base.Function; +import org.jclouds.http.HttpResponse; + +import javax.inject.Singleton; + +import static org.jclouds.http.HttpUtils.releasePayload; + +/** + * Parses an http response code from http responser + */ +@Singleton +public class StatusCodeParser implements Function { + public String apply(final HttpResponse from) { + releasePayload(from); + final String statusCode = Integer.toString(from.getStatusCode()); + if (statusCode != null) { + return statusCode; + } + throw new IllegalStateException("did not receive RequestId in: " + from); + } +} diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/handlers/AzureComputeErrorHandler.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/handlers/AzureComputeErrorHandler.java index d5a2d6917..653217352 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/handlers/AzureComputeErrorHandler.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/handlers/AzureComputeErrorHandler.java @@ -48,7 +48,12 @@ public void handleError(final HttpCommand command, final HttpResponse response) : message; switch (response.getStatusCode()) { case 400: - exception = new IllegalArgumentException(message, exception); + if (message.contains("unauthorized_client")) { + exception = new AuthorizationException(message, exception); + } + else { + exception = new IllegalArgumentException(message, exception); + } break; case 401: case 403: diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/util/DeploymentTemplateBuilder.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/util/DeploymentTemplateBuilder.java index ed5ec9eb9..f7850d463 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/util/DeploymentTemplateBuilder.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/util/DeploymentTemplateBuilder.java @@ -16,37 +16,42 @@ */ package org.jclouds.azurecompute.arm.util; -import com.google.common.collect.ImmutableMap; +import com.google.common.base.Strings; +import com.google.common.collect.Lists; import com.google.inject.assistedinject.Assisted; +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; import org.jclouds.azurecompute.arm.compute.config.AzureComputeServiceContextModule; -import org.jclouds.azurecompute.arm.domain.DeploymentProperties; -import org.jclouds.compute.options.TemplateOptions; +import org.jclouds.azurecompute.arm.compute.extensions.AzureComputeImageExtension; +import org.jclouds.azurecompute.arm.compute.options.AzureTemplateOptions; import org.jclouds.azurecompute.arm.domain.DataDisk; import org.jclouds.azurecompute.arm.domain.DeploymentBody; +import org.jclouds.azurecompute.arm.domain.DeploymentProperties; import org.jclouds.azurecompute.arm.domain.DeploymentTemplate; import org.jclouds.azurecompute.arm.domain.DiagnosticsProfile; import org.jclouds.azurecompute.arm.domain.DnsSettings; import org.jclouds.azurecompute.arm.domain.HardwareProfile; -import org.jclouds.azurecompute.arm.domain.IdReference; import org.jclouds.azurecompute.arm.domain.ImageReference; import org.jclouds.azurecompute.arm.domain.IpConfiguration; import org.jclouds.azurecompute.arm.domain.IpConfigurationProperties; +import org.jclouds.azurecompute.arm.domain.KeyVaultReference; import org.jclouds.azurecompute.arm.domain.NetworkInterfaceCardProperties; import org.jclouds.azurecompute.arm.domain.NetworkProfile; import org.jclouds.azurecompute.arm.domain.OSDisk; import org.jclouds.azurecompute.arm.domain.OSProfile; import org.jclouds.azurecompute.arm.domain.PublicIPAddressProperties; -import org.jclouds.azurecompute.arm.domain.ResourceDefinition; import org.jclouds.azurecompute.arm.domain.StorageProfile; import org.jclouds.azurecompute.arm.domain.StorageService; -import org.jclouds.azurecompute.arm.domain.StorageService.StorageServiceProperties; -import org.jclouds.azurecompute.arm.domain.Subnet; -import org.jclouds.azurecompute.arm.domain.Subnet.SubnetProperties; +import org.jclouds.azurecompute.arm.domain.TemplateParameterType; import org.jclouds.azurecompute.arm.domain.VHD; import org.jclouds.azurecompute.arm.domain.VirtualMachineProperties; -import org.jclouds.azurecompute.arm.domain.VirtualNetwork.VirtualNetworkProperties; -import org.jclouds.azurecompute.arm.domain.VirtualNetwork.AddressSpace; +import org.jclouds.azurecompute.arm.domain.StorageService.StorageServiceProperties; +import org.jclouds.azurecompute.arm.domain.IdReference; +import org.jclouds.azurecompute.arm.domain.ResourceDefinition; +import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroupProperties; +import org.jclouds.azurecompute.arm.domain.NetworkSecurityRule; +import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties; import org.jclouds.compute.domain.Template; import org.jclouds.json.Json; @@ -56,8 +61,10 @@ import java.util.List; import java.util.Map; +import static com.google.common.io.BaseEncoding.base64; import com.google.inject.Inject; +import static org.jclouds.azurecompute.arm.compute.extensions.AzureComputeImageExtension.CUSTOM_IMAGE_PREFIX; import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.STORAGE_API_VERSION; public class DeploymentTemplateBuilder { @@ -66,11 +73,14 @@ public interface Factory { } private final String name; + private final String azureGroup; private final String group; private final Template template; private final Json json; - private TemplateOptions options; + private AzureTemplateOptions options; + private Iterable tags; + private Map userMetaData; private List resources; private Map variables; private static String loginUser; @@ -79,10 +89,6 @@ public interface Factory { private AzureComputeServiceContextModule.AzureComputeConstants azureComputeConstants; private static final String DEPLOYMENT_MODE = "Incremental"; - private static final String DEFAULT_DATA_DISK_SIZE = "1023"; - - private static final String DEFAULT_vnAddresSpacePrefix = "10.0.0.0/16"; - private static final String DEFAULT_subnetAddressPrefix = "10.0.0.0/24"; @Inject DeploymentTemplateBuilder(Json json, @Assisted("group") String group, @Assisted("name") String name, @Assisted Template template, @@ -90,13 +96,16 @@ public interface Factory { this.name = name; this.group = group; this.template = template; - this.options = template.getOptions().as(TemplateOptions.class); + this.options = template.getOptions().as(AzureTemplateOptions.class); + this.tags = template.getOptions().getTags(); + this.userMetaData = template.getOptions().getUserMetadata(); this.variables = new HashMap(); this.resources = new ArrayList(); this.location = template.getLocation().getId(); this.json = json; this.azureComputeConstants = azureComputeConstants; + this.azureGroup = this.azureComputeConstants.azureResourceGroup(); String[] defaultLogin = this.azureComputeConstants.azureDefaultImageLogin().split(":"); String defaultUser = null; @@ -126,30 +135,53 @@ public Template getTemplate() { public DeploymentBody getDeploymentTemplate() { addStorageResource(); - addVirtualNetworkResource(); addPublicIpAddress(); + addNetworkSecurityGroup(); addNetworkInterfaceCard(); addVirtualMachine(); + + DeploymentTemplate.TemplateParameters templateParameters = null; + DeploymentTemplate.Parameters parameters = null; + + if (keyVaultInUse()){ + String[] keyVaultInfo = options.getKeyVaultIdAndSecret().split(":"); + Preconditions.checkArgument(keyVaultInfo.length == 2); + String vaultId = keyVaultInfo[0].trim(); + String secretName = keyVaultInfo[1].trim(); + + templateParameters = DeploymentTemplate.TemplateParameters.create(TemplateParameterType.create("securestring")); + parameters = DeploymentTemplate.Parameters.create(KeyVaultReference.create(KeyVaultReference.Reference.create(IdReference.create(vaultId), secretName))); + } else { + templateParameters = DeploymentTemplate.TemplateParameters.create(null); + parameters = DeploymentTemplate.Parameters.create(null); + } + + DeploymentTemplate template = DeploymentTemplate.builder() .schema("https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#") .contentVersion("1.0.0.0") .resources(resources) .variables(variables) - .parameters(DeploymentTemplate.Parameters.create()) + .parameters(templateParameters) .build(); - DeploymentBody body = DeploymentBody.create(template, DEPLOYMENT_MODE, DeploymentTemplate.Parameters.create()); + DeploymentBody body = DeploymentBody.create(template, DEPLOYMENT_MODE, parameters); return body; } - public String getDeploymentTemplateJson(DeploymentProperties properties){ + public String getDeploymentTemplateJson(DeploymentProperties properties) { return json.toJson(properties); } private void addStorageResource() { - String storageAccountName = name.replaceAll("[^A-Za-z0-9 ]", "") + "storage"; + String storageAccountName = name.replaceAll("[^A-Za-z0-9 ]", "") + "stor"; + + String storageName = template.getImage().getName(); + if (storageName.startsWith(CUSTOM_IMAGE_PREFIX)) { + storageAccountName = storageName.substring(CUSTOM_IMAGE_PREFIX.length()); // get group name + } variables.put("storageAccountName", storageAccountName); @@ -168,47 +200,13 @@ private void addStorageResource() { resources.add(storageAccount); } - private void addVirtualNetworkResource() { - String virtualNetworkName = group + "virtualnetwork"; - - String subnetName = group + "subnet"; - variables.put("virtualNetworkName", virtualNetworkName); - variables.put("virtualNetworkReference", "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]"); - variables.put("subnetName", subnetName); - variables.put("subnetReference", "[concat(variables('virtualNetworkReference'),'/subnets/',variables('subnetName'))]"); - - VirtualNetworkProperties properties = VirtualNetworkProperties.builder() - .addressSpace( - AddressSpace.create(Arrays.asList(DEFAULT_vnAddresSpacePrefix)) - ) - .subnets( - Arrays.asList( - Subnet.create("[variables('subnetName')]", null, null, - SubnetProperties.builder() - .addressPrefix(DEFAULT_subnetAddressPrefix).build() - )) - ) - .build(); - - - ResourceDefinition virtualNetwork = ResourceDefinition.builder() - .name("[variables('virtualNetworkName')]") - .type("Microsoft.Network/virtualNetworks") - .location(location) - .apiVersion(STORAGE_API_VERSION) - .properties(properties) - .build(); - - resources.add(virtualNetwork); - } - private void addPublicIpAddress() { String publicIPAddressName = name + "publicip"; - String dnsLabelPrefix = name; //TODO: read from Azure template properties + String dnsLabelPrefix = options.getDNSLabelPrefix(); PublicIPAddressProperties.Builder properties = PublicIPAddressProperties.builder(); - if (!dnsLabelPrefix.isEmpty()) { + if (!Strings.isNullOrEmpty(dnsLabelPrefix)) { properties.dnsSettings(DnsSettings.builder().domainNameLabel(dnsLabelPrefix).build()); variables.put("dnsLabelPrefix", dnsLabelPrefix); } @@ -233,7 +231,11 @@ private void addNetworkInterfaceCard() { List ipConfigurations = new ArrayList(); String ipConfigurationName = name + "ipconfig"; + String subnetId = options.getSubnetId(); + String vnetName = options.getVirtualNetworkName(); + variables.put("ipConfigurationName", ipConfigurationName); + variables.put("subnetReference", subnetId); IpConfiguration ipConfig = IpConfiguration.create(ipConfigurationName, null, null, null, IpConfigurationProperties.builder() @@ -244,9 +246,22 @@ private void addNetworkInterfaceCard() { ipConfigurations.add(ipConfig); - NetworkInterfaceCardProperties networkInterfaceCardProperties = NetworkInterfaceCardProperties.builder() - .ipConfigurations(ipConfigurations) - .build(); + // Check to see if we have defined a network security group + IdReference networkSecurityGroup = null; + int ports[] = options.getInboundPorts(); + if ((ports != null) && (ports.length > 0)) { + networkSecurityGroup = IdReference.create("[variables('networkSecurityGroupNameReference')]"); + } + + ArrayList depends = new ArrayList(Arrays.asList("[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]")); + + NetworkInterfaceCardProperties.Builder networkInterfaceCardPropertiesBuilder = NetworkInterfaceCardProperties.builder(); + networkInterfaceCardPropertiesBuilder.ipConfigurations(ipConfigurations); + if (networkSecurityGroup != null) { + networkInterfaceCardPropertiesBuilder.networkSecurityGroup(networkSecurityGroup); + depends.add("[concat('Microsoft.Network/networkSecurityGroups/', variables('networkSecurityGroupName'))]"); + } + NetworkInterfaceCardProperties networkInterfaceCardProperties = networkInterfaceCardPropertiesBuilder.build(); String networkInterfaceCardName = name + "nic"; variables.put("networkInterfaceCardName", networkInterfaceCardName); @@ -257,16 +272,58 @@ private void addNetworkInterfaceCard() { .type("Microsoft.Network/networkInterfaces") .location(location) .apiVersion(STORAGE_API_VERSION) - .dependsOn(Arrays.asList("[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]", - "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]")) + .dependsOn(depends) .properties(networkInterfaceCardProperties) .build(); resources.add(networkInterfaceCard); } - private void addVirtualMachine() { + private void addNetworkSecurityGroup() { + int ports[] = options.getInboundPorts(); + if ((ports != null) && (ports.length > 0)) { + variables.put("networkSecurityGroupName", name + "nsg"); + variables.put("networkSecurityGroupNameReference", "[resourceId('Microsoft.Network/networkSecurityGroups',variables('networkSecurityGroupName'))]"); + + List rules = new ArrayList(); + for (int i = 0; i < ports.length; i++) { + NetworkSecurityRuleProperties ruleProperties = NetworkSecurityRuleProperties.builder() + .description("default-allow-port-" + ports[i]) + .protocol(NetworkSecurityRuleProperties.Protocol.All) + .access(NetworkSecurityRuleProperties.Access.Allow) + .sourcePortRange("*") + .destinationPortRange(Integer.toString(ports[i])) + .sourceAddressPrefix("*") + .destinationAddressPrefix("*") + .priority(1234 + i) + .direction(NetworkSecurityRuleProperties.Direction.Inbound) + .build(); + + NetworkSecurityRule networkSecurityRule = NetworkSecurityRule.create( + "default-allow-port-" + ports[i], + null, + null, + ruleProperties); + + rules.add(networkSecurityRule); + } + + NetworkSecurityGroupProperties networkSecurityGroupProperties = NetworkSecurityGroupProperties.builder() + .securityRules(rules) + .build(); + + ResourceDefinition networkSecurityGroup = ResourceDefinition.builder() + .name("[variables('networkSecurityGroupName')]") + .type("Microsoft.Network/networkSecurityGroups").location(location) + .apiVersion(STORAGE_API_VERSION) + .properties(networkSecurityGroupProperties) + .build(); + resources.add(networkSecurityGroup); + } + } + + private void addVirtualMachine() { //Build OS Profile final String computerName = name + "pc"; @@ -275,51 +332,52 @@ private void addVirtualMachine() { .adminUsername(loginUser) .computerName(computerName); - boolean usePublicKey = options.getPublicKey() != null; + profileBuilder.adminPassword(loginPassword); + //boolean usePublicKey = options.getPublicKey() != null; - if (usePublicKey) { - OSProfile.LinuxConfiguration configuration = OSProfile.LinuxConfiguration.create("true", + if (keyVaultInUse()) { + OSProfile.LinuxConfiguration configuration = OSProfile.LinuxConfiguration.create("false", OSProfile.LinuxConfiguration.SSH.create(Arrays.asList( OSProfile.LinuxConfiguration.SSH.SSHPublicKey.create( "[concat('/home/',variables('loginUser'),'/.ssh/authorized_keys')]", - options.getPublicKey()) - )) - ); + "[parameters('publicKeyFromAzureKeyVault')]" + )) + )); profileBuilder.linuxConfiguration(configuration); - } else { - profileBuilder.adminPassword(loginPassword); } - OSProfile osProfile = profileBuilder.build(); - - //Build Image Reference - final String imagePublisher = template.getImage().getProviderId(); - final String imageOffer = template.getImage().getName(); - final String imageSku = template.getImage().getVersion(); + if (!Strings.isNullOrEmpty(options.getCustomData())){ + String encodedCustomData = base64().encode(options.getCustomData().getBytes()); + profileBuilder.customData(encodedCustomData); + } - ImageReference imageReference = ImageReference.builder() - .publisher(imagePublisher) - .offer(imageOffer) - .sku(imageSku) - .version("latest") - .build(); + OSProfile osProfile = profileBuilder.build(); //Build OsDisk - final String storageAccountContainerName = "vhds"; + final String storageAccountContainerName = name + "vhds"; variables.put("storageAccountContainerName", storageAccountContainerName); final String osDiskName = name + "osdisk"; variables.put("osDiskName", osDiskName); - OSDisk osDisk = OSDisk.builder() - .name("[variables('osDiskName')]") - .vhd( - VHD.create("[concat('http://',variables('storageAccountName'),'.blob.core.windows.net/',variables('storageAccountContainerName'),'/',variables('osDiskName'),'.vhd')]") - ) - .caching("ReadWrite") - .createOption("FromImage") - .build(); + boolean usingMarketplaceImage = true; + String cusotomImageUri = ""; + + // TODO: make new fields for group information + String publisher = template.getImage().getProviderId(); + String storageName = template.getImage().getName(); + String sku = template.getImage().getDescription(); // this is actual VHD + if (storageName.startsWith(CUSTOM_IMAGE_PREFIX)) { + storageName = storageName.substring(CUSTOM_IMAGE_PREFIX.length()); // get group name + cusotomImageUri = sku; + cusotomImageUri = "https://" + storageName + ".blob.core.windows.net/system/Microsoft.Compute/Images/" + AzureComputeImageExtension.CONTAINER_NAME + "/" + cusotomImageUri; + } + + if (!cusotomImageUri.isEmpty()) { + usingMarketplaceImage = false; + } + OSDisk osDisk = getOsDisk("[concat('http://',variables('storageAccountName'),'.blob.core.windows.net/',variables('storageAccountContainerName'),'/',variables('osDiskName'),'.vhd')]", cusotomImageUri); //Create Data Disk(s) and add to list final String dataDiskName = name + "datadisk"; @@ -328,7 +386,7 @@ private void addVirtualMachine() { List dataDisks = new ArrayList(); DataDisk dataDisk = DataDisk.builder() .name("[variables('dataDiskName')]") - .diskSizeGB(DEFAULT_DATA_DISK_SIZE) + .diskSizeGB(azureComputeConstants.azureDefaultDataDiskSizeProperty()) .lun(0) .vhd( VHD.create("[concat('http://',variables('storageAccountName'),'.blob.core.windows.net/',variables('storageAccountContainerName'),'/',variables('dataDiskName'),'.vhd')]") @@ -339,11 +397,20 @@ private void addVirtualMachine() { dataDisks.add(dataDisk); //Create Storage Profile - StorageProfile storageProfile = StorageProfile.builder() - .imageReference(imageReference) + StorageProfile.Builder storageProfileBuilder = StorageProfile.builder() .osDisk(osDisk) - .dataDisks(dataDisks) - .build(); + .dataDisks(dataDisks); + + if (usingMarketplaceImage) { + //Build Image Reference if marketplace image is used + ImageReference imageReference = getImageReference(template.getImage().getProviderId(), + template.getImage().getName(), + template.getImage().getVersion()); + + storageProfileBuilder.imageReference(imageReference); + } + StorageProfile storageProfile = storageProfileBuilder.build(); + //Create Network Profile for this VM (links to network interface cards) NetworkProfile networkProfile = NetworkProfile.create( @@ -370,19 +437,54 @@ private void addVirtualMachine() { .diagnosticsProfile(diagnosticsProfile) .build(); + + String tagString = Joiner.on(",").join(Lists.newArrayList(tags)); + if (tagString.isEmpty()) + tagString = "jclouds"; + userMetaData.put("tags", tagString); + variables.put("virtualMachineName", name); ResourceDefinition virtualMachine = ResourceDefinition.builder() .name("[variables('virtualMachineName')]") .type("Microsoft.Compute/virtualMachines") .location(location) - .apiVersion(STORAGE_API_VERSION) + .apiVersion("2015-06-15") .dependsOn(Arrays.asList("[concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]", "[concat('Microsoft.Network/networkInterfaces/', variables('networkInterfaceCardName'))]")) - .tags(ImmutableMap.of("displayName", "VirtualMachine")) + .tags(userMetaData) .properties(properties) .build(); resources.add(virtualMachine); } + + private ImageReference getImageReference(String publisher, String offer, String sku) { + return ImageReference.builder() + .publisher(publisher) + .offer(offer) + .sku(sku) + .version("latest") + .build(); + + } + + private OSDisk getOsDisk(String vhdUri, String imageUri) { + OSDisk.Builder builder = OSDisk.builder(); + builder.name("[variables('osDiskName')]"); + builder.caching("ReadWrite"); + builder.createOption("FromImage"); + builder.vhd(VHD.create(vhdUri)); + + if (!imageUri.isEmpty()) { + builder.osType("Linux"); + builder.image(VHD.create(imageUri)); + } + return builder.build(); + } + + private boolean keyVaultInUse(){ + return !Strings.isNullOrEmpty(options.getKeyVaultIdAndSecret()); + } + } diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/AzureComputeServiceContextLiveTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/AzureComputeServiceContextLiveTest.java deleted file mode 100644 index 940f7855c..000000000 --- a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/AzureComputeServiceContextLiveTest.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * 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.azurecompute.arm.compute; - -import com.google.common.base.Preconditions; -import com.google.common.base.Predicate; -import com.google.inject.Module; -import org.jclouds.azurecompute.arm.AzureComputeProviderMetadata; -import org.jclouds.azurecompute.arm.internal.AzureLiveTestUtils; -import org.jclouds.compute.RunNodesException; -import org.jclouds.compute.RunScriptOnNodesException; -import org.jclouds.compute.domain.ComputeMetadata; -import org.jclouds.compute.domain.ExecResponse; -import org.jclouds.compute.domain.NodeMetadata; -import org.jclouds.compute.domain.OsFamily; -import org.jclouds.compute.domain.Template; -import org.jclouds.compute.domain.TemplateBuilder; -import org.jclouds.compute.internal.BaseComputeServiceContextLiveTest; -import org.jclouds.domain.Credentials; -import org.jclouds.domain.LoginCredentials; -import org.jclouds.providers.ProviderMetadata; -import org.jclouds.sshj.config.SshjSshClientModule; -import org.testng.annotations.Test; - -import java.util.Map; -import java.util.Properties; -import java.util.Random; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -import static com.google.common.base.Preconditions.checkNotNull; -import static org.assertj.core.api.Assertions.assertThat; -import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.RESOURCE_GROUP_NAME; -import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING; -import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_SCRIPT_COMPLETE; - -import static org.jclouds.compute.predicates.NodePredicates.inGroup; -import static org.jclouds.compute.options.TemplateOptions.Builder.overrideLoginCredentials; -import static org.jclouds.scriptbuilder.domain.Statements.exec; -import static org.testng.Assert.assertTrue; - -@Test(groups = "live", testName = "AzureComputeServiceContextLiveTest") -public class AzureComputeServiceContextLiveTest extends BaseComputeServiceContextLiveTest { - - public String azureGroup; - protected static final int RAND = new Random().nextInt(999); - - @Override - protected Module getSshModule() { - return new SshjSshClientModule(); - } - - @Override protected Properties setupProperties() { - azureGroup = "jc" + RAND; - - Properties properties = super.setupProperties(); - long scriptTimeout = TimeUnit.MILLISECONDS.convert(60, TimeUnit.MINUTES); - properties.setProperty(TIMEOUT_SCRIPT_COMPLETE, scriptTimeout + ""); - properties.setProperty(TIMEOUT_NODE_RUNNING, scriptTimeout + ""); - - AzureLiveTestUtils.defaultProperties(properties); - checkNotNull(setIfTestSystemPropertyPresent(properties, "oauth.endpoint"), "test.oauth.endpoint"); - - properties.put(RESOURCE_GROUP_NAME, azureGroup); - return properties; - } - - public AzureComputeServiceContextLiveTest() { - provider = "azurecompute-arm"; - } - - @Test - public void testDefault() throws RunNodesException { - - final String groupName = this.azureGroup; - final TemplateBuilder templateBuilder = view.getComputeService().templateBuilder(); - templateBuilder.osFamily(OsFamily.UBUNTU); - templateBuilder.osVersionMatches("14.04"); - templateBuilder.hardwareId("Standard_A0"); - templateBuilder.locationId("westus"); - - final Template template = templateBuilder.build(); - - try { - Set nodes = view.getComputeService().createNodesInGroup(groupName, 1, template); - assertThat(nodes).hasSize(1); - } finally { -// Do not destroy view.getComputeService().destroyNodesMatching(inGroup(groupName)); - } - } - - private LoginCredentials getLogin() { - Credentials credentials = new Credentials("jclouds", "Password1!"); - LoginCredentials login = LoginCredentials.fromCredentials(credentials); - return login; - } - - @Test(dependsOnMethods = "testDefault") - public void testExec() throws RunScriptOnNodesException { - final String groupName = this.azureGroup; - String command = "echo hello"; - - Map responses = view.getComputeService().runScriptOnNodesMatching(// - inGroup(groupName), // predicate used to select nodes - exec(command), // what you actually intend to run - overrideLoginCredentials(getLogin()) // use my local user & - // ssh key - .runAsRoot(false) // don't attempt to run as root (sudo) - .wrapInInitScript(false)); // run command directly - - assertTrue(responses.size() > 0); - } - - public static Predicate nameStartsWith(final String prefix) { - Preconditions.checkNotNull(prefix, "prefix must be defined"); - - return new Predicate() { - @Override - public boolean apply(ComputeMetadata computeMetadata) { - return computeMetadata.getName().startsWith(prefix); - } - - @Override - public String toString() { - return "nameStartsWith(" + prefix + ")"; - } - }; - } - - @Test(dependsOnMethods = "testExec") - public void testStop() throws RunScriptOnNodesException { - final String groupName = this.azureGroup; - Set nodes = view.getComputeService().suspendNodesMatching(inGroup(groupName)); - assertTrue(nodes.size() > 0); - - boolean allStopped = false; - while (!allStopped) { - nodes = view.getComputeService().listNodesDetailsMatching(nameStartsWith(groupName)); - for (NodeMetadata node : nodes) { - if (node.getStatus() != NodeMetadata.Status.SUSPENDED) - { - // Not stopped yet - allStopped = false; - try { - Thread.sleep(15 * 1000); - } catch (InterruptedException e) { - } - continue; - } - else - { - allStopped = true; - } - } - } - assertTrue(allStopped); - } - - @Test(dependsOnMethods = "testStop") - public void testStart() throws RunScriptOnNodesException { - final String groupName = this.azureGroup; - Set nodes = view.getComputeService().resumeNodesMatching(inGroup(groupName)); - assertTrue(nodes.size() > 0); - - boolean allStarted = false; - while (!allStarted) { - nodes = view.getComputeService().listNodesDetailsMatching(nameStartsWith(groupName)); - for (NodeMetadata node : nodes) { - if (node.getStatus() != NodeMetadata.Status.RUNNING) - { - // Not started yet - allStarted = false; - try { - Thread.sleep(15 * 1000); - } catch (InterruptedException e) { - } - continue; - } - else - { - allStarted = true; - } - } - } - assertTrue(allStarted); - } - - @Test(dependsOnMethods = "testStart") - public void testRestart() throws RunScriptOnNodesException { - final String groupName = this.azureGroup; - Set nodes = view.getComputeService().rebootNodesMatching(inGroup(groupName)); - assertTrue(nodes.size() > 0); - - boolean allRestarted = false; - while (!allRestarted) { - nodes = view.getComputeService().listNodesDetailsMatching(nameStartsWith(groupName)); - for (NodeMetadata node : nodes) { - if (node.getStatus() != NodeMetadata.Status.RUNNING) - { - // Not started yet - allRestarted = false; - try { - Thread.sleep(30 * 1000); - } catch (InterruptedException e) { - } - continue; - } - else - { - allRestarted = true; - } - } - } - assertTrue(allRestarted); - - view.getComputeService().destroyNodesMatching(inGroup(groupName)); - } - - @Test(dependsOnMethods = "testRestart") - public void testLinuxNode() throws RunNodesException { - final String groupName = this.azureGroup; - final TemplateBuilder templateBuilder = view.getComputeService().templateBuilder(); - templateBuilder.osFamily(OsFamily.UBUNTU); - templateBuilder.osVersionMatches("14.04"); - templateBuilder.hardwareId("Standard_A0"); - templateBuilder.locationId("westus"); - final Template template = templateBuilder.build(); - - try { - Set nodes = view.getComputeService().createNodesInGroup(groupName, 1, template); - assertThat(nodes).hasSize(1); - } finally { - view.getComputeService().destroyNodesMatching(inGroup(groupName)); - } - } - - @Test(dependsOnMethods = "testLinuxNode") - public void testWindowsNode() throws RunNodesException { - final String groupName = this.azureGroup; - final TemplateBuilder templateBuilder = view.getComputeService().templateBuilder(); - templateBuilder.imageId("global/MicrosoftWindowsServer/WindowsServer/Windows-Server-Technical-Preview"); - templateBuilder.hardwareId("Standard_A0"); - templateBuilder.locationId("westus"); - final Template template = templateBuilder.build(); - - try { - Set nodes = view.getComputeService().createNodesInGroup(groupName, 1, template); - assertThat(nodes).hasSize(1); - } finally { - view.getComputeService().destroyNodesMatching(inGroup(groupName)); - } - } - - @Override - protected ProviderMetadata createProviderMetadata() { - AzureComputeProviderMetadata pm = AzureComputeProviderMetadata.builder().build(); - return pm; - } - - protected String setIfTestSystemPropertyPresent(Properties overrides, String key) { - if (System.getProperties().containsKey("test." + key)) { - String val = System.getProperty("test." + key); - overrides.setProperty(key, val); - return val; - } else { - return null; - } - } - -} diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/AzureComputeServiceLiveTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/AzureComputeServiceLiveTest.java index ae43511fe..b1223e2d0 100644 --- a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/AzureComputeServiceLiveTest.java +++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/AzureComputeServiceLiveTest.java @@ -16,33 +16,57 @@ */ package org.jclouds.azurecompute.arm.compute; +import org.jclouds.compute.RunScriptOnNodesException; +import org.jclouds.compute.domain.ExecResponse; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.OperatingSystem; +import org.jclouds.compute.domain.Template; import org.jclouds.compute.internal.BaseComputeServiceLiveTest; +import org.jclouds.compute.predicates.NodePredicates; +import org.jclouds.domain.LoginCredentials; +import org.jclouds.scriptbuilder.domain.Statement; +import org.jclouds.scriptbuilder.domain.Statements; +import org.jclouds.scriptbuilder.statements.java.InstallJDK; +import org.jclouds.scriptbuilder.statements.login.AdminAccess; import org.jclouds.sshj.config.SshjSshClientModule; import org.testng.annotations.Test; - import org.jclouds.providers.ProviderMetadata; - import org.jclouds.azurecompute.arm.AzureComputeProviderMetadata; import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.RESOURCE_GROUP_NAME; import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_PORT_OPEN; import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_SCRIPT_COMPLETE; import org.jclouds.azurecompute.arm.internal.AzureLiveTestUtils; - import com.google.inject.Module; +import java.util.Map; import java.util.Properties; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; + import static com.google.common.base.Preconditions.checkNotNull; +import org.jclouds.logging.slf4j.config.SLF4JLoggingModule; +import org.jclouds.logging.config.LoggingModule; + /** * Live tests for the {@link org.jclouds.compute.ComputeService} integration. */ @Test(groups = "live", singleThreaded = true, testName = "AzureComputeServiceLiveTest") public class AzureComputeServiceLiveTest extends BaseComputeServiceLiveTest { - public String azureGroup; + protected int nonBlockDurationSeconds = 30; public AzureComputeServiceLiveTest() { provider = "azurecompute-arm"; + nonBlockDurationSeconds = 300; + group = "az-u"; + } + + @Override + protected LoggingModule getLoggingModule() { + return new SLF4JLoggingModule(); } @Override @@ -56,18 +80,42 @@ protected ProviderMetadata createProviderMetadata() { return pm; } - @Override protected Properties setupProperties() { - azureGroup = "jc" + System.getProperty("user.name").substring(0, 3); + @Override + protected Properties setupProperties() { Properties properties = super.setupProperties(); - long scriptTimeout = TimeUnit.MILLISECONDS.convert(20, TimeUnit.MINUTES); + long scriptTimeout = TimeUnit.MILLISECONDS.convert(60, TimeUnit.MINUTES); properties.setProperty(TIMEOUT_SCRIPT_COMPLETE, scriptTimeout + ""); properties.setProperty(TIMEOUT_NODE_RUNNING, scriptTimeout + ""); - properties.put(RESOURCE_GROUP_NAME, azureGroup); + properties.setProperty(TIMEOUT_PORT_OPEN, scriptTimeout + ""); + properties.setProperty(TIMEOUT_NODE_TERMINATED, scriptTimeout + ""); + properties.setProperty(TIMEOUT_NODE_SUSPENDED, scriptTimeout + ""); + properties.put(RESOURCE_GROUP_NAME, "a4"); AzureLiveTestUtils.defaultProperties(properties); checkNotNull(setIfTestSystemPropertyPresent(properties, "oauth.endpoint"), "test.oauth.endpoint"); return properties; + } + @Override + protected Template refreshTemplate() { + return this.template = addRunScriptToTemplate(this.buildTemplate(this.client.templateBuilder())); + } + + @Override + protected Template addRunScriptToTemplate(Template template) { + template.getOptions().runScript(Statements.newStatementList(new Statement[]{AdminAccess.standard(), Statements.exec("sleep 50"), InstallJDK.fromOpenJDK()})); + return template; + } + + @Override + @Test( enabled = false) + protected void weCanCancelTasks(NodeMetadata node) throws InterruptedException, ExecutionException { + return; + } + + @Override + protected Map runScriptWithCreds(String group, OperatingSystem os, LoginCredentials creds) throws RunScriptOnNodesException { + return this.client.runScriptOnNodesMatching(NodePredicates.runningInGroup(group), Statements.newStatementList(Statements.exec("sleep 50"), InstallJDK.fromOpenJDK()), org.jclouds.compute.options.TemplateOptions.Builder.overrideLoginCredentials(creds).nameTask("runScriptWithCreds")); } } diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeImageExtensionLiveTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeImageExtensionLiveTest.java new file mode 100644 index 000000000..fecd0fdbd --- /dev/null +++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeImageExtensionLiveTest.java @@ -0,0 +1,89 @@ +/* + * 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.azurecompute.arm.compute.extensions; + +import com.google.inject.Module; +import org.jclouds.azurecompute.arm.AzureComputeProviderMetadata; +import org.jclouds.azurecompute.arm.config.AzureComputeProperties; +import org.jclouds.azurecompute.arm.internal.AzureLiveTestUtils; +import org.jclouds.compute.config.ComputeServiceProperties; +import org.jclouds.compute.extensions.internal.BaseImageExtensionLiveTest; +import org.jclouds.providers.ProviderMetadata; +import org.jclouds.sshj.config.SshjSshClientModule; +import org.testng.annotations.Test; + +import java.util.Properties; +import java.util.concurrent.TimeUnit; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.RESOURCE_GROUP_NAME; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_PORT_OPEN; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_SCRIPT_COMPLETE; + +/** + * Live tests for the {@link org.jclouds.compute.extensions.ImageExtension} integration. + */ +@Test(groups = "live", singleThreaded = true, testName = "AzureComputeImageExtensionLiveTest") +public class AzureComputeImageExtensionLiveTest extends BaseImageExtensionLiveTest { + + public AzureComputeImageExtensionLiveTest() { + provider = "azurecompute-arm"; + } + + public static String NAME_PREFIX = "%s"; + + @Override + protected Module getSshModule() { + return new SshjSshClientModule(); + } + + @Override + protected Properties setupProperties() { + Properties properties = super.setupProperties(); + long scriptTimeout = TimeUnit.MILLISECONDS.convert(60, TimeUnit.MINUTES); + properties.setProperty(TIMEOUT_SCRIPT_COMPLETE, scriptTimeout + ""); + properties.setProperty(TIMEOUT_NODE_RUNNING, scriptTimeout + ""); + properties.setProperty(TIMEOUT_PORT_OPEN, scriptTimeout + ""); + properties.setProperty(TIMEOUT_NODE_TERMINATED, scriptTimeout + ""); + properties.setProperty(TIMEOUT_NODE_SUSPENDED, scriptTimeout + ""); + properties.put(RESOURCE_GROUP_NAME, "a5"); + + properties.put(ComputeServiceProperties.POLL_INITIAL_PERIOD, 1000); + properties.put(ComputeServiceProperties.POLL_MAX_PERIOD, 10000); + properties.setProperty(AzureComputeProperties.OPERATION_TIMEOUT, "46000000"); + properties.setProperty(AzureComputeProperties.OPERATION_POLL_INITIAL_PERIOD, "5"); + properties.setProperty(AzureComputeProperties.OPERATION_POLL_MAX_PERIOD, "15"); + properties.setProperty(AzureComputeProperties.TCP_RULE_FORMAT, "tcp_%s-%s"); + properties.setProperty(AzureComputeProperties.TCP_RULE_REGEXP, "tcp_\\d{1,5}-\\d{1,5}"); + + AzureLiveTestUtils.defaultProperties(properties); + checkNotNull(setIfTestSystemPropertyPresent(properties, "oauth.endpoint"), "test.oauth.endpoint"); + + return properties; + + } + + @Override + protected ProviderMetadata createProviderMetadata() { + AzureComputeProviderMetadata pm = AzureComputeProviderMetadata.builder().build(); + return pm; + } + +} diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/DeploymentApiLiveTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/DeploymentApiLiveTest.java index 0e2baefcc..7493a63f7 100644 --- a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/DeploymentApiLiveTest.java +++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/DeploymentApiLiveTest.java @@ -18,6 +18,7 @@ import com.google.common.base.Predicate; import com.google.common.net.UrlEscapers; +import org.jclouds.azurecompute.arm.compute.options.AzureTemplateOptions; import org.jclouds.azurecompute.arm.domain.Deployment; import org.jclouds.azurecompute.arm.domain.Deployment.ProvisioningState; import org.jclouds.azurecompute.arm.domain.DeploymentBody; @@ -141,7 +142,7 @@ public void testValidate(){ public void testCreate() { String rsakey = new String("ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEAmfk/QSF0pvnrpdz+Ah2KulGruKU+8FFBdlw938MpOysRdmp7uwpH6Z7+5VNGNdxFIAyc/W3UaZXF9hTsU8+78TlwkZpsr2mzU+ycu37XLAQ8Uv7hjsAN0DkKKPrZ9lgUUfZVKV/8E/JIAs03gIbL6zO3y7eYJQ5fNeZb+nji7tQT+YLpGq/FDegvraPKVMQbCSCZhsHyWhdPLyFlu9/30npZ0ahYOPI/KyZxFDtM/pHp88+ZAk9Icq5owaLRWcJQqrBGWqjbZnHtjdDqvHZ+C0wPhdJZPyfkHOrSYTwSQBXfX4JLRRCz3J1jf62MbQWT1o6Y4JEs1ZP1Skxu6zR96Q== mocktest"); - TemplateOptions options = new TemplateOptions(); + TemplateOptions options = new AzureTemplateOptions(); options.authorizePublicKey(rsakey); DeploymentTemplateBuilder templateBuilder = getDeploymentTemplateBuilderWithOptions(options); DeploymentBody deploymentTemplateBody = templateBuilder.getDeploymentTemplate(); diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/DeploymentTemplateBuilderTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/DeploymentTemplateBuilderTest.java index bc505e7b9..ad5b1f3a7 100644 --- a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/DeploymentTemplateBuilderTest.java +++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/DeploymentTemplateBuilderTest.java @@ -18,18 +18,17 @@ import org.jclouds.azurecompute.arm.internal.BaseAzureComputeApiMockTest; import org.jclouds.compute.options.TemplateOptions; +import org.jclouds.azurecompute.arm.compute.options.AzureTemplateOptions; import org.jclouds.azurecompute.arm.domain.DeploymentBody; import org.jclouds.azurecompute.arm.domain.ImageReference; import org.jclouds.azurecompute.arm.domain.IpConfiguration; import org.jclouds.azurecompute.arm.domain.IpConfigurationProperties; import org.jclouds.azurecompute.arm.domain.NetworkInterfaceCardProperties; -import org.jclouds.azurecompute.arm.domain.OSProfile; import org.jclouds.azurecompute.arm.domain.PublicIPAddressProperties; import org.jclouds.azurecompute.arm.domain.ResourceDefinition; import org.jclouds.azurecompute.arm.domain.StorageService; import org.jclouds.azurecompute.arm.domain.StorageService.StorageServiceProperties; import org.jclouds.azurecompute.arm.domain.VirtualMachineProperties; -import org.jclouds.azurecompute.arm.domain.VirtualNetwork.VirtualNetworkProperties; import org.jclouds.azurecompute.arm.util.DeploymentTemplateBuilder; import org.jclouds.compute.domain.Hardware; import org.jclouds.compute.domain.HardwareBuilder; @@ -56,6 +55,8 @@ public class DeploymentTemplateBuilderTest extends BaseAzureComputeApiMockTest { final String group = "jcgroup"; + final String vnetName = group + "virtualnetwork"; + final String subnetId = ""; @Test public void testResourceGroup() { @@ -71,22 +72,6 @@ public void testResourceGroup() { assertTrue(variables.containsKey(parseVariableName(resource.name()))); } - @Test - void testVirtualNetwork() { - DeploymentTemplateBuilder builder = getMockDeploymentTemplateBuilderWithEmptyOptions(); - DeploymentBody deploymentBody = builder.getDeploymentTemplate(); - List resources = deploymentBody.template().resources(); - Map variables = deploymentBody.template().variables(); - - ResourceDefinition resource = getResourceByType(resources, "Microsoft.Network/virtualNetworks"); - - VirtualNetworkProperties properties = (VirtualNetworkProperties) resource.properties(); - assertTrue(properties.addressSpace().addressPrefixes().size() > 0); - assertTrue(properties.subnets().size() > 0); - - assertTrue(variables.containsKey(parseVariableName(resource.name()))); - } - @Test void testPublicIpAddress() { DeploymentTemplateBuilder builder = getMockDeploymentTemplateBuilderWithEmptyOptions(); @@ -147,42 +132,49 @@ void testVirtualMachine() { } @Test - void testVirtualMachineWithSSH() { + void testCustomOptions(){ + final String dnsLabelPrefix = "mydnslabel"; + final String customData = "echo customData"; + final String customData64 = "ZWNobyBjdXN0b21EYXRh"; + final String keyvaultString = "/url/to/vault/:publickeysecret"; + + AzureTemplateOptions options = new AzureTemplateOptions() + .customData(customData) + .DNSLabelPrefix(dnsLabelPrefix) + .keyVaultIdAndSecret(keyvaultString); - String rsakey = new String("ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEAmfk/QSF0pvnrpdz+Ah2KulGruKU+8FFBdlw938MpOysRdmp7uwpH6Z7+5VNGNdxFIAyc/W3UaZXF9hTsU8+78TlwkZpsr2mzU+ycu37XLAQ8Uv7hjsAN0DkKKPrZ9lgUUfZVKV/8E/JIAs03gIbL6zO3y7eYJQ5fNeZb+nji7tQT+YLpGq/FDegvraPKVMQbCSCZhsHyWhdPLyFlu9/30npZ0ahYOPI/KyZxFDtM/pHp88+ZAk9Icq5owaLRWcJQqrBGWqjbZnHtjdDqvHZ+C0wPhdJZPyfkHOrSYTwSQBXfX4JLRRCz3J1jf62MbQWT1o6Y4JEs1ZP1Skxu6zR96Q== mocktest"); + options.virtualNetworkName(vnetName); + options.subnetId(subnetId); - TemplateOptions options = new TemplateOptions(); - options.authorizePublicKey(rsakey); + assertEquals(options.as(AzureTemplateOptions.class).getCustomData(), customData); + assertEquals(options.getDNSLabelPrefix(), dnsLabelPrefix); + assertEquals(options.as(AzureTemplateOptions.class).getKeyVaultIdAndSecret(), keyvaultString); DeploymentTemplateBuilder builder = getMockDeploymentTemplateBuilderWithOptions(options); - Template template = builder.getTemplate(); DeploymentBody deploymentBody = builder.getDeploymentTemplate(); - List resources = deploymentBody.template().resources(); - Map variables = deploymentBody.template().variables(); - ResourceDefinition resource = getResourceByType(resources, "Microsoft.Compute/virtualMachines"); - assertNotNull(resource); + List resources = deploymentBody.template().resources(); + ResourceDefinition publicIpResource = getResourceByType(resources, "Microsoft.Network/publicIPAddresses"); + assertNotNull(publicIpResource); - VirtualMachineProperties properties = (VirtualMachineProperties) resource.properties(); - assertEquals(properties.hardwareProfile().vmSize(), template.getHardware().getId()); + PublicIPAddressProperties ipProperties = (PublicIPAddressProperties) publicIpResource.properties(); + assertEquals(ipProperties.dnsSettings().domainNameLabel(), dnsLabelPrefix); - ImageReference image = properties.storageProfile().imageReference(); - assertEquals(image.publisher(), template.getImage().getProviderId()); - assertEquals(image.offer(), template.getImage().getName()); - assertEquals(image.sku(), template.getImage().getVersion()); - assertEquals(image.version(), "latest"); + ResourceDefinition vmResource = getResourceByType(resources, "Microsoft.Compute/virtualMachines"); + assertNotNull(vmResource); - // Check that ssh key is in place - OSProfile.LinuxConfiguration osConfig = properties.osProfile().linuxConfiguration(); - assertEquals(osConfig.disablePasswordAuthentication(), "true"); - assertTrue(osConfig.ssh().publicKeys().size() > 0); - assertEquals(osConfig.ssh().publicKeys().get(0).keyData(), rsakey); + VirtualMachineProperties virtualMachineProperties = (VirtualMachineProperties) vmResource.properties(); + assertEquals(virtualMachineProperties.osProfile().customData(), customData64); - assertTrue(variables.containsKey(parseVariableName(resource.name()))); + //populated when keyvault is used to get public key. + assertNotNull(virtualMachineProperties.osProfile().linuxConfiguration().ssh().publicKeys()); } private Template getMockTemplate(TemplateOptions options) { + ((AzureTemplateOptions)options).virtualNetworkName(vnetName); + ((AzureTemplateOptions)options).subnetId(subnetId); + Location provider = (new LocationBuilder()).scope(LocationScope.PROVIDER).id("azurecompute-arm").description("azurecompute-arm").build(); Location region = (new LocationBuilder()).scope(LocationScope.REGION).id("northeurope").description("North Europe").parent(provider).build(); OperatingSystem os = OperatingSystem.builder().name("osName").version("osVersion").description("osDescription").arch("X86_32").build(); @@ -193,13 +185,19 @@ private Template getMockTemplate(TemplateOptions options) { } private DeploymentTemplateBuilder getMockDeploymentTemplateBuilderWithEmptyOptions() { - TemplateOptions options = new TemplateOptions(); + AzureTemplateOptions options = new AzureTemplateOptions(); + options.virtualNetworkName(vnetName); + options.subnetId(subnetId); + Template template = getMockTemplate(options); DeploymentTemplateBuilder templateBuilder = api.deploymentTemplateFactory().create(group, "mydeployment", template); return templateBuilder; } private DeploymentTemplateBuilder getMockDeploymentTemplateBuilderWithOptions(TemplateOptions options) { + ((AzureTemplateOptions)options).virtualNetworkName(vnetName); + ((AzureTemplateOptions)options).subnetId(subnetId); + Template template = getMockTemplate(options); DeploymentTemplateBuilder templateBuilder = api.deploymentTemplateFactory().create(group, "mydeployment", template); return templateBuilder; diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/JobApiMockTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/JobApiMockTest.java index 3e55df108..736414596 100644 --- a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/JobApiMockTest.java +++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/JobApiMockTest.java @@ -18,12 +18,15 @@ import java.io.IOException; import java.net.URI; +import java.util.List; +import org.jclouds.azurecompute.arm.domain.ResourceDefinition; import org.jclouds.azurecompute.arm.functions.ParseJobStatus.JobStatus; import org.jclouds.azurecompute.arm.internal.BaseAzureComputeApiMockTest; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; @Test(groups = "unit", testName = "JobApiMockTest", singleThreaded = true) public class JobApiMockTest extends BaseAzureComputeApiMockTest { @@ -70,4 +73,24 @@ public void testGetJobStatusFailed() throws InterruptedException { assertSent(server, "GET", requestUrl); } + public void testCaptureJobStatus() throws IOException, InterruptedException { + server.enqueue(jsonResponse("/resourceDefinition.json").setResponseCode(200)); + + List resourceDefinitionsList = api.getJobApi().captureStatus(URI.create(requestUrl)); + + assertTrue(resourceDefinitionsList.size() > 0); + + assertSent(server, "GET", requestUrl); + } + + public void testCaptureJobStatusFailed() throws InterruptedException { + server.enqueue(response404()); + + List resourceDefinitionsList = api.getJobApi().captureStatus(URI.create(requestUrl)); + + assertEquals(resourceDefinitionsList.size(), 0); + + assertSent(server, "GET", requestUrl); + } + } diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/NetworkInterfaceCardApiMockTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/NetworkInterfaceCardApiMockTest.java index 12ad0733f..bda8cad53 100644 --- a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/NetworkInterfaceCardApiMockTest.java +++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/NetworkInterfaceCardApiMockTest.java @@ -107,7 +107,8 @@ public void createNetworkInterfaceCard() throws InterruptedException { NetworkInterfaceCardProperties.create(null, null, null, Arrays.asList(IpConfiguration.create("myipconfig", null, null, null, IpConfigurationProperties.create(null, null, "Dynamic", IdReference.create(SubnetID), null)) - ) + ), + null ); final Map tags = ImmutableMap.of("mycustomtag", "foobar"); diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/TemplateToDeploymentTemplateLiveTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/TemplateToDeploymentTemplateLiveTest.java index be2eb3e3d..12d62556c 100644 --- a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/TemplateToDeploymentTemplateLiveTest.java +++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/TemplateToDeploymentTemplateLiveTest.java @@ -17,10 +17,12 @@ package org.jclouds.azurecompute.arm.features; import com.google.common.net.UrlEscapers; -import org.jclouds.compute.options.TemplateOptions; +import org.jclouds.azurecompute.arm.compute.options.AzureTemplateOptions; import org.jclouds.azurecompute.arm.domain.Deployment; import org.jclouds.azurecompute.arm.domain.DeploymentBody; import org.jclouds.azurecompute.arm.domain.DeploymentProperties; +import org.jclouds.azurecompute.arm.domain.Subnet; +import org.jclouds.azurecompute.arm.domain.VirtualNetwork; import org.jclouds.azurecompute.arm.internal.BaseAzureComputeApiLiveTest; import org.jclouds.azurecompute.arm.util.DeploymentTemplateBuilder; import org.jclouds.compute.domain.Hardware; @@ -31,6 +33,7 @@ import org.jclouds.compute.domain.OsFamily; import org.jclouds.compute.domain.Template; import org.jclouds.compute.domain.internal.TemplateImpl; +import org.jclouds.compute.options.TemplateOptions; import org.jclouds.domain.Location; import org.jclouds.domain.LocationBuilder; import org.jclouds.domain.LocationScope; @@ -47,12 +50,50 @@ public class TemplateToDeploymentTemplateLiveTest extends BaseAzureComputeApiLiv private int pollingInterval = 3; // how frequently to poll for create status private String resourceGroup; private String deploymentName; + private String vnetName; + private String subnetId; @BeforeClass @Override public void setup() { super.setup(); resourceGroup = getResourceGroupName(); + + //Subnets belong to a virtual network so that needs to be created first + VirtualNetwork vn = getOrCreateVirtualNetwork(VIRTUAL_NETWORK_NAME); + assertNotNull(vn); + vnetName = vn.name(); + + //Subnet needs to be up & running before NIC can be created + Subnet subnet = getOrCreateSubnet(DEFAULT_SUBNET_NAME, VIRTUAL_NETWORK_NAME); + assertNotNull(subnet); + assertNotNull(subnet.id()); + subnetId = subnet.id(); + } + + @Test(groups = "live") + public void testValidateDeploymentTemplateLinuxNodeWithOptions() { + Long now = System.currentTimeMillis(); + deploymentName = "jc" + now; + + AzureTemplateOptions options = new AzureTemplateOptions(); + options.virtualNetworkName(vnetName); + options.subnetId(subnetId); + + options.inboundPorts(22, 8080); + + DeploymentTemplateBuilder templateBuilder = getDeploymentTemplateBuilderWithOptions(options); + + DeploymentBody deploymentTemplateBody = templateBuilder.getDeploymentTemplate(); + + DeploymentProperties properties = DeploymentProperties.create(deploymentTemplateBody); + + String deploymentTemplate = templateBuilder.getDeploymentTemplateJson(properties); + deploymentTemplate = UrlEscapers.urlFormParameterEscaper().escape(deploymentTemplate); + + //Validates that template is syntactically correct + Deployment deployment = api().validate(deploymentName, deploymentTemplate); + assertNotNull(deployment); } @Test(groups = "live") @@ -74,6 +115,34 @@ public void testValidateDeploymentTemplateLinuxNode() { assertNotNull(deployment); } + @Test(groups = "live") + public void testValidateDeploymentTemplateWithCustomOptions() { + Long now = System.currentTimeMillis(); + deploymentName = "jc" + now; + + String rsakey = new String("ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEAmfk/QSF0pvnrpdz+Ah2KulGruKU+8FFBdlw938MpOysRdmp7uwpH6Z7+5VNGNdxFIAyc/W3UaZXF9hTsU8+78TlwkZpsr2mzU+ycu37XLAQ8Uv7hjsAN0DkKKPrZ9lgUUfZVKV/8E/JIAs03gIbL6zO3y7eYJQ5fNeZb+nji7tQT+YLpGq/FDegvraPKVMQbCSCZhsHyWhdPLyFlu9/30npZ0ahYOPI/KyZxFDtM/pHp88+ZAk9Icq5owaLRWcJQqrBGWqjbZnHtjdDqvHZ+C0wPhdJZPyfkHOrSYTwSQBXfX4JLRRCz3J1jf62MbQWT1o6Y4JEs1ZP1Skxu6zR96Q== mocktest"); + TemplateOptions options = new AzureTemplateOptions() + .DNSLabelPrefix("mydnslabel") + .virtualNetworkAddressPrefix("10.0.0.0/20") + .subnetAddressPrefix("10.0.0.0/25") + .authorizePublicKey(rsakey); + + ((AzureTemplateOptions)options).virtualNetworkName(vnetName); + ((AzureTemplateOptions)options).subnetId(subnetId); + + DeploymentTemplateBuilder templateBuilder = getDeploymentTemplateBuilderWithOptions(options); + + DeploymentBody deploymentTemplateBody = templateBuilder.getDeploymentTemplate(); + + DeploymentProperties properties = DeploymentProperties.create(deploymentTemplateBody); + + String deploymentTemplate = templateBuilder.getDeploymentTemplateJson(properties); + deploymentTemplate = UrlEscapers.urlFormParameterEscaper().escape(deploymentTemplate); + + Deployment deployment = api().validate(deploymentName, deploymentTemplate); + assertNotNull(deployment); + } + @Test(groups = "live") public void testValidateDeploymentTemplateLinuxNodeWithSSH() { Long now = System.currentTimeMillis(); @@ -81,7 +150,10 @@ public void testValidateDeploymentTemplateLinuxNodeWithSSH() { String rsakey = new String("ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEAmfk/QSF0pvnrpdz+Ah2KulGruKU+8FFBdlw938MpOysRdmp7uwpH6Z7+5VNGNdxFIAyc/W3UaZXF9hTsU8+78TlwkZpsr2mzU+ycu37XLAQ8Uv7hjsAN0DkKKPrZ9lgUUfZVKV/8E/JIAs03gIbL6zO3y7eYJQ5fNeZb+nji7tQT+YLpGq/FDegvraPKVMQbCSCZhsHyWhdPLyFlu9/30npZ0ahYOPI/KyZxFDtM/pHp88+ZAk9Icq5owaLRWcJQqrBGWqjbZnHtjdDqvHZ+C0wPhdJZPyfkHOrSYTwSQBXfX4JLRRCz3J1jf62MbQWT1o6Y4JEs1ZP1Skxu6zR96Q== mocktest"); - TemplateOptions options = new TemplateOptions(); + AzureTemplateOptions options = new AzureTemplateOptions(); + options.virtualNetworkName(vnetName); + options.subnetId(subnetId); + options.authorizePublicKey(rsakey); DeploymentTemplateBuilder templateBuilder = getDeploymentTemplateBuilderWithOptions(options); @@ -103,8 +175,12 @@ public void testCreateDeploymentTemplateLinuxNode() { String rsakey = new String("ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEAmfk/QSF0pvnrpdz+Ah2KulGruKU+8FFBdlw938MpOysRdmp7uwpH6Z7+5VNGNdxFIAyc/W3UaZXF9hTsU8+78TlwkZpsr2mzU+ycu37XLAQ8Uv7hjsAN0DkKKPrZ9lgUUfZVKV/8E/JIAs03gIbL6zO3y7eYJQ5fNeZb+nji7tQT+YLpGq/FDegvraPKVMQbCSCZhsHyWhdPLyFlu9/30npZ0ahYOPI/KyZxFDtM/pHp88+ZAk9Icq5owaLRWcJQqrBGWqjbZnHtjdDqvHZ+C0wPhdJZPyfkHOrSYTwSQBXfX4JLRRCz3J1jf62MbQWT1o6Y4JEs1ZP1Skxu6zR96Q== mocktest"); - TemplateOptions options = new TemplateOptions(); + AzureTemplateOptions options = new AzureTemplateOptions(); + options.virtualNetworkName(vnetName); + options.subnetId(subnetId); + options.authorizePublicKey(rsakey); + options.inboundPorts(22, 8080); DeploymentTemplateBuilder templateBuilder = getDeploymentTemplateBuilderWithOptions(options); DeploymentBody deploymentTemplateBody = templateBuilder.getDeploymentTemplate(); @@ -144,7 +220,7 @@ public void testCreateDeploymentTemplateLinuxNode() { private Template getTemplate(TemplateOptions options) { Location provider = (new LocationBuilder()).scope(LocationScope.PROVIDER).id("azurecompute-arm").description("azurecompute-arm").build(); - Location region = (new LocationBuilder()).scope(LocationScope.REGION).id("northeurope").description("North Europe").parent(provider).build(); + Location region = (new LocationBuilder()).scope(LocationScope.REGION).id(LOCATION).description(LOCATIONDESCRIPTION).parent(provider).build(); OperatingSystem os = OperatingSystem.builder() .family(OsFamily.UBUNTU) @@ -168,7 +244,10 @@ private Template getTemplate(TemplateOptions options) { } private DeploymentTemplateBuilder getDeploymentTemplateBuilderWithEmptyOptions() { - TemplateOptions options = new TemplateOptions(); + AzureTemplateOptions options = new AzureTemplateOptions(); + options.virtualNetworkName(vnetName); + options.subnetId(subnetId); + Template template = getTemplate(options); DeploymentTemplateBuilder templateBuilder = api.deploymentTemplateFactory().create(resourceGroup, deploymentName, template); return templateBuilder; diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VirtualMachineApiLiveTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VirtualMachineApiLiveTest.java index f117f1c7b..5271e2a25 100644 --- a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VirtualMachineApiLiveTest.java +++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VirtualMachineApiLiveTest.java @@ -17,6 +17,8 @@ package org.jclouds.azurecompute.arm.features; import com.google.common.base.Predicate; +import com.google.gson.internal.LinkedTreeMap; +import com.google.common.collect.Iterables; import org.jclouds.azurecompute.arm.domain.DataDisk; import org.jclouds.azurecompute.arm.domain.DiagnosticsProfile; import org.jclouds.azurecompute.arm.domain.HardwareProfile; @@ -33,16 +35,28 @@ import org.jclouds.azurecompute.arm.domain.VirtualMachineInstance; import org.jclouds.azurecompute.arm.domain.VirtualMachineProperties; import org.jclouds.azurecompute.arm.functions.ParseJobStatus; +import org.jclouds.azurecompute.arm.internal.AzureLiveTestUtils; import org.jclouds.azurecompute.arm.internal.BaseAzureComputeApiLiveTest; import org.jclouds.util.Predicates2; +import org.jclouds.azurecompute.arm.domain.ResourceDefinition; +import org.testng.Assert; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; +import static com.google.common.base.Preconditions.checkNotNull; +import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.RESOURCE_GROUP_NAME; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_SCRIPT_COMPLETE; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_PORT_OPEN; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED; import static org.testng.Assert.assertNotNull; import java.net.URI; import java.util.ArrayList; import java.util.List; +import java.util.Properties; +import java.util.concurrent.TimeUnit; import static org.testng.Assert.assertTrue; @@ -60,6 +74,23 @@ public void Setup() { nicName = nic.name(); } + @Override + protected Properties setupProperties() { + Properties properties = super.setupProperties(); + long scriptTimeout = TimeUnit.MILLISECONDS.convert(60, TimeUnit.MINUTES); + properties.setProperty(TIMEOUT_SCRIPT_COMPLETE, scriptTimeout + ""); + properties.setProperty(TIMEOUT_NODE_RUNNING, scriptTimeout + ""); + properties.setProperty(TIMEOUT_PORT_OPEN, scriptTimeout + ""); + properties.setProperty(TIMEOUT_NODE_TERMINATED, scriptTimeout + ""); + properties.setProperty(TIMEOUT_NODE_SUSPENDED, scriptTimeout + ""); + properties.put(RESOURCE_GROUP_NAME, getResourceGroupName()); + + AzureLiveTestUtils.defaultProperties(properties); + checkNotNull(setIfTestSystemPropertyPresent(properties, "oauth.endpoint"), "test.oauth.endpoint"); + + return properties; + } + private String getName() { if (vmName == null) { vmName = String.format("%3.24s", @@ -70,7 +101,6 @@ private String getName() { @Test public void testCreate() { - StorageAccountApi storageApi = api.getStorageAccountApi(getResourceGroupName()); StorageService storageAccount = storageApi.get(getStorageServiceName()); String blob = storageAccount.storageServiceProperties().primaryEndpoints().get("blob"); @@ -109,22 +139,7 @@ public void testGetInstanceView() { public void testStop() { api().stop(getName()); //Poll until resource is ready to be used - boolean jobDone = Predicates2.retry(new Predicate() { - @Override - public boolean apply(String name) { - String status = ""; - List statuses = api().getInstanceDetails(name).statuses(); - for (int c = 0; c < statuses.size(); c++) { - if (statuses.get(c).code().substring(0, 10).equals("PowerState")) { - status = statuses.get(c).displayStatus(); - break; - } - } - return status.equals("VM stopped"); - } - }, 60 * 4 * 1000).apply(getName()); - assertTrue(jobDone, "stop operation did not complete in the configured timeout"); - + nodeSuspendedPredicate.apply(getName()); } @Test(dependsOnMethods = "testGet") @@ -194,11 +209,53 @@ public boolean apply(String name) { @Test(dependsOnMethods = "testCreate") public void testList() { List list = api().list(); - VirtualMachine vm = api().get(getName()); - assertTrue(list.contains(vm)); + final VirtualMachine vm = api().get(getName()); + + boolean vmPresent = Iterables.any(list, new Predicate() { + public boolean apply(VirtualMachine input) { + return input.name().equals(vm.name()); + } + }); + + assertTrue(vmPresent); + } + + @Test(dependsOnMethods = "testRestart") + public void testGeneralize() throws IllegalStateException { + api().stop(getName()); + //Poll until resource is ready to be used + + if (nodeSuspendedPredicate.apply(getName())) { + api().generalize(getName()); + } + } + + @Test(dependsOnMethods = "testGeneralize") + public void testCapture() throws IllegalStateException { + URI uri = api().capture(getName(), getName(), getName()); + if (uri != null) { + if (imageAvailablePredicate.apply(uri)) { + List definitions = api.getJobApi().captureStatus(uri); + if (definitions != null) { + for (ResourceDefinition definition : definitions) { + LinkedTreeMap properties = (LinkedTreeMap) definition.properties(); + Object storageObject = properties.get("storageProfile"); + LinkedTreeMap properties2 = (LinkedTreeMap) storageObject; + Object osDiskObject = properties2.get("osDisk"); + LinkedTreeMap osProperties = (LinkedTreeMap) osDiskObject; + Object dataDisksObject = properties2.get("dataDisks"); + ArrayList dataProperties = (ArrayList) dataDisksObject; + LinkedTreeMap datadiskObject = (LinkedTreeMap) dataProperties.get(0); + + Assert.assertNotNull(osProperties.get("name")); + Assert.assertNotNull(datadiskObject.get("name")); + } + } + } + } } - @Test(dependsOnMethods = {"testRestart", "testList", "testGet"}, alwaysRun = true) + @Test(dependsOnMethods = "testCapture", alwaysRun = true) public void testDelete() throws Exception { URI uri = api().delete(getName()); @@ -227,8 +284,10 @@ private VirtualMachineProperties getProperties(String blob, String nic) { VHD vhd = VHD.create(blob + "vhds/" + getName() + ".vhd"); VHD vhd2 = VHD.create(blob + "vhds/" + getName() + "data.vhd"); DataDisk dataDisk = DataDisk.create(getName() + "data", "100", 0, vhd2, "Empty"); - OSDisk osDisk = OSDisk.create(null, getName(), vhd, "ReadWrite", "FromImage"); - StorageProfile storageProfile = StorageProfile.create(imgRef, osDisk, null); + List dataDisks = new ArrayList(); + dataDisks.add(dataDisk); + OSDisk osDisk = OSDisk.create(null, getName(), vhd, "ReadWrite", "FromImage", null); + StorageProfile storageProfile = StorageProfile.create(imgRef, osDisk, dataDisks); OSProfile.WindowsConfiguration windowsConfig = OSProfile.WindowsConfiguration.create(false, null, null, true, null); OSProfile osProfile = OSProfile.create(getName(), "azureuser", "RFe3&432dg", null, null, windowsConfig); diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VirtualMachineApiMockTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VirtualMachineApiMockTest.java index a2be833e7..f6583c813 100644 --- a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VirtualMachineApiMockTest.java +++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VirtualMachineApiMockTest.java @@ -74,7 +74,7 @@ public void testGetInstanceDetails() throws Exception { assertEquals(actual.statuses().get(0).code(), expected.statuses().get(0).code()); assertEquals(actual.statuses().get(0).displayStatus(), expected.statuses().get(0).displayStatus()); assertEquals(actual.statuses().get(0).level(), expected.statuses().get(0).level()); - assertEquals(actual.statuses().get(0).time().toString(), expected.statuses().get(0).time().toString()); + //assertEquals(actual.statuses().get(0).time().toString(), expected.statuses().get(0).time().toString()); assertSent(server, "GET", "/subscriptions/SUBSCRIPTIONID/resourceGroups/groupname/providers/Microsoft.Compute" + "/virtualMachines/windowsmachine/instanceView?api-version=2015-06-15"); } @@ -182,12 +182,40 @@ public void testStop() throws Exception { "/virtualMachines/windowsmachine/powerOff?api-version=2015-06-15"); } + public void testGeneralize() throws Exception { + server.enqueue(new MockResponse().setResponseCode(200)); + final VirtualMachineApi vmAPI = api.getVirtualMachineApi("groupname"); + vmAPI.generalize("vm"); // IllegalStateException if failed + assertSent(server, "POST", "/subscriptions/SUBSCRIPTIONID/resourceGroups/groupname/providers/Microsoft.Compute" + + "/virtualMachines/vm/generalize?api-version=2015-06-15"); + } + + public void testCapture() throws Exception { + server.enqueue(response202WithHeader()); + + final VirtualMachineApi vmAPI = api.getVirtualMachineApi("groupname"); + URI uri = vmAPI.capture("vm", "prefix", "container"); + assertNotNull(uri); + assertSent(server, "POST", "/subscriptions/SUBSCRIPTIONID/resourceGroups/groupname/providers/Microsoft.Compute" + + "/virtualMachines/vm/capture?api-version=2015-06-15", "{\"vhdPrefix\":\"prefix\",\"destinationContainerName\":\"container\"}"); + } + + public void testCapture404() throws Exception { + server.enqueue(response404()); + + final VirtualMachineApi vmAPI = api.getVirtualMachineApi("groupname"); + URI uri = vmAPI.capture("vm", "prefix", "container"); + assertNull(uri); + assertSent(server, "POST", "/subscriptions/SUBSCRIPTIONID/resourceGroups/groupname/providers/Microsoft.Compute" + + "/virtualMachines/vm/capture?api-version=2015-06-15", "{\"vhdPrefix\":\"prefix\",\"destinationContainerName\":\"container\"}"); + } + private VirtualMachineProperties getProperties() { HardwareProfile hwProf = HardwareProfile.create("Standard_D1"); ImageReference imgRef = ImageReference.create("publisher", "offer", "sku", "ver"); VHD vhd = VHD.create("https://groupname2760.blob.core.windows.net/vhds/windowsmachine201624102936.vhd"); List dataDisks = new ArrayList(); - OSDisk osDisk = OSDisk.create("Windows", "windowsmachine", vhd, "ReadWrite", "FromImage"); + OSDisk osDisk = OSDisk.create("Windows", "windowsmachine", vhd, "ReadWrite", "FromImage", null); StorageProfile storageProfile = StorageProfile.create(imgRef, osDisk, dataDisks); OSProfile.WindowsConfiguration windowsConfig = OSProfile.WindowsConfiguration.create(false, null, null, true, null); diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/internal/AbstractAzureComputeApiLiveTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/internal/AbstractAzureComputeApiLiveTest.java index 337812be9..f3134b70d 100644 --- a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/internal/AbstractAzureComputeApiLiveTest.java +++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/internal/AbstractAzureComputeApiLiveTest.java @@ -18,8 +18,11 @@ import static com.google.common.base.Preconditions.checkNotNull; +import java.net.URI; import java.util.Properties; import java.util.Random; + +import com.google.common.base.Predicate; import com.google.inject.Module; import com.google.inject.Injector; @@ -28,11 +31,19 @@ import org.jclouds.azurecompute.arm.AzureComputeApi; import org.jclouds.azurecompute.arm.AzureComputeProviderMetadata; import org.jclouds.providers.ProviderMetadata; +import com.google.inject.name.Names; +import com.google.inject.Key; +import com.google.inject.TypeLiteral; + +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_IMAGE_AVAILABLE; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED; public abstract class AbstractAzureComputeApiLiveTest extends BaseApiLiveTest { protected static final int RAND = new Random().nextInt(999); + protected Predicate nodeSuspendedPredicate; + protected Predicate imageAvailablePredicate; public AbstractAzureComputeApiLiveTest() { provider = "azurecompute-arm"; @@ -40,6 +51,10 @@ public AbstractAzureComputeApiLiveTest() { @Override protected AzureComputeApi create(Properties props, Iterable modules) { Injector injector = newBuilder().modules(modules).overrides(props).buildInjector(); + nodeSuspendedPredicate = injector.getInstance(Key.get(new TypeLiteral>() { + }, Names.named(TIMEOUT_NODE_SUSPENDED))); + imageAvailablePredicate = injector.getInstance(Key.get(new TypeLiteral>() { + }, Names.named(TIMEOUT_IMAGE_AVAILABLE))); return injector.getInstance(AzureComputeApi.class); } diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/internal/BaseAzureComputeApiLiveTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/internal/BaseAzureComputeApiLiveTest.java index bd9adfc4b..0eec5254c 100644 --- a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/internal/BaseAzureComputeApiLiveTest.java +++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/internal/BaseAzureComputeApiLiveTest.java @@ -48,6 +48,7 @@ public class BaseAzureComputeApiLiveTest extends AbstractAzureComputeApiLiveTest { public static final String LOCATION = "westeurope"; + public static final String LOCATIONDESCRIPTION = "West Europe"; public static final String DEFAULT_SUBNET_ADDRESS_SPACE = "10.2.0.0/23"; @@ -61,6 +62,8 @@ public class BaseAzureComputeApiLiveTest extends AbstractAzureComputeApiLiveTest private String resourceGroupName = null; + private String virtualNetworkName = null; + protected StorageService storageService; private String storageServiceName = null; @@ -101,12 +104,12 @@ protected String getResourceGroupName() { if (resourceGroupName == null) { resourceGroupName = String.format("%3.24s", System.getProperty("user.name") + RAND + "groupjclouds"); - createResourceGroup(resourceGroupName); + //createResourceGroup(resourceGroupName); } return resourceGroupName; } - private void createResourceGroup(String name) { + protected void createResourceGroup(String name) { ImmutableMap tags = ImmutableMap.builder().build(); final ResourceGroup resourceGroup = api.getResourceGroupApi().create( @@ -117,10 +120,12 @@ private void deleteResourceGroup(String name) { api.getResourceGroupApi().delete(name); } + @BeforeClass @Override public void setup() { super.setup(); + createResourceGroup(getResourceGroupName()); storageService = getOrCreateStorageService(getStorageServiceName()); } @@ -173,6 +178,7 @@ protected VirtualNetwork getOrCreateVirtualNetwork(final String virtualNetworkNa vn = vnApi.createOrUpdate(VIRTUAL_NETWORK_NAME, LOCATION, virtualNetworkProperties); + this.virtualNetworkName = virtualNetworkName; return vn; } diff --git a/azurecompute-arm/src/test/resources/logback.xml b/azurecompute-arm/src/test/resources/logback.xml new file mode 100644 index 000000000..412e0e234 --- /dev/null +++ b/azurecompute-arm/src/test/resources/logback.xml @@ -0,0 +1,82 @@ + + + + + target/test-data/jclouds.log + + + %d %-5p [%c] [%thread] %m%n + + + + + target/test-data/jclouds-wire.log + + + %d %-5p [%c] [%thread] %m%n + + + + + target/test-data/jclouds-compute.log + + + %d %-5p [%c] [%thread] %m%n + + + + + target/test-data/jclouds-ssh.log + + + %d %-5p [%c] [%thread] %m%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/azurecompute-arm/src/test/resources/resourceDefinition.json b/azurecompute-arm/src/test/resources/resourceDefinition.json new file mode 100644 index 000000000..245ce1f86 --- /dev/null +++ b/azurecompute-arm/src/test/resources/resourceDefinition.json @@ -0,0 +1,22 @@ +{ + "resources": [ + { + "name": "[parameters('vnName')]", + "type": "Microsoft.Compute/virtualMachines", + "location": "westeurope", + "apiVersion": "2015-06-15", + "properties": { + "hardwareProfile": { + "vmSize": "[paramters('vmSize')]" + }, + "storageProfile": { + "osDisk": { + "osType":"Windows", + "name":"testmachine-osDisk.539c38a7-642c-43cc-a20b-89b0f3e56afe.vhd" + } + }, + "provisioningState": "0.0" + } + } + ] +} \ No newline at end of file