Repository: jclouds-labs
Updated Branches:
  refs/heads/fix/AzureTemplateBuilderLiveTest d9600c00f -> 226633157


Fixed Azure ARM Image extension


Project: http://git-wip-us.apache.org/repos/asf/jclouds-labs/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds-labs/commit/22663315
Tree: http://git-wip-us.apache.org/repos/asf/jclouds-labs/tree/22663315
Diff: http://git-wip-us.apache.org/repos/asf/jclouds-labs/diff/22663315

Branch: refs/heads/fix/AzureTemplateBuilderLiveTest
Commit: 226633157222d11d6a708fedd5581e61de8672df
Parents: d9600c0
Author: Ignasi Barrera <n...@apache.org>
Authored: Wed Oct 12 20:33:53 2016 +0200
Committer: Ignasi Barrera <n...@apache.org>
Committed: Wed Oct 12 20:34:51 2016 +0200

----------------------------------------------------------------------
 .../arm/AzureComputeProviderMetadata.java       |   2 +-
 .../arm/compute/AzureComputeServiceAdapter.java | 110 ++++++------
 .../AzureComputeServiceContextModule.java       |  71 ++++----
 .../extensions/AzureComputeImageExtension.java  | 177 +++++++++----------
 .../ResourceDefinitionToCustomImage.java        |  76 ++++++++
 .../arm/compute/functions/VMImageToImage.java   |  25 +--
 .../functions/VirtualMachineToNodeMetadata.java |  50 ++++--
 .../CreateResourceGroupThenCreateNodes.java     |  23 +--
 .../azurecompute/arm/domain/VMImage.java        |  43 +++--
 .../arm/functions/CleanupResources.java         | 157 +++++++---------
 .../azurecompute/arm/util/BlobHelper.java       |  72 ++++----
 .../compute/AzureComputeServiceLiveTest.java    |  13 +-
 .../AzureComputeImageExtensionLiveTest.java     |  56 +++---
 13 files changed, 483 insertions(+), 392 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/22663315/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/AzureComputeProviderMetadata.java
----------------------------------------------------------------------
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 86f1420..a434079 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
@@ -88,7 +88,7 @@ public class AzureComputeProviderMetadata extends 
BaseProviderMetadata {
       properties.setProperty(TCP_RULE_REGEXP, "tcp_\\d{1,5}-\\d{1,5}");
       properties.put(RESOURCE, "https://management.azure.com/";);
       properties.put(CREDENTIAL_TYPE, CLIENT_CREDENTIALS_SECRET.toString());
-      properties.put(RESOURCE_GROUP_NAME, "jcloudsgroup");
+      properties.put(RESOURCE_GROUP_NAME, "jclouds");
       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");

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/22663315/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/AzureComputeServiceAdapter.java
----------------------------------------------------------------------
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 d21bd43..d6d9267 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
@@ -19,21 +19,21 @@ package org.jclouds.azurecompute.arm.compute;
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.collect.Iterables.contains;
 import static com.google.common.collect.Iterables.filter;
-import static 
org.jclouds.compute.config.ComputeServiceProperties.IMAGE_LOGIN_USER;
-import static 
org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING;
+import static com.google.common.collect.Iterables.find;
+import static 
org.jclouds.azurecompute.arm.compute.extensions.AzureComputeImageExtension.CONTAINER_NAME;
+import static 
org.jclouds.azurecompute.arm.compute.extensions.AzureComputeImageExtension.CUSTOM_IMAGE_OFFER;
+import static 
org.jclouds.azurecompute.arm.compute.functions.VMImageToImage.decodeFieldsFromUniqueId;
+import static 
org.jclouds.azurecompute.arm.compute.functions.VMImageToImage.encodeFieldsToUniqueIdCustom;
 
 import java.util.List;
 import java.util.Set;
 
-import javax.annotation.Resource;
 import javax.inject.Inject;
 import javax.inject.Named;
 import javax.inject.Singleton;
 
 import org.jclouds.azurecompute.arm.AzureComputeApi;
-import org.jclouds.azurecompute.arm.AzureComputeProviderMetadata;
 import 
org.jclouds.azurecompute.arm.compute.config.AzureComputeServiceContextModule.AzureComputeConstants;
-import org.jclouds.azurecompute.arm.compute.functions.VMImageToImage;
 import org.jclouds.azurecompute.arm.compute.options.AzureTemplateOptions;
 import org.jclouds.azurecompute.arm.domain.DataDisk;
 import org.jclouds.azurecompute.arm.domain.HardwareProfile;
@@ -54,6 +54,7 @@ import 
org.jclouds.azurecompute.arm.domain.ResourceProviderMetaData;
 import org.jclouds.azurecompute.arm.domain.SKU;
 import org.jclouds.azurecompute.arm.domain.StorageProfile;
 import org.jclouds.azurecompute.arm.domain.StorageService;
+import org.jclouds.azurecompute.arm.domain.StorageService.Status;
 import org.jclouds.azurecompute.arm.domain.VHD;
 import org.jclouds.azurecompute.arm.domain.VMHardware;
 import org.jclouds.azurecompute.arm.domain.VMImage;
@@ -67,10 +68,9 @@ import 
org.jclouds.azurecompute.arm.functions.CleanupResources;
 import org.jclouds.azurecompute.arm.util.BlobHelper;
 import org.jclouds.compute.ComputeServiceAdapter;
 import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.OsFamily;
 import org.jclouds.compute.domain.Template;
-import org.jclouds.compute.reference.ComputeServiceConstants;
 import org.jclouds.location.Region;
-import org.jclouds.logging.Logger;
 
 import com.google.common.base.Function;
 import com.google.common.base.Objects;
@@ -90,32 +90,22 @@ import com.google.common.collect.Lists;
 @Singleton
 public class AzureComputeServiceAdapter implements 
ComputeServiceAdapter<VirtualMachine, VMHardware, VMImage, Location> {
 
-   @Resource
-   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
-   private Logger logger = Logger.NULL;
-
    private final String azureGroup;
    private final CleanupResources cleanupResources;
    private final AzureComputeApi api;
    private final AzureComputeConstants azureComputeConstants;
    private final Supplier<Set<String>> regionIds;
-   private final Predicate<String> nodeRunningPredicate;
    private final Predicate<String> publicIpAvailable;
 
    @Inject
    AzureComputeServiceAdapter(final AzureComputeApi api, final 
AzureComputeConstants azureComputeConstants,
          CleanupResources cleanupResources, @Region Supplier<Set<String>> 
regionIds,
-         @Named(TIMEOUT_NODE_RUNNING) Predicate<String> nodeRunningPredicate,
          @Named("PublicIpAvailable") Predicate<String> publicIpAvailable) {
       this.api = api;
       this.azureComputeConstants = azureComputeConstants;
       this.azureGroup = azureComputeConstants.azureResourceGroup();
-
-      logger.debug("AzureComputeServiceAdapter set azuregroup to: " + 
azureGroup);
-
       this.cleanupResources = cleanupResources;
       this.regionIds = regionIds;
-      this.nodeRunningPredicate = nodeRunningPredicate;
       this.publicIpAvailable = publicIpAvailable;
    }
 
@@ -136,7 +126,7 @@ public class AzureComputeServiceAdapter implements 
ComputeServiceAdapter<Virtual
       NetworkInterfaceCard nic = createNetworkInterfaceCard(subnetId, name, 
locationName); 
       StorageProfile storageProfile = createStorageProfile(name, 
template.getImage(), templateOptions.getBlob());
       HardwareProfile hardwareProfile = 
HardwareProfile.builder().vmSize(template.getHardware().getId()).build();
-      OSProfile osProfile = createOsProfile(name, templateOptions);
+      OSProfile osProfile = createOsProfile(name, template);
       NetworkProfile networkProfile = 
NetworkProfile.builder().networkInterfaces(ImmutableList.of(IdReference.create(nic.id()))).build();
       VirtualMachineProperties virtualMachineProperties = 
VirtualMachineProperties.builder()
               .licenseType(null) // TODO
@@ -149,9 +139,6 @@ public class AzureComputeServiceAdapter implements 
ComputeServiceAdapter<Virtual
 
       VirtualMachine virtualMachine = 
api.getVirtualMachineApi(azureGroup).create(name, 
template.getLocation().getId(), virtualMachineProperties);
 
-      //Poll until resource is ready to be used
-      nodeRunningPredicate.apply(virtualMachine.name());
-
       // Safe to pass null credentials here, as jclouds will default populate 
the node with the default credentials from the image, or the ones in the 
options, if provided.
       return new NodeAndInitialCredentials<VirtualMachine>(virtualMachine, 
name, null);
    }
@@ -188,7 +175,8 @@ public class AzureComputeServiceAdapter implements 
ComputeServiceAdapter<Virtual
          for (SKU sku : skuList) {
             Iterable<Version> versionList = 
osImageApi.listVersions(publisherName, offer.name(), sku.name());
             for (Version version : versionList) {
-               VMImage vmImage = VMImage.create(publisherName, offer.name(), 
sku.name(), version.name(), location);
+               VMImage vmImage = 
VMImage.azureImage().publisher(publisherName).offer(offer.name()).sku(sku.name())
+                     .version(version.name()).location(location).build();
                osImagesRef.add(vmImage);
             }
          }
@@ -217,10 +205,10 @@ public class AzureComputeServiceAdapter implements 
ComputeServiceAdapter<Virtual
       for (StorageService storage : storages) {
          String name = storage.name();
          StorageService storageService = 
api.getStorageAccountApi(azureGroup).get(name);
-         if (storageService != null && 
storageService.storageServiceProperties().provisioningState().equals("Succeded"))
 {
+         if (storageService != null && Status.Succeeded == 
storageService.storageServiceProperties().provisioningState()) {
             String key = 
api.getStorageAccountApi(azureGroup).getKeys(name).key1();
-            List<VMImage> images = BlobHelper.getImages("jclouds", azureGroup, 
storage.name(), key,
-                    "custom", storage.location());
+            List<VMImage> images = BlobHelper.getImages(CONTAINER_NAME, 
azureGroup, storage.name(), key,
+                  CUSTOM_IMAGE_OFFER, storage.location());
             osImages.addAll(images);
          }
       }
@@ -230,14 +218,21 @@ public class AzureComputeServiceAdapter implements 
ComputeServiceAdapter<Virtual
 
    @Override
    public VMImage getImage(final String id) {
-      VMImage image = VMImageToImage.decodeFieldsFromUniqueId(id);
+      VMImage image = decodeFieldsFromUniqueId(id);
       if (image.custom()) {
+         VMImage customImage = null;
          String key = 
api.getStorageAccountApi(azureGroup).getKeys(image.storage()).key1();
-         if (BlobHelper.customImageExists(image.storage(), key))
-            return image;
-         else
-            return null;
-
+         if (BlobHelper.customImageExists(image.storage(), key)) {
+            List<VMImage> customImagesInStorage = 
BlobHelper.getImages(CONTAINER_NAME, azureGroup, image.storage(), key,
+                  CUSTOM_IMAGE_OFFER, image.location());
+            customImage = find(customImagesInStorage, new Predicate<VMImage>() 
{
+               @Override public boolean apply(VMImage input) {
+                  return id.equals(encodeFieldsToUniqueIdCustom(input));
+               }
+            }, null);
+         }
+         
+         return customImage;
       }
 
       String location = image.location();
@@ -248,7 +243,8 @@ public class AzureComputeServiceAdapter implements 
ComputeServiceAdapter<Virtual
       OSImageApi osImageApi = api.getOSImageApi(location);
       List<Version> versions = osImageApi.listVersions(publisher, offer, sku);
       if (!versions.isEmpty()) {
-         return VMImage.create(publisher, offer, sku, versions.get(0).name(), 
location);
+         return 
VMImage.azureImage().publisher(publisher).offer(offer).sku(sku).version(versions.get(0).name())
+               .location(location).build();
       }
       return null;
    }
@@ -328,20 +324,19 @@ public class AzureComputeServiceAdapter implements 
ComputeServiceAdapter<Virtual
    }
 
 
-   private OSProfile createOsProfile(String computerName, AzureTemplateOptions 
templateOptions) {
-      Iterable<String> splittedImageLoginUser = 
Splitter.on(":").split(AzureComputeProviderMetadata.defaultProperties().getProperty(IMAGE_LOGIN_USER));
-      String defaultLoginUser = Iterables.get(splittedImageLoginUser, 0);
-      String defaultLoginPassword = Iterables.get(splittedImageLoginUser, 1);
-      String adminUsername = 
Objects.firstNonNull(templateOptions.getLoginUser(), defaultLoginUser);
-      String adminPassword = 
Objects.firstNonNull(templateOptions.getLoginPassword(), defaultLoginPassword);
+   private OSProfile createOsProfile(String computerName, Template template) {
+      String defaultLoginUser = 
template.getImage().getDefaultCredentials().getUser();
+      String defaultLoginPassword = 
template.getImage().getDefaultCredentials().getOptionalPassword().get();
+      String adminUsername = 
Objects.firstNonNull(template.getOptions().getLoginUser(), defaultLoginUser);
+      String adminPassword = 
Objects.firstNonNull(template.getOptions().getLoginPassword(), 
defaultLoginPassword);
       OSProfile.Builder builder = 
OSProfile.builder().adminUsername(adminUsername).computerName(computerName);
       // prefer public key over password
-      if (templateOptions.getPublicKey() != null) {
+      if (template.getOptions().getPublicKey() != null) {
          OSProfile.LinuxConfiguration linuxConfiguration = 
OSProfile.LinuxConfiguration.create("true",
                  OSProfile.LinuxConfiguration.SSH.create(ImmutableList.of(
                          OSProfile.LinuxConfiguration.SSH.SSHPublicKey.create(
                                  
String.format("/home/%s/.ssh/authorized_keys", adminUsername),
-                                 templateOptions.getPublicKey())
+                                 template.getOptions().getPublicKey())
                  ))
          );
          builder.linuxConfiguration(linuxConfiguration);
@@ -362,10 +357,8 @@ public class AzureComputeServiceAdapter implements 
ComputeServiceAdapter<Virtual
 
       String publicIpAddressName = "public-address-" + name;
       PublicIPAddress ip = ipApi.createOrUpdate(publicIpAddressName, 
locationName, ImmutableMap.of("jclouds", name), properties);
-      publicIpAvailable.apply(publicIpAddressName);
-      // Refresh after last polling
-      ip = api.getPublicIPAddressApi(azureGroup).get(publicIpAddressName);
-      checkState(ip.properties().provisioningState().equals("Succeeded"),
+      
+      checkState(publicIpAvailable.apply(publicIpAddressName),
             "Public IP was not provisioned in the configured timeout");
 
       final NetworkInterfaceCardProperties networkInterfaceCardProperties =
@@ -386,14 +379,29 @@ public class AzureComputeServiceAdapter implements 
ComputeServiceAdapter<Virtual
    }
 
    private StorageProfile createStorageProfile(String name, Image image, 
String blob) {
-      ImageReference imageReference = ImageReference.builder()
-              .publisher(image.getProviderId())
-              .offer(image.getName())
-              .sku(image.getVersion())
-              .version("latest")
-              .build();
+      VMImage imageRef = decodeFieldsFromUniqueId(image.getId());
+      ImageReference imageReference = null;
+      VHD sourceImage = null;
+      String osType = null;
+      
+      if (!imageRef.custom()) {
+         imageReference = ImageReference.builder()
+               .publisher(image.getProviderId())
+               .offer(image.getName())
+               .sku(image.getVersion())
+               .version("latest")
+               .build();
+      } else {
+         sourceImage = VHD.create(image.getProviderId());
+
+         // TODO: read the ostype from the image blob
+         OsFamily osFamily = image.getOperatingSystem().getFamily();
+         osType = osFamily == OsFamily.WINDOWS ? "Windows" : "Linux";
+      }
+      
       VHD vhd = VHD.create(blob + "vhds/" + name + ".vhd");
-      OSDisk osDisk = OSDisk.create(null, name, vhd, "ReadWrite", "FromImage", 
null);
+      OSDisk osDisk = OSDisk.create(osType, name, vhd, "ReadWrite", 
"FromImage", sourceImage);
+      
       return StorageProfile.create(imageReference, osDisk, 
ImmutableList.<DataDisk>of());
    }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/22663315/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/config/AzureComputeServiceContextModule.java
----------------------------------------------------------------------
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 7af1730..24f02ea 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,10 +16,27 @@
  */
 package org.jclouds.azurecompute.arm.compute.config;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+import static 
org.jclouds.azurecompute.arm.config.AzureComputeProperties.DEFAULT_DATADISKSIZE;
+import static 
org.jclouds.azurecompute.arm.config.AzureComputeProperties.DEFAULT_SUBNET_ADDRESS_PREFIX;
+import static 
org.jclouds.azurecompute.arm.config.AzureComputeProperties.DEFAULT_VNET_ADDRESS_SPACE_PREFIX;
+import static 
org.jclouds.azurecompute.arm.config.AzureComputeProperties.IMAGE_PUBLISHERS;
+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.OPERATION_TIMEOUT;
+import static 
org.jclouds.azurecompute.arm.config.AzureComputeProperties.RESOURCE_GROUP_NAME;
+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.TIMEOUT_RESOURCE_DELETED;
+import static 
org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_IMAGE_AVAILABLE;
+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.util.Predicates2.retry;
+
 import java.net.URI;
 import java.util.List;
 
-import javax.annotation.Resource;
 import javax.inject.Named;
 import javax.inject.Singleton;
 
@@ -28,6 +45,7 @@ import 
org.jclouds.azurecompute.arm.compute.AzureComputeService;
 import org.jclouds.azurecompute.arm.compute.AzureComputeServiceAdapter;
 import 
org.jclouds.azurecompute.arm.compute.extensions.AzureComputeImageExtension;
 import org.jclouds.azurecompute.arm.compute.functions.LocationToLocation;
+import 
org.jclouds.azurecompute.arm.compute.functions.ResourceDefinitionToCustomImage;
 import org.jclouds.azurecompute.arm.compute.functions.VMHardwareToHardware;
 import org.jclouds.azurecompute.arm.compute.functions.VMImageToImage;
 import 
org.jclouds.azurecompute.arm.compute.functions.VirtualMachineToNodeMetadata;
@@ -39,7 +57,8 @@ import org.jclouds.azurecompute.arm.domain.ResourceDefinition;
 import org.jclouds.azurecompute.arm.domain.VMHardware;
 import org.jclouds.azurecompute.arm.domain.VMImage;
 import org.jclouds.azurecompute.arm.domain.VirtualMachine;
-import 
org.jclouds.azurecompute.arm.domain.VirtualMachineProperties.ProvisioningState;
+import org.jclouds.azurecompute.arm.domain.VirtualMachineInstance;
+import 
org.jclouds.azurecompute.arm.domain.VirtualMachineInstance.VirtualMachineStatus.PowerState;
 import org.jclouds.azurecompute.arm.functions.ParseJobStatus;
 import org.jclouds.compute.ComputeService;
 import org.jclouds.compute.ComputeServiceAdapter;
@@ -50,11 +69,9 @@ import org.jclouds.compute.extensions.ImageExtension;
 import org.jclouds.compute.functions.NodeAndTemplateOptionsToStatement;
 import 
org.jclouds.compute.functions.NodeAndTemplateOptionsToStatementWithoutPublicKey;
 import org.jclouds.compute.options.TemplateOptions;
-import org.jclouds.compute.reference.ComputeServiceConstants;
 import org.jclouds.compute.reference.ComputeServiceConstants.PollPeriod;
 import org.jclouds.compute.reference.ComputeServiceConstants.Timeouts;
 import org.jclouds.compute.strategy.CreateNodesInGroupThenAddToSet;
-import org.jclouds.logging.Logger;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
@@ -62,37 +79,18 @@ import com.google.common.base.Predicate;
 import com.google.inject.Inject;
 import com.google.inject.Provides;
 import com.google.inject.TypeLiteral;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static 
org.jclouds.azurecompute.arm.config.AzureComputeProperties.DEFAULT_DATADISKSIZE;
-import static 
org.jclouds.azurecompute.arm.config.AzureComputeProperties.DEFAULT_SUBNET_ADDRESS_PREFIX;
-import static 
org.jclouds.azurecompute.arm.config.AzureComputeProperties.DEFAULT_VNET_ADDRESS_SPACE_PREFIX;
-import static 
org.jclouds.azurecompute.arm.config.AzureComputeProperties.IMAGE_PUBLISHERS;
-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.OPERATION_TIMEOUT;
-import static 
org.jclouds.azurecompute.arm.config.AzureComputeProperties.RESOURCE_GROUP_NAME;
-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.TIMEOUT_RESOURCE_DELETED;
-import static 
org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_IMAGE_AVAILABLE;
-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.util.Predicates2.retry;
+import com.google.inject.assistedinject.FactoryModuleBuilder;
 
 public class AzureComputeServiceContextModule
         extends ComputeServiceAdapterContextModule<VirtualMachine, VMHardware, 
VMImage, Location> {
 
-   @Resource
-   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
-   protected Logger logger = Logger.NULL;
-
    @Override
    protected void configure() {
       super.configure();
+      
       bind(new TypeLiteral<ComputeServiceAdapter<VirtualMachine, VMHardware, 
VMImage, Location>>() {
       }).to(AzureComputeServiceAdapter.class);
+      
       bind(new TypeLiteral<Function<VMImage, 
org.jclouds.compute.domain.Image>>() {
       }).to(VMImageToImage.class);
       bind(new TypeLiteral<Function<VMHardware, Hardware>>() {
@@ -102,12 +100,16 @@ public class AzureComputeServiceContextModule
       bind(new TypeLiteral<Function<Location, org.jclouds.domain.Location>>() {
       }).to(LocationToLocation.class);
       bind(ComputeService.class).to(AzureComputeService.class);
+      
       install(new LocationsFromComputeServiceAdapterModule<VirtualMachine, 
VMHardware, VMImage, Location>() {
       });
+      
+      install(new 
FactoryModuleBuilder().build(ResourceDefinitionToCustomImage.Factory.class));
 
       bind(TemplateOptions.class).to(AzureTemplateOptions.class);
       
bind(NodeAndTemplateOptionsToStatement.class).to(NodeAndTemplateOptionsToStatementWithoutPublicKey.class);
       
bind(CreateNodesInGroupThenAddToSet.class).to(CreateResourceGroupThenCreateNodes.class);
+      
       bind(new TypeLiteral<ImageExtension>() {
       }).to(AzureComputeImageExtension.class);
    }
@@ -200,7 +202,7 @@ public class AzureComputeServiceContextModule
    @com.google.inject.name.Named(TIMEOUT_NODE_RUNNING)
    protected Predicate<String> provideVirtualMachineRunningPredicate(final 
AzureComputeApi api, final 
AzureComputeServiceContextModule.AzureComputeConstants azureComputeConstants, 
Timeouts timeouts, PollPeriod pollPeriod) {
       String azureGroup = azureComputeConstants.azureResourceGroup();
-      return retry(new VirtualMachineInStatePredicate(api, azureGroup, 
ProvisioningState.SUCCEEDED), timeouts.nodeRunning,
+      return retry(new VirtualMachineInStatePredicate(api, azureGroup, 
PowerState.RUNNING), timeouts.nodeRunning,
               pollPeriod.pollInitialPeriod, pollPeriod.pollMaxPeriod);
    }
    
@@ -230,7 +232,7 @@ public class AzureComputeServiceContextModule
    protected Predicate<String> provideNodeSuspendedPredicate(final 
AzureComputeApi api, final 
AzureComputeServiceContextModule.AzureComputeConstants azureComputeConstants,
                                                              Timeouts 
timeouts, PollPeriod pollPeriod) {
       String azureGroup = azureComputeConstants.azureResourceGroup();
-      return retry(new VirtualMachineInStatePredicate(api, azureGroup, 
ProvisioningState.DELETED), timeouts.nodeTerminated,
+      return retry(new VirtualMachineInStatePredicate(api, azureGroup, 
PowerState.STOPPED), timeouts.nodeTerminated,
               pollPeriod.pollInitialPeriod, pollPeriod.pollMaxPeriod);
    }
    
@@ -283,21 +285,20 @@ public class AzureComputeServiceContextModule
 
       private final AzureComputeApi api;
       private final String azureGroup;
-      private final ProvisioningState provisioningState;
+      private final PowerState powerState;
 
-      public VirtualMachineInStatePredicate(AzureComputeApi api, String 
azureGroup, ProvisioningState provisioningState) {
+      public VirtualMachineInStatePredicate(AzureComputeApi api, String 
azureGroup, PowerState powerState) {
          this.api = checkNotNull(api, "api must not be null");
          this.azureGroup = checkNotNull(azureGroup, "azuregroup must not be 
null");
-         this.provisioningState = provisioningState;
+         this.powerState = checkNotNull(powerState, "powerState must not be 
null");
       }
 
       @Override
       public boolean apply(String name) {
          checkNotNull(name, "name cannot be null");
-         VirtualMachine virtualMachine = 
api.getVirtualMachineApi(this.azureGroup).get(name);
-         if (virtualMachine == null) return false;
-         ProvisioningState state = 
virtualMachine.properties().provisioningState();
-         return state == provisioningState;
+         VirtualMachineInstance vmInstance = 
api.getVirtualMachineApi(this.azureGroup).getInstanceDetails(name);
+         if (vmInstance == null) return false;
+         return powerState == vmInstance.powerState();
       }
    }
    

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/22663315/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeImageExtension.java
----------------------------------------------------------------------
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
index 786c17a..5817e9e 100644
--- 
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
@@ -16,141 +16,132 @@
  */
 package org.jclouds.azurecompute.arm.compute.extensions;
 
-import com.google.common.base.Predicate;
-import com.google.common.base.Supplier;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.common.util.concurrent.UncheckedTimeoutException;
-import com.google.gson.internal.LinkedTreeMap;
-import com.google.inject.Inject;
-import com.google.inject.name.Named;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static 
org.jclouds.azurecompute.arm.compute.functions.VMImageToImage.decodeFieldsFromUniqueId;
+import static 
org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_IMAGE_AVAILABLE;
+import static 
org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
+
+import java.net.URI;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import javax.annotation.Resource;
+
 import org.jclouds.Constants;
-import org.jclouds.View;
 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.compute.config.AzureComputeServiceContextModule.AzureComputeConstants;
+import 
org.jclouds.azurecompute.arm.compute.functions.ResourceDefinitionToCustomImage;
 import org.jclouds.azurecompute.arm.domain.ResourceDefinition;
 import org.jclouds.azurecompute.arm.domain.StorageServiceKeys;
 import org.jclouds.azurecompute.arm.domain.VMImage;
-import org.jclouds.azurecompute.arm.domain.VirtualMachine;
-import static java.lang.String.format;
+import org.jclouds.azurecompute.arm.functions.CleanupResources;
 import org.jclouds.azurecompute.arm.util.BlobHelper;
 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 org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.logging.Logger;
 
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Callable;
+import com.google.common.base.Predicate;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
 
-import static 
org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_IMAGE_AVAILABLE;
-import static 
org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
+public class AzureComputeImageExtension implements ImageExtension {
+   public static final String CONTAINER_NAME = "jclouds";
+   public static final String CUSTOM_IMAGE_OFFER = "custom";
 
+   @Resource
+   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
 
-public class AzureComputeImageExtension implements ImageExtension {
    private final AzureComputeApi api;
-   private final ListeningExecutorService userExecutor;
-   private final Supplier<View> blobstore = null;
    private final String group;
+   private final ListeningExecutorService userExecutor;
    private final Predicate<URI> imageAvailablePredicate;
    private final Predicate<String> nodeSuspendedPredicate;
-   private final AzureComputeConstants azureComputeConstants;
-   private final VMImageToImage imageReferenceToImage;
-   public static final String CONTAINER_NAME = "jclouds";
-   public static final String CUSTOM_IMAGE_PREFIX = "#";
+   private final ResourceDefinitionToCustomImage.Factory 
resourceDefinitionToImage;
+   private final CleanupResources cleanupResources;
 
    @Inject
    AzureComputeImageExtension(AzureComputeApi api,
-                              @Named(TIMEOUT_IMAGE_AVAILABLE) Predicate<URI> 
imageAvailablePredicate,
-                              @Named(TIMEOUT_NODE_SUSPENDED) Predicate<String> 
nodeSuspendedPredicate,
-                              final 
AzureComputeServiceContextModule.AzureComputeConstants azureComputeConstants,
-                              @Named(Constants.PROPERTY_USER_THREADS) 
ListeningExecutorService userExecutor,
-                              VMImageToImage imageReferenceToImage) {
-      this.userExecutor = userExecutor;
-      this.group = azureComputeConstants.azureResourceGroup();
-      this.imageReferenceToImage = imageReferenceToImage;
+         @Named(TIMEOUT_IMAGE_AVAILABLE) Predicate<URI> 
imageAvailablePredicate,
+         @Named(TIMEOUT_NODE_SUSPENDED) Predicate<String> 
nodeSuspendedPredicate,
+         AzureComputeConstants azureComputeConstants,
+         @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService 
userExecutor,
+         ResourceDefinitionToCustomImage.Factory resourceDefinitionToImage,
+         CleanupResources cleanupResources) {
+      this.api = api;
       this.imageAvailablePredicate = imageAvailablePredicate;
       this.nodeSuspendedPredicate = nodeSuspendedPredicate;
-      this.azureComputeConstants = azureComputeConstants;
-      this.api = api;
+      this.group = azureComputeConstants.azureResourceGroup();
+      this.userExecutor = userExecutor;
+      this.resourceDefinitionToImage = resourceDefinitionToImage;
+      this.cleanupResources = cleanupResources;
    }
 
    @Override
    public ImageTemplate buildImageTemplateFromNode(String name, String id) {
-      String nameLowerCase = name.toLowerCase();
-      return new 
ImageTemplateBuilder.CloneImageTemplateBuilder().nodeId(id).name(nameLowerCase).build();
+      return new 
ImageTemplateBuilder.CloneImageTemplateBuilder().nodeId(id).name(name.toLowerCase()).build();
    }
 
    @Override
    public ListenableFuture<Image> createImage(ImageTemplate template) {
-
-
       final CloneImageTemplate cloneTemplate = (CloneImageTemplate) template;
       final String id = cloneTemplate.getSourceNodeId();
       final String name = cloneTemplate.getName();
-      final String storageAccountName = id.replaceAll("[^A-Za-z0-9 ]", "") + 
"stor";
 
+      logger.debug(">> stopping node %s...", id);
       api.getVirtualMachineApi(group).stop(id);
-      if (nodeSuspendedPredicate.apply(id)) {
-         return userExecutor.submit(new Callable<Image>() {
-            @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<ResourceDefinition> definitions = 
api.getJobApi().captureStatus(uri);
-                     if (definitions != null) {
-                        for (ResourceDefinition definition : definitions) {
-                           LinkedTreeMap<String, String> properties = 
(LinkedTreeMap<String, String>) definition.properties();
-                           Object storageObject = 
properties.get("storageProfile");
-                           LinkedTreeMap<String, String> properties2 = 
(LinkedTreeMap<String, String>) storageObject;
-                           Object osDiskObject = properties2.get("osDisk");
-                           LinkedTreeMap<String, String> osProperties = 
(LinkedTreeMap<String, String>) osDiskObject;
-                           Object dataDisksObject = 
properties2.get("dataDisks");
-                           ArrayList<Object> dataProperties = 
(ArrayList<Object>) dataDisksObject;
-                           LinkedTreeMap<String, String> datadiskObject = 
(LinkedTreeMap<String, String>) dataProperties.get(0);
-
-                           disks[0] = osProperties.get("name");
-                           disks[1] = datadiskObject.get("name");
-
-                           VirtualMachine vm = 
api.getVirtualMachineApi(group).get(id);
-                           final VMImage ref = VMImage.create(group, 
storageAccountName, disks[0], disks[1], name, "custom", vm.location());
-                           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);
-      }
+      checkState(nodeSuspendedPredicate.apply(id), "Node %s was not suspended 
within the configured time limit", id);
+
+      return userExecutor.submit(new Callable<Image>() {
+         @Override
+         public Image call() throws Exception {
+            logger.debug(">> generalizing virtal machine %s...", id);
+            api.getVirtualMachineApi(group).generalize(id);
+
+            logger.debug(">> capturing virtual machine %s to container %s...", 
id, CONTAINER_NAME);
+            URI uri = api.getVirtualMachineApi(group).capture(id, 
cloneTemplate.getName(), CONTAINER_NAME);
+            checkState(uri != null && imageAvailablePredicate.apply(uri),
+                  "Image %s was not created within the configured time limit", 
cloneTemplate.getName());
+
+            List<ResourceDefinition> definitions = 
api.getJobApi().captureStatus(uri);
+            checkState(definitions.size() == 1,
+                  "Expected one resource definition after creating the image 
but %s were returned", definitions.size());
+
+            Image image =  resourceDefinitionToImage.create(id, 
name).apply(definitions.get(0));
+            logger.debug(">> created %s", image);
+            return image;
+         }
+      });
    }
 
    @Override
    public boolean deleteImage(String id) {
-
-      VMImage image = VMImageToImage.decodeFieldsFromUniqueId(id);
-      if (image.custom()) {
-         StorageServiceKeys keys = 
api.getStorageAccountApi(image.group()).getKeys(image.storage());
-
-         // This removes now all the images in this storage. At least in 
theory, there should be just one and if there is
-         // more, they should be copies of each other.
-         BlobHelper.deleteContainerIfExists(image.storage(), keys.key1(), 
"system");
-         return !BlobHelper.customImageExists(image.storage(), keys.key1());
+      VMImage image = decodeFieldsFromUniqueId(id);
+      checkArgument(image.custom(), "Only custom images can be deleted");
+      
+      logger.debug(">> deleting image %s", id);
+
+      StorageServiceKeys keys = 
api.getStorageAccountApi(image.group()).getKeys(image.storage());
+      // This removes now all the images in this storage. At least in theory,
+      // there should be just one and if there is
+      // more, they should be copies of each other.
+      // TODO: Reuse the blobstore context in these two calls
+      BlobHelper.deleteContainerIfExists(image.storage(), keys.key1(), 
"system");
+      boolean result = !BlobHelper.customImageExists(image.storage(), 
keys.key1());
+
+      if (!BlobHelper.hasContainers(image.storage(), keys.key1())) {
+         logger.debug(">> storage account is empty after deleting the custom 
image. Deleting the storage account...");
+         api.getStorageAccountApi(image.group()).delete(image.storage());
+         cleanupResources.deleteResourceGroupIfEmpty(image.group());
       }
 
-      return false;
+      return result;
    }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/22663315/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/ResourceDefinitionToCustomImage.java
----------------------------------------------------------------------
diff --git 
a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/ResourceDefinitionToCustomImage.java
 
b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/ResourceDefinitionToCustomImage.java
new file mode 100644
index 0000000..bf744eb
--- /dev/null
+++ 
b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/ResourceDefinitionToCustomImage.java
@@ -0,0 +1,76 @@
+/*
+ * 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.functions;
+
+import static 
org.jclouds.azurecompute.arm.compute.extensions.AzureComputeImageExtension.CUSTOM_IMAGE_OFFER;
+
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import org.jclouds.azurecompute.arm.AzureComputeApi;
+import 
org.jclouds.azurecompute.arm.compute.config.AzureComputeServiceContextModule.AzureComputeConstants;
+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.Image;
+
+import com.google.common.base.Function;
+import com.google.inject.assistedinject.Assisted;
+
+public class ResourceDefinitionToCustomImage implements 
Function<ResourceDefinition, Image> {
+
+   public interface Factory {
+      ResourceDefinitionToCustomImage create(@Assisted("nodeId") String 
nodeId, @Assisted("imageName") String imageName);
+   }
+
+   private final String resourceGroup;
+   private final Function<VMImage, Image> vmImageToImage;
+   private final String imageName;
+   private final String storageAccountName;
+   private final VirtualMachine vm;
+
+   @Inject
+   ResourceDefinitionToCustomImage(AzureComputeApi api, AzureComputeConstants 
azureComputeConstants,
+         Function<VMImage, Image> vmImageToImage, @Assisted("nodeId") String 
nodeId,
+         @Assisted("imageName") String imageName) {
+      this.vmImageToImage = vmImageToImage;
+      this.resourceGroup = azureComputeConstants.azureResourceGroup();
+      this.imageName = imageName;
+      this.storageAccountName = nodeId.replaceAll("[^A-Za-z0-9 ]", "") + 
"stor";
+      this.vm = api.getVirtualMachineApi(resourceGroup).get(nodeId);
+   }
+
+   @SuppressWarnings("unchecked")
+   @Override
+   public Image apply(ResourceDefinition input) {
+      VMImage.Builder builder = 
VMImage.customImage().group(resourceGroup).storage(storageAccountName).name(imageName)
+            .offer(CUSTOM_IMAGE_OFFER).location(vm.location());
+
+      Map<String, String> properties = (Map<String, String>) 
input.properties();
+
+      Object storageObject = properties.get("storageProfile");
+      Map<String, String> storageProperties = (Map<String, String>) 
storageObject;
+
+      Object osDiskObject = storageProperties.get("osDisk");
+      Map<String, String> osProperties = (Map<String, String>) osDiskObject;
+      builder.vhd1(osProperties.get("name"));
+
+      return vmImageToImage.apply(builder.build());
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/22663315/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/VMImageToImage.java
----------------------------------------------------------------------
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 5c10654..9d9eceb 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
@@ -59,7 +59,7 @@ public class VMImageToImage implements Function<VMImage, 
Image> {
    }
 
    public static String encodeFieldsToUniqueIdCustom(VMImage imageReference){
-      return (imageReference.globallyAvailable() ? "global" : 
imageReference.location()) + "/" + imageReference.group() + "/" + 
imageReference.storage() + "/" + imageReference.vhd1() + "/" + 
imageReference.offer();
+      return (imageReference.globallyAvailable() ? "global" : 
imageReference.location()) + "/" + imageReference.group() + "/" + 
imageReference.storage() + "/" + imageReference.offer() + "/" + 
imageReference.name();
    }
 
    public static VMImage decodeFieldsFromUniqueId(final String id) {
@@ -71,10 +71,10 @@ public class VMImageToImage implements Function<VMImage, 
Image> {
          0: imageReference.location) + "/" +
          1: imageReference.group + "/" +
          2: imageReference.storage + "/" +
-         3: imageReference.vhd1 + "/" +
-         4: imageReference.offer
+         3: imageReference.offer + "/" +
+         4: imageReference.name
          */
-         vmImage = VMImage.create(fields[1], fields[2], fields[3], null, null, 
fields[4], fields[0]);
+         vmImage = 
VMImage.customImage().location(fields[0]).group(fields[1]).storage(fields[2]).vhd1(fields[3]).offer(fields[4]).build();
       } else {
          /* id fields indexes
          0: imageReference.location) + "/" +
@@ -82,7 +82,7 @@ public class VMImageToImage implements Function<VMImage, 
Image> {
          2: imageReference.offer + "/" +
          3: imageReference.sku + "/" +
          */
-         vmImage = VMImage.create(fields[1], fields[2], fields[3], null, 
fields[0]);
+         vmImage = 
VMImage.azureImage().location(fields[0]).publisher(fields[1]).offer(fields[2]).sku(fields[3]).build();
       }
       return vmImage;
    }
@@ -94,24 +94,21 @@ public class VMImageToImage implements Function<VMImage, 
Image> {
 
    @Override
    public Image apply(final VMImage image) {
-
       if (image.custom()) {
-
          final ImageBuilder builder = new ImageBuilder()
                .location(FluentIterable.from(locations.get())
                      .firstMatch(LocationPredicates.idEquals(image.location()))
                      .get())
                .name(image.name())
-               .description("#" + image.group())
+               .description(image.group())
                .status(Image.Status.AVAILABLE)
-               .version(image.storage())
+               .version("latest")
                .providerId(image.vhd1())
                .id(encodeFieldsToUniqueIdCustom(image));
 
          final OperatingSystem.Builder osBuilder = osFamily().apply(image);
          Image retimage = builder.operatingSystem(osBuilder.build()).build();
          return retimage;
-
       }
       else {
          final ImageBuilder builder = new ImageBuilder()
@@ -154,16 +151,12 @@ public class VMImageToImage implements Function<VMImage, 
Image> {
                family = OsFamily.RHEL;
             }
 
-            String sku = image.sku();
-            if (image.custom())
-               sku = image.vhd1();
-
             // only 64bit OS images are supported by Azure ARM
             return OperatingSystem.builder().
                   family(family).
                   is64Bit(true).
-                  description(sku).
-                  version(sku);
+                  description(image.custom() ? image.vhd1() : image.sku()).
+                  version(image.custom() ? "latest" : image.sku());
          }
       };
    }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/22663315/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/VirtualMachineToNodeMetadata.java
----------------------------------------------------------------------
diff --git 
a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/VirtualMachineToNodeMetadata.java
 
b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/VirtualMachineToNodeMetadata.java
index 2c28954..de373b5 100644
--- 
a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/VirtualMachineToNodeMetadata.java
+++ 
b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/VirtualMachineToNodeMetadata.java
@@ -16,6 +16,15 @@
  */
 package org.jclouds.azurecompute.arm.compute.functions;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.find;
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.collect.Iterables.tryFind;
+import static 
org.jclouds.azurecompute.arm.compute.extensions.AzureComputeImageExtension.CONTAINER_NAME;
+import static 
org.jclouds.azurecompute.arm.compute.extensions.AzureComputeImageExtension.CUSTOM_IMAGE_OFFER;
+import static 
org.jclouds.azurecompute.arm.compute.functions.VMImageToImage.encodeFieldsToUniqueId;
+
+import java.net.URI;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -27,15 +36,18 @@ import javax.inject.Named;
 import org.jclouds.azurecompute.arm.AzureComputeApi;
 import 
org.jclouds.azurecompute.arm.compute.config.AzureComputeServiceContextModule.AzureComputeConstants;
 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.NetworkInterfaceCard;
+import org.jclouds.azurecompute.arm.domain.StorageProfile;
+import org.jclouds.azurecompute.arm.domain.StorageServiceKeys;
+import org.jclouds.azurecompute.arm.domain.VMImage;
 import org.jclouds.azurecompute.arm.domain.VirtualMachine;
 import org.jclouds.azurecompute.arm.domain.VirtualMachineInstance;
 import 
org.jclouds.azurecompute.arm.domain.VirtualMachineInstance.VirtualMachineStatus;
 import 
org.jclouds.azurecompute.arm.domain.VirtualMachineInstance.VirtualMachineStatus.PowerState;
 import org.jclouds.azurecompute.arm.domain.VirtualMachineProperties;
 import 
org.jclouds.azurecompute.arm.domain.VirtualMachineProperties.ProvisioningState;
+import org.jclouds.azurecompute.arm.util.BlobHelper;
 import org.jclouds.collect.Memoized;
 import org.jclouds.compute.domain.Hardware;
 import org.jclouds.compute.domain.Image;
@@ -59,10 +71,6 @@ import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.collect.Iterables.find;
-import static com.google.common.collect.Iterables.transform;
-
 public class VirtualMachineToNodeMetadata  implements Function<VirtualMachine, 
NodeMetadata> {
 
    @Resource
@@ -105,12 +113,14 @@ public class VirtualMachineToNodeMetadata  implements 
Function<VirtualMachine, N
    private final Supplier<Set<? extends Location>> locations;
    private final Supplier<Map<String, ? extends Hardware>> hardwares;
    private final Map<String, Credentials> credentialStore;
+   private final Function<VMImage, Image> vmImageToImge;
 
    @Inject
    VirtualMachineToNodeMetadata(AzureComputeApi api, 
GroupNamingConvention.Factory namingConvention,
          Supplier<Map<String, ? extends Image>> images, Supplier<Map<String, ? 
extends Hardware>> hardwares,
          @Memoized Supplier<Set<? extends Location>> locations, Map<String, 
Credentials> credentialStore,
-         final AzureComputeConstants azureComputeConstants) {
+         final AzureComputeConstants azureComputeConstants,
+         Function<VMImage, Image> vmImageToImge) {
       this.api = api;
       this.nodeNamingConvention = namingConvention.createWithoutPrefix();
       this.images = checkNotNull(images, "images cannot be null");
@@ -118,6 +128,7 @@ public class VirtualMachineToNodeMetadata  implements 
Function<VirtualMachine, N
       this.hardwares = checkNotNull(hardwares, "hardwares cannot be null");
       this.credentialStore = credentialStore;
       this.azureGroup = azureComputeConstants.azureResourceGroup();
+      this.vmImageToImge = vmImageToImge;
    }
    @Override
    public NodeMetadata apply(VirtualMachine virtualMachine) {
@@ -158,8 +169,7 @@ public class VirtualMachineToNodeMetadata  implements 
Function<VirtualMachine, N
       String locationName = virtualMachine.location();
       builder.location(getLocation(locationName));
 
-      ImageReference imageReference = 
virtualMachine.properties().storageProfile().imageReference();
-      Optional<? extends Image> image = findImage(imageReference, 
locationName);
+      Optional<? extends Image> image = 
findImage(virtualMachine.properties().storageProfile(), locationName);
       if (image.isPresent()) {
          builder.imageId(image.get().getId());
          builder.operatingSystem(image.get().getOperatingSystem());
@@ -217,8 +227,28 @@ public class VirtualMachineToNodeMetadata  implements 
Function<VirtualMachine, N
       }, null);
    }
 
-   protected Optional<? extends Image> findImage(ImageReference 
imageReference, String locatioName) {
-      return 
Optional.fromNullable(images.get().get(VMImageToImage.encodeFieldsToUniqueId(false,
 locatioName, imageReference)));
+   protected Optional<? extends Image> findImage(final StorageProfile 
storageProfile, String locatioName) {
+      if (storageProfile.imageReference() != null) {
+         return 
Optional.fromNullable(images.get().get(encodeFieldsToUniqueId(false, 
locatioName, storageProfile.imageReference())));
+      } else {
+         String storageAccountNameURI = storageProfile.osDisk().vhd().uri();
+         String storageAccountName = 
Iterables.get(Splitter.on(".").split(URI.create(storageAccountNameURI).getHost()),
+               0);
+         StorageServiceKeys keys = 
api.getStorageAccountApi(azureGroup).getKeys(storageAccountName);
+
+         // Custom image. Let's find it by uri
+         List<VMImage> customImagesInStorage = 
BlobHelper.getImages(CONTAINER_NAME, azureGroup, storageAccountName,
+               keys.key1(), CUSTOM_IMAGE_OFFER, locatioName);
+         Optional<VMImage> customImage = tryFind(customImagesInStorage, new 
Predicate<VMImage>() {
+            @Override
+            public boolean apply(VMImage input) {
+               return 
input.vhd1().equals(storageProfile.osDisk().image().uri());
+            }
+         });
+
+         return customImage.isPresent() ? 
Optional.of(vmImageToImge.apply(customImage.get())) : Optional
+               .<Image> absent();
+      }
    }
 
    protected Hardware getHardware(final String vmSize) {

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/22663315/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/strategy/CreateResourceGroupThenCreateNodes.java
----------------------------------------------------------------------
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 0840689..99528fd 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
@@ -16,6 +16,11 @@
  */
 package org.jclouds.azurecompute.arm.compute.strategy;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static 
org.jclouds.azurecompute.arm.compute.functions.VMImageToImage.decodeFieldsFromUniqueId;
+import static org.jclouds.util.Predicates2.retry;
+
 import java.net.URI;
 import java.util.Arrays;
 import java.util.Map;
@@ -34,6 +39,7 @@ import 
org.jclouds.azurecompute.arm.compute.options.AzureTemplateOptions;
 import org.jclouds.azurecompute.arm.domain.ResourceGroup;
 import org.jclouds.azurecompute.arm.domain.StorageService;
 import org.jclouds.azurecompute.arm.domain.Subnet;
+import org.jclouds.azurecompute.arm.domain.VMImage;
 import org.jclouds.azurecompute.arm.domain.VirtualNetwork;
 import org.jclouds.azurecompute.arm.features.ResourceGroupApi;
 import org.jclouds.azurecompute.arm.features.SubnetApi;
@@ -58,10 +64,6 @@ import com.google.common.collect.Multimap;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-import static 
org.jclouds.azurecompute.arm.compute.extensions.AzureComputeImageExtension.CUSTOM_IMAGE_PREFIX;
-import static org.jclouds.util.Predicates2.retry;
-
 @Singleton
 public class CreateResourceGroupThenCreateNodes extends 
CreateNodesWithGroupEncodedIntoNameThenAddToSet {
 
@@ -163,9 +165,9 @@ public class CreateResourceGroupThenCreateNodes extends 
CreateNodesWithGroupEnco
 
    public StorageService getOrCreateStorageService(String name, String 
resourceGroupName, String locationName, Image image) {
       String storageAccountName = null;
-      String imageName = image.getName();
-      if (imageName.startsWith(CUSTOM_IMAGE_PREFIX)) {
-         storageAccountName = image.getVersion();
+      VMImage imageRef = decodeFieldsFromUniqueId(image.getId());
+      if (imageRef.custom()) {
+         storageAccountName = imageRef.storage();
       }
 
       if (Strings.isNullOrEmpty(storageAccountName)) {
@@ -177,15 +179,16 @@ public class CreateResourceGroupThenCreateNodes extends 
CreateNodesWithGroupEnco
 
       URI uri = 
api.getStorageAccountApi(resourceGroupName).create(storageAccountName, 
locationName, ImmutableMap.of("jclouds",
               name), ImmutableMap.of("accountType", 
StorageService.AccountType.Standard_LRS.toString()));
-      retry(new Predicate<URI>() {
+      boolean starageAccountCreated = retry(new Predicate<URI>() {
          @Override
          public boolean apply(URI uri) {
             return ParseJobStatus.JobStatus.DONE == 
api.getJobApi().jobStatus(uri);
          }
       }, 60 * 2 * 1000 /* 2 minutes timeout */).apply(uri);
       // TODO check provisioning state of the primary
-      storageService = 
api.getStorageAccountApi(resourceGroupName).get(storageAccountName);
-      return storageService;
+      checkState(starageAccountCreated, "Storage account %s was not created in 
the configured timeout",
+            storageAccountName);
+      return 
api.getStorageAccountApi(resourceGroupName).get(storageAccountName);
    }
 
    /**

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/22663315/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/VMImage.java
----------------------------------------------------------------------
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 c83eafe..e4f2301 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
@@ -16,9 +16,9 @@
  */
 package org.jclouds.azurecompute.arm.domain;
 
-import com.google.auto.value.AutoValue;
 import org.jclouds.javax.annotation.Nullable;
-import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
 
 @AutoValue
 public abstract class VMImage {
@@ -91,16 +91,35 @@ public abstract class VMImage {
     * True if custom image
     */
    public abstract boolean custom();
-
-   @SerializedNames({ "publisher", "offer", "sku", "version", "location"})
-   public static VMImage create(String publisher, String offer, String sku, 
String version, String location) {
-
-      return new AutoValue_VMImage(publisher, offer, sku, version, location, 
false, null, null, null, null, null, false);
+   
+   public static Builder builder() {
+      return new AutoValue_VMImage.Builder();
    }
-
-   @SerializedNames({ "group", "storage", "vhd1", "vhd2", "name", "offer", 
"location"})
-   public static VMImage create(String group, String storage, String vhd1, 
String vhd2, String name, String offer, String location) {
-
-      return new AutoValue_VMImage(null, offer, null, null, location, false, 
group, storage, vhd1, vhd2, name, true);
+   
+   public static Builder azureImage() {
+      return new 
AutoValue_VMImage.Builder().globallyAvailable(false).custom(false);
+   }
+   
+   public static Builder customImage() {
+      return new 
AutoValue_VMImage.Builder().globallyAvailable(false).custom(true);
+   }
+   
+   @AutoValue.Builder
+   public abstract static class Builder {
+      
+      public abstract Builder publisher(String published);
+      public abstract Builder offer(String offer);
+      public abstract Builder sku(String sku);
+      public abstract Builder version(String version);
+      public abstract Builder location(String location);
+      public abstract Builder globallyAvailable(boolean globallyAvailable);
+      public abstract Builder group(String group);
+      public abstract Builder storage(String storage);
+      public abstract Builder vhd1(String vhd1);
+      public abstract Builder vhd2(String vhd2);
+      public abstract Builder name(String name);
+      public abstract Builder custom(boolean custom);
+      
+      public abstract VMImage build();
    }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/22663315/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/functions/CleanupResources.java
----------------------------------------------------------------------
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 f44c761..5fda879 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
@@ -14,7 +14,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package  org.jclouds.azurecompute.arm.functions;
+package org.jclouds.azurecompute.arm.functions;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Predicates.notNull;
+import static com.google.common.collect.Iterables.filter;
+import static com.google.common.collect.Iterables.transform;
+import static 
org.jclouds.azurecompute.arm.config.AzureComputeProperties.TIMEOUT_RESOURCE_DELETED;
 
 import java.net.URI;
 import java.util.List;
@@ -27,10 +33,12 @@ import javax.inject.Singleton;
 
 import org.jclouds.azurecompute.arm.AzureComputeApi;
 import org.jclouds.azurecompute.arm.domain.IdReference;
+import org.jclouds.azurecompute.arm.domain.IpConfiguration;
+import org.jclouds.azurecompute.arm.domain.NetworkInterfaceCard;
 import org.jclouds.azurecompute.arm.domain.ResourceGroup;
-import org.jclouds.azurecompute.arm.domain.Subnet;
+import org.jclouds.azurecompute.arm.domain.StorageServiceKeys;
 import org.jclouds.azurecompute.arm.domain.VirtualMachine;
-import org.jclouds.azurecompute.arm.domain.VirtualNetwork;
+import org.jclouds.azurecompute.arm.util.BlobHelper;
 import org.jclouds.compute.reference.ComputeServiceConstants;
 import org.jclouds.logging.Logger;
 
@@ -42,9 +50,6 @@ import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-import static 
org.jclouds.azurecompute.arm.config.AzureComputeProperties.TIMEOUT_RESOURCE_DELETED;
-
 @Singleton
 public class CleanupResources implements Function<String, Boolean> {
 
@@ -53,113 +58,86 @@ public class CleanupResources implements Function<String, 
Boolean> {
    protected Logger logger = Logger.NULL;
 
    protected final AzureComputeApi api;
-   private Predicate<URI> resourceDeleted;
+   private final Predicate<URI> resourceDeleted;
 
    @Inject
-   public CleanupResources(AzureComputeApi azureComputeApi, 
@Named(TIMEOUT_RESOURCE_DELETED) Predicate<URI> resourceDeleted) {
+   CleanupResources(AzureComputeApi azureComputeApi, 
@Named(TIMEOUT_RESOURCE_DELETED) Predicate<URI> resourceDeleted) {
       this.api = azureComputeApi;
       this.resourceDeleted = resourceDeleted;
    }
 
    @Override
    public Boolean apply(final String id) {
-      logger.debug("Destroying %s ...", id);
+      logger.debug(">> destroying %s ...", id);
 
       Map<String, VirtualMachine> resourceGroupNamesAndVirtualMachines = 
getResourceGroupNamesAndVirtualMachines(id);
-      if (resourceGroupNamesAndVirtualMachines.isEmpty()) return true;
-      String group = 
checkNotNull(resourceGroupNamesAndVirtualMachines.entrySet().iterator().next().getKey(),
 "resourceGroup name must not be null");
-      VirtualMachine virtualMachine = 
checkNotNull(resourceGroupNamesAndVirtualMachines.get(group), "virtualMachine 
must not be null");
+      if (resourceGroupNamesAndVirtualMachines.isEmpty())
+         return true;
+
+      String group = 
checkNotNull(resourceGroupNamesAndVirtualMachines.entrySet().iterator().next().getKey(),
+            "resourceGroup name must not be null");
+      VirtualMachine virtualMachine = 
checkNotNull(resourceGroupNamesAndVirtualMachines.get(group),
+            "virtualMachine must not be null");
+
       boolean vmDeleted = deleteVirtualMachine(group, virtualMachine);
-      // delete networkCardInterfaces
-      List<String> nics = getNetworkCardInterfaceNames(virtualMachine);
-      for (String nicName : nics) {
+
+      for (String nicName : getNetworkCardInterfaceNames(virtualMachine)) {
+         NetworkInterfaceCard nic = 
api.getNetworkInterfaceCardApi(group).get(nicName);
+         Iterable<String> publicIps = getPublicIps(group, nic);
+
+         logger.debug(">> destroying nic %s...", nicName);
          URI nicDeletionURI = 
api.getNetworkInterfaceCardApi(group).delete(nicName);
-         // todo is a collection!
-         boolean nicDeleted = resourceDeleted.apply(nicDeletionURI);
-      }
-      // delete virtual networks
-      for (VirtualNetwork virtualNetwork : 
api.getVirtualNetworkApi(group).list()) {
-         for (Subnet subnet : virtualNetwork.properties().subnets()) {
-            // delete subnets
-            api.getSubnetApi(group, 
virtualNetwork.name()).delete(subnet.name());
+         resourceDeleted.apply(nicDeletionURI);
+
+         for (String publicIp : publicIps) {
+            logger.debug(">> deleting public ip nic %s...", publicIp);
+            api.getPublicIPAddressApi(group).delete(publicIp);
          }
-         // todo is a collection!
-         boolean virtualNetworkDeleted = 
api.getVirtualNetworkApi(group).delete(virtualNetwork.name());
       }
-      // delete storage account
+
       String storageAccountNameURI = 
virtualMachine.properties().storageProfile().osDisk().vhd().uri();
-      boolean storageAccountDeleted = 
api.getStorageAccountApi(group).delete(Iterables.get(Splitter.on(".").split(URI.create(storageAccountNameURI).getHost()),
 0));
-
-      // delete resource group if empty
-      if (api.getVirtualMachineApi(group).list().isEmpty() &&
-              api.getVirtualNetworkApi(group).list().isEmpty() &&
-              api.getStorageAccountApi(group).list().isEmpty() &&
-              api.getNetworkInterfaceCardApi(group).list().isEmpty()) {
-         boolean resourceGroupDeleted = 
resourceDeleted.apply(api.getResourceGroupApi().delete(group));
+      String storageAccountName = 
Iterables.get(Splitter.on(".").split(URI.create(storageAccountNameURI).getHost()),
 0);
+      StorageServiceKeys keys = 
api.getStorageAccountApi(group).getKeys(storageAccountName);
+
+      // Remove the virtual machine files
+      logger.debug(">> deleting virtual machine disk storage...");
+      BlobHelper.deleteContainerIfExists(storageAccountName, keys.key1(), 
"vhds");
+
+      if (!BlobHelper.customImageExists(storageAccountName, keys.key1())) {
+         logger.debug(">> deleting storage account %s...", storageAccountName);
+         api.getStorageAccountApi(group).delete(storageAccountName);
+      } else {
+         logger.debug(">> the storage account contains custom images. Will not 
delete it!");
       }
-      return vmDeleted;
-   }
 
-   private List<String> getNetworkCardInterfaceNames(VirtualMachine 
virtualMachine) {
-      List<String> nics = Lists.newArrayList();
-      for (IdReference idReference : 
virtualMachine.properties().networkProfile().networkInterfaces()) {
-         nics.add(Iterables.getLast(Splitter.on("/").split(idReference.id())));
-      }
-      return nics;
-   }
+      deleteResourceGroupIfEmpty(group);
 
-   private boolean deleteVirtualMachine(String group, VirtualMachine 
virtualMachine) {
-      return 
resourceDeleted.apply(api.getVirtualMachineApi(group).delete(virtualMachine.name()));
+      return vmDeleted;
    }
 
-   private Map<String, VirtualMachine> 
getResourceGroupNamesAndVirtualMachines(String id) {
-      for (ResourceGroup resourceGroup : api.getResourceGroupApi().list()) {
-         String group = resourceGroup.name();
-         VirtualMachine virtualMachine = 
api.getVirtualMachineApi(group).get(id);
-         if (virtualMachine != null) {
-            return ImmutableMap.of(group, virtualMachine);
-         }
+   public void deleteResourceGroupIfEmpty(String group) {
+      if (api.getVirtualMachineApi(group).list().isEmpty() 
+            && api.getStorageAccountApi(group).list().isEmpty()
+            && api.getNetworkInterfaceCardApi(group).list().isEmpty()
+            && api.getPublicIPAddressApi(group).list().isEmpty()) {
+         logger.debug(">> the resource group %s is empty. Deleting...", group);
+         resourceDeleted.apply(api.getResourceGroupApi().delete(group));
       }
-      return Maps.newHashMap();
    }
-}
-      
-/*      
-      for (ResourceGroup resourceGroup : api.getResourceGroupApi().list()) {
-         String group = resourceGroup.name();
-         VirtualMachine virtualMachine = 
api.getVirtualMachineApi(group).get(id);
-         if (virtualMachine != null) {
-            vmDeleted = 
resourceDeleted.apply(api.getVirtualMachineApi(group).delete(id));
-            for (IdReference idReference : 
virtualMachine.properties().networkProfile().networkInterfaces()) {
-               String nicName = 
Iterables.getLast(Splitter.on("/").split(idReference.id()));
-               NetworkInterfaceCard networkInterfaceCard = 
api.getNetworkInterfaceCardApi(group).get(nicName);
-               URI nicDeletionURI = 
api.getNetworkInterfaceCardApi(group).delete(nicName);
-               nicDeleted = resourceDeleted.apply(nicDeletionURI);
-               for (IpConfiguration ipConfiguration : 
networkInterfaceCard.properties().ipConfigurations()) {
-                  if (ipConfiguration.properties().publicIPAddress() != null) {
-                     String publicIpId = 
ipConfiguration.properties().publicIPAddress().id();
-                     String publicIpAddressName = 
Iterables.getLast(Splitter.on("/").split(publicIpId));
-                     publicIpAddressDeleted = 
api.getPublicIPAddressApi(group).delete(publicIpAddressName);
-                  }
+
+   private Iterable<String> getPublicIps(String group, NetworkInterfaceCard 
nic) {
+      return transform(
+            filter(transform(nic.properties().ipConfigurations(), new 
Function<IpConfiguration, IdReference>() {
+               @Override
+               public IdReference apply(IpConfiguration input) {
+                  return input.properties().publicIPAddress();
                }
-            }
-            for (VirtualNetwork virtualNetwork : 
api.getVirtualNetworkApi(group).list()) {
-               for (Subnet subnet : virtualNetwork.properties().subnets()) {
-                  api.getSubnetApi(group, 
virtualNetwork.name()).delete(subnet.name());
+            }), notNull()), new Function<IdReference, String>() {
+               @Override
+               public String apply(IdReference input) {
+                  return Iterables.getLast(Splitter.on("/").split(input.id()));
                }
-               virtualNetworkDeleted = 
api.getVirtualNetworkApi(group).delete(virtualNetwork.name());
-            }
-            String storageAccountNameURI = 
virtualMachine.properties().storageProfile().osDisk().vhd().uri();
-            storageAccountDeleted = 
api.getStorageAccountApi(group).delete(Iterables.get(Splitter.on(".").split(URI.create(storageAccountNameURI).getHost()),
 0));
-         }
-         if (api.getVirtualMachineApi(group).list().isEmpty() &&
-                 api.getVirtualNetworkApi(group).list().isEmpty() &&
-                 api.getStorageAccountApi(group).list().isEmpty() &&
-                 api.getNetworkInterfaceCardApi(group).list().isEmpty()) {
-            resourceGroupDeleted = 
resourceDeleted.apply(api.getResourceGroupApi().delete(group));
-         }
-      }
-      return vmDeleted && nicDeleted && publicIpAddressDeleted && 
virtualNetworkDeleted && storageAccountDeleted && resourceGroupDeleted;
+            });
    }
 
    private List<String> getNetworkCardInterfaceNames(VirtualMachine 
virtualMachine) {
@@ -185,4 +163,3 @@ public class CleanupResources implements Function<String, 
Boolean> {
       return Maps.newHashMap();
    }
 }
-*/

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/22663315/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/util/BlobHelper.java
----------------------------------------------------------------------
diff --git 
a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/util/BlobHelper.java
 
b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/util/BlobHelper.java
index f5bfc75..2124901 100644
--- 
a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/util/BlobHelper.java
+++ 
b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/util/BlobHelper.java
@@ -16,76 +16,80 @@
  */
 package org.jclouds.azurecompute.arm.util;
 
+import static org.jclouds.util.Closeables2.closeQuietly;
+
 import java.util.ArrayList;
 import java.util.List;
 
 import org.jclouds.ContextBuilder;
-import org.jclouds.azure.storage.domain.BoundedSet;
 import org.jclouds.azureblob.AzureBlobClient;
 import org.jclouds.azureblob.domain.BlobProperties;
 import org.jclouds.azureblob.domain.ContainerProperties;
 import org.jclouds.azureblob.domain.ListBlobsResponse;
 import org.jclouds.azurecompute.arm.domain.VMImage;
-import org.jclouds.util.Closeables2;
 
 public class BlobHelper {
 
    public static void deleteContainerIfExists(String storage, String key, 
String containerName) {
-      final AzureBlobClient azureBlob = ContextBuilder.newBuilder("azureblob")
-              .credentials(storage, key)
-              .buildApi(AzureBlobClient.class);
+      final AzureBlobClient azureBlob = 
ContextBuilder.newBuilder("azureblob").credentials(storage, key)
+            .buildApi(AzureBlobClient.class);
 
       try {
          azureBlob.deleteContainer(containerName);
+      } finally {
+         closeQuietly(azureBlob);
       }
-      finally {
-         Closeables2.closeQuietly(azureBlob);
+   }
+   
+   public static boolean hasContainers(String storage, String key) {
+      final AzureBlobClient azureBlob = 
ContextBuilder.newBuilder("azureblob").credentials(storage, key)
+            .buildApi(AzureBlobClient.class);
+
+      try {
+         return !azureBlob.listContainers().isEmpty();
+      } finally {
+         closeQuietly(azureBlob);
       }
    }
 
    public static boolean customImageExists(String storage, String key) {
-      final AzureBlobClient azureBlob = ContextBuilder.newBuilder("azureblob")
-              .credentials(storage, key)
-              .buildApi(AzureBlobClient.class);
+      final AzureBlobClient azureBlob = 
ContextBuilder.newBuilder("azureblob").credentials(storage, key)
+            .buildApi(AzureBlobClient.class);
 
       try {
          return azureBlob.containerExists("system");
-      }
-      finally {
-         Closeables2.closeQuietly(azureBlob);
+      } finally {
+         closeQuietly(azureBlob);
       }
    }
 
-   public static List<VMImage> getImages(String containerName, String group,
-                                         String storageAccountName, String 
key, String offer, String location) {
-      final AzureBlobClient azureBlob = ContextBuilder.newBuilder("azureblob")
-              .credentials(storageAccountName, key)
-              .buildApi(AzureBlobClient.class);
-
+   public static List<VMImage> getImages(String containerName, String group, 
String storageAccountName, String key,
+         String offer, String location) {
+      final AzureBlobClient azureBlob = 
ContextBuilder.newBuilder("azureblob").credentials(storageAccountName, key)
+            .buildApi(AzureBlobClient.class);
 
       List<VMImage> list = new ArrayList<VMImage>();
+
       try {
-         BoundedSet<ContainerProperties> containerList = 
azureBlob.listContainers();
-         for (ContainerProperties props : containerList) {
-            if (props.getName().equals("system")) {
-               ListBlobsResponse blobList = azureBlob.listBlobs("system");
-               String osDisk = "";
-               String dataDisk = "";
+         ContainerProperties systemContainer = 
azureBlob.getContainerProperties("system");
+         if (systemContainer != null) {
+            ListBlobsResponse blobList = 
azureBlob.listBlobs(systemContainer.getName());
+            for (BlobProperties blob : blobList) {
+               String name = blob.getName();
 
-               for (BlobProperties blob : blobList) {
-                  String name = blob.getName();
+               if (name.contains("-osDisk")) {
+                  String imageName = name.substring(name.lastIndexOf('/') + 1, 
name.indexOf("-osDisk"));
+                  String imageUrl = blob.getUrl().toString();
 
-                  if (dataDisk.length() == 0) dataDisk = name.substring(1 + 
name.lastIndexOf('/'));
-                  else if (osDisk.length() == 0) osDisk = name.substring(1 + 
name.lastIndexOf('/'));
+                  
list.add(VMImage.customImage().group(group).storage(storageAccountName).vhd1(imageUrl).name(imageName)
+                        .offer(offer).location(location).build());
                }
-               final VMImage ref = VMImage.create(group, storageAccountName, 
osDisk, dataDisk, "test-create-image", "custom", location);
-               list.add(ref);
             }
          }
+      } finally {
+         closeQuietly(azureBlob);
       }
-      finally {
-         Closeables2.closeQuietly(azureBlob);
-      }
+
       return list;
    }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/22663315/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/AzureComputeServiceLiveTest.java
----------------------------------------------------------------------
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 f4f5427..c338954 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
@@ -55,12 +55,9 @@ import com.google.inject.Module;
  */
 @Test(groups = "live", singleThreaded = true, testName = 
"AzureComputeServiceLiveTest")
 public class AzureComputeServiceLiveTest extends BaseComputeServiceLiveTest {
-
    
    public AzureComputeServiceLiveTest() {
       provider = "azurecompute-arm";
-      nonBlockDurationSeconds = 300;
-      group = "az-r";
    }
 
    @Override
@@ -104,17 +101,17 @@ public class AzureComputeServiceLiveTest extends 
BaseComputeServiceLiveTest {
    }
 
    @Override
-   protected void checkUserMetadataContains(NodeMetadata node, 
ImmutableMap<String, String> userMetadata) {
-      // User metadata not yet supported
-   }
-
-   @Override
    protected Template addRunScriptToTemplate(Template template) {
       template.getOptions().runScript(
             Statements.newStatementList(new Statement[] { 
AdminAccess.standard(), Statements.exec("sleep 50"),
                   InstallJDK.fromOpenJDK() }));
       return template;
    }
+   
+   @Override
+   protected void checkUserMetadataContains(NodeMetadata node, 
ImmutableMap<String, String> userMetadata) {
+      // User metadata not yet supported
+   }
 
    @Override
    protected void checkTagsInNodeEquals(NodeMetadata node, 
ImmutableSet<String> tags) {

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/22663315/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeImageExtensionLiveTest.java
----------------------------------------------------------------------
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
index 06f9ab7..2c48876 100644
--- 
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
@@ -16,26 +16,27 @@
  */
 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.IMAGE_PUBLISHERS;
 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 static 
org.jclouds.location.reference.LocationConstants.PROPERTY_REGIONS;
+
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+
+import org.jclouds.azurecompute.arm.AzureComputeProviderMetadata;
+import org.jclouds.azurecompute.arm.internal.AzureLiveTestUtils;
+import org.jclouds.compute.extensions.internal.BaseImageExtensionLiveTest;
+import org.jclouds.providers.ProviderMetadata;
+import org.jclouds.sshj.config.SshjSshClientModule;
+import org.testng.annotations.Test;
+
+import com.google.inject.Module;
 
 /**
  * Live tests for the {@link org.jclouds.compute.extensions.ImageExtension} 
integration.
@@ -57,34 +58,25 @@ public class AzureComputeImageExtensionLiveTest extends 
BaseImageExtensionLiveTe
    @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, "jcloudsgroup");
-
-      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}");
+      String defaultTimeout = String.valueOf(TimeUnit.MILLISECONDS.convert(60, 
TimeUnit.MINUTES));
+      properties.setProperty(TIMEOUT_SCRIPT_COMPLETE, defaultTimeout);
+      properties.setProperty(TIMEOUT_NODE_RUNNING, defaultTimeout);
+      properties.setProperty(TIMEOUT_PORT_OPEN, defaultTimeout);
+      properties.setProperty(TIMEOUT_NODE_TERMINATED, defaultTimeout);
+      properties.setProperty(TIMEOUT_NODE_SUSPENDED, defaultTimeout);
+      properties.put(RESOURCE_GROUP_NAME, "jc");
+      properties.put(PROPERTY_REGIONS, "eastus");
+      properties.put(IMAGE_PUBLISHERS, "Canonical");
 
       AzureLiveTestUtils.defaultProperties(properties);
       checkNotNull(setIfTestSystemPropertyPresent(properties, 
"oauth.endpoint"), "test.oauth.endpoint");
 
       return properties;
-
    }
 
    @Override
    protected ProviderMetadata createProviderMetadata() {
-      AzureComputeProviderMetadata pm = 
AzureComputeProviderMetadata.builder().build();
-      return pm;
+      return AzureComputeProviderMetadata.builder().build();
    }
 
-
 }

Reply via email to