You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by na...@apache.org on 2017/01/26 21:39:59 UTC

[1/4] jclouds-labs git commit: Cleanup legacy code and introduce the resource group cache

Repository: jclouds-labs
Updated Branches:
  refs/heads/master a91ff3bdb -> fde928eb0


http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fde928eb/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 524eb69..378030f 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
@@ -21,7 +21,8 @@ import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.collect.Iterables.getOnlyElement;
 import static org.jclouds.azurecompute.arm.compute.functions.VMImageToImage.decodeFieldsFromUniqueId;
-import static org.jclouds.util.Predicates2.retry;
+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 java.net.URI;
 import java.util.Arrays;
@@ -36,9 +37,7 @@ import javax.inject.Singleton;
 
 import org.jclouds.Constants;
 import org.jclouds.azurecompute.arm.AzureComputeApi;
-import org.jclouds.azurecompute.arm.compute.config.AzureComputeServiceContextModule;
 import org.jclouds.azurecompute.arm.compute.domain.RegionAndIdAndIngressRules;
-import org.jclouds.azurecompute.arm.compute.functions.LocationToResourceGroupName;
 import org.jclouds.azurecompute.arm.compute.options.AzureTemplateOptions;
 import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroup;
 import org.jclouds.azurecompute.arm.domain.RegionAndId;
@@ -47,10 +46,8 @@ 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;
 import org.jclouds.azurecompute.arm.features.VirtualNetworkApi;
-import org.jclouds.azurecompute.arm.functions.ParseJobStatus;
 import org.jclouds.compute.config.CustomizationResponse;
 import org.jclouds.compute.domain.Image;
 import org.jclouds.compute.domain.NodeMetadata;
@@ -65,6 +62,7 @@ import org.jclouds.compute.strategy.impl.CreateNodesWithGroupEncodedIntoNameThen
 import org.jclouds.domain.Location;
 import org.jclouds.logging.Logger;
 
+import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.base.Strings;
 import com.google.common.cache.LoadingCache;
@@ -81,9 +79,11 @@ public class CreateResourceGroupThenCreateNodes extends CreateNodesWithGroupEnco
    protected Logger logger = Logger.NULL;
 
    private final AzureComputeApi api;
-   private final AzureComputeServiceContextModule.AzureComputeConstants azureComputeConstants;
-   private final LocationToResourceGroupName locationToResourceGroupName;
    private final LoadingCache<RegionAndIdAndIngressRules, String> securityGroupMap;
+   private final LoadingCache<String, ResourceGroup> resourceGroupMap;
+   private final String defaultVnetAddressPrefix;
+   private final String defaultSubnetAddressPrefix;
+   private final Predicate<URI> storageAccountCreated;
 
    @Inject
    protected CreateResourceGroupThenCreateNodes(
@@ -92,22 +92,28 @@ public class CreateResourceGroupThenCreateNodes extends CreateNodesWithGroupEnco
          GroupNamingConvention.Factory namingConvention,
          @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor,
          CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory,
-         AzureComputeApi api, AzureComputeServiceContextModule.AzureComputeConstants azureComputeConstants,
-         LocationToResourceGroupName locationToResourceGroupName,
-         LoadingCache<RegionAndIdAndIngressRules, String> securityGroupMap) {
+         AzureComputeApi api, @Named(DEFAULT_VNET_ADDRESS_SPACE_PREFIX) String defaultVnetAddressPrefix,
+         @Named(DEFAULT_SUBNET_ADDRESS_PREFIX) String defaultSubnetAddressPrefix,
+         LoadingCache<RegionAndIdAndIngressRules, String> securityGroupMap,
+         LoadingCache<String, ResourceGroup> resourceGroupMap, @Named("STORAGE") Predicate<URI> storageAccountCreated) {
       super(addNodeWithGroupStrategy, listNodesStrategy, namingConvention, userExecutor,
             customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory);
       this.api = checkNotNull(api, "api cannot be null");
       checkNotNull(userExecutor, "userExecutor cannot be null");
-      this.azureComputeConstants = azureComputeConstants;
-      this.locationToResourceGroupName = locationToResourceGroupName;
       this.securityGroupMap = securityGroupMap;
+      this.resourceGroupMap = resourceGroupMap;
+      this.defaultVnetAddressPrefix = defaultVnetAddressPrefix;
+      this.defaultSubnetAddressPrefix = defaultSubnetAddressPrefix;
+      this.storageAccountCreated = storageAccountCreated;
    }
 
    @Override
    public Map<?, ListenableFuture<Void>> execute(String group, int count, Template template,
          Set<NodeMetadata> goodNodes, Map<NodeMetadata, Exception> badNodes,
          Multimap<NodeMetadata, CustomizationResponse> customizationResponses) {
+
+      AzureTemplateOptions options = template.getOptions().as(AzureTemplateOptions.class);
+
       // If there is a script to be run on the node and public key
       // authentication has been configured, warn users if the private key
       // is not present
@@ -115,56 +121,39 @@ public class CreateResourceGroupThenCreateNodes extends CreateNodesWithGroupEnco
          logger.warn(">> a runScript was configured but no SSH key has been provided. "
                + "Authentication will delegate to the ssh-agent");
       }
-      String azureGroupName = locationToResourceGroupName.apply(template.getLocation().getId());
-
-      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(azureGroupName);
-      final String location = template.getLocation().getId();
-
-      if (resourceGroup == null) {
-         final Map<String, String> tags = ImmutableMap.of("description", "jclouds managed VMs");
-         resourceGroupApi.create(azureGroupName, location, tags).name();
-      }
 
-      String vnetName = azureGroupName + "virtualnetwork";
-      String subnetName = azureGroupName + "subnet";
+      // This sill create the resource group if it does not exist
+      String location = template.getLocation().getId();
+      ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(location);
+      String azureGroupName = resourceGroup.name();
 
-      if (options.getVirtualNetworkName() != null) {
-         vnetName = options.getVirtualNetworkName();
-      }
-
-      this.getOrCreateVirtualNetworkWithSubnet(vnetName, subnetName, location, options, azureGroupName);
+      getOrCreateVirtualNetworkWithSubnet(location, options, azureGroupName);
       configureSecurityGroupForOptions(group, azureGroupName, template.getLocation(), options);
 
       StorageService storageService = getOrCreateStorageService(group, azureGroupName, location, template.getImage());
-      String blob = storageService.storageServiceProperties().primaryEndpoints().get("blob");
-      options.blob(blob);
-
-      Map<?, ListenableFuture<Void>> responses = super.execute(group, count, template, goodNodes, badNodes,
-            customizationResponses);
+      options.blob(storageService.storageServiceProperties().primaryEndpoints().get("blob"));
 
-      return responses;
+      return super.execute(group, count, template, goodNodes, badNodes, customizationResponses);
    }
 
-   protected synchronized void getOrCreateVirtualNetworkWithSubnet(final String virtualNetworkName,
-         final String subnetName, final String location, AzureTemplateOptions options, final String azureGroupName) {
+   protected synchronized void getOrCreateVirtualNetworkWithSubnet(final String location, AzureTemplateOptions options,
+         final String azureGroupName) {
+      String virtualNetworkName = Optional.fromNullable(options.getVirtualNetworkName()).or(
+            azureGroupName + "virtualnetwork");
+      String subnetName = azureGroupName + "subnet";
 
       // 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) {
+         Subnet subnet = Subnet.create(subnetName, null, null,
+               Subnet.SubnetProperties.builder().addressPrefix(defaultSubnetAddressPrefix).build());
+
          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();
+               .builder().addressSpace(VirtualNetwork.AddressSpace.create(Arrays.asList(defaultVnetAddressPrefix)))
+               .subnets(Arrays.asList(subnet)).build();
+
          vn = vnApi.createOrUpdate(virtualNetworkName, location, virtualNetworkProperties);
       }
 
@@ -199,15 +188,10 @@ 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()));
-      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
-      checkState(starageAccountCreated, "Storage account %s was not created in the configured timeout",
+
+      checkState(storageAccountCreated.apply(uri), "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/fde928eb/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/AzureComputeProperties.java
----------------------------------------------------------------------
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 2a15e4b..4ac5eaa 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
@@ -22,30 +22,16 @@ package org.jclouds.azurecompute.arm.config;
  */
 public class AzureComputeProperties {
 
-   public static final String STORAGE_API_VERSION = "2015-06-15";
-
    public static final String OPERATION_TIMEOUT = "jclouds.azurecompute.arm.operation.timeout";
 
-   public static final String OPERATION_POLL_INITIAL_PERIOD = "jclouds.azurecompute.arm.operation.poll.initial.period";
-
-   public static final String OPERATION_POLL_MAX_PERIOD = "jclouds.azurecompute.arm.operation.poll.max.period";
-
-   public static final String TCP_RULE_FORMAT = "jclouds.azurecompute.arm.tcp.rule.format";
-
-   public static final String TCP_RULE_REGEXP = "jclouds.azurecompute.arm.tcp.rule.regexp";
-
    public static final String IMAGE_PUBLISHERS = "jclouds.azurecompute.arm.publishers";
 
-   public static final String DEFAULT_IMAGE_LOGIN = "jclouds.azurecompute.arm.defaultimagelogin";
-
    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";
-
    public static final String API_VERSION_PREFIX = "jclouds.azurecompute.arm.apiversion.";
 
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fde928eb/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
deleted file mode 100644
index 9d2a1c1..0000000
--- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/functions/CleanupResources.java
+++ /dev/null
@@ -1,202 +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.functions;
-
-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 static org.jclouds.util.Closeables2.closeQuietly;
-
-import java.net.URI;
-import java.util.List;
-
-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.compute.functions.LocationToResourceGroupName;
-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.NetworkSecurityGroup;
-import org.jclouds.azurecompute.arm.domain.RegionAndId;
-import org.jclouds.azurecompute.arm.domain.StorageServiceKeys;
-import org.jclouds.azurecompute.arm.domain.VirtualMachine;
-import org.jclouds.azurecompute.arm.features.NetworkSecurityGroupApi;
-import org.jclouds.azurecompute.arm.util.BlobHelper;
-import org.jclouds.compute.functions.GroupNamingConvention;
-import org.jclouds.compute.reference.ComputeServiceConstants;
-import org.jclouds.logging.Logger;
-
-import com.google.common.base.Function;
-import com.google.common.base.Predicate;
-import com.google.common.base.Splitter;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-
-@Singleton
-public class CleanupResources {
-
-   @Resource
-   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
-   protected Logger logger = Logger.NULL;
-
-   protected final AzureComputeApi api;
-   private final Predicate<URI> resourceDeleted;
-   private final StorageProfileToStorageAccountName storageProfileToStorageAccountName;
-   private final LocationToResourceGroupName locationToResourceGroupName;
-   private final GroupNamingConvention.Factory namingConvention;
-
-   @Inject
-   CleanupResources(AzureComputeApi azureComputeApi, @Named(TIMEOUT_RESOURCE_DELETED) Predicate<URI> resourceDeleted,
-         StorageProfileToStorageAccountName storageProfileToStorageAccountName,
-         LocationToResourceGroupName locationToResourceGroupName, GroupNamingConvention.Factory namingConvention) {
-      this.api = azureComputeApi;
-      this.resourceDeleted = resourceDeleted;
-      this.storageProfileToStorageAccountName = storageProfileToStorageAccountName;
-      this.locationToResourceGroupName = locationToResourceGroupName;
-      this.namingConvention = namingConvention;
-   }
-
-   public boolean cleanupNode(final String id) {
-      RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id);
-      String group = locationToResourceGroupName.apply(regionAndId.region());
-
-      VirtualMachine virtualMachine = api.getVirtualMachineApi(group).get(regionAndId.id());
-      if (virtualMachine == null) {
-         return true;
-      }
-
-      logger.debug(">> destroying %s ...", regionAndId.slashEncode());
-      boolean vmDeleted = deleteVirtualMachine(group, virtualMachine);
-
-      // We don't delete the network here, as it is global to the resource
-      // group. It will be deleted when the resource group is deleted
-
-      cleanupVirtualMachineNICs(group, virtualMachine);
-      cleanupVirtualMachineStorage(group, virtualMachine);
-
-      return vmDeleted;
-   }
-
-   public void cleanupVirtualMachineNICs(String group, VirtualMachine virtualMachine) {
-      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);
-         resourceDeleted.apply(nicDeletionURI);
-
-         for (String publicIp : publicIps) {
-            logger.debug(">> deleting public ip nic %s...", publicIp);
-            api.getPublicIPAddressApi(group).delete(publicIp);
-         }
-      }
-   }
-
-   public void cleanupVirtualMachineStorage(String group, VirtualMachine virtualMachine) {
-      String storageAccountName = storageProfileToStorageAccountName
-            .apply(virtualMachine.properties().storageProfile());
-      StorageServiceKeys keys = api.getStorageAccountApi(group).getKeys(storageAccountName);
-
-      // Remove the virtual machine files
-      logger.debug(">> deleting virtual machine disk storage...");
-      BlobHelper blobHelper = new BlobHelper(storageAccountName, keys.key1());
-      try {
-         blobHelper.deleteContainerIfExists("vhds");
-
-         if (!blobHelper.customImageExists()) {
-            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!");
-         }
-      } finally {
-         closeQuietly(blobHelper);
-      }
-   }
-
-   public boolean cleanupSecurityGroupIfOrphaned(String resourceGroup, String group) {
-      String name = namingConvention.create().sharedNameForGroup(group);
-      NetworkSecurityGroupApi sgapi = api.getNetworkSecurityGroupApi(resourceGroup);
-
-      boolean deleted = false;
-
-      try {
-         NetworkSecurityGroup securityGroup = sgapi.get(name);
-         if (securityGroup != null) {
-            List<NetworkInterfaceCard> nics = securityGroup.properties().networkInterfaces();
-            if (nics == null || nics.isEmpty()) {
-               logger.debug(">> deleting orphaned security group %s from %s...", name, resourceGroup);
-               try {
-                  deleted = resourceDeleted.apply(sgapi.delete(name));
-               } catch (Exception ex) {
-                  logger.warn(ex, ">> error deleting orphaned security group %s from %s...", name, resourceGroup);
-               }
-            }
-         }
-      } catch (Exception ex) {
-         logger.warn(ex, "Error deleting security groups for %s and group %s", resourceGroup, group);
-      }
-
-      return deleted;
-   }
-
-   public boolean deleteResourceGroupIfEmpty(String group) {
-      boolean deleted = false;
-      if (api.getVirtualMachineApi(group).list().isEmpty() && api.getStorageAccountApi(group).list().isEmpty()
-            && api.getNetworkInterfaceCardApi(group).list().isEmpty()
-            && api.getPublicIPAddressApi(group).list().isEmpty()
-            && api.getNetworkSecurityGroupApi(group).list().isEmpty()) {
-         logger.debug(">> the resource group %s is empty. Deleting...", group);
-         deleted = resourceDeleted.apply(api.getResourceGroupApi().delete(group));
-      }
-      return deleted;
-   }
-
-   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();
-               }
-            }), notNull()), new Function<IdReference, String>() {
-               @Override
-               public String apply(IdReference input) {
-                  return Iterables.getLast(Splitter.on("/").split(input.id()));
-               }
-            });
-   }
-
-   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;
-   }
-
-   private boolean deleteVirtualMachine(String group, VirtualMachine virtualMachine) {
-      return resourceDeleted.apply(api.getVirtualMachineApi(group).delete(virtualMachine.name()));
-   }
-
-}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fde928eb/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeSecurityGroupExtensionLiveTest.java
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeSecurityGroupExtensionLiveTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeSecurityGroupExtensionLiveTest.java
index 02fe52e..cb13c7b 100644
--- a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeSecurityGroupExtensionLiveTest.java
+++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeSecurityGroupExtensionLiveTest.java
@@ -18,7 +18,6 @@ package org.jclouds.azurecompute.arm.compute.extensions;
 
 import static com.google.common.collect.Iterables.get;
 import static com.google.common.collect.Iterables.getOnlyElement;
-import static java.util.logging.Logger.getAnonymousLogger;
 import static org.jclouds.compute.options.TemplateOptions.Builder.inboundPorts;
 import static org.jclouds.compute.options.TemplateOptions.Builder.securityGroups;
 import static org.jclouds.compute.predicates.NodePredicates.inGroup;
@@ -26,14 +25,12 @@ import static org.jclouds.net.domain.IpProtocol.TCP;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
 
-import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 
-import org.jclouds.azurecompute.arm.AzureComputeApi;
 import org.jclouds.azurecompute.arm.AzureComputeProviderMetadata;
-import org.jclouds.azurecompute.arm.compute.functions.LocationToResourceGroupName;
+import org.jclouds.azurecompute.arm.compute.strategy.CleanupResources;
 import org.jclouds.azurecompute.arm.domain.ResourceGroup;
 import org.jclouds.azurecompute.arm.internal.AzureLiveTestUtils;
 import org.jclouds.compute.ComputeService;
@@ -50,7 +47,9 @@ import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 import com.google.common.base.Optional;
-import com.google.common.collect.ImmutableMap;
+import com.google.common.cache.LoadingCache;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
 
 /**
  * Live test for AzureCompute
@@ -59,10 +58,9 @@ import com.google.common.collect.ImmutableMap;
 @Test(groups = "live", singleThreaded = true, testName = "AzureComputeSecurityGroupExtensionLiveTest")
 public class AzureComputeSecurityGroupExtensionLiveTest extends BaseSecurityGroupExtensionLiveTest {
 
-   private AzureComputeApi api;
-   private LocationToResourceGroupName locationToResourceGroupName;
-   private String resourceGroupName;
+   private LoadingCache<String, ResourceGroup> resourceGroupMap;
    private ResourceGroup testResourceGroup;
+   private CleanupResources cleanupResources;
 
    public AzureComputeSecurityGroupExtensionLiveTest() {
       provider = "azurecompute-arm";
@@ -71,8 +69,10 @@ public class AzureComputeSecurityGroupExtensionLiveTest extends BaseSecurityGrou
    @BeforeClass(groups = { "integration", "live" })
    public void setupContext() {
       super.setupContext();
-      api = context.utils().injector().getInstance(AzureComputeApi.class);
-      locationToResourceGroupName = context.utils().injector().getInstance(LocationToResourceGroupName.class);
+      resourceGroupMap = context.utils().injector()
+            .getInstance(Key.get(new TypeLiteral<LoadingCache<String, ResourceGroup>>() {
+            }));
+      cleanupResources = context.utils().injector().getInstance(CleanupResources.class);
       createResourceGroupIfMissing();
    }
 
@@ -119,12 +119,7 @@ public class AzureComputeSecurityGroupExtensionLiveTest extends BaseSecurityGrou
    @Override
    protected void tearDownContext() {
       super.tearDownContext();
-      if (testResourceGroup != null) {
-         // Cleanup the resource group we created for the tests
-         getAnonymousLogger().info(
-               "deleting resource group " + testResourceGroup.name() + " for the security group live tests...");
-         api.getResourceGroupApi().delete(testResourceGroup.name());
-      }
+      cleanupResources.deleteResourceGroupIfEmpty(testResourceGroup.name());
    }
 
    @Override
@@ -142,13 +137,6 @@ public class AzureComputeSecurityGroupExtensionLiveTest extends BaseSecurityGrou
 
    private void createResourceGroupIfMissing() {
       Location location = getNodeTemplate().getLocation();
-      resourceGroupName = locationToResourceGroupName.apply(location.getId());
-      ResourceGroup resourceGroupInLocation = api.getResourceGroupApi().get(resourceGroupName);
-      if (resourceGroupInLocation == null) {
-         getAnonymousLogger().info(
-               "creating resource group " + resourceGroupName + " for the security group live tests...");
-         final Map<String, String> tags = ImmutableMap.of("description", "AzureComputeSecurityGroupExtensionLiveTest");
-         testResourceGroup = api.getResourceGroupApi().create(resourceGroupName, location.getId(), tags);
-      }
+      testResourceGroup = resourceGroupMap.getUnchecked(location.getId());
    }
 }


[4/4] jclouds-labs git commit: JCLOUDS-1231: Implement the SecurityGroupExtension in ARM

Posted by na...@apache.org.
JCLOUDS-1231: Implement the SecurityGroupExtension in ARM


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

Branch: refs/heads/master
Commit: 867ddef6e9355aac624bf0981e46de45bb42843c
Parents: a91ff3b
Author: Daniel Estevez <co...@danielestevez.com>
Authored: Tue Jan 17 19:13:14 2017 -0500
Committer: Ignasi Barrera <na...@apache.org>
Committed: Thu Jan 26 21:58:23 2017 +0100

----------------------------------------------------------------------
 .../arm/compute/AzureComputeService.java        |  82 +++--
 .../arm/compute/AzureComputeServiceAdapter.java |  20 +-
 .../AzureComputeServiceContextModule.java       | 145 ++++++---
 .../domain/RegionAndIdAndIngressRules.java      |  66 ++++
 .../AzureComputeSecurityGroupExtension.java     | 315 +++++++++++++++++++
 .../NetworkSecurityGroupToSecurityGroup.java    |  72 +++++
 .../NetworkSecurityRuleToIpPermission.java      |  76 +++++
 .../functions/VirtualMachineToNodeMetadata.java |   4 +-
 .../loaders/CreateSecurityGroupIfNeeded.java    |  97 ++++++
 .../CreateResourceGroupThenCreateNodes.java     | 112 +++++--
 .../arm/domain/NetworkSecurityGroup.java        |  17 +-
 .../azurecompute/arm/domain/RegionAndId.java    |   2 +-
 .../arm/functions/CleanupResources.java         |  66 +++-
 .../AzureComputeImageExtensionLiveTest.java     |   1 +
 ...reComputeSecurityGroupExtensionLiveTest.java | 154 +++++++++
 .../NetworkSecurityGroupApiMockTest.java        |   2 +-
 .../internal/BaseAzureComputeApiLiveTest.java   |   2 +-
 .../src/test/resources/logback-test.xml         |   2 +-
 18 files changed, 1100 insertions(+), 135 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/AzureComputeService.java
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/AzureComputeService.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/AzureComputeService.java
index c215e37..a561375 100644
--- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/AzureComputeService.java
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/AzureComputeService.java
@@ -16,11 +16,12 @@
  */
 package org.jclouds.azurecompute.arm.compute;
 
-import static com.google.common.base.Preconditions.checkNotNull;
 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 java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -30,6 +31,8 @@ import javax.inject.Provider;
 import javax.inject.Singleton;
 
 import org.jclouds.Constants;
+import org.jclouds.azurecompute.arm.compute.functions.LocationToResourceGroupName;
+import org.jclouds.azurecompute.arm.functions.CleanupResources;
 import org.jclouds.collect.Memoized;
 import org.jclouds.compute.ComputeServiceContext;
 import org.jclouds.compute.callables.RunScriptOnNode;
@@ -54,51 +57,74 @@ import org.jclouds.compute.strategy.ResumeNodeStrategy;
 import org.jclouds.compute.strategy.SuspendNodeStrategy;
 import org.jclouds.domain.Credentials;
 import org.jclouds.domain.Location;
-import org.jclouds.azurecompute.arm.functions.CleanupResources;
 import org.jclouds.scriptbuilder.functions.InitAdminAccess;
 
 import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.util.concurrent.ListeningExecutorService;
 
 @Singleton
 public class AzureComputeService extends BaseComputeService {
-   protected final CleanupResources cleanupResources;
+   private final CleanupResources cleanupResources;
+   private final LocationToResourceGroupName locationToResourceGroupName;
 
    @Inject
    protected AzureComputeService(ComputeServiceContext context, Map<String, Credentials> credentialStore,
-                                @Memoized Supplier<Set<? extends Image>> images, @Memoized Supplier<Set<? extends Hardware>> sizes,
-                                @Memoized Supplier<Set<? extends Location>> locations, ListNodesStrategy listNodesStrategy,
-                                GetImageStrategy getImageStrategy, GetNodeMetadataStrategy getNodeMetadataStrategy,
-                                CreateNodesInGroupThenAddToSet runNodesAndAddToSetStrategy, RebootNodeStrategy rebootNodeStrategy,
-                                DestroyNodeStrategy destroyNodeStrategy, ResumeNodeStrategy startNodeStrategy,
-                                SuspendNodeStrategy stopNodeStrategy, Provider<TemplateBuilder> templateBuilderProvider,
-                                @Named("DEFAULT") Provider<TemplateOptions> templateOptionsProvider,
-                                @Named(TIMEOUT_NODE_RUNNING) Predicate<AtomicReference<NodeMetadata>> nodeRunning,
-                                @Named(TIMEOUT_NODE_TERMINATED) Predicate<AtomicReference<NodeMetadata>> nodeTerminated,
-                                @Named(TIMEOUT_NODE_SUSPENDED) Predicate<AtomicReference<NodeMetadata>> nodeSuspended,
-                                InitializeRunScriptOnNodeOrPlaceInBadMap.Factory initScriptRunnerFactory,
-                                RunScriptOnNode.Factory runScriptOnNodeFactory, InitAdminAccess initAdminAccess,
-                                PersistNodeCredentials persistNodeCredentials, Timeouts timeouts,
-                                @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor,
-                                 CleanupResources cleanupResources,
-                                Optional<ImageExtension> imageExtension,
-                                Optional<SecurityGroupExtension> securityGroupExtension) {
+         @Memoized Supplier<Set<? extends Image>> images, @Memoized Supplier<Set<? extends Hardware>> sizes,
+         @Memoized Supplier<Set<? extends Location>> locations, ListNodesStrategy listNodesStrategy,
+         GetImageStrategy getImageStrategy, GetNodeMetadataStrategy getNodeMetadataStrategy,
+         CreateNodesInGroupThenAddToSet runNodesAndAddToSetStrategy, RebootNodeStrategy rebootNodeStrategy,
+         DestroyNodeStrategy destroyNodeStrategy, ResumeNodeStrategy startNodeStrategy,
+         SuspendNodeStrategy stopNodeStrategy, Provider<TemplateBuilder> templateBuilderProvider,
+         @Named("DEFAULT") Provider<TemplateOptions> templateOptionsProvider,
+         @Named(TIMEOUT_NODE_RUNNING) Predicate<AtomicReference<NodeMetadata>> nodeRunning,
+         @Named(TIMEOUT_NODE_TERMINATED) Predicate<AtomicReference<NodeMetadata>> nodeTerminated,
+         @Named(TIMEOUT_NODE_SUSPENDED) Predicate<AtomicReference<NodeMetadata>> nodeSuspended,
+         InitializeRunScriptOnNodeOrPlaceInBadMap.Factory initScriptRunnerFactory,
+         RunScriptOnNode.Factory runScriptOnNodeFactory, InitAdminAccess initAdminAccess,
+         PersistNodeCredentials persistNodeCredentials, Timeouts timeouts,
+         @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor,
+         CleanupResources cleanupResources, Optional<ImageExtension> imageExtension,
+         Optional<SecurityGroupExtension> securityGroupExtension,
+         LocationToResourceGroupName locationToResourceGroupName) {
       super(context, credentialStore, images, sizes, locations, listNodesStrategy, getImageStrategy,
-              getNodeMetadataStrategy, runNodesAndAddToSetStrategy, rebootNodeStrategy, destroyNodeStrategy,
-              startNodeStrategy, stopNodeStrategy, templateBuilderProvider, templateOptionsProvider, nodeRunning,
-              nodeTerminated, nodeSuspended, initScriptRunnerFactory, initAdminAccess, runScriptOnNodeFactory,
-              persistNodeCredentials, timeouts, userExecutor, imageExtension, securityGroupExtension);
-      this.cleanupResources = checkNotNull(cleanupResources, "cleanupResources");
-
+            getNodeMetadataStrategy, runNodesAndAddToSetStrategy, rebootNodeStrategy, destroyNodeStrategy,
+            startNodeStrategy, stopNodeStrategy, templateBuilderProvider, templateOptionsProvider, nodeRunning,
+            nodeTerminated, nodeSuspended, initScriptRunnerFactory, initAdminAccess, runScriptOnNodeFactory,
+            persistNodeCredentials, timeouts, userExecutor, imageExtension, securityGroupExtension);
+      this.cleanupResources = cleanupResources;
+      this.locationToResourceGroupName = locationToResourceGroupName;
    }
 
    @Override
    protected void cleanUpIncidentalResourcesOfDeadNodes(Set<? extends NodeMetadata> deadNodes) {
+      ImmutableMultimap.Builder<String, String> regionGroups = ImmutableMultimap.builder();
+      ImmutableSet.Builder<String> resourceGroups = ImmutableSet.builder();
+
       for (NodeMetadata deadNode : deadNodes) {
-         cleanupResources.apply(deadNode.getId());
+         String resourceGroup = locationToResourceGroupName.apply(deadNode.getLocation().getId());
+
+         resourceGroups.add(resourceGroup);
+         if (deadNode.getGroup() != null) {
+            regionGroups.put(resourceGroup, deadNode.getGroup());
+         }
+
+         try {
+            cleanupResources.cleanupNode(deadNode.getId());
+         } catch (Exception ex) {
+            logger.warn(ex, "Error cleaning up resources for node %s", deadNode);
+         }
+      }
+
+      for (Entry<String, String> regionGroup : regionGroups.build().entries()) {
+         cleanupResources.cleanupSecurityGroupIfOrphaned(regionGroup.getKey(), regionGroup.getValue());
       }
-   }
 
+      for (String resourceGroup : resourceGroups.build()) {
+         cleanupResources.deleteResourceGroupIfEmpty(resourceGroup);
+      }
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/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 3b207b7..269f6b0 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
@@ -20,6 +20,7 @@ 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 com.google.common.collect.Iterables.find;
+import static com.google.common.collect.Iterables.getOnlyElement;
 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;
@@ -81,6 +82,7 @@ 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.options.TemplateOptions;
 import org.jclouds.compute.reference.ComputeServiceConstants;
 import org.jclouds.location.Region;
 import org.jclouds.logging.Logger;
@@ -137,11 +139,10 @@ public class AzureComputeServiceAdapter implements ComputeServiceAdapter<Virtual
 
       // TODO ARM specific options
       // TODO network ids => create one nic in each network
-      // TODO inbound ports
 
       String locationName = template.getLocation().getId();
       String subnetId = templateOptions.getSubnetId();
-      NetworkInterfaceCard nic = createNetworkInterfaceCard(subnetId, name, locationName, azureGroup);
+      NetworkInterfaceCard nic = createNetworkInterfaceCard(subnetId, name, locationName, azureGroup, template.getOptions());
       StorageProfile storageProfile = createStorageProfile(name, template.getImage(), templateOptions.getBlob());
       HardwareProfile hardwareProfile = HardwareProfile.builder().vmSize(template.getHardware().getId()).build();
       OSProfile osProfile = createOsProfile(name, template);
@@ -341,7 +342,7 @@ public class AzureComputeServiceAdapter implements ComputeServiceAdapter<Virtual
 
    @Override
    public void destroyNode(final String id) {
-      checkState(cleanupResources.apply(id), "server(%s) and its resources still there after deleting!?", id);
+      checkState(cleanupResources.cleanupNode(id), "server(%s) and its resources still there after deleting!?", id);
    }
 
    @Override
@@ -405,7 +406,7 @@ public class AzureComputeServiceAdapter implements ComputeServiceAdapter<Virtual
    }
 
    private NetworkInterfaceCard createNetworkInterfaceCard(String subnetId, String name, String locationName,
-         String azureGroup) {
+         String azureGroup, TemplateOptions options) {
       final PublicIPAddressApi ipApi = api.getPublicIPAddressApi(azureGroup);
 
       PublicIPAddressProperties properties = PublicIPAddressProperties.builder().publicIPAllocationMethod("Static")
@@ -418,7 +419,7 @@ public class AzureComputeServiceAdapter implements ComputeServiceAdapter<Virtual
       checkState(publicIpAvailable.create(azureGroup).apply(publicIpAddressName),
             "Public IP was not provisioned in the configured timeout");
 
-      final NetworkInterfaceCardProperties networkInterfaceCardProperties = NetworkInterfaceCardProperties
+      final NetworkInterfaceCardProperties.Builder networkInterfaceCardProperties = NetworkInterfaceCardProperties
             .builder()
             .ipConfigurations(
                   ImmutableList.of(IpConfiguration
@@ -427,11 +428,16 @@ public class AzureComputeServiceAdapter implements ComputeServiceAdapter<Virtual
                         .properties(
                               IpConfigurationProperties.builder().privateIPAllocationMethod("Dynamic")
                                     .publicIPAddress(IdReference.create(ip.id())).subnet(IdReference.create(subnetId))
-                                    .build()).build())).build();
+                                    .build()).build()));
+
+      String securityGroup = getOnlyElement(options.getGroups(), null);
+      if (securityGroup != null) {
+         networkInterfaceCardProperties.networkSecurityGroup(IdReference.create(securityGroup));
+      }
 
       String networkInterfaceCardName = "jc-nic-" + name;
       return api.getNetworkInterfaceCardApi(azureGroup).createOrUpdate(networkInterfaceCardName, locationName,
-            networkInterfaceCardProperties, ImmutableMap.of("jclouds", name));
+            networkInterfaceCardProperties.build(), ImmutableMap.of("jclouds", name));
    }
 
    private StorageProfile createStorageProfile(String name, Image image, String blob) {

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/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 34a5160..6a577eb 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
@@ -42,15 +42,22 @@ import javax.inject.Singleton;
 import org.jclouds.azurecompute.arm.AzureComputeApi;
 import org.jclouds.azurecompute.arm.compute.AzureComputeService;
 import org.jclouds.azurecompute.arm.compute.AzureComputeServiceAdapter;
+import org.jclouds.azurecompute.arm.compute.domain.RegionAndIdAndIngressRules;
 import org.jclouds.azurecompute.arm.compute.extensions.AzureComputeImageExtension;
+import org.jclouds.azurecompute.arm.compute.extensions.AzureComputeSecurityGroupExtension;
 import org.jclouds.azurecompute.arm.compute.functions.LocationToLocation;
+import org.jclouds.azurecompute.arm.compute.functions.NetworkSecurityGroupToSecurityGroup;
+import org.jclouds.azurecompute.arm.compute.functions.NetworkSecurityRuleToIpPermission;
 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;
+import org.jclouds.azurecompute.arm.compute.loaders.CreateSecurityGroupIfNeeded;
 import org.jclouds.azurecompute.arm.compute.options.AzureTemplateOptions;
 import org.jclouds.azurecompute.arm.compute.strategy.CreateResourceGroupThenCreateNodes;
 import org.jclouds.azurecompute.arm.domain.Location;
+import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroup;
+import org.jclouds.azurecompute.arm.domain.NetworkSecurityRule;
 import org.jclouds.azurecompute.arm.domain.PublicIPAddress;
 import org.jclouds.azurecompute.arm.domain.ResourceDefinition;
 import org.jclouds.azurecompute.arm.domain.VMHardware;
@@ -64,32 +71,38 @@ 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.domain.SecurityGroup;
 import org.jclouds.compute.extensions.ImageExtension;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
 import org.jclouds.compute.functions.NodeAndTemplateOptionsToStatement;
 import org.jclouds.compute.functions.NodeAndTemplateOptionsToStatementWithoutPublicKey;
 import org.jclouds.compute.options.TemplateOptions;
 import org.jclouds.compute.reference.ComputeServiceConstants.PollPeriod;
 import org.jclouds.compute.reference.ComputeServiceConstants.Timeouts;
 import org.jclouds.compute.strategy.CreateNodesInGroupThenAddToSet;
+import org.jclouds.net.domain.IpPermission;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
 import com.google.common.base.Predicate;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
 import com.google.inject.Inject;
 import com.google.inject.Provides;
 import com.google.inject.TypeLiteral;
 import com.google.inject.assistedinject.FactoryModuleBuilder;
 
-public class AzureComputeServiceContextModule
-        extends ComputeServiceAdapterContextModule<VirtualMachine, VMHardware, VMImage, Location> {
+public class AzureComputeServiceContextModule extends
+      ComputeServiceAdapterContextModule<VirtualMachine, VMHardware, VMImage, Location> {
 
    @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>>() {
@@ -98,19 +111,27 @@ public class AzureComputeServiceContextModule
       }).to(VirtualMachineToNodeMetadata.class);
       bind(new TypeLiteral<Function<Location, org.jclouds.domain.Location>>() {
       }).to(LocationToLocation.class);
+      bind(new TypeLiteral<Function<NetworkSecurityGroup, SecurityGroup>>() {
+      }).to(NetworkSecurityGroupToSecurityGroup.class);
+      bind(new TypeLiteral<Function<NetworkSecurityRule, IpPermission>>() {
+      }).to(NetworkSecurityRuleToIpPermission.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<CacheLoader<RegionAndIdAndIngressRules, String>>() {
+      }).to(CreateSecurityGroupIfNeeded.class);
+
       bind(new TypeLiteral<ImageExtension>() {
       }).to(AzureComputeImageExtension.class);
+      bind(new TypeLiteral<SecurityGroupExtension>() {
+      }).to(AzureComputeSecurityGroupExtension.class);
    }
 
    @Singleton
@@ -190,63 +211,82 @@ public class AzureComputeServiceContextModule
    }
 
    @Provides
+   @Singleton
+   protected final LoadingCache<RegionAndIdAndIngressRules, String> securityGroupMap(
+         CacheLoader<RegionAndIdAndIngressRules, String> in) {
+      return CacheBuilder.newBuilder().build(in);
+   }
+
+   @Provides
    @Named(TIMEOUT_NODE_RUNNING)
    protected VirtualMachineInStatePredicateFactory provideVirtualMachineRunningPredicate(final AzureComputeApi api,
-         Timeouts timeouts, PollPeriod pollPeriod) {
+         final Timeouts timeouts, final PollPeriod pollPeriod) {
       return new VirtualMachineInStatePredicateFactory(api, PowerState.RUNNING, timeouts.nodeRunning,
             pollPeriod.pollInitialPeriod, pollPeriod.pollMaxPeriod);
    }
-   
+
    @Provides
    @Named(TIMEOUT_NODE_TERMINATED)
-   protected Predicate<URI> provideNodeTerminatedPredicate(final AzureComputeApi api, Timeouts timeouts, PollPeriod pollPeriod) {
+   protected Predicate<URI> provideNodeTerminatedPredicate(final AzureComputeApi api, final Timeouts timeouts,
+         final PollPeriod pollPeriod) {
       return retry(new ActionDonePredicate(api), timeouts.nodeTerminated, pollPeriod.pollInitialPeriod,
-              pollPeriod.pollMaxPeriod);
+            pollPeriod.pollMaxPeriod);
    }
 
    @Provides
    @Named(TIMEOUT_IMAGE_AVAILABLE)
-   protected Predicate<URI> provideImageAvailablePredicate(final AzureComputeApi api, Timeouts timeouts, PollPeriod pollPeriod) {
+   protected Predicate<URI> provideImageAvailablePredicate(final AzureComputeApi api, final Timeouts timeouts,
+         final PollPeriod pollPeriod) {
       return retry(new ImageDonePredicate(api), timeouts.imageAvailable, pollPeriod.pollInitialPeriod,
-              pollPeriod.pollMaxPeriod);
+            pollPeriod.pollMaxPeriod);
    }
 
    @Provides
    @Named(TIMEOUT_RESOURCE_DELETED)
-   protected Predicate<URI> provideResourceDeletedPredicate(final AzureComputeApi api, Timeouts timeouts, PollPeriod pollPeriod) {
+   protected Predicate<URI> provideResourceDeletedPredicate(final AzureComputeApi api, final Timeouts timeouts,
+         final PollPeriod pollPeriod) {
       return retry(new ActionDonePredicate(api), timeouts.nodeTerminated, pollPeriod.pollInitialPeriod,
-              pollPeriod.pollMaxPeriod);
+            pollPeriod.pollMaxPeriod);
    }
 
    @Provides
    @Named(TIMEOUT_NODE_SUSPENDED)
    protected VirtualMachineInStatePredicateFactory provideNodeSuspendedPredicate(final AzureComputeApi api,
-         Timeouts timeouts, PollPeriod pollPeriod) {
+         final Timeouts timeouts, final PollPeriod pollPeriod) {
       return new VirtualMachineInStatePredicateFactory(api, PowerState.STOPPED, timeouts.nodeTerminated,
             pollPeriod.pollInitialPeriod, pollPeriod.pollMaxPeriod);
    }
-   
+
    @Provides
    protected PublicIpAvailablePredicateFactory providePublicIpAvailablePredicate(final AzureComputeApi api,
-         final AzureComputeServiceContextModule.AzureComputeConstants azureComputeConstants, Timeouts timeouts,
-         PollPeriod pollPeriod) {
+         final AzureComputeServiceContextModule.AzureComputeConstants azureComputeConstants, final Timeouts timeouts,
+         final PollPeriod pollPeriod) {
       return new PublicIpAvailablePredicateFactory(api, azureComputeConstants.operationTimeout(),
             azureComputeConstants.operationPollInitialPeriod(), azureComputeConstants.operationPollMaxPeriod());
    }
 
+   @Provides
+   protected SecurityGroupAvailablePredicateFactory provideSecurityGroupAvailablePredicate(final AzureComputeApi api,
+         final AzureComputeServiceContextModule.AzureComputeConstants azureComputeConstants, final Timeouts timeouts,
+         final PollPeriod pollPeriod) {
+      return new SecurityGroupAvailablePredicateFactory(api, azureComputeConstants.operationTimeout(),
+            azureComputeConstants.operationPollInitialPeriod(), azureComputeConstants.operationPollMaxPeriod());
+   }
+
    @VisibleForTesting
    static class ActionDonePredicate implements Predicate<URI> {
 
       private final AzureComputeApi api;
 
-      public ActionDonePredicate(AzureComputeApi api) {
+      public ActionDonePredicate(final AzureComputeApi api) {
          this.api = checkNotNull(api, "api must not be null");
       }
 
       @Override
-      public boolean apply(URI uri) {
+      public boolean apply(final URI uri) {
          checkNotNull(uri, "uri cannot be null");
-         return (ParseJobStatus.JobStatus.DONE == api.getJobApi().jobStatus(uri)) || (ParseJobStatus.JobStatus.NO_CONTENT == api.getJobApi().jobStatus(uri));
+         return ParseJobStatus.JobStatus.DONE == api.getJobApi().jobStatus(uri)
+               || ParseJobStatus.JobStatus.NO_CONTENT == api.getJobApi().jobStatus(uri);
       }
 
    }
@@ -256,14 +296,16 @@ public class AzureComputeServiceContextModule
 
       private final AzureComputeApi api;
 
-      public ImageDonePredicate(AzureComputeApi api) {
+      public ImageDonePredicate(final AzureComputeApi api) {
          this.api = checkNotNull(api, "api must not be null");
       }
 
       @Override
-      public boolean apply(URI uri) {
+      public boolean apply(final URI uri) {
          checkNotNull(uri, "uri cannot be null");
-         if (api.getJobApi().jobStatus(uri) != ParseJobStatus.JobStatus.DONE) return false;
+         if (api.getJobApi().jobStatus(uri) != ParseJobStatus.JobStatus.DONE) {
+            return false;
+         }
          List<ResourceDefinition> definitions = api.getJobApi().captureStatus(uri);
          return definitions != null;
       }
@@ -277,8 +319,8 @@ public class AzureComputeServiceContextModule
       private final long period;
       private final long maxPeriod;
 
-      VirtualMachineInStatePredicateFactory(AzureComputeApi api, PowerState powerState, long timeout,
-            long period, long maxPeriod) {
+      VirtualMachineInStatePredicateFactory(final AzureComputeApi api, final PowerState powerState, final long timeout,
+            final long period, final long maxPeriod) {
          this.api = checkNotNull(api, "api cannot be null");
          this.powerState = checkNotNull(powerState, "powerState cannot be null");
          this.timeout = timeout;
@@ -289,17 +331,18 @@ public class AzureComputeServiceContextModule
       public Predicate<String> create(final String azureGroup) {
          return retry(new Predicate<String>() {
             @Override
-            public boolean apply(String name) {
+            public boolean apply(final String name) {
                checkNotNull(name, "name cannot be null");
                VirtualMachineInstance vmInstance = api.getVirtualMachineApi(azureGroup).getInstanceDetails(name);
-               if (vmInstance == null)
+               if (vmInstance == null) {
                   return false;
+               }
                return powerState == vmInstance.powerState();
             }
          }, timeout, period, maxPeriod);
       }
    }
-   
+
    public static class PublicIpAvailablePredicateFactory {
 
       private final AzureComputeApi api;
@@ -307,25 +350,57 @@ public class AzureComputeServiceContextModule
       private final long period;
       private final long maxPeriod;
 
-      PublicIpAvailablePredicateFactory(AzureComputeApi api, long timeout,
-            long period, long maxPeriod) {
+      PublicIpAvailablePredicateFactory(final AzureComputeApi api, final long timeout, final long period,
+            final long maxPeriod) {
          this.api = checkNotNull(api, "api cannot be null");
          this.timeout = timeout;
          this.period = period;
          this.maxPeriod = maxPeriod;
       }
-      
+
       public Predicate<String> create(final String azureGroup) {
          return retry(new Predicate<String>() {
             @Override
-            public boolean apply(String name) {
+            public boolean apply(final String name) {
                checkNotNull(name, "name cannot be null");
                PublicIPAddress publicIp = api.getPublicIPAddressApi(azureGroup).get(name);
-               if (publicIp == null) return false;
+               if (publicIp == null) {
+                  return false;
+               }
                return publicIp.properties().provisioningState().equalsIgnoreCase("Succeeded");
             }
          }, timeout, period, maxPeriod);
       }
    }
 
+   public static class SecurityGroupAvailablePredicateFactory {
+      private final AzureComputeApi api;
+      private final long timeout;
+      private final long period;
+      private final long maxPeriod;
+
+      SecurityGroupAvailablePredicateFactory(final AzureComputeApi api, final long timeout, final long period,
+            final long maxPeriod) {
+         this.api = checkNotNull(api, "api cannot be null");
+         this.timeout = timeout;
+         this.period = period;
+         this.maxPeriod = maxPeriod;
+      }
+
+      public Predicate<String> create(final String resourceGroup) {
+         checkNotNull(resourceGroup, "resourceGroup cannot be null");
+         return retry(new Predicate<String>() {
+            @Override
+            public boolean apply(final String name) {
+               checkNotNull(name, "name cannot be null");
+               NetworkSecurityGroup sg = api.getNetworkSecurityGroupApi(resourceGroup).get(name);
+               if (sg == null) {
+                  return false;
+               }
+               return sg.properties().provisioningState().equalsIgnoreCase("Succeeded");
+            }
+         }, timeout, period, maxPeriod);
+      }
+   }
+
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/domain/RegionAndIdAndIngressRules.java
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/domain/RegionAndIdAndIngressRules.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/domain/RegionAndIdAndIngressRules.java
new file mode 100644
index 0000000..fa9730d
--- /dev/null
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/domain/RegionAndIdAndIngressRules.java
@@ -0,0 +1,66 @@
+/*
+ * 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.domain;
+
+import org.jclouds.azurecompute.arm.domain.RegionAndId;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Objects;
+
+@AutoValue
+public abstract class RegionAndIdAndIngressRules {
+
+   abstract RegionAndId regionAndId(); // Intentionally hidden
+   public abstract int[] inboundPorts();
+
+   RegionAndIdAndIngressRules() {
+
+   }
+
+   public static RegionAndIdAndIngressRules create(String region, String id, int[] inboundPorts) {
+      return new AutoValue_RegionAndIdAndIngressRules(RegionAndId.fromRegionAndId(region, id), inboundPorts);
+   }
+
+   public String id() {
+      return regionAndId().id();
+   }
+
+   public String region() {
+      return regionAndId().region();
+   }
+
+   // Intentionally delegate equals and hashcode to the fields in the parent
+   // class so that we can search only by region/id in a map
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(region(), id());
+   }
+
+   @Override
+   public boolean equals(Object obj) {
+      if (obj == this) {
+         return true;
+      }
+      if (!(obj instanceof RegionAndId)) {
+         return false;
+      }
+      RegionAndId that = (RegionAndId) obj;
+      return Objects.equal(region(), that.region()) && Objects.equal(id(), that.id());
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeSecurityGroupExtension.java
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeSecurityGroupExtension.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeSecurityGroupExtension.java
new file mode 100644
index 0000000..12d140b
--- /dev/null
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeSecurityGroupExtension.java
@@ -0,0 +1,315 @@
+/*
+ * 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 static com.google.common.base.Preconditions.checkState;
+import static com.google.common.base.Predicates.equalTo;
+import static com.google.common.base.Predicates.notNull;
+import static com.google.common.collect.Iterables.any;
+import static com.google.common.collect.Iterables.concat;
+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.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.jclouds.azurecompute.arm.AzureComputeApi;
+import org.jclouds.azurecompute.arm.compute.config.AzureComputeServiceContextModule.SecurityGroupAvailablePredicateFactory;
+import org.jclouds.azurecompute.arm.compute.functions.LocationToResourceGroupName;
+import org.jclouds.azurecompute.arm.domain.IdReference;
+import org.jclouds.azurecompute.arm.domain.NetworkInterfaceCard;
+import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroup;
+import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroupProperties;
+import org.jclouds.azurecompute.arm.domain.NetworkSecurityRule;
+import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties;
+import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties.Access;
+import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties.Direction;
+import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties.Protocol;
+import org.jclouds.azurecompute.arm.domain.RegionAndId;
+import org.jclouds.azurecompute.arm.domain.VirtualMachine;
+import org.jclouds.azurecompute.arm.features.NetworkSecurityGroupApi;
+import org.jclouds.azurecompute.arm.features.NetworkSecurityRuleApi;
+import org.jclouds.collect.Memoized;
+import org.jclouds.compute.domain.SecurityGroup;
+import org.jclouds.compute.domain.SecurityGroupBuilder;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.domain.Location;
+import org.jclouds.logging.Logger;
+import org.jclouds.net.domain.IpPermission;
+import org.jclouds.net.domain.IpProtocol;
+
+import com.google.common.base.Function;
+import com.google.common.base.Objects;
+import com.google.common.base.Predicate;
+import com.google.common.base.Splitter;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Ordering;
+
+public class AzureComputeSecurityGroupExtension implements SecurityGroupExtension {
+   @Resource
+   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   private final AzureComputeApi api;
+   private final Function<NetworkSecurityGroup, SecurityGroup> securityGroupConverter;
+   private final LocationToResourceGroupName locationToResourceGroupName;
+   private final Supplier<Set<? extends Location>> locations;
+   private final SecurityGroupAvailablePredicateFactory securityGroupAvailable;
+   private final Predicate<URI> resourceDeleted;
+
+   @Inject
+   AzureComputeSecurityGroupExtension(AzureComputeApi api, @Memoized Supplier<Set<? extends Location>> locations,
+         LocationToResourceGroupName locationToResourceGroupName,
+         Function<NetworkSecurityGroup, SecurityGroup> groupConverter,
+         SecurityGroupAvailablePredicateFactory securityRuleAvailable,
+         @Named(TIMEOUT_RESOURCE_DELETED) Predicate<URI> resourceDeleted) {
+      this.api = api;
+      this.locations = locations;
+      this.securityGroupConverter = groupConverter;
+      this.locationToResourceGroupName = locationToResourceGroupName;
+      this.securityGroupAvailable = securityRuleAvailable;
+      this.resourceDeleted = resourceDeleted;
+   }
+
+   @Override
+   public Set<SecurityGroup> listSecurityGroups() {
+      return ImmutableSet.copyOf(concat(transform(locations.get(), new Function<Location, Set<SecurityGroup>>() {
+         @Override
+         public Set<SecurityGroup> apply(Location input) {
+            return listSecurityGroupsInLocation(input);
+         }
+      })));
+   }
+
+   @Override
+   public Set<SecurityGroup> listSecurityGroupsInLocation(Location location) {
+      logger.debug(">> getting security groups for %s...", location);
+      final String resourcegroup = locationToResourceGroupName.apply(location.getId());
+      List<NetworkSecurityGroup> networkGroups = api.getNetworkSecurityGroupApi(resourcegroup).list();
+      return ImmutableSet.copyOf(transform(filter(networkGroups, notNull()), securityGroupConverter));
+   }
+
+   @Override
+   public Set<SecurityGroup> listSecurityGroupsForNode(String nodeId) {
+      logger.debug(">> getting security groups for node %s...", nodeId);
+
+      final RegionAndId regionAndId = RegionAndId.fromSlashEncoded(nodeId);
+      final String resourceGroup = locationToResourceGroupName.apply(regionAndId.region());
+
+      VirtualMachine vm = api.getVirtualMachineApi(resourceGroup).get(regionAndId.id());
+      List<IdReference> networkInterfacesIdReferences = vm.properties().networkProfile().networkInterfaces();
+      List<NetworkSecurityGroup> networkGroups = new ArrayList<NetworkSecurityGroup>();
+
+      for (IdReference networkInterfaceCardIdReference : networkInterfacesIdReferences) {
+         String nicName = Iterables.getLast(Splitter.on("/").split(networkInterfaceCardIdReference.id()));
+         NetworkInterfaceCard card = api.getNetworkInterfaceCardApi(resourceGroup).get(nicName);
+         if (card != null && card.properties().networkSecurityGroup() != null) {
+            String secGroupName = Iterables.getLast(Splitter.on("/").split(
+                  card.properties().networkSecurityGroup().id()));
+            NetworkSecurityGroup group = api.getNetworkSecurityGroupApi(resourceGroup).get(secGroupName);
+            networkGroups.add(group);
+         }
+      }
+
+      return ImmutableSet.copyOf(transform(filter(networkGroups, notNull()), securityGroupConverter));
+   }
+
+   @Override
+   public SecurityGroup getSecurityGroupById(String id) {
+      logger.debug(">> getting security group %s...", id);
+      final RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id);
+      final String resourceGroup = locationToResourceGroupName.apply(regionAndId.region());
+
+      return securityGroupConverter.apply(api.getNetworkSecurityGroupApi(resourceGroup).get(regionAndId.id()));
+   }
+
+   @Override
+   public SecurityGroup createSecurityGroup(String name, Location location) {
+      final String resourceGroup = locationToResourceGroupName.apply(location.getId());
+
+      logger.debug(">> creating security group %s in %s...", name, location);
+
+      SecurityGroupBuilder builder = new SecurityGroupBuilder();
+      builder.name(name);
+      builder.location(location);
+
+      return securityGroupConverter.apply(api.getNetworkSecurityGroupApi(resourceGroup).createOrUpdate(name,
+            location.getId(), null, NetworkSecurityGroupProperties.builder().build()));
+   }
+
+   @Override
+   public boolean removeSecurityGroup(String id) {
+      logger.debug(">> deleting security group %s...", id);
+
+      final RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id);
+      final String resourcegroup = locationToResourceGroupName.apply(regionAndId.region());
+      URI uri = api.getNetworkSecurityGroupApi(resourcegroup).delete(regionAndId.id());
+      return resourceDeleted.apply(uri);
+   }
+
+   @Override
+   public SecurityGroup addIpPermission(IpPermission ipPermission, SecurityGroup group) {
+      return addIpPermission(ipPermission.getIpProtocol(), ipPermission.getFromPort(), ipPermission.getToPort(),
+            ipPermission.getTenantIdGroupNamePairs(), ipPermission.getCidrBlocks(), ipPermission.getGroupIds(), group);
+   }
+
+   @Override
+   public SecurityGroup removeIpPermission(IpPermission ipPermission, SecurityGroup group) {
+      return removeIpPermission(ipPermission.getIpProtocol(), ipPermission.getFromPort(), ipPermission.getToPort(),
+            ipPermission.getTenantIdGroupNamePairs(), ipPermission.getCidrBlocks(), ipPermission.getGroupIds(), group);
+   }
+
+   @Override
+   public SecurityGroup addIpPermission(IpProtocol protocol, int startPort, int endPort,
+         Multimap<String, String> tenantIdGroupNamePairs, Iterable<String> ipRanges, Iterable<String> groupIds,
+         SecurityGroup group) {
+      String portRange = startPort + "-" + endPort;
+      String ruleName = protocol + "-" + portRange;
+
+      logger.debug(">> adding ip permission [%s] to %s...", ruleName, group.getName());
+
+      // TODO: Support Azure network tags somehow?
+
+      final RegionAndId regionAndId = RegionAndId.fromSlashEncoded(group.getId());
+      final String resourceGroup = locationToResourceGroupName.apply(regionAndId.region());
+
+      NetworkSecurityGroupApi groupApi = api.getNetworkSecurityGroupApi(resourceGroup);
+      NetworkSecurityGroup networkSecurityGroup = groupApi.get(regionAndId.id());
+
+      if (networkSecurityGroup == null) {
+         throw new IllegalArgumentException("Security group " + group.getName() + " was not found");
+      }
+
+      NetworkSecurityRuleApi ruleApi = api.getNetworkSecurityRuleApi(resourceGroup, networkSecurityGroup.name());
+      int nextPriority = getRuleStartingPriority(ruleApi);
+
+      for (String ipRange : ipRanges) {
+         NetworkSecurityRuleProperties properties = NetworkSecurityRuleProperties.builder()
+               .protocol(Protocol.fromValue(protocol.name())) //
+               .sourceAddressPrefix(ipRange) //
+               .sourcePortRange("*") //
+               .destinationAddressPrefix("*") //
+               .destinationPortRange(portRange) //
+               .direction(Direction.Inbound) //
+               .access(Access.Allow) //
+               .priority(nextPriority++) //
+               .build();
+
+         logger.debug(">> creating network security rule %s for %s...", ruleName, ipRange);
+
+         ruleApi.createOrUpdate(ruleName, properties);
+
+         checkState(securityGroupAvailable.create(resourceGroup).apply(networkSecurityGroup.name()),
+               "Security group was not updated in the configured timeout");
+      }
+
+      return getSecurityGroupById(group.getId());
+   }
+
+   @Override
+   public SecurityGroup removeIpPermission(final IpProtocol protocol, int startPort, int endPort,
+         Multimap<String, String> tenantIdGroupNamePairs, final Iterable<String> ipRanges, Iterable<String> groupIds,
+         SecurityGroup group) {
+      final String portRange = startPort + "-" + endPort;
+      String ruleName = protocol + "-" + portRange;
+
+      logger.debug(">> deleting ip permissions matching [%s] from %s...", ruleName, group.getName());
+
+      final RegionAndId regionAndId = RegionAndId.fromSlashEncoded(group.getId());
+      final String resourceGroup = locationToResourceGroupName.apply(regionAndId.region());
+
+      NetworkSecurityGroupApi groupApi = api.getNetworkSecurityGroupApi(resourceGroup);
+      NetworkSecurityGroup networkSecurityGroup = groupApi.get(regionAndId.id());
+
+      if (networkSecurityGroup == null) {
+         throw new IllegalArgumentException("Security group " + group.getName() + " was not found");
+      }
+
+      NetworkSecurityRuleApi ruleApi = api.getNetworkSecurityRuleApi(resourceGroup, networkSecurityGroup.name());
+      Iterable<NetworkSecurityRule> rules = filter(ruleApi.list(), new Predicate<NetworkSecurityRule>() {
+         @Override
+         public boolean apply(NetworkSecurityRule input) {
+            NetworkSecurityRuleProperties props = input.properties();
+            return Objects.equal(portRange, props.destinationPortRange())
+                  && Objects.equal(Protocol.fromValue(protocol.name()), props.protocol())
+                  && Objects.equal(Direction.Inbound, props.direction()) //
+                  && Objects.equal(Access.Allow, props.access())
+                  && any(ipRanges, equalTo(props.sourceAddressPrefix().replace("*", "0.0.0.0/0")));
+         }
+      });
+
+      for (NetworkSecurityRule matchingRule : rules) {
+         logger.debug(">> deleting network security rule %s from %s...", matchingRule.name(), group.getName());
+         ruleApi.delete(matchingRule.name());
+         checkState(securityGroupAvailable.create(resourceGroup).apply(networkSecurityGroup.name()),
+               "Security group was not updated in the configured timeout");
+      }
+
+      return getSecurityGroupById(group.getId());
+   }
+
+   @Override
+   public boolean supportsTenantIdGroupNamePairs() {
+      return false;
+   }
+
+   @Override
+   public boolean supportsTenantIdGroupIdPairs() {
+      return false;
+   }
+
+   @Override
+   public boolean supportsGroupIds() {
+      return false;
+   }
+
+   @Override
+   public boolean supportsPortRangesForGroups() {
+      return false;
+   }
+
+   @Override
+   public boolean supportsExclusionCidrBlocks() {
+      return false;
+   }
+
+   private int getRuleStartingPriority(NetworkSecurityRuleApi ruleApi) {
+      List<NetworkSecurityRule> existingRules = ruleApi.list();
+      return existingRules.isEmpty() ? 100 : rulesByPriority().max(existingRules).properties().priority() + 1;
+   }
+
+   private static Ordering<NetworkSecurityRule> rulesByPriority() {
+      return new Ordering<NetworkSecurityRule>() {
+         @Override
+         public int compare(NetworkSecurityRule left, NetworkSecurityRule right) {
+            return left.properties().priority() - right.properties().priority();
+         }
+      };
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/NetworkSecurityGroupToSecurityGroup.java
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/NetworkSecurityGroupToSecurityGroup.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/NetworkSecurityGroupToSecurityGroup.java
new file mode 100644
index 0000000..65f5b0d
--- /dev/null
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/NetworkSecurityGroupToSecurityGroup.java
@@ -0,0 +1,72 @@
+/*
+ * 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 com.google.common.collect.Iterables.filter;
+import static com.google.common.collect.Iterables.transform;
+import static org.jclouds.azurecompute.arm.compute.functions.NetworkSecurityRuleToIpPermission.InboundRule;
+import static org.jclouds.azurecompute.arm.compute.functions.VirtualMachineToNodeMetadata.getLocation;
+
+import java.util.Set;
+
+import javax.inject.Singleton;
+
+import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroup;
+import org.jclouds.azurecompute.arm.domain.NetworkSecurityRule;
+import org.jclouds.azurecompute.arm.domain.RegionAndId;
+import org.jclouds.collect.Memoized;
+import org.jclouds.compute.domain.SecurityGroup;
+import org.jclouds.compute.domain.SecurityGroupBuilder;
+import org.jclouds.domain.Location;
+import org.jclouds.net.domain.IpPermission;
+
+import com.google.common.base.Function;
+import com.google.common.base.Supplier;
+import com.google.inject.Inject;
+
+@Singleton
+public class NetworkSecurityGroupToSecurityGroup implements Function<NetworkSecurityGroup, SecurityGroup> {
+   private final Function<NetworkSecurityRule, IpPermission> ruleToPermission;
+   private final Supplier<Set<? extends Location>> locations;
+
+   @Inject
+   NetworkSecurityGroupToSecurityGroup(Function<NetworkSecurityRule, IpPermission> ruleToPermission,
+         @Memoized Supplier<Set<? extends Location>> locations) {
+      this.ruleToPermission = ruleToPermission;
+      this.locations = locations;
+   }
+
+   @Override
+   public SecurityGroup apply(NetworkSecurityGroup input) {
+      SecurityGroupBuilder builder = new SecurityGroupBuilder();
+
+      builder.id(RegionAndId.fromRegionAndId(input.location(), input.name()).slashEncode());
+      builder.providerId(input.properties().resourceGuid());
+      builder.name(input.name());
+      builder.location(getLocation(locations, input.location()));
+
+      if (input.properties().securityRules() != null) {
+         // We just supoprt security groups that allow traffic to a set of
+         // targets. We don't support deny rules or origin based rules in the
+         // security group api.
+         builder.ipPermissions(transform(filter(input.properties().securityRules(), InboundRule), ruleToPermission));
+      }
+
+      return builder.build();
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/NetworkSecurityRuleToIpPermission.java
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/NetworkSecurityRuleToIpPermission.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/NetworkSecurityRuleToIpPermission.java
new file mode 100644
index 0000000..e601d59
--- /dev/null
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/NetworkSecurityRuleToIpPermission.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 javax.annotation.Resource;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.azurecompute.arm.domain.NetworkSecurityRule;
+import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties.Access;
+import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties.Direction;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.logging.Logger;
+import org.jclouds.net.domain.IpPermission;
+import org.jclouds.net.domain.IpProtocol;
+import org.jclouds.net.util.IpPermissions;
+import org.jclouds.net.util.IpPermissions.PortSelection;
+import org.jclouds.net.util.IpPermissions.ToSourceSelection;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+
+@Singleton
+public class NetworkSecurityRuleToIpPermission implements Function<NetworkSecurityRule, IpPermission> {
+
+   public static final Predicate<NetworkSecurityRule> InboundRule = new Predicate<NetworkSecurityRule>() {
+      @Override
+      public boolean apply(NetworkSecurityRule input) {
+         return Direction.Inbound.equals(input.properties().direction())
+               && Access.Allow.equals(input.properties().access());
+      }
+   };
+
+   @Resource
+   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   @Override
+   public IpPermission apply(final NetworkSecurityRule rule) {
+      if (!InboundRule.apply(rule)) {
+         logger.warn(">> ignoring non-inbound networks ecurity rule %s...", rule.name());
+         return null;
+      }
+
+      IpPermission permissions = IpPermissions.permit(IpProtocol.fromValue(rule.properties().protocol().name()));
+
+      String portRange = rule.properties().destinationPortRange();
+      if (!"*".equals(portRange)) {
+         String[] range = portRange.split("-");
+         permissions = PortSelection.class.cast(permissions).fromPort(Integer.parseInt(range[0]))
+               .to(Integer.parseInt(range[1]));
+      }
+
+      if (!"*".equals(rule.properties().sourceAddressPrefix())) {
+         permissions = ToSourceSelection.class.cast(permissions).originatingFromCidrBlock(
+               rule.properties().sourceAddressPrefix());
+      }
+
+      return permissions;
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/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 5e98fbf..22c818c 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
@@ -191,7 +191,7 @@ public class VirtualMachineToNodeMetadata implements Function<VirtualMachine, No
             .name()));
       
       String locationName = virtualMachine.location();
-      builder.location(getLocation(locationName));
+      builder.location(getLocation(locations, locationName));
 
       Optional<? extends Image> image = findImage(virtualMachine.properties().storageProfile(), locationName, azureGroup);
       if (image.isPresent()) {
@@ -251,7 +251,7 @@ public class VirtualMachineToNodeMetadata implements Function<VirtualMachine, No
       return publicIpAddresses;
    }
 
-   protected Location getLocation(final String locationName) {
+   protected static Location getLocation(Supplier<Set<? extends Location>> locations, final String locationName) {
       return find(locations.get(), new Predicate<Location>() {
          @Override
          public boolean apply(Location location) {

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/loaders/CreateSecurityGroupIfNeeded.java
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/loaders/CreateSecurityGroupIfNeeded.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/loaders/CreateSecurityGroupIfNeeded.java
new file mode 100644
index 0000000..efb8abc
--- /dev/null
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/loaders/CreateSecurityGroupIfNeeded.java
@@ -0,0 +1,97 @@
+/*
+ * 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.loaders;
+
+import static org.jclouds.compute.util.ComputeServiceUtils.getPortRangesFromList;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+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.compute.domain.RegionAndIdAndIngressRules;
+import org.jclouds.azurecompute.arm.compute.functions.LocationToResourceGroupName;
+import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroup;
+import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroupProperties;
+import org.jclouds.azurecompute.arm.domain.NetworkSecurityRule;
+import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties;
+import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties.Access;
+import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties.Direction;
+import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties.Protocol;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.logging.Logger;
+
+import com.google.common.cache.CacheLoader;
+
+@Singleton
+public class CreateSecurityGroupIfNeeded extends CacheLoader<RegionAndIdAndIngressRules, String> {
+   @Resource
+   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   private final AzureComputeApi api;
+   private final LocationToResourceGroupName locationToResourceGroupName;
+
+   @Inject
+   CreateSecurityGroupIfNeeded(AzureComputeApi api, LocationToResourceGroupName locationToResourceGroupName) {
+      this.api = api;
+      this.locationToResourceGroupName = locationToResourceGroupName;
+   }
+
+   @Override
+   public String load(RegionAndIdAndIngressRules key) throws Exception {
+      String resourceGroup = locationToResourceGroupName.apply(key.region());
+      return createSecurityGroup(key.region(), resourceGroup, key.id(), key.inboundPorts());
+   }
+
+   private String createSecurityGroup(String location, String resourceGroup, String name, int[] inboundPorts) {
+      logger.debug(">> creating security group %s in %s...", name, location);
+
+      Map<Integer, Integer> portRanges = getPortRangesFromList(inboundPorts);
+
+      List<NetworkSecurityRule> rules = new ArrayList<NetworkSecurityRule>();
+
+      int startPriority = 100;
+      for (Map.Entry<Integer, Integer> portRange : portRanges.entrySet()) {
+         String range = portRange.getKey() + "-" + portRange.getValue();
+         String ruleName = "tcp-" + range;
+
+         NetworkSecurityRuleProperties properties = NetworkSecurityRuleProperties.builder().protocol(Protocol.Tcp) //
+               .sourceAddressPrefix("*") //
+               .sourcePortRange("*") //
+               .destinationAddressPrefix("*") //
+               .destinationPortRange(range) //
+               .direction(Direction.Inbound) //
+               .access(Access.Allow) //
+               .priority(startPriority++) //
+               .build();
+
+         rules.add(NetworkSecurityRule.create(ruleName, null, null, properties));
+      }
+
+      NetworkSecurityGroup securityGroup = api.getNetworkSecurityGroupApi(resourceGroup).createOrUpdate(name, location,
+            null, NetworkSecurityGroupProperties.builder().securityRules(rules).build());
+
+      return securityGroup.id();
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/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 5ce30b0..524eb69 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,8 +16,10 @@
  */
 package org.jclouds.azurecompute.arm.compute.strategy;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.Iterables.getOnlyElement;
 import static org.jclouds.azurecompute.arm.compute.functions.VMImageToImage.decodeFieldsFromUniqueId;
 import static org.jclouds.util.Predicates2.retry;
 
@@ -35,8 +37,11 @@ import javax.inject.Singleton;
 import org.jclouds.Constants;
 import org.jclouds.azurecompute.arm.AzureComputeApi;
 import org.jclouds.azurecompute.arm.compute.config.AzureComputeServiceContextModule;
+import org.jclouds.azurecompute.arm.compute.domain.RegionAndIdAndIngressRules;
 import org.jclouds.azurecompute.arm.compute.functions.LocationToResourceGroupName;
 import org.jclouds.azurecompute.arm.compute.options.AzureTemplateOptions;
+import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroup;
+import org.jclouds.azurecompute.arm.domain.RegionAndId;
 import org.jclouds.azurecompute.arm.domain.ResourceGroup;
 import org.jclouds.azurecompute.arm.domain.StorageService;
 import org.jclouds.azurecompute.arm.domain.Subnet;
@@ -51,15 +56,18 @@ import org.jclouds.compute.domain.Image;
 import org.jclouds.compute.domain.NodeMetadata;
 import org.jclouds.compute.domain.Template;
 import org.jclouds.compute.functions.GroupNamingConvention;
+import org.jclouds.compute.options.TemplateOptions;
 import org.jclouds.compute.reference.ComputeServiceConstants;
 import org.jclouds.compute.strategy.CreateNodeWithGroupEncodedIntoName;
 import org.jclouds.compute.strategy.CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap;
 import org.jclouds.compute.strategy.ListNodesStrategy;
 import org.jclouds.compute.strategy.impl.CreateNodesWithGroupEncodedIntoNameThenAddToSet;
+import org.jclouds.domain.Location;
 import org.jclouds.logging.Logger;
 
 import com.google.common.base.Predicate;
 import com.google.common.base.Strings;
+import com.google.common.cache.LoadingCache;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Multimap;
 import com.google.common.util.concurrent.ListenableFuture;
@@ -75,34 +83,37 @@ public class CreateResourceGroupThenCreateNodes extends CreateNodesWithGroupEnco
    private final AzureComputeApi api;
    private final AzureComputeServiceContextModule.AzureComputeConstants azureComputeConstants;
    private final LocationToResourceGroupName locationToResourceGroupName;
+   private final LoadingCache<RegionAndIdAndIngressRules, String> securityGroupMap;
 
    @Inject
    protected CreateResourceGroupThenCreateNodes(
-           CreateNodeWithGroupEncodedIntoName addNodeWithGroupStrategy,
-           ListNodesStrategy listNodesStrategy,
-           GroupNamingConvention.Factory namingConvention,
+         CreateNodeWithGroupEncodedIntoName addNodeWithGroupStrategy,
+         ListNodesStrategy listNodesStrategy,
+         GroupNamingConvention.Factory namingConvention,
          @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor,
          CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory,
          AzureComputeApi api, AzureComputeServiceContextModule.AzureComputeConstants azureComputeConstants,
-         LocationToResourceGroupName locationToResourceGroupName) {
+         LocationToResourceGroupName locationToResourceGroupName,
+         LoadingCache<RegionAndIdAndIngressRules, String> securityGroupMap) {
       super(addNodeWithGroupStrategy, listNodesStrategy, namingConvention, userExecutor,
-              customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory);
+            customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory);
       this.api = checkNotNull(api, "api cannot be null");
       checkNotNull(userExecutor, "userExecutor cannot be null");
       this.azureComputeConstants = azureComputeConstants;
       this.locationToResourceGroupName = locationToResourceGroupName;
+      this.securityGroupMap = securityGroupMap;
    }
 
    @Override
    public Map<?, ListenableFuture<Void>> execute(String group, int count, Template template,
-                                                 Set<NodeMetadata> goodNodes, Map<NodeMetadata, Exception> badNodes,
-                                                 Multimap<NodeMetadata, CustomizationResponse> customizationResponses) {
+         Set<NodeMetadata> goodNodes, Map<NodeMetadata, Exception> badNodes,
+         Multimap<NodeMetadata, CustomizationResponse> customizationResponses) {
       // If there is a script to be run on the node and public key
       // authentication has been configured, warn users if the private key
       // is not present
       if (hasRunScriptWithKeyAuthAndNoPrivateKey(template)) {
-         logger.warn(">> a runScript was configured but no SSH key has been provided. " +
-                 "Authentication will delegate to the ssh-agent");
+         logger.warn(">> a runScript was configured but no SSH key has been provided. "
+               + "Authentication will delegate to the ssh-agent");
       }
       String azureGroupName = locationToResourceGroupName.apply(template.getLocation().getId());
 
@@ -112,7 +123,7 @@ public class CreateResourceGroupThenCreateNodes extends CreateNodesWithGroupEnco
       ResourceGroup resourceGroup = resourceGroupApi.get(azureGroupName);
       final String location = template.getLocation().getId();
 
-      if (resourceGroup == null){
+      if (resourceGroup == null) {
          final Map<String, String> tags = ImmutableMap.of("description", "jclouds managed VMs");
          resourceGroupApi.create(azureGroupName, location, tags).name();
       }
@@ -125,33 +136,35 @@ public class CreateResourceGroupThenCreateNodes extends CreateNodesWithGroupEnco
       }
 
       this.getOrCreateVirtualNetworkWithSubnet(vnetName, subnetName, location, options, azureGroupName);
+      configureSecurityGroupForOptions(group, azureGroupName, template.getLocation(), options);
 
       StorageService storageService = getOrCreateStorageService(group, azureGroupName, location, template.getImage());
       String blob = storageService.storageServiceProperties().primaryEndpoints().get("blob");
       options.blob(blob);
-      
+
       Map<?, ListenableFuture<Void>> responses = super.execute(group, count, template, goodNodes, badNodes,
-              customizationResponses);
+            customizationResponses);
 
       return responses;
    }
 
-   protected synchronized void getOrCreateVirtualNetworkWithSubnet(
-           final String virtualNetworkName, final String subnetName, final String location,
-           AzureTemplateOptions options, final String azureGroupName) {
+   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
+      // 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();
+         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);
       }
 
@@ -164,10 +177,11 @@ public class CreateResourceGroupThenCreateNodes extends CreateNodesWithGroupEnco
 
    private static boolean hasRunScriptWithKeyAuthAndNoPrivateKey(Template template) {
       return template.getOptions().getRunScript() != null && template.getOptions().getPublicKey() != null
-              && !template.getOptions().hasLoginPrivateKeyOption();
+            && !template.getOptions().hasLoginPrivateKeyOption();
    }
 
-   public StorageService getOrCreateStorageService(String name, String resourceGroupName, String locationName, Image image) {
+   public StorageService getOrCreateStorageService(String name, String resourceGroupName, String locationName,
+         Image image) {
       String storageAccountName = null;
       VMImage imageRef = decodeFieldsFromUniqueId(image.getId());
       if (imageRef.custom()) {
@@ -179,10 +193,12 @@ public class CreateResourceGroupThenCreateNodes extends CreateNodesWithGroupEnco
       }
 
       StorageService storageService = api.getStorageAccountApi(resourceGroupName).get(storageAccountName);
-      if (storageService != null) return storageService;
+      if (storageService != null)
+         return storageService;
 
-      URI uri = api.getStorageAccountApi(resourceGroupName).create(storageAccountName, locationName, ImmutableMap.of("jclouds",
-              name), ImmutableMap.of("accountType", StorageService.AccountType.Standard_LRS.toString()));
+      URI uri = api.getStorageAccountApi(resourceGroupName).create(storageAccountName, locationName,
+            ImmutableMap.of("jclouds", name),
+            ImmutableMap.of("accountType", StorageService.AccountType.Standard_LRS.toString()));
       boolean starageAccountCreated = retry(new Predicate<URI>() {
          @Override
          public boolean apply(URI uri) {
@@ -195,16 +211,44 @@ public class CreateResourceGroupThenCreateNodes extends CreateNodesWithGroupEnco
       return api.getStorageAccountApi(resourceGroupName).get(storageAccountName);
    }
 
+   private void configureSecurityGroupForOptions(String group, String resourceGroup, Location location,
+         TemplateOptions options) {
+
+      checkArgument(options.getGroups().size() <= 1,
+            "Only one security group can be configured for each network interface");
+
+      if (!options.getGroups().isEmpty()) {
+         String groupName = getOnlyElement(options.getGroups());
+         String groupNameWithourRegion = groupName.indexOf('/') == -1 ? groupName : RegionAndId.fromSlashEncoded(
+               groupName).id();
+         NetworkSecurityGroup securityGroup = api.getNetworkSecurityGroupApi(resourceGroup).get(groupNameWithourRegion);
+         checkArgument(securityGroup != null, "Security group %s was not found", groupName);
+         options.securityGroups(securityGroup.id());
+      } else if (options.getInboundPorts().length > 0) {
+         String name = namingConvention.create().sharedNameForGroup(group);
+         RegionAndIdAndIngressRules regionAndIdAndIngressRules = RegionAndIdAndIngressRules.create(location.getId(),
+               name, options.getInboundPorts());
+         // this will create if not yet exists.
+         String securityGroupId = securityGroupMap.getUnchecked(regionAndIdAndIngressRules);
+         options.securityGroups(securityGroupId);
+      }
+   }
+
    /**
     * Generates a valid storage account
     *
-    * Storage account names must be between 3 and 24 characters in length and may contain numbers and lowercase letters only.
+    * Storage account names must be between 3 and 24 characters in length and
+    * may contain numbers and lowercase letters only.
     *
-    * @param name the node name
-    * @return the storage account name starting from a sanitized name (with only numbers and lowercase letters only ).
-    * If sanitized name is between 3 and 24 characters, storage account name is equals to sanitized name.
-    * If sanitized name is less than 3 characters, storage account is sanitized name plus 4 random chars.
-    * If sanitized name is more than 24 characters, storage account is first 10 chars of sanitized name plus 4 random chars plus last 10 chars of sanitized name.
+    * @param name
+    *           the node name
+    * @return the storage account name starting from a sanitized name (with only
+    *         numbers and lowercase letters only ). If sanitized name is between
+    *         3 and 24 characters, storage account name is equals to sanitized
+    *         name. If sanitized name is less than 3 characters, storage account
+    *         is sanitized name plus 4 random chars. If sanitized name is more
+    *         than 24 characters, storage account is first 10 chars of sanitized
+    *         name plus 4 random chars plus last 10 chars of sanitized name.
     */
    public static String generateStorageAccountName(String name) {
       String random = UUID.randomUUID().toString().substring(0, 4);

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/NetworkSecurityGroup.java
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/NetworkSecurityGroup.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/NetworkSecurityGroup.java
index ebe842e..e7c75e8 100644
--- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/NetworkSecurityGroup.java
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/NetworkSecurityGroup.java
@@ -25,7 +25,7 @@ import java.util.Map;
 
 @AutoValue
 public abstract class NetworkSecurityGroup {
-   @Nullable
+   public abstract String id();
    public abstract String name();
 
    @Nullable
@@ -40,15 +40,10 @@ public abstract class NetworkSecurityGroup {
    @Nullable
    public abstract String etag();
 
-   @SerializedNames({"name", "location", "tags", "properties", "etag"})
-   public static NetworkSecurityGroup create(final String name,
-                                             final String location,
-                                             final Map<String, String> tags,
-                                             final NetworkSecurityGroupProperties properties,
-                                             final String etag) {
-      return new AutoValue_NetworkSecurityGroup(name, location,
-                                                (tags == null) ? null : ImmutableMap.copyOf(tags),
-                                                properties, etag);
+   @SerializedNames({ "id", "name", "location", "tags", "properties", "etag" })
+   public static NetworkSecurityGroup create(final String id, final String name, final String location,
+         final Map<String, String> tags, final NetworkSecurityGroupProperties properties, final String etag) {
+      return new AutoValue_NetworkSecurityGroup(id, name, location, (tags == null) ? null : ImmutableMap.copyOf(tags),
+            properties, etag);
    }
 }
-

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/RegionAndId.java
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/RegionAndId.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/RegionAndId.java
index 233109a..4105ee3 100644
--- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/RegionAndId.java
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/RegionAndId.java
@@ -29,7 +29,7 @@ public abstract class RegionAndId {
    public abstract String region();
    public abstract String id();
    
-   RegionAndId() {
+   protected RegionAndId() {
       
    }
    

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/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 ead676a..9d2a1c1 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
@@ -35,10 +35,13 @@ import org.jclouds.azurecompute.arm.compute.functions.LocationToResourceGroupNam
 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.NetworkSecurityGroup;
 import org.jclouds.azurecompute.arm.domain.RegionAndId;
 import org.jclouds.azurecompute.arm.domain.StorageServiceKeys;
 import org.jclouds.azurecompute.arm.domain.VirtualMachine;
+import org.jclouds.azurecompute.arm.features.NetworkSecurityGroupApi;
 import org.jclouds.azurecompute.arm.util.BlobHelper;
+import org.jclouds.compute.functions.GroupNamingConvention;
 import org.jclouds.compute.reference.ComputeServiceConstants;
 import org.jclouds.logging.Logger;
 
@@ -49,7 +52,7 @@ import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 
 @Singleton
-public class CleanupResources implements Function<String, Boolean> {
+public class CleanupResources {
 
    @Resource
    @Named(ComputeServiceConstants.COMPUTE_LOGGER)
@@ -59,22 +62,23 @@ public class CleanupResources implements Function<String, Boolean> {
    private final Predicate<URI> resourceDeleted;
    private final StorageProfileToStorageAccountName storageProfileToStorageAccountName;
    private final LocationToResourceGroupName locationToResourceGroupName;
+   private final GroupNamingConvention.Factory namingConvention;
 
    @Inject
    CleanupResources(AzureComputeApi azureComputeApi, @Named(TIMEOUT_RESOURCE_DELETED) Predicate<URI> resourceDeleted,
          StorageProfileToStorageAccountName storageProfileToStorageAccountName,
-         LocationToResourceGroupName locationToResourceGroupName) {
+         LocationToResourceGroupName locationToResourceGroupName, GroupNamingConvention.Factory namingConvention) {
       this.api = azureComputeApi;
       this.resourceDeleted = resourceDeleted;
       this.storageProfileToStorageAccountName = storageProfileToStorageAccountName;
       this.locationToResourceGroupName = locationToResourceGroupName;
+      this.namingConvention = namingConvention;
    }
 
-   @Override
-   public Boolean apply(final String id) {
+   public boolean cleanupNode(final String id) {
       RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id);
       String group = locationToResourceGroupName.apply(regionAndId.region());
-      
+
       VirtualMachine virtualMachine = api.getVirtualMachineApi(group).get(regionAndId.id());
       if (virtualMachine == null) {
          return true;
@@ -82,10 +86,17 @@ public class CleanupResources implements Function<String, Boolean> {
 
       logger.debug(">> destroying %s ...", regionAndId.slashEncode());
       boolean vmDeleted = deleteVirtualMachine(group, virtualMachine);
-      
+
       // We don't delete the network here, as it is global to the resource
       // group. It will be deleted when the resource group is deleted
 
+      cleanupVirtualMachineNICs(group, virtualMachine);
+      cleanupVirtualMachineStorage(group, virtualMachine);
+
+      return vmDeleted;
+   }
+
+   public void cleanupVirtualMachineNICs(String group, VirtualMachine virtualMachine) {
       for (String nicName : getNetworkCardInterfaceNames(virtualMachine)) {
          NetworkInterfaceCard nic = api.getNetworkInterfaceCardApi(group).get(nicName);
          Iterable<String> publicIps = getPublicIps(group, nic);
@@ -99,8 +110,11 @@ public class CleanupResources implements Function<String, Boolean> {
             api.getPublicIPAddressApi(group).delete(publicIp);
          }
       }
+   }
 
-      String storageAccountName = storageProfileToStorageAccountName.apply(virtualMachine.properties().storageProfile());
+   public void cleanupVirtualMachineStorage(String group, VirtualMachine virtualMachine) {
+      String storageAccountName = storageProfileToStorageAccountName
+            .apply(virtualMachine.properties().storageProfile());
       StorageServiceKeys keys = api.getStorageAccountApi(group).getKeys(storageAccountName);
 
       // Remove the virtual machine files
@@ -118,20 +132,44 @@ public class CleanupResources implements Function<String, Boolean> {
       } finally {
          closeQuietly(blobHelper);
       }
+   }
 
-      deleteResourceGroupIfEmpty(group);
+   public boolean cleanupSecurityGroupIfOrphaned(String resourceGroup, String group) {
+      String name = namingConvention.create().sharedNameForGroup(group);
+      NetworkSecurityGroupApi sgapi = api.getNetworkSecurityGroupApi(resourceGroup);
 
-      return vmDeleted;
+      boolean deleted = false;
+
+      try {
+         NetworkSecurityGroup securityGroup = sgapi.get(name);
+         if (securityGroup != null) {
+            List<NetworkInterfaceCard> nics = securityGroup.properties().networkInterfaces();
+            if (nics == null || nics.isEmpty()) {
+               logger.debug(">> deleting orphaned security group %s from %s...", name, resourceGroup);
+               try {
+                  deleted = resourceDeleted.apply(sgapi.delete(name));
+               } catch (Exception ex) {
+                  logger.warn(ex, ">> error deleting orphaned security group %s from %s...", name, resourceGroup);
+               }
+            }
+         }
+      } catch (Exception ex) {
+         logger.warn(ex, "Error deleting security groups for %s and group %s", resourceGroup, group);
+      }
+
+      return deleted;
    }
 
-   public void deleteResourceGroupIfEmpty(String group) {
-      if (api.getVirtualMachineApi(group).list().isEmpty() 
-            && api.getStorageAccountApi(group).list().isEmpty()
+   public boolean deleteResourceGroupIfEmpty(String group) {
+      boolean deleted = false;
+      if (api.getVirtualMachineApi(group).list().isEmpty() && api.getStorageAccountApi(group).list().isEmpty()
             && api.getNetworkInterfaceCardApi(group).list().isEmpty()
-            && api.getPublicIPAddressApi(group).list().isEmpty()) {
+            && api.getPublicIPAddressApi(group).list().isEmpty()
+            && api.getNetworkSecurityGroupApi(group).list().isEmpty()) {
          logger.debug(">> the resource group %s is empty. Deleting...", group);
-         resourceDeleted.apply(api.getResourceGroupApi().delete(group));
+         deleted = resourceDeleted.apply(api.getResourceGroupApi().delete(group));
       }
+      return deleted;
    }
 
    private Iterable<String> getPublicIps(String group, NetworkInterfaceCard nic) {

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/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 c8a9259..8939eb1 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
@@ -71,4 +71,5 @@ public class AzureComputeImageExtensionLiveTest extends BaseImageExtensionLiveTe
             .overrideLoginPrivateKey(keyPair.get("private")));
    }
 
+
 }


[3/4] jclouds-labs git commit: JCLOUDS-1231: Implement the SecurityGroupExtension in ARM

Posted by na...@apache.org.
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeSecurityGroupExtensionLiveTest.java
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeSecurityGroupExtensionLiveTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeSecurityGroupExtensionLiveTest.java
new file mode 100644
index 0000000..02fe52e
--- /dev/null
+++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeSecurityGroupExtensionLiveTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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 static com.google.common.collect.Iterables.get;
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static java.util.logging.Logger.getAnonymousLogger;
+import static org.jclouds.compute.options.TemplateOptions.Builder.inboundPorts;
+import static org.jclouds.compute.options.TemplateOptions.Builder.securityGroups;
+import static org.jclouds.compute.predicates.NodePredicates.inGroup;
+import static org.jclouds.net.domain.IpProtocol.TCP;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+
+import org.jclouds.azurecompute.arm.AzureComputeApi;
+import org.jclouds.azurecompute.arm.AzureComputeProviderMetadata;
+import org.jclouds.azurecompute.arm.compute.functions.LocationToResourceGroupName;
+import org.jclouds.azurecompute.arm.domain.ResourceGroup;
+import org.jclouds.azurecompute.arm.internal.AzureLiveTestUtils;
+import org.jclouds.compute.ComputeService;
+import org.jclouds.compute.RunNodesException;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.SecurityGroup;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
+import org.jclouds.compute.extensions.internal.BaseSecurityGroupExtensionLiveTest;
+import org.jclouds.domain.Location;
+import org.jclouds.net.util.IpPermissions;
+import org.jclouds.providers.ProviderMetadata;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Live test for AzureCompute
+ * {@link org.jclouds.compute.extensions.SecurityGroupExtension} implementation.
+ */
+@Test(groups = "live", singleThreaded = true, testName = "AzureComputeSecurityGroupExtensionLiveTest")
+public class AzureComputeSecurityGroupExtensionLiveTest extends BaseSecurityGroupExtensionLiveTest {
+
+   private AzureComputeApi api;
+   private LocationToResourceGroupName locationToResourceGroupName;
+   private String resourceGroupName;
+   private ResourceGroup testResourceGroup;
+
+   public AzureComputeSecurityGroupExtensionLiveTest() {
+      provider = "azurecompute-arm";
+   }
+
+   @BeforeClass(groups = { "integration", "live" })
+   public void setupContext() {
+      super.setupContext();
+      api = context.utils().injector().getInstance(AzureComputeApi.class);
+      locationToResourceGroupName = context.utils().injector().getInstance(LocationToResourceGroupName.class);
+      createResourceGroupIfMissing();
+   }
+
+   @Test(groups = { "integration", "live" }, dependsOnMethods = "testCreateSecurityGroup")
+   public void testCreateNodeWithSecurityGroup() throws RunNodesException, InterruptedException, ExecutionException {
+      ComputeService computeService = view.getComputeService();
+      Optional<SecurityGroupExtension> securityGroupExtension = computeService.getSecurityGroupExtension();
+      assertTrue(securityGroupExtension.isPresent(), "security group extension was not present");
+
+      NodeMetadata node = getOnlyElement(computeService.createNodesInGroup(nodeGroup, 1, securityGroups(groupId)));
+
+      try {
+         Set<SecurityGroup> groups = securityGroupExtension.get().listSecurityGroupsForNode(node.getId());
+         assertEquals(groups.size(), 1, "node has " + groups.size() + " groups");
+         assertEquals(getOnlyElement(groups).getId(), groupId);
+      } finally {
+         computeService.destroyNodesMatching(inGroup(node.getGroup()));
+      }
+   }
+
+   @Test(groups = { "integration", "live" }, dependsOnMethods = "testCreateSecurityGroup")
+   public void testCreateNodeWithInboundPorts() throws RunNodesException, InterruptedException, ExecutionException {
+      ComputeService computeService = view.getComputeService();
+      Optional<SecurityGroupExtension> securityGroupExtension = computeService.getSecurityGroupExtension();
+      assertTrue(securityGroupExtension.isPresent(), "security group extension was not present");
+
+      NodeMetadata node = getOnlyElement(computeService
+            .createNodesInGroup(nodeGroup, 1, inboundPorts(22, 23, 24, 8000)));
+
+      try {
+         Set<SecurityGroup> groups = securityGroupExtension.get().listSecurityGroupsForNode(node.getId());
+         assertEquals(groups.size(), 1, "node has " + groups.size() + " groups");
+
+         SecurityGroup group = getOnlyElement(groups);
+         assertEquals(group.getIpPermissions().size(), 2);
+         assertEquals(get(group.getIpPermissions(), 0), IpPermissions.permit(TCP).fromPort(22).to(24));
+         assertEquals(get(group.getIpPermissions(), 1), IpPermissions.permit(TCP).port(8000));
+      } finally {
+         computeService.destroyNodesMatching(inGroup(node.getGroup()));
+      }
+   }
+
+   @AfterClass(groups = { "integration", "live" })
+   @Override
+   protected void tearDownContext() {
+      super.tearDownContext();
+      if (testResourceGroup != null) {
+         // Cleanup the resource group we created for the tests
+         getAnonymousLogger().info(
+               "deleting resource group " + testResourceGroup.name() + " for the security group live tests...");
+         api.getResourceGroupApi().delete(testResourceGroup.name());
+      }
+   }
+
+   @Override
+   protected Properties setupProperties() {
+      Properties properties = super.setupProperties();
+      AzureLiveTestUtils.defaultProperties(properties);
+      setIfTestSystemPropertyPresent(properties, "oauth.endpoint");
+      return properties;
+   }
+
+   @Override
+   protected ProviderMetadata createProviderMetadata() {
+      return AzureComputeProviderMetadata.builder().build();
+   }
+
+   private void createResourceGroupIfMissing() {
+      Location location = getNodeTemplate().getLocation();
+      resourceGroupName = locationToResourceGroupName.apply(location.getId());
+      ResourceGroup resourceGroupInLocation = api.getResourceGroupApi().get(resourceGroupName);
+      if (resourceGroupInLocation == null) {
+         getAnonymousLogger().info(
+               "creating resource group " + resourceGroupName + " for the security group live tests...");
+         final Map<String, String> tags = ImmutableMap.of("description", "AzureComputeSecurityGroupExtensionLiveTest");
+         testResourceGroup = api.getResourceGroupApi().create(resourceGroupName, location.getId(), tags);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/NetworkSecurityGroupApiMockTest.java
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/NetworkSecurityGroupApiMockTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/NetworkSecurityGroupApiMockTest.java
index 2d43694..3dc0e4a 100644
--- a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/NetworkSecurityGroupApiMockTest.java
+++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/NetworkSecurityGroupApiMockTest.java
@@ -57,7 +57,7 @@ public class NetworkSecurityGroupApiMockTest extends BaseAzureComputeApiMockTest
                       .build());
       ArrayList<NetworkSecurityRule> ruleList = new ArrayList<NetworkSecurityRule>();
       ruleList.add(rule);
-      NetworkSecurityGroup nsg = NetworkSecurityGroup.create("samplensg", "westus", null,
+      NetworkSecurityGroup nsg = NetworkSecurityGroup.create("id", "samplensg", "westus", null,
               NetworkSecurityGroupProperties.builder()
                       .securityRules(ruleList)
                       .build(),

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/internal/BaseAzureComputeApiLiveTest.java
----------------------------------------------------------------------
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 bd7cde4..c3c6aa7 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
@@ -154,7 +154,7 @@ public class BaseAzureComputeApiLiveTest extends BaseApiLiveTest<AzureComputeApi
                       .build());
       List<NetworkSecurityRule> ruleList = Lists.newArrayList();
       ruleList.add(rule);
-      NetworkSecurityGroup nsg = NetworkSecurityGroup.create(nsgName, locationName, null,
+      NetworkSecurityGroup nsg = NetworkSecurityGroup.create("id", nsgName, locationName, null,
               NetworkSecurityGroupProperties.builder()
                       .securityRules(ruleList)
                       .build(),

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/test/resources/logback-test.xml
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/test/resources/logback-test.xml b/azurecompute-arm/src/test/resources/logback-test.xml
index cb55d49..b9e9616 100644
--- a/azurecompute-arm/src/test/resources/logback-test.xml
+++ b/azurecompute-arm/src/test/resources/logback-test.xml
@@ -13,7 +13,7 @@
         </encoder>
     </appender>
     <appender name="COMPUTEFILE" class="ch.qos.logback.core.FileAppender">
-        <file>target/jclouds-compute.log</file>
+        <file>target/test-data/jclouds-compute.log</file>
         <encoder>
             <Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
         </encoder>


[2/4] jclouds-labs git commit: Cleanup legacy code and introduce the resource group cache

Posted by na...@apache.org.
Cleanup legacy code and introduce the resource group cache


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

Branch: refs/heads/master
Commit: fde928eb084c7d7291702265acec8fd4528e795d
Parents: 867ddef
Author: Ignasi Barrera <na...@apache.org>
Authored: Thu Jan 26 10:34:49 2017 +0100
Committer: Ignasi Barrera <na...@apache.org>
Committed: Thu Jan 26 21:58:23 2017 +0100

----------------------------------------------------------------------
 .../arm/AzureComputeProviderMetadata.java       |  17 +-
 .../arm/compute/AzureComputeService.java        |  18 +-
 .../arm/compute/AzureComputeServiceAdapter.java |  51 +++--
 .../AzureComputeServiceContextModule.java       | 116 +++--------
 .../extensions/AzureComputeImageExtension.java  |  25 ++-
 .../AzureComputeSecurityGroupExtension.java     |  75 +++----
 .../NetworkSecurityRuleToIpPermission.java      |   4 +-
 .../ResourceDefinitionToCustomImage.java        |  33 +--
 .../functions/VirtualMachineToNodeMetadata.java |  41 ++--
 .../loaders/CreateSecurityGroupIfNeeded.java    |  13 +-
 .../loaders/ResourceGroupForLocation.java       |  62 ++++++
 .../compute/options/AzureTemplateOptions.java   | 108 +---------
 .../predicates/IsDeploymentInRegions.java       |  47 -----
 .../arm/compute/strategy/CleanupResources.java  | 205 +++++++++++++++++++
 .../CreateResourceGroupThenCreateNodes.java     |  94 ++++-----
 .../arm/config/AzureComputeProperties.java      |  14 --
 .../arm/functions/CleanupResources.java         | 202 ------------------
 ...reComputeSecurityGroupExtensionLiveTest.java |  36 ++--
 18 files changed, 482 insertions(+), 679 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fde928eb/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 dd516ef..1590bf2 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
@@ -19,16 +19,10 @@ package org.jclouds.azurecompute.arm;
 
 import static org.jclouds.Constants.PROPERTY_MAX_RATE_LIMIT_WAIT;
 import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.API_VERSION_PREFIX;
-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.STORAGE_API_VERSION;
-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.compute.config.ComputeServiceProperties.IMAGE_AUTHENTICATE_SUDO;
 import static org.jclouds.compute.config.ComputeServiceProperties.IMAGE_LOGIN_USER;
 import static org.jclouds.compute.config.ComputeServiceProperties.POLL_INITIAL_PERIOD;
@@ -83,27 +77,22 @@ public class AzureComputeProviderMetadata extends BaseProviderMetadata {
    public static Properties defaultProperties() {
       final Properties properties = AzureManagementApiMetadata.defaultProperties();
       properties.put(POLL_INITIAL_PERIOD, 1000);
-      properties.put(POLL_MAX_PERIOD, 10000);
+      properties.put(POLL_MAX_PERIOD, 15000);
       properties.put(OPERATION_TIMEOUT, 46000000);
-      properties.put(OPERATION_POLL_INITIAL_PERIOD, 5);
-      properties.put(OPERATION_POLL_MAX_PERIOD, 15);
+      properties.put(TIMEOUT_NODE_TERMINATED, 60 * 10 * 1000);
       // Default max wait in rate limit: 5m30s
       properties.put(PROPERTY_MAX_RATE_LIMIT_WAIT, 330000);
-      properties.put(TCP_RULE_FORMAT, "tcp_%s-%s");
-      properties.put(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(DEFAULT_VNET_ADDRESS_SPACE_PREFIX, "10.0.0.0/16");
       properties.put(DEFAULT_SUBNET_ADDRESS_PREFIX, "10.0.0.0/24");
       properties.put(RESOURCENAME_PREFIX, "jclouds");
       properties.put(RESOURCENAME_DELIMITER, "-");
-      properties.put(DEFAULT_DATADISKSIZE, 100);
       properties.put(IMAGE_PUBLISHERS, "Canonical,RedHat");
       // Default credentials for all images
       properties.put(IMAGE_LOGIN_USER, "jclouds:Password12345!");
       properties.put(IMAGE_AUTHENTICATE_SUDO, "true");
       properties.put(TEMPLATE, "imageNameMatches=UbuntuServer,osVersionMatches=1[45]\\.[01][04]\\.[0-9]-LTS");
-      properties.put(TIMEOUT_NODE_TERMINATED, 60 * 10 * 1000);
       // Api versions used in each API
       properties.put(API_VERSION_PREFIX + DeploymentApi.class.getSimpleName(), "2016-02-01");
       properties.put(API_VERSION_PREFIX + LocationApi.class.getSimpleName(), "2015-11-01");
@@ -114,7 +103,7 @@ public class AzureComputeProviderMetadata extends BaseProviderMetadata {
       properties.put(API_VERSION_PREFIX + PublicIPAddressApi.class.getSimpleName(), "2015-06-15");
       properties.put(API_VERSION_PREFIX + ResourceGroupApi.class.getSimpleName(), "2015-01-01");
       properties.put(API_VERSION_PREFIX + ResourceProviderApi.class.getSimpleName(), "2015-01-01");
-      properties.put(API_VERSION_PREFIX + StorageAccountApi.class.getSimpleName(), STORAGE_API_VERSION);
+      properties.put(API_VERSION_PREFIX + StorageAccountApi.class.getSimpleName(), "2015-06-15");
       properties.put(API_VERSION_PREFIX + SubnetApi.class.getSimpleName(), "2015-06-15");
       properties.put(API_VERSION_PREFIX + VirtualNetworkApi.class.getSimpleName(), "2015-06-15");
       properties.put(API_VERSION_PREFIX + VMSizeApi.class.getSimpleName(), "2015-06-15");

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fde928eb/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/AzureComputeService.java
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/AzureComputeService.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/AzureComputeService.java
index a561375..f566a69 100644
--- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/AzureComputeService.java
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/AzureComputeService.java
@@ -31,8 +31,8 @@ import javax.inject.Provider;
 import javax.inject.Singleton;
 
 import org.jclouds.Constants;
-import org.jclouds.azurecompute.arm.compute.functions.LocationToResourceGroupName;
-import org.jclouds.azurecompute.arm.functions.CleanupResources;
+import org.jclouds.azurecompute.arm.compute.strategy.CleanupResources;
+import org.jclouds.azurecompute.arm.domain.ResourceGroup;
 import org.jclouds.collect.Memoized;
 import org.jclouds.compute.ComputeServiceContext;
 import org.jclouds.compute.callables.RunScriptOnNode;
@@ -62,6 +62,7 @@ import org.jclouds.scriptbuilder.functions.InitAdminAccess;
 import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.base.Supplier;
+import com.google.common.cache.LoadingCache;
 import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.util.concurrent.ListeningExecutorService;
@@ -69,7 +70,7 @@ import com.google.common.util.concurrent.ListeningExecutorService;
 @Singleton
 public class AzureComputeService extends BaseComputeService {
    private final CleanupResources cleanupResources;
-   private final LocationToResourceGroupName locationToResourceGroupName;
+   private final LoadingCache<String, ResourceGroup> resourceGroupMap;
 
    @Inject
    protected AzureComputeService(ComputeServiceContext context, Map<String, Credentials> credentialStore,
@@ -88,15 +89,14 @@ public class AzureComputeService extends BaseComputeService {
          PersistNodeCredentials persistNodeCredentials, Timeouts timeouts,
          @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor,
          CleanupResources cleanupResources, Optional<ImageExtension> imageExtension,
-         Optional<SecurityGroupExtension> securityGroupExtension,
-         LocationToResourceGroupName locationToResourceGroupName) {
+         Optional<SecurityGroupExtension> securityGroupExtension, LoadingCache<String, ResourceGroup> resourceGroupMap) {
       super(context, credentialStore, images, sizes, locations, listNodesStrategy, getImageStrategy,
             getNodeMetadataStrategy, runNodesAndAddToSetStrategy, rebootNodeStrategy, destroyNodeStrategy,
             startNodeStrategy, stopNodeStrategy, templateBuilderProvider, templateOptionsProvider, nodeRunning,
             nodeTerminated, nodeSuspended, initScriptRunnerFactory, initAdminAccess, runScriptOnNodeFactory,
             persistNodeCredentials, timeouts, userExecutor, imageExtension, securityGroupExtension);
       this.cleanupResources = cleanupResources;
-      this.locationToResourceGroupName = locationToResourceGroupName;
+      this.resourceGroupMap = resourceGroupMap;
    }
 
    @Override
@@ -105,11 +105,11 @@ public class AzureComputeService extends BaseComputeService {
       ImmutableSet.Builder<String> resourceGroups = ImmutableSet.builder();
 
       for (NodeMetadata deadNode : deadNodes) {
-         String resourceGroup = locationToResourceGroupName.apply(deadNode.getLocation().getId());
+         ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(deadNode.getLocation().getId());
 
-         resourceGroups.add(resourceGroup);
+         resourceGroups.add(resourceGroup.name());
          if (deadNode.getGroup() != null) {
-            regionGroups.put(resourceGroup, deadNode.getGroup());
+            regionGroups.put(resourceGroup.name(), deadNode.getGroup());
          }
 
          try {

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fde928eb/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 269f6b0..5a8204e 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
@@ -26,6 +26,7 @@ import static org.jclouds.azurecompute.arm.compute.extensions.AzureComputeImageE
 import static org.jclouds.azurecompute.arm.compute.functions.VMImageToImage.decodeFieldsFromUniqueId;
 import static org.jclouds.azurecompute.arm.compute.functions.VMImageToImage.encodeFieldsToUniqueIdCustom;
 import static org.jclouds.azurecompute.arm.compute.functions.VMImageToImage.getMarketplacePlanFromImageMetadata;
+import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.IMAGE_PUBLISHERS;
 import static org.jclouds.compute.util.ComputeServiceUtils.metadataAndTagsAsCommaDelimitedValue;
 import static org.jclouds.util.Closeables2.closeQuietly;
 
@@ -39,10 +40,9 @@ import javax.inject.Named;
 import javax.inject.Singleton;
 
 import org.jclouds.azurecompute.arm.AzureComputeApi;
-import org.jclouds.azurecompute.arm.compute.config.AzureComputeServiceContextModule.AzureComputeConstants;
 import org.jclouds.azurecompute.arm.compute.config.AzureComputeServiceContextModule.PublicIpAvailablePredicateFactory;
-import org.jclouds.azurecompute.arm.compute.functions.LocationToResourceGroupName;
 import org.jclouds.azurecompute.arm.compute.options.AzureTemplateOptions;
+import org.jclouds.azurecompute.arm.compute.strategy.CleanupResources;
 import org.jclouds.azurecompute.arm.domain.DataDisk;
 import org.jclouds.azurecompute.arm.domain.HardwareProfile;
 import org.jclouds.azurecompute.arm.domain.IdReference;
@@ -76,7 +76,6 @@ import org.jclouds.azurecompute.arm.domain.VirtualMachine;
 import org.jclouds.azurecompute.arm.domain.VirtualMachineProperties;
 import org.jclouds.azurecompute.arm.features.OSImageApi;
 import org.jclouds.azurecompute.arm.features.PublicIPAddressApi;
-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;
@@ -92,6 +91,7 @@ import com.google.common.base.Objects;
 import com.google.common.base.Predicate;
 import com.google.common.base.Splitter;
 import com.google.common.base.Supplier;
+import com.google.common.cache.LoadingCache;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -113,21 +113,21 @@ public class AzureComputeServiceAdapter implements ComputeServiceAdapter<Virtual
    
    private final CleanupResources cleanupResources;
    private final AzureComputeApi api;
-   private final AzureComputeConstants azureComputeConstants;
+   private final List<String> imagePublishers;
    private final Supplier<Set<String>> regionIds;
    private final PublicIpAvailablePredicateFactory publicIpAvailable;
-   private final LocationToResourceGroupName locationToResourceGroupName;
+   private final LoadingCache<String, ResourceGroup> resourceGroupMap;
 
    @Inject
-   AzureComputeServiceAdapter(final AzureComputeApi api, final AzureComputeConstants azureComputeConstants,
+   AzureComputeServiceAdapter(final AzureComputeApi api, @Named(IMAGE_PUBLISHERS) String imagePublishers,
          CleanupResources cleanupResources, @Region Supplier<Set<String>> regionIds,
-         PublicIpAvailablePredicateFactory publicIpAvailable, LocationToResourceGroupName locationToResourceGroupName) {
+         PublicIpAvailablePredicateFactory publicIpAvailable, LoadingCache<String, ResourceGroup> resourceGroupMap) {
       this.api = api;
-      this.azureComputeConstants = azureComputeConstants;
+      this.imagePublishers = Splitter.on(',').trimResults().omitEmptyStrings().splitToList(imagePublishers);
       this.cleanupResources = cleanupResources;
       this.regionIds = regionIds;
       this.publicIpAvailable = publicIpAvailable;
-      this.locationToResourceGroupName = locationToResourceGroupName;
+      this.resourceGroupMap = resourceGroupMap;
    }
 
    @Override
@@ -135,14 +135,15 @@ public class AzureComputeServiceAdapter implements ComputeServiceAdapter<Virtual
          final String name, final Template template) {
 
       AzureTemplateOptions templateOptions = template.getOptions().as(AzureTemplateOptions.class);
-      String azureGroup = locationToResourceGroupName.apply(template.getLocation().getId());
+      ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(template.getLocation().getId());
 
       // TODO ARM specific options
       // TODO network ids => create one nic in each network
 
       String locationName = template.getLocation().getId();
       String subnetId = templateOptions.getSubnetId();
-      NetworkInterfaceCard nic = createNetworkInterfaceCard(subnetId, name, locationName, azureGroup, template.getOptions());
+      NetworkInterfaceCard nic = createNetworkInterfaceCard(subnetId, name, locationName, resourceGroup.name(),
+            template.getOptions());
       StorageProfile storageProfile = createStorageProfile(name, template.getImage(), templateOptions.getBlob());
       HardwareProfile hardwareProfile = HardwareProfile.builder().vmSize(template.getHardware().getId()).build();
       OSProfile osProfile = createOsProfile(name, template);
@@ -160,7 +161,7 @@ public class AzureComputeServiceAdapter implements ComputeServiceAdapter<Virtual
       Map<String, String> metadataAndTags = metadataAndTagsAsCommaDelimitedValue(template.getOptions());
       Plan plan = getMarketplacePlanFromImageMetadata(template.getImage());
 
-      VirtualMachine virtualMachine = api.getVirtualMachineApi(azureGroup).create(name, template.getLocation().getId(),
+      VirtualMachine virtualMachine = api.getVirtualMachineApi(resourceGroup.name()).create(name, template.getLocation().getId(),
             virtualMachineProperties, metadataAndTags, plan);
 
       // Safe to pass null credentials here, as jclouds will default populate
@@ -210,9 +211,7 @@ public class AzureComputeServiceAdapter implements ComputeServiceAdapter<Virtual
 
    private List<VMImage> listImagesByLocation(String location) {
       final List<VMImage> osImages = Lists.newArrayList();
-      Iterable<String> publishers = Splitter.on(',').trimResults().omitEmptyStrings()
-            .split(this.azureComputeConstants.azureImagePublishers());
-      for (String publisher : publishers) {
+      for (String publisher : imagePublishers) {
          osImages.addAll(getImagesFromPublisher(publisher, location));
       }
       return osImages;
@@ -259,11 +258,11 @@ public class AzureComputeServiceAdapter implements ComputeServiceAdapter<Virtual
    @Override
    public VMImage getImage(final String id) {
       VMImage image = decodeFieldsFromUniqueId(id);
-      String azureGroup = locationToResourceGroupName.apply(image.location());
+      ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(image.location());
 
       if (image.custom()) {
          VMImage customImage = null;
-         StorageServiceKeys keys = api.getStorageAccountApi(azureGroup).getKeys(image.storage());
+         StorageServiceKeys keys = api.getStorageAccountApi(resourceGroup.name()).getKeys(image.storage());
          if (keys == null) {
             // If the storage account for the image does not exist, it means the
             // image was deleted
@@ -273,7 +272,7 @@ public class AzureComputeServiceAdapter implements ComputeServiceAdapter<Virtual
          BlobHelper blobHelper = new BlobHelper(image.storage(), keys.key1());
          try {
             if (blobHelper.customImageExists()) {
-               List<VMImage> customImagesInStorage = blobHelper.getImages(CONTAINER_NAME, azureGroup,
+               List<VMImage> customImagesInStorage = blobHelper.getImages(CONTAINER_NAME, resourceGroup.name(),
                      CUSTOM_IMAGE_OFFER, image.location());
                customImage = find(customImagesInStorage, new Predicate<VMImage>() {
                   @Override
@@ -336,8 +335,8 @@ public class AzureComputeServiceAdapter implements ComputeServiceAdapter<Virtual
    @Override
    public VirtualMachine getNode(final String id) {
       RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id);
-      String azureGroup = locationToResourceGroupName.apply(regionAndId.region());
-      return api.getVirtualMachineApi(azureGroup).get(regionAndId.id());
+      ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(regionAndId.region());
+      return api.getVirtualMachineApi(resourceGroup.name()).get(regionAndId.id());
    }
 
    @Override
@@ -348,22 +347,22 @@ public class AzureComputeServiceAdapter implements ComputeServiceAdapter<Virtual
    @Override
    public void rebootNode(final String id) {
       RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id);
-      String azureGroup = locationToResourceGroupName.apply(regionAndId.region());
-      api.getVirtualMachineApi(azureGroup).restart(regionAndId.id());
+      ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(regionAndId.region());
+      api.getVirtualMachineApi(resourceGroup.name()).restart(regionAndId.id());
    }
 
    @Override
    public void resumeNode(final String id) {
       RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id);
-      String azureGroup = locationToResourceGroupName.apply(regionAndId.region());
-      api.getVirtualMachineApi(azureGroup).start(regionAndId.id());
+      ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(regionAndId.region());
+      api.getVirtualMachineApi(resourceGroup.name()).start(regionAndId.id());
    }
 
    @Override
    public void suspendNode(final String id) {
       RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id);
-      String azureGroup = locationToResourceGroupName.apply(regionAndId.region());
-      api.getVirtualMachineApi(azureGroup).stop(regionAndId.id());
+      ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(regionAndId.region());
+      api.getVirtualMachineApi(resourceGroup.name()).stop(regionAndId.id());
    }
 
    @Override

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fde928eb/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 6a577eb..8ea823b 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
@@ -17,15 +17,7 @@
 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.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;
@@ -53,6 +45,7 @@ import org.jclouds.azurecompute.arm.compute.functions.VMHardwareToHardware;
 import org.jclouds.azurecompute.arm.compute.functions.VMImageToImage;
 import org.jclouds.azurecompute.arm.compute.functions.VirtualMachineToNodeMetadata;
 import org.jclouds.azurecompute.arm.compute.loaders.CreateSecurityGroupIfNeeded;
+import org.jclouds.azurecompute.arm.compute.loaders.ResourceGroupForLocation;
 import org.jclouds.azurecompute.arm.compute.options.AzureTemplateOptions;
 import org.jclouds.azurecompute.arm.compute.strategy.CreateResourceGroupThenCreateNodes;
 import org.jclouds.azurecompute.arm.domain.Location;
@@ -60,6 +53,7 @@ import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroup;
 import org.jclouds.azurecompute.arm.domain.NetworkSecurityRule;
 import org.jclouds.azurecompute.arm.domain.PublicIPAddress;
 import org.jclouds.azurecompute.arm.domain.ResourceDefinition;
+import org.jclouds.azurecompute.arm.domain.ResourceGroup;
 import org.jclouds.azurecompute.arm.domain.VMHardware;
 import org.jclouds.azurecompute.arm.domain.VMImage;
 import org.jclouds.azurecompute.arm.domain.VirtualMachine;
@@ -88,7 +82,6 @@ import com.google.common.base.Predicate;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
-import com.google.inject.Inject;
 import com.google.inject.Provides;
 import com.google.inject.TypeLiteral;
 import com.google.inject.assistedinject.FactoryModuleBuilder;
@@ -125,8 +118,11 @@ public class AzureComputeServiceContextModule extends
       bind(TemplateOptions.class).to(AzureTemplateOptions.class);
       bind(NodeAndTemplateOptionsToStatement.class).to(NodeAndTemplateOptionsToStatementWithoutPublicKey.class);
       bind(CreateNodesInGroupThenAddToSet.class).to(CreateResourceGroupThenCreateNodes.class);
+
       bind(new TypeLiteral<CacheLoader<RegionAndIdAndIngressRules, String>>() {
       }).to(CreateSecurityGroupIfNeeded.class);
+      bind(new TypeLiteral<CacheLoader<String, ResourceGroup>>() {
+      }).to(ResourceGroupForLocation.class);
 
       bind(new TypeLiteral<ImageExtension>() {
       }).to(AzureComputeImageExtension.class);
@@ -134,86 +130,16 @@ public class AzureComputeServiceContextModule extends
       }).to(AzureComputeSecurityGroupExtension.class);
    }
 
+   @Provides
    @Singleton
-   public static class AzureComputeConstants {
-
-      @Named(OPERATION_TIMEOUT)
-      @Inject
-      private String operationTimeoutProperty;
-
-      @Named(OPERATION_POLL_INITIAL_PERIOD)
-      @Inject
-      private String operationPollInitialPeriodProperty;
-
-      @Named(OPERATION_POLL_MAX_PERIOD)
-      @Inject
-      private String operationPollMaxPeriodProperty;
-
-      @Named(TCP_RULE_FORMAT)
-      @Inject
-      private String tcpRuleFormatProperty;
-
-      @Named(TCP_RULE_REGEXP)
-      @Inject
-      private String tcpRuleRegexpProperty;
-
-      @Named(IMAGE_PUBLISHERS)
-      @Inject
-      private String azureImagePublishersProperty;
-
-      @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);
-      }
-
-      public String azureImagePublishers() {
-         return azureImagePublishersProperty;
-      }
-
-      public String azureDefaultVnetAddressPrefixProperty() {
-         return azureDefaultVnetAddressPrefixProperty;
-      }
-
-      public String azureDefaultSubnetAddressPrefixProperty() {
-         return azureDefaultSubnetAddressPrefixProperty;
-      }
-
-      public String azureDefaultDataDiskSizeProperty() {
-         return azureDefaultDataDiskSizeProperty;
-      }
-
-      public Integer operationPollInitialPeriod() {
-         return Integer.parseInt(operationPollInitialPeriodProperty);
-      }
-
-      public Integer operationPollMaxPeriod() {
-         return Integer.parseInt(operationPollMaxPeriodProperty);
-      }
-
-      public String tcpRuleFormat() {
-         return tcpRuleFormatProperty;
-      }
-
-      public String tcpRuleRegexp() {
-         return tcpRuleRegexpProperty;
-      }
+   protected final LoadingCache<RegionAndIdAndIngressRules, String> securityGroupMap(
+         CacheLoader<RegionAndIdAndIngressRules, String> in) {
+      return CacheBuilder.newBuilder().build(in);
    }
 
    @Provides
    @Singleton
-   protected final LoadingCache<RegionAndIdAndIngressRules, String> securityGroupMap(
-         CacheLoader<RegionAndIdAndIngressRules, String> in) {
+   protected final LoadingCache<String, ResourceGroup> resourceGroupMap(CacheLoader<String, ResourceGroup> in) {
       return CacheBuilder.newBuilder().build(in);
    }
 
@@ -259,18 +185,24 @@ public class AzureComputeServiceContextModule extends
 
    @Provides
    protected PublicIpAvailablePredicateFactory providePublicIpAvailablePredicate(final AzureComputeApi api,
-         final AzureComputeServiceContextModule.AzureComputeConstants azureComputeConstants, final Timeouts timeouts,
-         final PollPeriod pollPeriod) {
-      return new PublicIpAvailablePredicateFactory(api, azureComputeConstants.operationTimeout(),
-            azureComputeConstants.operationPollInitialPeriod(), azureComputeConstants.operationPollMaxPeriod());
+         @Named(OPERATION_TIMEOUT) Integer operationTimeout, PollPeriod pollPeriod) {
+      return new PublicIpAvailablePredicateFactory(api, operationTimeout, pollPeriod.pollInitialPeriod,
+            pollPeriod.pollMaxPeriod);
    }
 
    @Provides
    protected SecurityGroupAvailablePredicateFactory provideSecurityGroupAvailablePredicate(final AzureComputeApi api,
-         final AzureComputeServiceContextModule.AzureComputeConstants azureComputeConstants, final Timeouts timeouts,
-         final PollPeriod pollPeriod) {
-      return new SecurityGroupAvailablePredicateFactory(api, azureComputeConstants.operationTimeout(),
-            azureComputeConstants.operationPollInitialPeriod(), azureComputeConstants.operationPollMaxPeriod());
+         @Named(OPERATION_TIMEOUT) Integer operationTimeout, PollPeriod pollPeriod) {
+      return new SecurityGroupAvailablePredicateFactory(api, operationTimeout, pollPeriod.pollInitialPeriod,
+            pollPeriod.pollMaxPeriod);
+   }
+
+   @Provides
+   @Named("STORAGE")
+   protected Predicate<URI> provideStorageAccountAvailablePredicate(final AzureComputeApi api,
+         @Named(OPERATION_TIMEOUT) Integer operationTimeout, PollPeriod pollPeriod) {
+      return retry(new ActionDonePredicate(api), operationTimeout, pollPeriod.pollInitialPeriod,
+            pollPeriod.pollMaxPeriod);
    }
 
    @VisibleForTesting

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fde928eb/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 1e57899..7028081 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
@@ -32,13 +32,13 @@ import javax.annotation.Resource;
 import org.jclouds.Constants;
 import org.jclouds.azurecompute.arm.AzureComputeApi;
 import org.jclouds.azurecompute.arm.compute.config.AzureComputeServiceContextModule.VirtualMachineInStatePredicateFactory;
-import org.jclouds.azurecompute.arm.compute.functions.LocationToResourceGroupName;
 import org.jclouds.azurecompute.arm.compute.functions.ResourceDefinitionToCustomImage;
+import org.jclouds.azurecompute.arm.compute.strategy.CleanupResources;
 import org.jclouds.azurecompute.arm.domain.RegionAndId;
 import org.jclouds.azurecompute.arm.domain.ResourceDefinition;
+import org.jclouds.azurecompute.arm.domain.ResourceGroup;
 import org.jclouds.azurecompute.arm.domain.StorageServiceKeys;
 import org.jclouds.azurecompute.arm.domain.VMImage;
-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;
@@ -49,6 +49,7 @@ import org.jclouds.compute.reference.ComputeServiceConstants;
 import org.jclouds.logging.Logger;
 
 import com.google.common.base.Predicate;
+import com.google.common.cache.LoadingCache;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.inject.Inject;
@@ -68,7 +69,7 @@ public class AzureComputeImageExtension implements ImageExtension {
    private final VirtualMachineInStatePredicateFactory nodeSuspendedPredicate;
    private final ResourceDefinitionToCustomImage.Factory resourceDefinitionToImage;
    private final CleanupResources cleanupResources;
-   private final LocationToResourceGroupName locationToResourceGroupName;
+   private final LoadingCache<String, ResourceGroup> resourceGroupMap;
 
    @Inject
    AzureComputeImageExtension(AzureComputeApi api,
@@ -76,14 +77,14 @@ public class AzureComputeImageExtension implements ImageExtension {
          @Named(TIMEOUT_NODE_SUSPENDED) VirtualMachineInStatePredicateFactory nodeSuspendedPredicate,
          @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor,
          ResourceDefinitionToCustomImage.Factory resourceDefinitionToImage, CleanupResources cleanupResources,
-         LocationToResourceGroupName locationToResourceGroupName) {
+         LoadingCache<String, ResourceGroup> resourceGroupMap) {
       this.api = api;
       this.imageAvailablePredicate = imageAvailablePredicate;
       this.nodeSuspendedPredicate = nodeSuspendedPredicate;
       this.userExecutor = userExecutor;
       this.resourceDefinitionToImage = resourceDefinitionToImage;
       this.cleanupResources = cleanupResources;
-      this.locationToResourceGroupName = locationToResourceGroupName;
+      this.resourceGroupMap = resourceGroupMap;
    }
 
    @Override
@@ -94,23 +95,25 @@ public class AzureComputeImageExtension implements ImageExtension {
    @Override
    public ListenableFuture<Image> createImage(ImageTemplate template) {
       final CloneImageTemplate cloneTemplate = (CloneImageTemplate) template;
-      
+
       final RegionAndId regionAndId = RegionAndId.fromSlashEncoded(cloneTemplate.getSourceNodeId());
-      final String group = locationToResourceGroupName.apply(regionAndId.region());
+      ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(regionAndId.region());
+      final String resourceGroupName = resourceGroup.name();
 
       logger.debug(">> stopping node %s...", regionAndId.slashEncode());
-      api.getVirtualMachineApi(group).stop(regionAndId.id());
-      checkState(nodeSuspendedPredicate.create(group).apply(regionAndId.id()),
+      api.getVirtualMachineApi(resourceGroupName).stop(regionAndId.id());
+      checkState(nodeSuspendedPredicate.create(resourceGroupName).apply(regionAndId.id()),
             "Node %s was not suspended within the configured time limit", regionAndId.slashEncode());
 
       return userExecutor.submit(new Callable<Image>() {
          @Override
          public Image call() throws Exception {
             logger.debug(">> generalizing virtal machine %s...", regionAndId.id());
-            api.getVirtualMachineApi(group).generalize(regionAndId.id());
+            api.getVirtualMachineApi(resourceGroupName).generalize(regionAndId.id());
 
             logger.debug(">> capturing virtual machine %s to container %s...", regionAndId.id(), CONTAINER_NAME);
-            URI uri = api.getVirtualMachineApi(group).capture(regionAndId.id(), cloneTemplate.getName(), CONTAINER_NAME);
+            URI uri = api.getVirtualMachineApi(resourceGroupName)
+                  .capture(regionAndId.id(), cloneTemplate.getName(), CONTAINER_NAME);
             checkState(uri != null && imageAvailablePredicate.apply(uri),
                   "Image %s was not created within the configured time limit", cloneTemplate.getName());
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fde928eb/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeSecurityGroupExtension.java
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeSecurityGroupExtension.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeSecurityGroupExtension.java
index 12d140b..340e51c 100644
--- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeSecurityGroupExtension.java
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeSecurityGroupExtension.java
@@ -36,7 +36,6 @@ import javax.inject.Named;
 
 import org.jclouds.azurecompute.arm.AzureComputeApi;
 import org.jclouds.azurecompute.arm.compute.config.AzureComputeServiceContextModule.SecurityGroupAvailablePredicateFactory;
-import org.jclouds.azurecompute.arm.compute.functions.LocationToResourceGroupName;
 import org.jclouds.azurecompute.arm.domain.IdReference;
 import org.jclouds.azurecompute.arm.domain.NetworkInterfaceCard;
 import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroup;
@@ -47,6 +46,7 @@ import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties.Access;
 import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties.Direction;
 import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties.Protocol;
 import org.jclouds.azurecompute.arm.domain.RegionAndId;
+import org.jclouds.azurecompute.arm.domain.ResourceGroup;
 import org.jclouds.azurecompute.arm.domain.VirtualMachine;
 import org.jclouds.azurecompute.arm.features.NetworkSecurityGroupApi;
 import org.jclouds.azurecompute.arm.features.NetworkSecurityRuleApi;
@@ -65,6 +65,7 @@ import com.google.common.base.Objects;
 import com.google.common.base.Predicate;
 import com.google.common.base.Splitter;
 import com.google.common.base.Supplier;
+import com.google.common.cache.LoadingCache;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Multimap;
@@ -77,23 +78,23 @@ public class AzureComputeSecurityGroupExtension implements SecurityGroupExtensio
 
    private final AzureComputeApi api;
    private final Function<NetworkSecurityGroup, SecurityGroup> securityGroupConverter;
-   private final LocationToResourceGroupName locationToResourceGroupName;
    private final Supplier<Set<? extends Location>> locations;
    private final SecurityGroupAvailablePredicateFactory securityGroupAvailable;
    private final Predicate<URI> resourceDeleted;
+   private final LoadingCache<String, ResourceGroup> resourceGroupMap;
 
    @Inject
    AzureComputeSecurityGroupExtension(AzureComputeApi api, @Memoized Supplier<Set<? extends Location>> locations,
-         LocationToResourceGroupName locationToResourceGroupName,
          Function<NetworkSecurityGroup, SecurityGroup> groupConverter,
          SecurityGroupAvailablePredicateFactory securityRuleAvailable,
-         @Named(TIMEOUT_RESOURCE_DELETED) Predicate<URI> resourceDeleted) {
+         @Named(TIMEOUT_RESOURCE_DELETED) Predicate<URI> resourceDeleted,
+         LoadingCache<String, ResourceGroup> resourceGroupMap) {
       this.api = api;
       this.locations = locations;
       this.securityGroupConverter = groupConverter;
-      this.locationToResourceGroupName = locationToResourceGroupName;
       this.securityGroupAvailable = securityRuleAvailable;
       this.resourceDeleted = resourceDeleted;
+      this.resourceGroupMap = resourceGroupMap;
    }
 
    @Override
@@ -109,8 +110,8 @@ public class AzureComputeSecurityGroupExtension implements SecurityGroupExtensio
    @Override
    public Set<SecurityGroup> listSecurityGroupsInLocation(Location location) {
       logger.debug(">> getting security groups for %s...", location);
-      final String resourcegroup = locationToResourceGroupName.apply(location.getId());
-      List<NetworkSecurityGroup> networkGroups = api.getNetworkSecurityGroupApi(resourcegroup).list();
+      ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(location.getId());
+      List<NetworkSecurityGroup> networkGroups = api.getNetworkSecurityGroupApi(resourceGroup.name()).list();
       return ImmutableSet.copyOf(transform(filter(networkGroups, notNull()), securityGroupConverter));
    }
 
@@ -119,19 +120,19 @@ public class AzureComputeSecurityGroupExtension implements SecurityGroupExtensio
       logger.debug(">> getting security groups for node %s...", nodeId);
 
       final RegionAndId regionAndId = RegionAndId.fromSlashEncoded(nodeId);
-      final String resourceGroup = locationToResourceGroupName.apply(regionAndId.region());
+      ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(regionAndId.region());
 
-      VirtualMachine vm = api.getVirtualMachineApi(resourceGroup).get(regionAndId.id());
+      VirtualMachine vm = api.getVirtualMachineApi(resourceGroup.name()).get(regionAndId.id());
       List<IdReference> networkInterfacesIdReferences = vm.properties().networkProfile().networkInterfaces();
       List<NetworkSecurityGroup> networkGroups = new ArrayList<NetworkSecurityGroup>();
 
       for (IdReference networkInterfaceCardIdReference : networkInterfacesIdReferences) {
          String nicName = Iterables.getLast(Splitter.on("/").split(networkInterfaceCardIdReference.id()));
-         NetworkInterfaceCard card = api.getNetworkInterfaceCardApi(resourceGroup).get(nicName);
+         NetworkInterfaceCard card = api.getNetworkInterfaceCardApi(resourceGroup.name()).get(nicName);
          if (card != null && card.properties().networkSecurityGroup() != null) {
             String secGroupName = Iterables.getLast(Splitter.on("/").split(
                   card.properties().networkSecurityGroup().id()));
-            NetworkSecurityGroup group = api.getNetworkSecurityGroupApi(resourceGroup).get(secGroupName);
+            NetworkSecurityGroup group = api.getNetworkSecurityGroupApi(resourceGroup.name()).get(secGroupName);
             networkGroups.add(group);
          }
       }
@@ -143,14 +144,14 @@ public class AzureComputeSecurityGroupExtension implements SecurityGroupExtensio
    public SecurityGroup getSecurityGroupById(String id) {
       logger.debug(">> getting security group %s...", id);
       final RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id);
-      final String resourceGroup = locationToResourceGroupName.apply(regionAndId.region());
-
-      return securityGroupConverter.apply(api.getNetworkSecurityGroupApi(resourceGroup).get(regionAndId.id()));
+      ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(regionAndId.region());
+      NetworkSecurityGroup securityGroup = api.getNetworkSecurityGroupApi(resourceGroup.name()).get(regionAndId.id());
+      return securityGroup == null ? null : securityGroupConverter.apply(securityGroup);
    }
 
    @Override
    public SecurityGroup createSecurityGroup(String name, Location location) {
-      final String resourceGroup = locationToResourceGroupName.apply(location.getId());
+      ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(location.getId());
 
       logger.debug(">> creating security group %s in %s...", name, location);
 
@@ -158,7 +159,7 @@ public class AzureComputeSecurityGroupExtension implements SecurityGroupExtensio
       builder.name(name);
       builder.location(location);
 
-      return securityGroupConverter.apply(api.getNetworkSecurityGroupApi(resourceGroup).createOrUpdate(name,
+      return securityGroupConverter.apply(api.getNetworkSecurityGroupApi(resourceGroup.name()).createOrUpdate(name,
             location.getId(), null, NetworkSecurityGroupProperties.builder().build()));
    }
 
@@ -167,8 +168,8 @@ public class AzureComputeSecurityGroupExtension implements SecurityGroupExtensio
       logger.debug(">> deleting security group %s...", id);
 
       final RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id);
-      final String resourcegroup = locationToResourceGroupName.apply(regionAndId.region());
-      URI uri = api.getNetworkSecurityGroupApi(resourcegroup).delete(regionAndId.id());
+      ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(regionAndId.region());
+      URI uri = api.getNetworkSecurityGroupApi(resourceGroup.name()).delete(regionAndId.id());
       return resourceDeleted.apply(uri);
    }
 
@@ -196,35 +197,35 @@ public class AzureComputeSecurityGroupExtension implements SecurityGroupExtensio
       // TODO: Support Azure network tags somehow?
 
       final RegionAndId regionAndId = RegionAndId.fromSlashEncoded(group.getId());
-      final String resourceGroup = locationToResourceGroupName.apply(regionAndId.region());
+      ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(regionAndId.region());
 
-      NetworkSecurityGroupApi groupApi = api.getNetworkSecurityGroupApi(resourceGroup);
+      NetworkSecurityGroupApi groupApi = api.getNetworkSecurityGroupApi(resourceGroup.name());
       NetworkSecurityGroup networkSecurityGroup = groupApi.get(regionAndId.id());
 
       if (networkSecurityGroup == null) {
          throw new IllegalArgumentException("Security group " + group.getName() + " was not found");
       }
 
-      NetworkSecurityRuleApi ruleApi = api.getNetworkSecurityRuleApi(resourceGroup, networkSecurityGroup.name());
-      int nextPriority = getRuleStartingPriority(ruleApi);
+      NetworkSecurityRuleApi ruleApi = api.getNetworkSecurityRuleApi(resourceGroup.name(), networkSecurityGroup.name());
+      int nextPriority = getRuleStartingPriority(networkSecurityGroup);
 
       for (String ipRange : ipRanges) {
          NetworkSecurityRuleProperties properties = NetworkSecurityRuleProperties.builder()
-               .protocol(Protocol.fromValue(protocol.name())) //
-               .sourceAddressPrefix(ipRange) //
-               .sourcePortRange("*") //
-               .destinationAddressPrefix("*") //
-               .destinationPortRange(portRange) //
-               .direction(Direction.Inbound) //
-               .access(Access.Allow) //
-               .priority(nextPriority++) //
+               .protocol(Protocol.fromValue(protocol.name()))
+               .sourceAddressPrefix(ipRange)
+               .sourcePortRange("*")
+               .destinationAddressPrefix("*")
+               .destinationPortRange(portRange)
+               .direction(Direction.Inbound)
+               .access(Access.Allow)
+               .priority(nextPriority++)
                .build();
 
          logger.debug(">> creating network security rule %s for %s...", ruleName, ipRange);
 
          ruleApi.createOrUpdate(ruleName, properties);
 
-         checkState(securityGroupAvailable.create(resourceGroup).apply(networkSecurityGroup.name()),
+         checkState(securityGroupAvailable.create(resourceGroup.name()).apply(networkSecurityGroup.name()),
                "Security group was not updated in the configured timeout");
       }
 
@@ -241,16 +242,16 @@ public class AzureComputeSecurityGroupExtension implements SecurityGroupExtensio
       logger.debug(">> deleting ip permissions matching [%s] from %s...", ruleName, group.getName());
 
       final RegionAndId regionAndId = RegionAndId.fromSlashEncoded(group.getId());
-      final String resourceGroup = locationToResourceGroupName.apply(regionAndId.region());
+      ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(regionAndId.region());
 
-      NetworkSecurityGroupApi groupApi = api.getNetworkSecurityGroupApi(resourceGroup);
+      NetworkSecurityGroupApi groupApi = api.getNetworkSecurityGroupApi(resourceGroup.name());
       NetworkSecurityGroup networkSecurityGroup = groupApi.get(regionAndId.id());
 
       if (networkSecurityGroup == null) {
          throw new IllegalArgumentException("Security group " + group.getName() + " was not found");
       }
 
-      NetworkSecurityRuleApi ruleApi = api.getNetworkSecurityRuleApi(resourceGroup, networkSecurityGroup.name());
+      NetworkSecurityRuleApi ruleApi = api.getNetworkSecurityRuleApi(resourceGroup.name(), networkSecurityGroup.name());
       Iterable<NetworkSecurityRule> rules = filter(ruleApi.list(), new Predicate<NetworkSecurityRule>() {
          @Override
          public boolean apply(NetworkSecurityRule input) {
@@ -266,7 +267,7 @@ public class AzureComputeSecurityGroupExtension implements SecurityGroupExtensio
       for (NetworkSecurityRule matchingRule : rules) {
          logger.debug(">> deleting network security rule %s from %s...", matchingRule.name(), group.getName());
          ruleApi.delete(matchingRule.name());
-         checkState(securityGroupAvailable.create(resourceGroup).apply(networkSecurityGroup.name()),
+         checkState(securityGroupAvailable.create(resourceGroup.name()).apply(networkSecurityGroup.name()),
                "Security group was not updated in the configured timeout");
       }
 
@@ -298,8 +299,8 @@ public class AzureComputeSecurityGroupExtension implements SecurityGroupExtensio
       return false;
    }
 
-   private int getRuleStartingPriority(NetworkSecurityRuleApi ruleApi) {
-      List<NetworkSecurityRule> existingRules = ruleApi.list();
+   private int getRuleStartingPriority(NetworkSecurityGroup securityGroup) {
+      List<NetworkSecurityRule> existingRules = securityGroup.properties().securityRules();
       return existingRules.isEmpty() ? 100 : rulesByPriority().max(existingRules).properties().priority() + 1;
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fde928eb/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/NetworkSecurityRuleToIpPermission.java
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/NetworkSecurityRuleToIpPermission.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/NetworkSecurityRuleToIpPermission.java
index e601d59..50a0954 100644
--- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/NetworkSecurityRuleToIpPermission.java
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/NetworkSecurityRuleToIpPermission.java
@@ -60,9 +60,9 @@ public class NetworkSecurityRuleToIpPermission implements Function<NetworkSecuri
 
       String portRange = rule.properties().destinationPortRange();
       if (!"*".equals(portRange)) {
-         String[] range = portRange.split("-");
+         String[] range = portRange.split("-"); // One single element if it is a single port
          permissions = PortSelection.class.cast(permissions).fromPort(Integer.parseInt(range[0]))
-               .to(Integer.parseInt(range[1]));
+               .to(Integer.parseInt(range[range.length - 1]));
       }
 
       if (!"*".equals(rule.properties().sourceAddressPrefix())) {

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fde928eb/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
index 02fb0f4..dbde188 100644
--- 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
@@ -25,12 +25,14 @@ import javax.inject.Inject;
 import org.jclouds.azurecompute.arm.AzureComputeApi;
 import org.jclouds.azurecompute.arm.domain.RegionAndId;
 import org.jclouds.azurecompute.arm.domain.ResourceDefinition;
+import org.jclouds.azurecompute.arm.domain.ResourceGroup;
 import org.jclouds.azurecompute.arm.domain.VMImage;
 import org.jclouds.azurecompute.arm.domain.VirtualMachine;
 import org.jclouds.azurecompute.arm.functions.StorageProfileToStorageAccountName;
 import org.jclouds.compute.domain.Image;
 
 import com.google.common.base.Function;
+import com.google.common.cache.LoadingCache;
 import com.google.inject.assistedinject.Assisted;
 
 public class ResourceDefinitionToCustomImage implements Function<ResourceDefinition, Image> {
@@ -41,30 +43,35 @@ public class ResourceDefinitionToCustomImage implements Function<ResourceDefinit
 
    private final Function<VMImage, Image> vmImageToImage;
    private final String imageName;
-   private final String storageAccountName;
-   private final VirtualMachine vm;
-   private final String resourceGroup;
+   private final String nodeId;
+   private final AzureComputeApi api;
+   private final StorageProfileToStorageAccountName storageProfileToStorageAccountName;
+   private final LoadingCache<String, ResourceGroup> resourceGroupMap;
 
    @Inject
    ResourceDefinitionToCustomImage(AzureComputeApi api,
          StorageProfileToStorageAccountName storageProfileToStorageAccountName,
-         Function<VMImage, Image> vmImageToImage, LocationToResourceGroupName locationToResourceGroupName,
-         @Assisted("nodeId") String nodeId,
-         @Assisted("imageName") String imageName) {
+         Function<VMImage, Image> vmImageToImage, LoadingCache<String, ResourceGroup> resourceGroupMap,
+         @Assisted("nodeId") String nodeId, @Assisted("imageName") String imageName) {
+      this.api = api;
       this.vmImageToImage = vmImageToImage;
+      this.nodeId = nodeId;
       this.imageName = imageName;
-      
-      RegionAndId regionAndId = RegionAndId.fromSlashEncoded(nodeId);
-      this.resourceGroup = locationToResourceGroupName.apply(regionAndId.region());
-      this.vm = api.getVirtualMachineApi(this.resourceGroup).get(regionAndId.id());
-      this.storageAccountName = storageProfileToStorageAccountName.apply(vm.properties().storageProfile());
+      this.storageProfileToStorageAccountName = storageProfileToStorageAccountName;
+      this.resourceGroupMap = resourceGroupMap;
    }
 
    @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());
+      RegionAndId regionAndId = RegionAndId.fromSlashEncoded(nodeId);
+      ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(regionAndId.region());
+      
+      VirtualMachine vm = api.getVirtualMachineApi(resourceGroup.name()).get(regionAndId.id());
+      String storageAccountName = storageProfileToStorageAccountName.apply(vm.properties().storageProfile());
+
+      VMImage.Builder builder = VMImage.customImage().group(resourceGroup.name()).storage(storageAccountName)
+            .name(imageName).offer(CUSTOM_IMAGE_OFFER).location(vm.location());
 
       Map<String, String> properties = (Map<String, String>) input.properties();
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fde928eb/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 22c818c..92689c2 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
@@ -17,6 +17,7 @@
 package org.jclouds.azurecompute.arm.compute.functions;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Strings.nullToEmpty;
 import static com.google.common.collect.Iterables.find;
 import static com.google.common.collect.Iterables.transform;
 import static com.google.common.collect.Iterables.tryFind;
@@ -25,6 +26,7 @@ import static org.jclouds.azurecompute.arm.compute.extensions.AzureComputeImageE
 import static org.jclouds.azurecompute.arm.compute.extensions.AzureComputeImageExtension.CUSTOM_IMAGE_OFFER;
 import static org.jclouds.azurecompute.arm.compute.functions.VMImageToImage.encodeFieldsToUniqueId;
 import static org.jclouds.compute.util.ComputeServiceUtils.addMetadataAndParseTagsFromCommaDelimitedValue;
+import static org.jclouds.location.predicates.LocationPredicates.idEquals;
 import static org.jclouds.util.Closeables2.closeQuietly;
 
 import java.util.List;
@@ -36,11 +38,11 @@ import javax.inject.Inject;
 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.IpConfiguration;
 import org.jclouds.azurecompute.arm.domain.NetworkInterfaceCard;
 import org.jclouds.azurecompute.arm.domain.RegionAndId;
+import org.jclouds.azurecompute.arm.domain.ResourceGroup;
 import org.jclouds.azurecompute.arm.domain.StorageProfile;
 import org.jclouds.azurecompute.arm.domain.StorageServiceKeys;
 import org.jclouds.azurecompute.arm.domain.VMImage;
@@ -71,6 +73,7 @@ import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.base.Splitter;
 import com.google.common.base.Supplier;
+import com.google.common.cache.LoadingCache;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
@@ -120,15 +123,14 @@ public class VirtualMachineToNodeMetadata implements Function<VirtualMachine, No
    private final Map<String, Credentials> credentialStore;
    private final Function<VMImage, Image> vmImageToImge;
    private final StorageProfileToStorageAccountName storageProfileToStorageAccountName;
-   private final LocationToResourceGroupName locationToResourceGroupName;
+   private final LoadingCache<String, ResourceGroup> resourceGroupMap;
 
    @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, Function<VMImage, Image> vmImageToImge,
-         StorageProfileToStorageAccountName storageProfileToStorageAccountName,
-         LocationToResourceGroupName locationToResourceGroupName) {
+         Function<VMImage, Image> vmImageToImge, StorageProfileToStorageAccountName storageProfileToStorageAccountName,
+         LoadingCache<String, ResourceGroup> resourceGroupMap) {
       this.api = api;
       this.nodeNamingConvention = namingConvention.createWithoutPrefix();
       this.images = checkNotNull(images, "images cannot be null");
@@ -137,13 +139,13 @@ public class VirtualMachineToNodeMetadata implements Function<VirtualMachine, No
       this.credentialStore = credentialStore;
       this.vmImageToImge = vmImageToImge;
       this.storageProfileToStorageAccountName = storageProfileToStorageAccountName;
-      this.locationToResourceGroupName = locationToResourceGroupName;
+      this.resourceGroupMap = resourceGroupMap;
    }
 
    @Override
    public NodeMetadata apply(VirtualMachine virtualMachine) {
-      String azureGroup = locationToResourceGroupName.apply(virtualMachine.location());
-      
+      ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(virtualMachine.location());
+
       NodeMetadataBuilder builder = new NodeMetadataBuilder();
       builder.id(RegionAndId.fromRegionAndId(virtualMachine.location(), virtualMachine.name()).slashEncode());
       builder.providerId(virtualMachine.id());
@@ -154,7 +156,7 @@ public class VirtualMachineToNodeMetadata implements Function<VirtualMachine, No
       if (ProvisioningState.SUCCEEDED.equals(provisioningState)) {
          // If the provisioning succeeded, we need to query the *real* status of
          // the VM
-         VirtualMachineInstance instanceDetails = api.getVirtualMachineApi(azureGroup).getInstanceDetails(
+         VirtualMachineInstance instanceDetails = api.getVirtualMachineApi(resourceGroup.name()).getInstanceDetails(
                virtualMachine.name());
          if (instanceDetails != null && instanceDetails.powerState() != null) {
             builder.status(POWERSTATE_TO_NODESTATUS.apply(instanceDetails.powerState()));
@@ -185,15 +187,18 @@ public class VirtualMachineToNodeMetadata implements Function<VirtualMachine, No
          addMetadataAndParseTagsFromCommaDelimitedValue(builder, virtualMachine.tags());
          groupFromMetadata = virtualMachine.tags().get(GROUP_KEY);
       }
-      
-      // Try to read the group from the virtual machine tags, and parse the name if missing
+
+      // Try to read the group from the virtual machine tags, and parse the name
+      // if missing
       builder.group(groupFromMetadata != null ? groupFromMetadata : nodeNamingConvention.extractGroup(virtualMachine
             .name()));
-      
+
       String locationName = virtualMachine.location();
       builder.location(getLocation(locations, locationName));
 
-      Optional<? extends Image> image = findImage(virtualMachine.properties().storageProfile(), locationName, azureGroup);
+      Optional<? extends Image> image = findImage(virtualMachine.properties().storageProfile(), locationName,
+            resourceGroup.name());
+      
       if (image.isPresent()) {
          builder.imageId(image.get().getId());
          builder.operatingSystem(image.get().getOperatingSystem());
@@ -252,15 +257,11 @@ public class VirtualMachineToNodeMetadata implements Function<VirtualMachine, No
    }
 
    protected static Location getLocation(Supplier<Set<? extends Location>> locations, final String locationName) {
-      return find(locations.get(), new Predicate<Location>() {
-         @Override
-         public boolean apply(Location location) {
-            return locationName != null && locationName.equals(location.getId());
-         }
-      }, null);
+      return find(locations.get(), idEquals(nullToEmpty(locationName)), null);
    }
 
-   protected Optional<? extends Image> findImage(final StorageProfile storageProfile, String locatioName, String azureGroup) {
+   protected Optional<? extends Image> findImage(final StorageProfile storageProfile, String locatioName,
+         String azureGroup) {
       if (storageProfile.imageReference() != null) {
          return Optional.fromNullable(images.get().get(
                encodeFieldsToUniqueId(false, locatioName, storageProfile.imageReference())));

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fde928eb/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/loaders/CreateSecurityGroupIfNeeded.java
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/loaders/CreateSecurityGroupIfNeeded.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/loaders/CreateSecurityGroupIfNeeded.java
index efb8abc..0ff96d8 100644
--- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/loaders/CreateSecurityGroupIfNeeded.java
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/loaders/CreateSecurityGroupIfNeeded.java
@@ -29,7 +29,6 @@ import javax.inject.Singleton;
 
 import org.jclouds.azurecompute.arm.AzureComputeApi;
 import org.jclouds.azurecompute.arm.compute.domain.RegionAndIdAndIngressRules;
-import org.jclouds.azurecompute.arm.compute.functions.LocationToResourceGroupName;
 import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroup;
 import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroupProperties;
 import org.jclouds.azurecompute.arm.domain.NetworkSecurityRule;
@@ -37,10 +36,12 @@ import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties;
 import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties.Access;
 import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties.Direction;
 import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties.Protocol;
+import org.jclouds.azurecompute.arm.domain.ResourceGroup;
 import org.jclouds.compute.reference.ComputeServiceConstants;
 import org.jclouds.logging.Logger;
 
 import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
 
 @Singleton
 public class CreateSecurityGroupIfNeeded extends CacheLoader<RegionAndIdAndIngressRules, String> {
@@ -49,18 +50,18 @@ public class CreateSecurityGroupIfNeeded extends CacheLoader<RegionAndIdAndIngre
    protected Logger logger = Logger.NULL;
 
    private final AzureComputeApi api;
-   private final LocationToResourceGroupName locationToResourceGroupName;
+   private final LoadingCache<String, ResourceGroup> resourceGroupMap;
 
    @Inject
-   CreateSecurityGroupIfNeeded(AzureComputeApi api, LocationToResourceGroupName locationToResourceGroupName) {
+   CreateSecurityGroupIfNeeded(AzureComputeApi api, LoadingCache<String, ResourceGroup> resourceGroupMap) {
       this.api = api;
-      this.locationToResourceGroupName = locationToResourceGroupName;
+      this.resourceGroupMap = resourceGroupMap;
    }
 
    @Override
    public String load(RegionAndIdAndIngressRules key) throws Exception {
-      String resourceGroup = locationToResourceGroupName.apply(key.region());
-      return createSecurityGroup(key.region(), resourceGroup, key.id(), key.inboundPorts());
+      ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(key.region());
+      return createSecurityGroup(key.region(), resourceGroup.name(), key.id(), key.inboundPorts());
    }
 
    private String createSecurityGroup(String location, String resourceGroup, String name, int[] inboundPorts) {

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fde928eb/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/loaders/ResourceGroupForLocation.java
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/loaders/ResourceGroupForLocation.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/loaders/ResourceGroupForLocation.java
new file mode 100644
index 0000000..ddbbb26
--- /dev/null
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/loaders/ResourceGroupForLocation.java
@@ -0,0 +1,62 @@
+/*
+ * 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.loaders;
+
+import java.util.Map;
+
+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.compute.functions.LocationToResourceGroupName;
+import org.jclouds.azurecompute.arm.domain.ResourceGroup;
+import org.jclouds.azurecompute.arm.features.ResourceGroupApi;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.logging.Logger;
+
+import com.google.common.cache.CacheLoader;
+import com.google.common.collect.ImmutableMap;
+
+@Singleton
+public class ResourceGroupForLocation extends CacheLoader<String, ResourceGroup> {
+   @Resource
+   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   private final ResourceGroupApi api;
+   private final LocationToResourceGroupName locationToResourceGroupName;
+
+   @Inject
+   ResourceGroupForLocation(AzureComputeApi api, LocationToResourceGroupName locationToResourceGroupName) {
+      this.api = api.getResourceGroupApi();
+      this.locationToResourceGroupName = locationToResourceGroupName;
+   }
+
+   @Override
+   public ResourceGroup load(String locationId) throws Exception {
+      String azureGroupName = locationToResourceGroupName.apply(locationId);
+      ResourceGroup resourceGroup = api.get(azureGroupName);
+      if (resourceGroup == null) {
+         logger.debug(">> creating resource group %s", azureGroupName);
+         final Map<String, String> tags = ImmutableMap.of("description", "jclouds managed VMs");
+         resourceGroup = api.create(azureGroupName, locationId, tags);
+      }
+      return resourceGroup;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fde928eb/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/options/AzureTemplateOptions.java
----------------------------------------------------------------------
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
index c71a7da..ecbc237 100644
--- 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
@@ -27,57 +27,11 @@ import static com.google.common.base.Objects.equal;
  */
 public class AzureTemplateOptions extends TemplateOptions implements Cloneable {
 
-
-   private String customData;
-   private String virtualNetworkAddressPrefix;
-   private String subnetAddressPrefix;
-   private String DNSLabelPrefix;
-   private String keyVaultIdAndSecret;
    private String virtualNetworkName;
    private String subnetId;
    private String blob;
 
    /**
-    * Custom options for the Azure ARM API
-    */
-   public  AzureTemplateOptions customData(String customData) {
-      this.customData = customData;
-      return this;
-   }
-   
-   /**
-    * 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;
-   }
-
-   /**
     * Sets the virtual network name
     */
    public  AzureTemplateOptions virtualNetworkName(String virtualNetworkName) {
@@ -101,11 +55,6 @@ public class AzureTemplateOptions extends TemplateOptions implements Cloneable {
       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; }
    public String getBlob() { return blob; }
@@ -123,11 +72,6 @@ public class AzureTemplateOptions extends TemplateOptions implements Cloneable {
       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);
          eTo.blob(blob);
@@ -136,7 +80,7 @@ public class AzureTemplateOptions extends TemplateOptions implements Cloneable {
 
    @Override
    public int hashCode() {
-      return Objects.hashCode(super.hashCode(), virtualNetworkAddressPrefix, subnetAddressPrefix, DNSLabelPrefix, customData, keyVaultIdAndSecret, virtualNetworkName, subnetId, blob);
+      return Objects.hashCode(super.hashCode(), virtualNetworkName, subnetId, blob);
    }
 
    @Override
@@ -152,11 +96,6 @@ public class AzureTemplateOptions extends TemplateOptions implements Cloneable {
       }
       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)
             && equal(this.blob, other.blob);
@@ -165,11 +104,6 @@ public class AzureTemplateOptions extends TemplateOptions implements Cloneable {
    @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);
       toString.add("blob", blob);
@@ -179,46 +113,6 @@ public class AzureTemplateOptions extends TemplateOptions implements Cloneable {
    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) {

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fde928eb/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/predicates/IsDeploymentInRegions.java
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/predicates/IsDeploymentInRegions.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/predicates/IsDeploymentInRegions.java
deleted file mode 100644
index 66590e1..0000000
--- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/predicates/IsDeploymentInRegions.java
+++ /dev/null
@@ -1,47 +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.predicates;
-
-import java.util.Set;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-import org.jclouds.azurecompute.arm.domain.Deployment;
-import org.jclouds.azurecompute.arm.domain.Value;
-import org.jclouds.location.Region;
-
-import com.google.common.base.Predicate;
-import com.google.common.base.Supplier;
-
-@Singleton
-public class IsDeploymentInRegions implements Predicate<Deployment> {
-
-   private final Supplier<Set<String>> regionIds;
-
-   @Inject
-   IsDeploymentInRegions(@Region Supplier<Set<String>> regionIds) {
-      this.regionIds = regionIds;
-   }
-
-   @Override
-   public boolean apply(Deployment deployment) {
-      if (deployment.properties() == null || deployment.properties().parameters() == null || deployment.properties().parameters().get("location") == null) return false;
-      Value locationValue = deployment.properties().parameters().get("location");
-      return regionIds.get().contains(locationValue.value());
-   }
-}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fde928eb/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/strategy/CleanupResources.java
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/strategy/CleanupResources.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/strategy/CleanupResources.java
new file mode 100644
index 0000000..3914bc0
--- /dev/null
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/strategy/CleanupResources.java
@@ -0,0 +1,205 @@
+/*
+ * 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 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 static org.jclouds.util.Closeables2.closeQuietly;
+
+import java.net.URI;
+import java.util.List;
+
+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.domain.IdReference;
+import org.jclouds.azurecompute.arm.domain.IpConfiguration;
+import org.jclouds.azurecompute.arm.domain.NetworkInterfaceCard;
+import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroup;
+import org.jclouds.azurecompute.arm.domain.RegionAndId;
+import org.jclouds.azurecompute.arm.domain.ResourceGroup;
+import org.jclouds.azurecompute.arm.domain.StorageServiceKeys;
+import org.jclouds.azurecompute.arm.domain.VirtualMachine;
+import org.jclouds.azurecompute.arm.features.NetworkSecurityGroupApi;
+import org.jclouds.azurecompute.arm.functions.StorageProfileToStorageAccountName;
+import org.jclouds.azurecompute.arm.util.BlobHelper;
+import org.jclouds.compute.functions.GroupNamingConvention;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.logging.Logger;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.base.Splitter;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+@Singleton
+public class CleanupResources {
+
+   @Resource
+   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   private final AzureComputeApi api;
+   private final Predicate<URI> resourceDeleted;
+   private final StorageProfileToStorageAccountName storageProfileToStorageAccountName;
+   private final LoadingCache<String, ResourceGroup> resourceGroupMap;
+   private final GroupNamingConvention.Factory namingConvention;
+
+   @Inject
+   CleanupResources(AzureComputeApi azureComputeApi, @Named(TIMEOUT_RESOURCE_DELETED) Predicate<URI> resourceDeleted,
+         StorageProfileToStorageAccountName storageProfileToStorageAccountName,
+         LoadingCache<String, ResourceGroup> resourceGroupMap, GroupNamingConvention.Factory namingConvention) {
+      this.api = azureComputeApi;
+      this.resourceDeleted = resourceDeleted;
+      this.storageProfileToStorageAccountName = storageProfileToStorageAccountName;
+      this.resourceGroupMap = resourceGroupMap;
+      this.namingConvention = namingConvention;
+   }
+
+   public boolean cleanupNode(final String id) {
+      RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id);
+      ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(regionAndId.region());
+      String resourceGroupName = resourceGroup.name();
+
+      VirtualMachine virtualMachine = api.getVirtualMachineApi(resourceGroupName).get(regionAndId.id());
+      if (virtualMachine == null) {
+         return true;
+      }
+
+      logger.debug(">> destroying %s ...", regionAndId.slashEncode());
+      boolean vmDeleted = deleteVirtualMachine(resourceGroupName, virtualMachine);
+
+      // We don't delete the network here, as it is global to the resource
+      // group. It will be deleted when the resource group is deleted
+
+      cleanupVirtualMachineNICs(resourceGroupName, virtualMachine);
+      cleanupVirtualMachineStorage(resourceGroupName, virtualMachine);
+
+      return vmDeleted;
+   }
+
+   public void cleanupVirtualMachineNICs(String group, VirtualMachine virtualMachine) {
+      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);
+         resourceDeleted.apply(nicDeletionURI);
+
+         for (String publicIp : publicIps) {
+            logger.debug(">> deleting public ip nic %s...", publicIp);
+            api.getPublicIPAddressApi(group).delete(publicIp);
+         }
+      }
+   }
+
+   public void cleanupVirtualMachineStorage(String group, VirtualMachine virtualMachine) {
+      String storageAccountName = storageProfileToStorageAccountName
+            .apply(virtualMachine.properties().storageProfile());
+      StorageServiceKeys keys = api.getStorageAccountApi(group).getKeys(storageAccountName);
+
+      // Remove the virtual machine files
+      logger.debug(">> deleting virtual machine disk storage...");
+      BlobHelper blobHelper = new BlobHelper(storageAccountName, keys.key1());
+      try {
+         blobHelper.deleteContainerIfExists("vhds");
+
+         if (!blobHelper.customImageExists()) {
+            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!");
+         }
+      } finally {
+         closeQuietly(blobHelper);
+      }
+   }
+
+   public boolean cleanupSecurityGroupIfOrphaned(String resourceGroup, String group) {
+      String name = namingConvention.create().sharedNameForGroup(group);
+      NetworkSecurityGroupApi sgapi = api.getNetworkSecurityGroupApi(resourceGroup);
+
+      boolean deleted = false;
+
+      try {
+         NetworkSecurityGroup securityGroup = sgapi.get(name);
+         if (securityGroup != null) {
+            List<NetworkInterfaceCard> nics = securityGroup.properties().networkInterfaces();
+            if (nics == null || nics.isEmpty()) {
+               logger.debug(">> deleting orphaned security group %s from %s...", name, resourceGroup);
+               try {
+                  deleted = resourceDeleted.apply(sgapi.delete(name));
+               } catch (Exception ex) {
+                  logger.warn(ex, ">> error deleting orphaned security group %s from %s...", name, resourceGroup);
+               }
+            }
+         }
+      } catch (Exception ex) {
+         logger.warn(ex, "Error deleting security groups for %s and group %s", resourceGroup, group);
+      }
+
+      return deleted;
+   }
+
+   public boolean deleteResourceGroupIfEmpty(String group) {
+      boolean deleted = false;
+      if (api.getVirtualMachineApi(group).list().isEmpty() && api.getStorageAccountApi(group).list().isEmpty()
+            && api.getNetworkInterfaceCardApi(group).list().isEmpty()
+            && api.getPublicIPAddressApi(group).list().isEmpty()
+            && api.getNetworkSecurityGroupApi(group).list().isEmpty()) {
+         logger.debug(">> the resource group %s is empty. Deleting...", group);
+         deleted = resourceDeleted.apply(api.getResourceGroupApi().delete(group));
+      }
+      return deleted;
+   }
+
+   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();
+               }
+            }), notNull()), new Function<IdReference, String>() {
+               @Override
+               public String apply(IdReference input) {
+                  return Iterables.getLast(Splitter.on("/").split(input.id()));
+               }
+            });
+   }
+
+   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;
+   }
+
+   private boolean deleteVirtualMachine(String group, VirtualMachine virtualMachine) {
+      return resourceDeleted.apply(api.getVirtualMachineApi(group).delete(virtualMachine.name()));
+   }
+
+}