JCLOUDS-1278: Allow to configure virtual machine NICs
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 9d3f05d..c35455c 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
@@ -99,7 +99,7 @@
       // 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");
-      properties.put(API_VERSION_PREFIX + NetworkInterfaceCardApi.class.getSimpleName(), "2015-06-15");
+      properties.put(API_VERSION_PREFIX + NetworkInterfaceCardApi.class.getSimpleName(), "2017-03-01");
       properties.put(API_VERSION_PREFIX + NetworkSecurityGroupApi.class.getSimpleName(), "2016-03-30");
       properties.put(API_VERSION_PREFIX + NetworkSecurityRuleApi.class.getSimpleName(), "2016-03-30");
       properties.put(API_VERSION_PREFIX + OSImageApi.class.getSimpleName(), "2015-06-15");
@@ -107,7 +107,7 @@
       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(), "2015-06-15");
-      properties.put(API_VERSION_PREFIX + SubnetApi.class.getSimpleName(), "2015-06-15");
+      properties.put(API_VERSION_PREFIX + SubnetApi.class.getSimpleName(), "2017-03-01");
       properties.put(API_VERSION_PREFIX + VirtualNetworkApi.class.getSimpleName(), "2015-06-15");
       properties.put(API_VERSION_PREFIX + VMSizeApi.class.getSimpleName(), "2015-06-15");
       properties.put(API_VERSION_PREFIX + VirtualMachineApi.class.getSimpleName(), "2016-04-30-preview");
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 d01d6ec..02e69fa 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
@@ -27,9 +27,14 @@
 import static org.jclouds.azurecompute.arm.compute.domain.ResourceGroupAndName.fromResourceGroupAndName;
 import static org.jclouds.azurecompute.arm.compute.functions.VMImageToImage.getMarketplacePlanFromImageMetadata;
 import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.IMAGE_PUBLISHERS;
+import static org.jclouds.azurecompute.arm.domain.IdReference.extractName;
+import static org.jclouds.azurecompute.arm.domain.IdReference.extractResourceGroup;
 import static org.jclouds.azurecompute.arm.util.VMImages.isCustom;
 import static org.jclouds.compute.util.ComputeServiceUtils.metadataAndTagsAsCommaDelimitedValue;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -44,6 +49,7 @@
 import org.jclouds.azurecompute.arm.compute.domain.ResourceGroupAndName;
 import org.jclouds.azurecompute.arm.compute.functions.CustomImageToVMImage;
 import org.jclouds.azurecompute.arm.compute.options.AzureTemplateOptions;
+import org.jclouds.azurecompute.arm.compute.options.IpOptions;
 import org.jclouds.azurecompute.arm.compute.strategy.CleanupResources;
 import org.jclouds.azurecompute.arm.domain.AvailabilitySet;
 import org.jclouds.azurecompute.arm.domain.CreationData;
@@ -62,6 +68,7 @@
 import org.jclouds.azurecompute.arm.domain.OSProfile;
 import org.jclouds.azurecompute.arm.domain.Offer;
 import org.jclouds.azurecompute.arm.domain.Plan;
+import org.jclouds.azurecompute.arm.domain.Provisionable;
 import org.jclouds.azurecompute.arm.domain.PublicIPAddress;
 import org.jclouds.azurecompute.arm.domain.PublicIPAddressProperties;
 import org.jclouds.azurecompute.arm.domain.ResourceGroup;
@@ -75,13 +82,15 @@
 import org.jclouds.azurecompute.arm.domain.Version;
 import org.jclouds.azurecompute.arm.domain.VirtualMachine;
 import org.jclouds.azurecompute.arm.domain.VirtualMachineProperties;
+import org.jclouds.azurecompute.arm.domain.NetworkProfile.NetworkInterface;
+import org.jclouds.azurecompute.arm.domain.NetworkProfile.NetworkInterface.NetworkInterfaceProperties;
+import org.jclouds.azurecompute.arm.features.NetworkInterfaceCardApi;
 import org.jclouds.azurecompute.arm.features.OSImageApi;
-import org.jclouds.azurecompute.arm.features.PublicIPAddressApi;
 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.functions.GroupNamingConvention;
 import org.jclouds.compute.reference.ComputeServiceConstants;
 import org.jclouds.location.Region;
 import org.jclouds.logging.Logger;
@@ -105,6 +114,7 @@
 public class AzureComputeServiceAdapter implements ComputeServiceAdapter<VirtualMachine, VMHardware, VMImage, Location> {
 
    public static final String GROUP_KEY = "jclouds_group";
+   public static final String AUTOGENERATED_IP_KEY = "jclouds-autogenerated";
 
    @Resource
    @Named(ComputeServiceConstants.COMPUTE_LOGGER)
@@ -116,40 +126,40 @@
    private final Supplier<Set<String>> regionIds;
    private final PublicIpAvailablePredicateFactory publicIpAvailable;
    private final CustomImageToVMImage customImagetoVmImage;
+   private final GroupNamingConvention namingConvention;
+   private Predicate<Supplier<Provisionable>> resourceAvailable;
 
    @Inject
    AzureComputeServiceAdapter(final AzureComputeApi api, @Named(IMAGE_PUBLISHERS) String imagePublishers,
          CleanupResources cleanupResources, @Region Supplier<Set<String>> regionIds,
-         PublicIpAvailablePredicateFactory publicIpAvailable,
-         CustomImageToVMImage customImagetoVmImage) {
+         PublicIpAvailablePredicateFactory publicIpAvailable, CustomImageToVMImage customImagetoVmImage,
+         GroupNamingConvention.Factory namingConvention, Predicate<Supplier<Provisionable>> resourceAvailable) {
       this.api = api;
       this.imagePublishers = Splitter.on(',').trimResults().omitEmptyStrings().splitToList(imagePublishers);
       this.cleanupResources = cleanupResources;
       this.regionIds = regionIds;
       this.publicIpAvailable = publicIpAvailable;
       this.customImagetoVmImage = customImagetoVmImage;
+      this.namingConvention = namingConvention.create();
+      this.resourceAvailable = resourceAvailable;
    }
 
    @Override
    public NodeAndInitialCredentials<VirtualMachine> createNodeWithGroupEncodedIntoName(final String group, final String name, final Template template) {
-      // TODO network ids => create one nic in each network
-
       String locationName = template.getLocation().getId();
       Image image = template.getImage();
       String hardwareId = fromSlashEncoded(template.getHardware().getId()).name();
-      // TODO ARM specific options
       AzureTemplateOptions templateOptions = template.getOptions().as(AzureTemplateOptions.class);
-      String subnetId = templateOptions.getSubnetId();
       String resourceGroupName = templateOptions.getResourceGroup();
       
       IdReference availabilitySet = getAvailabilitySetIdReference(templateOptions.getAvailabilitySet());
+      NetworkProfile networkProfile = createNetworkProfile(createNetworkInterfaceCards(name, locationName,
+            templateOptions));
       StorageProfile storageProfile = createStorageProfile(image, templateOptions.getDataDisks());
-      NetworkInterfaceCard nic = createNetworkInterfaceCard(subnetId, name, locationName, resourceGroupName, template.getOptions());
       HardwareProfile hardwareProfile = HardwareProfile.builder().vmSize(hardwareId).build();
       OSProfile osProfile = createOsProfile(name, template);
-      NetworkProfile networkProfile = NetworkProfile.builder().networkInterfaces(of(IdReference.create(nic.id()))).build();
+      
       VirtualMachineProperties virtualMachineProperties = VirtualMachineProperties.builder()
-              .licenseType(null) // TODO
               .availabilitySet(availabilitySet)
               .hardwareProfile(hardwareProfile)
               .storageProfile(storageProfile)
@@ -159,11 +169,11 @@
 
       // Store group apart from the name to be able to identify nodes with
       // custom names in the configured group
-      template.getOptions().getUserMetadata().put(GROUP_KEY, group);
-      Map<String, String> metadataAndTags = metadataAndTagsAsCommaDelimitedValue(template.getOptions());
-      Plan plan = getMarketplacePlanFromImageMetadata(template.getImage());
+      templateOptions.getUserMetadata().put(GROUP_KEY, group);
+      Map<String, String> metadataAndTags = metadataAndTagsAsCommaDelimitedValue(templateOptions);
+      Plan plan = getMarketplacePlanFromImageMetadata(image);
 
-      VirtualMachine virtualMachine = api.getVirtualMachineApi(resourceGroupName).createOrUpdate(name, template.getLocation().getId(),
+      VirtualMachine virtualMachine = api.getVirtualMachineApi(resourceGroupName).createOrUpdate(name, locationName,
             virtualMachineProperties, metadataAndTags, plan);
 
       // Safe to pass null credentials here, as jclouds will default populate
@@ -383,39 +393,113 @@
       return builder.build();
    }
 
-   private NetworkInterfaceCard createNetworkInterfaceCard(String subnetId, String name, String locationName,
-                                                           String azureGroup, TemplateOptions options) {
-      final PublicIPAddressApi ipApi = api.getPublicIPAddressApi(azureGroup);
+   private List<NetworkInterfaceCard> createNetworkInterfaceCards(final String nodeName, final String location,
+         AzureTemplateOptions options) {
+      // Prefer a sorted list of NICs with the ones with public IPs first, to
+      // make sure the primary NIC is the public one
+      final String securityGroup = getOnlyElement(options.getGroups(), null);
+      return Lists.transform(publicIpsFirst(options.getIpOptions()), new Function<IpOptions, NetworkInterfaceCard>() {
+         @Override
+         public NetworkInterfaceCard apply(IpOptions input) {
+            return createNetworkInterfaceCard(input, nodeName, location, securityGroup);
+         }
+      });
+   }
+   
+   private NetworkInterfaceCard createNetworkInterfaceCard(IpOptions ipConfig, String nodeName, String location,
+         String securityGroup) {
+      String resourceGroup = extractResourceGroup(ipConfig.subnet());
+      String subnetName = extractName(ipConfig.subnet());
 
-      PublicIPAddressProperties properties = PublicIPAddressProperties.builder().publicIPAllocationMethod("Static")
-              .idleTimeoutInMinutes(4).build();
+      IpConfigurationProperties.Builder ipProperties = IpConfigurationProperties.builder()
+            .subnet(IdReference.create(ipConfig.subnet()))
+            .privateIPAllocationMethod(ipConfig.address().isPresent() ? "Static" : "Dynamic")
+            .privateIPAddress(ipConfig.address().orNull());
 
-      String publicIpAddressName = "public-address-" + name;
-      PublicIPAddress ip = ipApi.createOrUpdate(publicIpAddressName, locationName, ImmutableMap.of("jclouds", name),
-              properties);
+      configurePublicIP(ipConfig, ipProperties, resourceGroup, location, nodeName);
 
-      checkState(publicIpAvailable.create(azureGroup).apply(publicIpAddressName),
-              "Public IP was not provisioned in the configured timeout");
+      String ipName = namingConvention.uniqueNameForGroup(subnetName);
+      final String nicName = namingConvention.uniqueNameForGroup(subnetName);
 
-      final NetworkInterfaceCardProperties.Builder networkInterfaceCardProperties = NetworkInterfaceCardProperties
-              .builder()
-              .ipConfigurations(
-                      of(IpConfiguration
-                              .builder()
-                              .name("ipConfig-" + name)
-                              .properties(
-                                      IpConfigurationProperties.builder().privateIPAllocationMethod("Dynamic")
-                                              .publicIPAddress(IdReference.create(ip.id())).subnet(IdReference.create(subnetId))
-                                              .build()).build()));
+      IpConfiguration config = IpConfiguration.builder().name(ipName).properties(ipProperties.build()).build();
 
-      String securityGroup = getOnlyElement(options.getGroups(), null);
+      NetworkInterfaceCardProperties.Builder nicProperties = NetworkInterfaceCardProperties.builder().ipConfigurations(
+            ImmutableList.of(config));
+
       if (securityGroup != null) {
-         networkInterfaceCardProperties.networkSecurityGroup(IdReference.create(securityGroup));
+         nicProperties.networkSecurityGroup(IdReference.create(securityGroup));
       }
 
-      String networkInterfaceCardName = "jc-nic-" + name;
-      return api.getNetworkInterfaceCardApi(azureGroup).createOrUpdate(networkInterfaceCardName, locationName,
-              networkInterfaceCardProperties.build(), ImmutableMap.of("jclouds", name));
+      logger.debug(">> creating nic %s(%s) with security groups (%s)", nicName, config,
+            securityGroup != null ? securityGroup : "");
+
+      final NetworkInterfaceCardApi nicApi = api.getNetworkInterfaceCardApi(resourceGroup);
+      NetworkInterfaceCard nic = nicApi.createOrUpdate(nicName, location, nicProperties.build(),
+            ImmutableMap.of("jclouds", nodeName));
+
+      resourceAvailable.apply(new Supplier<Provisionable>() {
+         @Override
+         public Provisionable get() {
+            NetworkInterfaceCard updated = nicApi.get(nicName);
+            return updated == null ? null : updated.properties();
+         }
+      });
+
+      return nic;
+   }
+   
+   private void configurePublicIP(IpOptions ipConfig, IpConfigurationProperties.Builder ipProperties,
+         String resourceGroup, String location, String nodeName) {
+      if (ipConfig.publicIpId() != null) {
+         logger.debug(">> configuring public ip: %s",  extractName(ipConfig.publicIpId()));
+         PublicIPAddress publicIp = api.getPublicIPAddressApi(extractResourceGroup(ipConfig.publicIpId())).get(
+               extractName(ipConfig.publicIpId()));
+         ipProperties.publicIPAddress(IdReference.create(publicIp.id()));
+      } else if (ipConfig.allocateNewPublicIp()) {
+         PublicIPAddress publicIp = createPublicIp(resourceGroup, location, nodeName);
+         ipProperties.publicIPAddress(IdReference.create(publicIp.id()));
+      }
+   }
+   
+   /**
+    * Create the network profile and configure the first NIC as primary.
+    */
+   private NetworkProfile createNetworkProfile(List<NetworkInterfaceCard> nics) {
+      List<NetworkInterface> nicAttachments = new ArrayList<NetworkInterface>(nics.size());
+      for (int i = 0; i < nics.size(); i++) {
+         nicAttachments.add(NetworkInterface.create(nics.get(i).id(), NetworkInterfaceProperties.create(i == 0)));
+      }
+      return NetworkProfile.create(nicAttachments);
+   }
+   
+   private static List<IpOptions> publicIpsFirst(List<IpOptions> ipOptions) {
+      List<IpOptions> sorted = new ArrayList<IpOptions>(ipOptions);
+      Collections.sort(sorted, new Comparator<IpOptions>() {
+         @Override
+         public int compare(IpOptions o1, IpOptions o2) {
+            return o1.allocateNewPublicIp() == o2.allocateNewPublicIp() ? 0 : o1.allocateNewPublicIp() ? -1 : 1;
+         }
+      });
+      return sorted;
+   }
+   
+   private PublicIPAddress createPublicIp(String resourceGroup, String location, String nodeName) {
+      String name = namingConvention.uniqueNameForGroup(nodeName);
+      
+      PublicIPAddressProperties properties = PublicIPAddressProperties.builder()
+            .publicIPAllocationMethod("Static")
+            .idleTimeoutInMinutes(4)
+            .build();
+      
+      logger.debug(">> allocating new public ip address: %s", name);
+
+      PublicIPAddress ip = api.getPublicIPAddressApi(resourceGroup).createOrUpdate(name, location,
+            ImmutableMap.of("jclouds", nodeName, AUTOGENERATED_IP_KEY, "true"), properties);
+
+      checkState(publicIpAvailable.create(resourceGroup).apply(name),
+              "Public IP was not provisioned in the configured timeout");
+      
+      return ip;
    }
 
    private StorageProfile createStorageProfile(Image image, List<DataDisk> dataDisks) {
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 59608a4..50cb75a 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
@@ -23,6 +23,8 @@
 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.azurecompute.arm.domain.IdReference.extractName;
+import static org.jclouds.azurecompute.arm.domain.IdReference.extractResourceGroup;
 
 import java.net.URI;
 import java.util.ArrayList;
@@ -36,8 +38,8 @@
 import org.jclouds.azurecompute.arm.AzureComputeApi;
 import org.jclouds.azurecompute.arm.compute.config.AzureComputeServiceContextModule.SecurityGroupAvailablePredicateFactory;
 import org.jclouds.azurecompute.arm.compute.domain.ResourceGroupAndName;
-import org.jclouds.azurecompute.arm.domain.IdReference;
 import org.jclouds.azurecompute.arm.domain.NetworkInterfaceCard;
+import org.jclouds.azurecompute.arm.domain.NetworkProfile.NetworkInterface;
 import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroup;
 import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroupProperties;
 import org.jclouds.azurecompute.arm.domain.NetworkSecurityRule;
@@ -135,12 +137,12 @@
       if (vm == null) {
          throw new IllegalArgumentException("Node " + nodeId + " was not found");
       }
-      List<IdReference> networkInterfacesIdReferences = vm.properties().networkProfile().networkInterfaces();
+      List<NetworkInterface> networkInterfaces = vm.properties().networkProfile().networkInterfaces();
       List<NetworkSecurityGroup> networkGroups = new ArrayList<NetworkSecurityGroup>();
 
-      for (IdReference networkInterfaceCardIdReference : networkInterfacesIdReferences) {
-         String nicName = networkInterfaceCardIdReference.name();
-         String nicResourceGroup = networkInterfaceCardIdReference.resourceGroup();
+      for (NetworkInterface networkInterfaceCardIdReference : networkInterfaces) {
+         String nicName = extractName(networkInterfaceCardIdReference.id());
+         String nicResourceGroup = extractResourceGroup(networkInterfaceCardIdReference.id());
          NetworkInterfaceCard card = api.getNetworkInterfaceCardApi(nicResourceGroup).get(nicName);
          if (card != null && card.properties().networkSecurityGroup() != null) {
             String secGroupName = card.properties().networkSecurityGroup().name();
@@ -171,9 +173,14 @@
       SecurityGroupBuilder builder = new SecurityGroupBuilder();
       builder.name(name);
       builder.location(location);
+      
+      NetworkSecurityGroup sg = api.getNetworkSecurityGroupApi(resourceGroup.name()).createOrUpdate(name,
+            location.getId(), null, NetworkSecurityGroupProperties.builder().build());
+      
+      checkState(securityGroupAvailable.create(resourceGroup.name()).apply(name),
+            "Security group was not created in the configured timeout");
 
-      return securityGroupConverter.apply(api.getNetworkSecurityGroupApi(resourceGroup.name()).createOrUpdate(name,
-            location.getId(), null, NetworkSecurityGroupProperties.builder().build()));
+      return securityGroupConverter.apply(sg);
    }
 
    @Override
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 e4b7da8..af43cbb 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
@@ -22,6 +22,7 @@
 import static org.jclouds.azurecompute.arm.compute.AzureComputeServiceAdapter.GROUP_KEY;
 import static org.jclouds.azurecompute.arm.compute.domain.LocationAndName.fromLocationAndName;
 import static org.jclouds.azurecompute.arm.compute.domain.ResourceGroupAndName.fromResourceGroupAndName;
+import static org.jclouds.azurecompute.arm.domain.IdReference.extractName;
 import static org.jclouds.azurecompute.arm.domain.IdReference.extractResourceGroup;
 import static org.jclouds.compute.util.ComputeServiceUtils.addMetadataAndParseTagsFromCommaDelimitedValue;
 import static org.jclouds.location.predicates.LocationPredicates.idEquals;
@@ -40,6 +41,7 @@
 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.NetworkProfile.NetworkInterface;
 import org.jclouds.azurecompute.arm.domain.PublicIPAddress;
 import org.jclouds.azurecompute.arm.domain.StorageProfile;
 import org.jclouds.azurecompute.arm.domain.VirtualMachine;
@@ -136,9 +138,9 @@
       return builder.build();
    }
 
-   private Iterable<String> getPrivateIpAddresses(List<IdReference> idReferences) {
+   private Iterable<String> getPrivateIpAddresses(List<NetworkInterface> networkInterfaces) {
       List<String> privateIpAddresses = Lists.newArrayList();
-      for (IdReference networkInterfaceCardIdReference : idReferences) {
+      for (NetworkInterface networkInterfaceCardIdReference : networkInterfaces) {
          NetworkInterfaceCard networkInterfaceCard = getNetworkInterfaceCard(networkInterfaceCardIdReference);
          if (networkInterfaceCard != null && networkInterfaceCard.properties() != null
                && networkInterfaceCard.properties().ipConfigurations() != null) {
@@ -152,21 +154,21 @@
       return privateIpAddresses;
    }
 
-   private NetworkInterfaceCard getNetworkInterfaceCard(IdReference nic) {
-      return api.getNetworkInterfaceCardApi(nic.resourceGroup()).get(nic.name());
+   private NetworkInterfaceCard getNetworkInterfaceCard(NetworkInterface nic) {
+      return api.getNetworkInterfaceCardApi(extractResourceGroup(nic.id())).get(extractName(nic.id()));
    }
 
-   private Iterable<String> getPublicIpAddresses(List<IdReference> idReferences) {
+   private Iterable<String> getPublicIpAddresses(List<NetworkInterface> networkInterfaces) {
       List<String> publicIpAddresses = Lists.newArrayList();
-      for (IdReference networkInterfaceCardIdReference : idReferences) {
+      for (NetworkInterface networkInterfaceCardIdReference : networkInterfaces) {
          NetworkInterfaceCard networkInterfaceCard = getNetworkInterfaceCard(networkInterfaceCardIdReference);
          if (networkInterfaceCard != null && networkInterfaceCard.properties() != null
                && networkInterfaceCard.properties().ipConfigurations() != null) {
-            String resourceGroup = networkInterfaceCardIdReference.resourceGroup();
             for (IpConfiguration ipConfiguration : networkInterfaceCard.properties().ipConfigurations()) {
                if (ipConfiguration.properties().publicIPAddress() != null) {
                   IdReference publicIpId = ipConfiguration.properties().publicIPAddress();
-                  PublicIPAddress publicIp = api.getPublicIPAddressApi(resourceGroup).get(publicIpId.name());
+                  PublicIPAddress publicIp = api.getPublicIPAddressApi(publicIpId.resourceGroup()).get(
+                        publicIpId.name());
                   if (publicIp != null && publicIp.properties().ipAddress() != null) {
                      publicIpAddresses.add(publicIp.properties().ipAddress());
                   }
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 bb5dc09..98732d2 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
@@ -16,6 +16,7 @@
  */
 package org.jclouds.azurecompute.arm.compute.loaders;
 
+import static com.google.common.base.Preconditions.checkState;
 import static org.jclouds.compute.util.ComputeServiceUtils.getPortRangesFromList;
 
 import java.util.ArrayList;
@@ -28,6 +29,7 @@
 import javax.inject.Singleton;
 
 import org.jclouds.azurecompute.arm.AzureComputeApi;
+import org.jclouds.azurecompute.arm.compute.config.AzureComputeServiceContextModule.SecurityGroupAvailablePredicateFactory;
 import org.jclouds.azurecompute.arm.compute.domain.ResourceGroupAndNameAndIngressRules;
 import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroup;
 import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroupProperties;
@@ -48,10 +50,12 @@
    protected Logger logger = Logger.NULL;
 
    private final AzureComputeApi api;
+   private final SecurityGroupAvailablePredicateFactory securityGroupAvailable;
 
    @Inject
-   CreateSecurityGroupIfNeeded(AzureComputeApi api) {
+   CreateSecurityGroupIfNeeded(AzureComputeApi api, SecurityGroupAvailablePredicateFactory securityRuleAvailable) {
       this.api = api;
+      this.securityGroupAvailable = securityRuleAvailable;
    }
 
    @Override
@@ -86,6 +90,9 @@
 
       NetworkSecurityGroup securityGroup = api.getNetworkSecurityGroupApi(resourceGroup).createOrUpdate(name, location,
             null, NetworkSecurityGroupProperties.builder().securityRules(rules).build());
+      
+      checkState(securityGroupAvailable.create(resourceGroup).apply(name),
+            "Security group was not created in the configured timeout");
 
       return securityGroup.id();
    }
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 d211421..c3140dd 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
@@ -32,28 +32,11 @@
  */
 public class AzureTemplateOptions extends TemplateOptions implements Cloneable {
 
-   private String virtualNetworkName;
-   private String subnetId;
    private AvailabilitySet availabilitySet;
    private String availabilitySetName;
    private List<DataDisk> dataDisks = ImmutableList.of();
    private String resourceGroup;
-
-   /**
-    * Sets the virtual network name
-    */
-   public  AzureTemplateOptions virtualNetworkName(String virtualNetworkName) {
-      this.virtualNetworkName = virtualNetworkName;
-      return this;
-   }
-
-   /**
-    * Sets the subnet name
-    */
-   public  AzureTemplateOptions subnetId(String subnetId) {
-      this.subnetId = subnetId;
-      return this;
-   }
+   private List<IpOptions> ipOptions = ImmutableList.of();
    
    /**
     * Sets the availability set where the nodes will be configured. If it does
@@ -92,12 +75,35 @@
       return dataDisks(ImmutableList.copyOf(checkNotNull(dataDisks, "dataDisks")));
    }
    
-   public String getVirtualNetworkName() { return virtualNetworkName; }
-   public String getSubnetId() { return subnetId; }
+   /**
+    * Configure the NICs that will be attached to the created nodes.
+    * <p>
+    * Note that the number of NICs that can be attached depends on the size of
+    * the virtual machine, and that the guest operating system needs to be
+    * prepared to set up all the configured interfaces.
+    * <p>
+    * Depending on the image being used, a cloud-init or bootstrap script might
+    * be needed to make the interface setup.
+    */
+   public AzureTemplateOptions ipOptions(Iterable<IpOptions> ipOptions) {
+      for (IpOptions ipOption : checkNotNull(ipOptions, "ipOptions"))
+         checkNotNull(ipOption, "all ipOptions must be non-empty");
+      this.ipOptions = ImmutableList.copyOf(ipOptions);
+      return this;
+   }
+
+   /**
+    * @see {@link AzureTemplateOptions#ipOptions(Iterable)
+    */
+   public AzureTemplateOptions ipOptions(IpOptions... ipOptions) {
+      return ipOptions(ImmutableList.copyOf(checkNotNull(ipOptions, "ipOptions")));
+   }
+   
    public AvailabilitySet getAvailabilitySet() { return availabilitySet; }
    public String getAvailabilitySetName() { return availabilitySetName; }
    public List<DataDisk> getDataDisks() { return dataDisks; }
    public String getResourceGroup() { return resourceGroup; }
+   public List<IpOptions> getIpOptions() { return ipOptions; }
 
    @Override
    public AzureTemplateOptions clone() {
@@ -111,12 +117,11 @@
       super.copyTo(to);
       if (to instanceof AzureTemplateOptions) {
          AzureTemplateOptions eTo = AzureTemplateOptions.class.cast(to);
-         eTo.virtualNetworkName(virtualNetworkName);
-         eTo.subnetId(subnetId);
          eTo.availabilitySet(availabilitySet);
          eTo.availabilitySet(availabilitySetName);
          eTo.dataDisks(dataDisks);
          eTo.resourceGroup(resourceGroup);
+         eTo.ipOptions(ipOptions);
       }
    }
 
@@ -128,27 +133,22 @@
 
       AzureTemplateOptions that = (AzureTemplateOptions) o;
       
-      return Objects.equal(virtualNetworkName, that.virtualNetworkName) &&
-            Objects.equal(subnetId, that.subnetId) &&
+      return Objects.equal(availabilitySetName, that.availabilitySetName) &&
+            Objects.equal(resourceGroup, that.resourceGroup) &&
             Objects.equal(availabilitySet, that.availabilitySet) &&
-            Objects.equal(availabilitySetName, that.availabilitySetName) &&
             Objects.equal(dataDisks, that.dataDisks) &&
-            Objects.equal(resourceGroup, that.resourceGroup);
+            Objects.equal(ipOptions, that.ipOptions);
    }
 
    @Override
    public int hashCode() {
-      return Objects.hashCode(virtualNetworkName, subnetId, availabilitySet, availabilitySetName, dataDisks,
-            resourceGroup);
+      return Objects.hashCode(availabilitySet, availabilitySetName, dataDisks,
+            resourceGroup, ipOptions);
    }
 
    @Override
    public Objects.ToStringHelper string() {
       Objects.ToStringHelper toString = super.string();
-      if (virtualNetworkName != null)
-         toString.add("virtualNetworkName", virtualNetworkName);
-      if (subnetId != null)
-         toString.add("subnetId", subnetId);
       if (availabilitySet != null)
          toString.add("availabilitySet", availabilitySet);
       if (availabilitySetName != null)
@@ -157,26 +157,12 @@
          toString.add("dataDisks", dataDisks);
       if (resourceGroup != null)
          toString.add("resourceGroup", resourceGroup);
+      if (!ipOptions.isEmpty())
+         toString.add("ipOptions", ipOptions);
       return toString;
    }
 
    public static class Builder {
-
-      /**
-       * @see AzureTemplateOptions#virtualNetworkName(String)
-       */
-      public static AzureTemplateOptions virtualNetworkName(String virtualNetworkName) {
-         AzureTemplateOptions options = new AzureTemplateOptions();
-         return options.virtualNetworkName(virtualNetworkName);
-      }
-
-      /**
-       * @see AzureTemplateOptions#subnetId(String)
-       */
-      public static AzureTemplateOptions subnetId(String subnetId) {
-         AzureTemplateOptions options = new AzureTemplateOptions();
-         return options.subnetId(subnetId);
-      }
       
       /**
        * @see AzureTemplateOptions#availabilitySet(AvailabilitySet)
@@ -217,5 +203,21 @@
          AzureTemplateOptions options = new AzureTemplateOptions();
          return options.resourceGroup(resourceGroup);
       }
+      
+      /**
+       * @see AzureTemplateOptions#ipOptions(IpOptions...)
+       */
+      public static AzureTemplateOptions ipOptions(IpOptions... ipOptions) {
+         AzureTemplateOptions options = new AzureTemplateOptions();
+         return options.ipOptions(ipOptions);
+      }
+
+      /**
+       * @see AzureTemplateOptions#ipOptions(Iterable)
+       */
+      public static AzureTemplateOptions ipOptions(Iterable<IpOptions> ipOptions) {
+         AzureTemplateOptions options = new AzureTemplateOptions();
+         return options.ipOptions(ipOptions);
+      }
    }
 }
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/options/IpOptions.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/options/IpOptions.java
new file mode 100644
index 0000000..73c4c6c
--- /dev/null
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/options/IpOptions.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.options;
+
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Optional;
+
+/**
+ * Configures the ip addresses to be configured for the created nodes.
+ */
+@AutoValue
+public abstract class IpOptions {
+
+   /**
+    * The subnet where the NIC will be attached.
+    */
+   public abstract String subnet();
+
+   /**
+    * The IP address to be configured, in case of static allocation, or absent
+    * for dynamic assignment.
+    */
+   public abstract Optional<String> address();
+
+   /**
+    * Flag to indicate if a public ip address should be allocated and bound to
+    * this NIC.
+    */
+   public abstract boolean allocateNewPublicIp();
+   
+   /**
+    * ID of the public IP to associate with the NIC.
+    */
+   @Nullable
+   public abstract String publicIpId();
+   
+   IpOptions() {
+      
+   }
+
+   public abstract Builder toBuilder();
+
+   public static Builder builder() {
+      return new AutoValue_IpOptions.Builder().address((String) null).allocateNewPublicIp(false);
+   }
+
+   @AutoValue.Builder
+   public abstract static class Builder {
+      public abstract Builder subnet(String subnet);
+      public abstract Builder allocateNewPublicIp(boolean allocatePublicIp);
+      public abstract Builder publicIpId(String publicIpId);
+      
+      abstract Builder address(Optional<String> address);
+      public Builder address(String address) {
+         return address(Optional.fromNullable(address));
+      }
+      
+      public abstract IpOptions build();
+   }
+}
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
index 1f1a37d..3ca1a5d 100644
--- 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
@@ -21,7 +21,10 @@
 import static com.google.common.collect.Iterables.filter;
 import static com.google.common.collect.Iterables.transform;
 import static com.google.common.collect.Maps.filterValues;
+import static org.jclouds.azurecompute.arm.compute.AzureComputeServiceAdapter.AUTOGENERATED_IP_KEY;
 import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.TIMEOUT_RESOURCE_DELETED;
+import static org.jclouds.azurecompute.arm.domain.IdReference.extractName;
+import static org.jclouds.azurecompute.arm.domain.IdReference.extractResourceGroup;
 
 import java.net.URI;
 import java.util.HashMap;
@@ -42,8 +45,10 @@
 import org.jclouds.azurecompute.arm.domain.IpConfiguration;
 import org.jclouds.azurecompute.arm.domain.ManagedDiskParameters;
 import org.jclouds.azurecompute.arm.domain.NetworkInterfaceCard;
+import org.jclouds.azurecompute.arm.domain.NetworkProfile.NetworkInterface;
 import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroup;
 import org.jclouds.azurecompute.arm.domain.OSDisk;
+import org.jclouds.azurecompute.arm.domain.PublicIPAddress;
 import org.jclouds.azurecompute.arm.domain.VirtualMachine;
 import org.jclouds.azurecompute.arm.features.NetworkSecurityGroupApi;
 import org.jclouds.compute.functions.GroupNamingConvention;
@@ -98,11 +103,11 @@
 
    public boolean cleanupVirtualMachineNICs(VirtualMachine virtualMachine) {
       boolean deleted = true;
-      for (IdReference nicRef : virtualMachine.properties().networkProfile().networkInterfaces()) {
-         String nicResourceGroup = nicRef.resourceGroup();
-         String nicName = nicRef.name();
-         NetworkInterfaceCard nic = api.getNetworkInterfaceCardApi(nicRef.resourceGroup()).get(nicName);
-         
+      for (NetworkInterface nicRef : virtualMachine.properties().networkProfile().networkInterfaces()) {
+         String nicResourceGroup = extractResourceGroup(nicRef.id());
+         String nicName = extractName(nicRef.id());
+         NetworkInterfaceCard nic = api.getNetworkInterfaceCardApi(nicResourceGroup).get(nicName);
+
          Iterable<IdReference> publicIps = getPublicIps(nic);
 
          logger.debug(">> destroying nic %s...", nicName);
@@ -112,9 +117,12 @@
          for (IdReference publicIp : publicIps) {
             String publicIpResourceGroup = publicIp.resourceGroup();
             String publicIpName = publicIp.name();
-            
-            logger.debug(">> deleting public ip nic %s...", publicIpName);
-            deleted &= api.getPublicIPAddressApi(publicIpResourceGroup).delete(publicIpName);
+
+            PublicIPAddress ip = api.getPublicIPAddressApi(publicIpResourceGroup).get(publicIpName);
+            if (ip.tags() != null && Boolean.parseBoolean(ip.tags().get(AUTOGENERATED_IP_KEY))) {
+               logger.debug(">> deleting public ip %s...", publicIpName);
+               deleted &= api.getPublicIPAddressApi(publicIpResourceGroup).delete(publicIpName);
+            }
          }
       }
       return deleted;
@@ -129,15 +137,15 @@
       for (DataDisk dataDisk : virtualMachine.properties().storageProfile().dataDisks()) {
          deleteManagedDisk(dataDisk.managedDiskParameters(), deleteJobs);
       }
-      
+
       Set<String> nonDeletedDisks = filterValues(deleteJobs, not(resourceDeleted)).keySet();
       if (!nonDeletedDisks.isEmpty()) {
          logger.warn(">> could not delete disks: %s", Joiner.on(',').join(nonDeletedDisks));
       }
-      
+
       return nonDeletedDisks.isEmpty();
    }
-   
+
    private void deleteManagedDisk(@Nullable ManagedDiskParameters managedDisk, Map<String, URI> deleteJobs) {
       if (managedDisk != null) {
          IdReference diskRef = IdReference.create(managedDisk.id());
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/strategy/CreateResourcesThenCreateNodes.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/strategy/CreateResourcesThenCreateNodes.java
index c3bdbdd..2ddb340 100644
--- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/strategy/CreateResourcesThenCreateNodes.java
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/strategy/CreateResourcesThenCreateNodes.java
@@ -17,10 +17,13 @@
 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.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.domain.IdReference.extractName;
+import static org.jclouds.azurecompute.arm.domain.IdReference.extractResourceGroup;
+import static org.jclouds.azurecompute.arm.domain.Subnet.extractVirtualNetwork;
 
 import java.util.Arrays;
 import java.util.Map;
@@ -37,13 +40,15 @@
 import org.jclouds.azurecompute.arm.compute.domain.ResourceGroupAndNameAndIngressRules;
 import org.jclouds.azurecompute.arm.compute.functions.TemplateToAvailabilitySet;
 import org.jclouds.azurecompute.arm.compute.options.AzureTemplateOptions;
+import org.jclouds.azurecompute.arm.compute.options.IpOptions;
 import org.jclouds.azurecompute.arm.domain.AvailabilitySet;
 import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroup;
+import org.jclouds.azurecompute.arm.domain.PublicIPAddress;
 import org.jclouds.azurecompute.arm.domain.ResourceGroup;
 import org.jclouds.azurecompute.arm.domain.Subnet;
-import org.jclouds.azurecompute.arm.domain.VirtualNetwork;
-import org.jclouds.azurecompute.arm.features.SubnetApi;
-import org.jclouds.azurecompute.arm.features.VirtualNetworkApi;
+import org.jclouds.azurecompute.arm.domain.Subnet.SubnetProperties;
+import org.jclouds.azurecompute.arm.domain.VirtualNetwork.AddressSpace;
+import org.jclouds.azurecompute.arm.domain.VirtualNetwork.VirtualNetworkProperties;
 import org.jclouds.compute.config.CustomizationResponse;
 import org.jclouds.compute.domain.NodeMetadata;
 import org.jclouds.compute.domain.Template;
@@ -56,8 +61,9 @@
 import org.jclouds.domain.Location;
 import org.jclouds.logging.Logger;
 
-import com.google.common.base.Optional;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Multimap;
 import com.google.common.util.concurrent.ListenableFuture;
@@ -89,8 +95,7 @@
          TemplateToAvailabilitySet templateToAvailabilitySet) {
       super(addNodeWithGroupStrategy, listNodesStrategy, namingConvention, userExecutor,
             customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory);
-      this.api = checkNotNull(api, "api cannot be null");
-      checkNotNull(userExecutor, "userExecutor cannot be null");
+      this.api = api;
       this.securityGroupMap = securityGroupMap;
       this.defaultVnetAddressPrefix = defaultVnetAddressPrefix;
       this.defaultSubnetAddressPrefix = defaultSubnetAddressPrefix;
@@ -103,7 +108,7 @@
          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
@@ -112,42 +117,38 @@
                + "Authentication will delegate to the ssh-agent");
       }
 
-      // This sill create the resource group if it does not exist
       String location = template.getLocation().getId();
 
       createResourceGroupIfNeeded(group, location, options);
-      getOrCreateVirtualNetworkWithSubnet(location, options);
+      
+      normalizeNetworkOptions(options);
+      createDefaultNetworkIfNeeded(group, location, options);
+      
       configureSecurityGroupForOptions(group, template.getLocation(), options);
       configureAvailabilitySetForTemplate(template);
 
       return super.execute(group, count, template, goodNodes, badNodes, customizationResponses);
    }
 
-   protected synchronized void getOrCreateVirtualNetworkWithSubnet(final String location, AzureTemplateOptions options) {
-      String virtualNetworkName = Optional.fromNullable(options.getVirtualNetworkName()).or(
-            options.getResourceGroup() + "virtualnetwork");
-      String subnetName = options.getResourceGroup() + "subnet";
-
-      // Subnets belong to a virtual network so that needs to be created first
-      VirtualNetworkApi vnApi = api.getVirtualNetworkApi(options.getResourceGroup());
-      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(defaultVnetAddressPrefix)))
+   protected synchronized void createDefaultNetworkIfNeeded(String group, String location, AzureTemplateOptions options) {
+      if (options.getIpOptions().isEmpty()) {
+         String name = namingConvention.create().sharedNameForGroup(group);
+         
+         Subnet subnet = Subnet.builder().name(name)
+               .properties(SubnetProperties.builder().addressPrefix(defaultSubnetAddressPrefix).build()).build();
+         
+         VirtualNetworkProperties properties = VirtualNetworkProperties.builder()
+               .addressSpace(AddressSpace.create(Arrays.asList(defaultVnetAddressPrefix)))
                .subnets(Arrays.asList(subnet)).build();
-
-         vn = vnApi.createOrUpdate(virtualNetworkName, location, virtualNetworkProperties);
+         
+         logger.debug(">> network options have not been configured. Creating network %s(%s) and subnet %s(%s)", name,
+               defaultVnetAddressPrefix, name, defaultSubnetAddressPrefix);
+         
+         api.getVirtualNetworkApi(options.getResourceGroup()).createOrUpdate(name, location, properties);
+         Subnet createdSubnet = api.getSubnetApi(options.getResourceGroup(), name).get(name);
+         
+         options.ipOptions(IpOptions.builder().subnet(createdSubnet.id()).allocateNewPublicIp(true).build());
       }
-
-      SubnetApi subnetApi = api.getSubnetApi(options.getResourceGroup(), virtualNetworkName);
-      Subnet subnet = subnetApi.get(subnetName);
-
-      options.virtualNetworkName(virtualNetworkName);
-      options.subnetId(subnet.id());
    }
 
    private static boolean hasRunScriptWithKeyAuthAndNoPrivateKey(Template template) {
@@ -196,4 +197,45 @@
                ImmutableMap.of("description", "jclouds default resource group"));
       }
    }
+   
+   @VisibleForTesting
+   void normalizeNetworkOptions(AzureTemplateOptions options) {
+      if (!options.getNetworks().isEmpty() && !options.getIpOptions().isEmpty()) {
+         throw new IllegalArgumentException("The options.networks and options.ipOptions are exclusive");
+      }
+      
+      if (!options.getNetworks().isEmpty() && options.getIpOptions().isEmpty()) {
+         // The portable interface allows to configure network IDs (subnet IDs),
+         // but we don't know the type of the IP configurations to be applied
+         // when attaching nodes to those networks. We'll assume private IPs
+         // with Dynamic allocation and no public ip address associated.
+         ImmutableList.Builder<IpOptions> ipOptions = ImmutableList.builder();
+         for (String subnetId : options.getNetworks()) {
+            ipOptions.add(IpOptions.builder().subnet(subnetId).build());
+         }
+         options.ipOptions(ipOptions.build());
+      }
+      
+      if (!options.getIpOptions().isEmpty()) {
+         // Eagerly validate that all configured subnets exist.
+         for (IpOptions ipConfig : options.getIpOptions()) {
+            if (ipConfig.allocateNewPublicIp() && ipConfig.publicIpId() != null) {
+               throw new IllegalArgumentException("The allocateNewPublicIps and publicIpId are exclusive");
+            }
+            
+            String resourceGroup = extractResourceGroup(ipConfig.subnet());
+            String networkName = extractVirtualNetwork(ipConfig.subnet());
+            String subnetName = extractName(ipConfig.subnet());
+            
+            Subnet subnet = api.getSubnetApi(resourceGroup, networkName).get(subnetName);
+            checkState(subnet != null, "Configured subnet %s does not exist", ipConfig.subnet());
+            
+            if (ipConfig.publicIpId() != null) {
+               PublicIPAddress publicIp = api.getPublicIPAddressApi(extractResourceGroup(ipConfig.publicIpId())).get(
+                     extractName(ipConfig.publicIpId()));
+               checkState(publicIp != null, "Configured public ip %s does not exist", ipConfig.publicIpId());               
+            }
+         }
+      }
+   }
 }
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/NetworkInterfaceCardProperties.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/NetworkInterfaceCardProperties.java
index d1976f1..84c8ca2 100644
--- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/NetworkInterfaceCardProperties.java
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/NetworkInterfaceCardProperties.java
@@ -26,23 +26,16 @@
 @AutoValue
 public abstract class NetworkInterfaceCardProperties implements Provisionable {
 
-   @Nullable
-   public abstract String provisioningState();
-
-   @Nullable
-   public abstract String resourceGuid();
-
-   @Nullable
-   public abstract Boolean enableIPForwarding();
-
-   @Nullable
-   public abstract List<IpConfiguration> ipConfigurations();
-
-   @Nullable
-   public abstract IdReference networkSecurityGroup();
+   @Nullable public abstract String provisioningState();
+   @Nullable public abstract String resourceGuid();
+   @Nullable public abstract Boolean enableIPForwarding();
+   @Nullable public abstract List<IpConfiguration> ipConfigurations();
+   @Nullable public abstract IdReference networkSecurityGroup();
 
    @SerializedNames({"provisioningState", "resourceGuid", "enableIPForwarding", "ipConfigurations", "networkSecurityGroup"})
-   public static NetworkInterfaceCardProperties create(final String provisioningState, final String resourceGuid, final Boolean enableIPForwarding, final List<IpConfiguration> ipConfigurations, final IdReference networkSecurityGroup) {
+   public static NetworkInterfaceCardProperties create(final String provisioningState, final String resourceGuid,
+         final Boolean enableIPForwarding, final List<IpConfiguration> ipConfigurations,
+         final IdReference networkSecurityGroup) {
       NetworkInterfaceCardProperties.Builder builder = NetworkInterfaceCardProperties.builder()
               .provisioningState(provisioningState)
               .resourceGuid(resourceGuid)
@@ -52,28 +45,26 @@
 
       return builder.build();
    }
-   
+
+   NetworkInterfaceCardProperties() {
+
+   }
+
    public abstract Builder toBuilder();
 
    public static Builder builder() {
-
       return new AutoValue_NetworkInterfaceCardProperties.Builder();
    }
 
    @AutoValue.Builder
    public abstract static class Builder {
       public abstract Builder provisioningState(String provisioningState);
-
       public abstract Builder resourceGuid(String resourceGuid);
-
       public abstract Builder enableIPForwarding(Boolean enableIPForwarding);
-
       public abstract Builder ipConfigurations(List<IpConfiguration> ipConfigurations);
-
-      abstract List<IpConfiguration> ipConfigurations();
-
       public abstract Builder networkSecurityGroup(IdReference networkSecurityGroup);
 
+      abstract List<IpConfiguration> ipConfigurations();
       abstract NetworkInterfaceCardProperties autoBuild();
 
       public NetworkInterfaceCardProperties build() {
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/NetworkProfile.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/NetworkProfile.java
index b26305f..cdb6d51 100644
--- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/NetworkProfile.java
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/NetworkProfile.java
@@ -19,6 +19,7 @@
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableList;
 
+import org.jclouds.javax.annotation.Nullable;
 import org.jclouds.json.SerializedNames;
 
 import java.util.List;
@@ -26,16 +27,49 @@
 @AutoValue
 public abstract class NetworkProfile {
 
+   @AutoValue
+   public abstract static class NetworkInterface {
+      public abstract String id();
+      @Nullable public abstract NetworkInterfaceProperties properties();
+      
+      @AutoValue
+      public abstract static class NetworkInterfaceProperties {
+         public abstract boolean primary();
+         
+         NetworkInterfaceProperties() {
+            
+         }
+         
+         @SerializedNames({"primary"})
+         public static NetworkInterfaceProperties create(boolean primary) {
+            return new AutoValue_NetworkProfile_NetworkInterface_NetworkInterfaceProperties(primary);
+         }
+      }
+      
+      NetworkInterface() {
+         
+      }
+      
+      @SerializedNames({"id", "properties"})
+      public static NetworkInterface create(String id, NetworkInterfaceProperties properties) {
+         return new AutoValue_NetworkProfile_NetworkInterface(id, properties);
+      }
+   }
+   
    /**
     * List of network interfaces
     */
-   public abstract List<IdReference> networkInterfaces();
+   public abstract List<NetworkInterface> networkInterfaces();
 
    @SerializedNames({"networkInterfaces"})
-   public static NetworkProfile create(final List<IdReference> networkInterfaces) {
+   public static NetworkProfile create(final List<NetworkInterface> networkInterfaces) {
       return builder().networkInterfaces(networkInterfaces).build();
    }
    
+   NetworkProfile() {
+      
+   }
+   
    public abstract Builder toBuilder();
 
    public static Builder builder() {
@@ -44,14 +78,14 @@
 
    @AutoValue.Builder
    public abstract static class Builder {
-      public abstract Builder networkInterfaces(List<IdReference> networkInterfaces);
+      public abstract Builder networkInterfaces(List<NetworkInterface> networkInterfaces);
 
-      abstract List<IdReference> networkInterfaces();
+      abstract List<NetworkInterface> networkInterfaces();
 
       abstract NetworkProfile autoBuild();
 
       public NetworkProfile build() {
-         networkInterfaces(networkInterfaces() != null ? ImmutableList.copyOf(networkInterfaces()) : ImmutableList.<IdReference>of());
+         networkInterfaces(networkInterfaces() != null ? ImmutableList.copyOf(networkInterfaces()) : ImmutableList.<NetworkInterface>of());
          return autoBuild();
       }
    }
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/PublicIPAddress.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/PublicIPAddress.java
index a08fadd..8d0cb2b 100644
--- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/PublicIPAddress.java
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/PublicIPAddress.java
@@ -17,36 +17,55 @@
 
 package org.jclouds.azurecompute.arm.domain;
 
-import com.google.auto.value.AutoValue;
-import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+
 import org.jclouds.javax.annotation.Nullable;
 import org.jclouds.json.SerializedNames;
 
-import java.util.Map;
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableMap;
 
 @AutoValue
 public abstract class PublicIPAddress {
 
    public abstract String name();
-
    public abstract String id();
-
    public abstract String etag();
-
    public abstract String location();
-
-   @Nullable
-   public abstract Map<String, String> tags();
-
+   @Nullable public abstract Map<String, String> tags();
    public abstract PublicIPAddressProperties properties();
 
-   @SerializedNames({"name", "id", "etag", "location", "tags", "properties"})
-   public static PublicIPAddress create(final String name,
-                                        final String id,
-                                        final String etag,
-                                        final String location,
-                                        final Map<String, String> tags,
-                                        final PublicIPAddressProperties properties) {
-      return new AutoValue_PublicIPAddress(name, id, etag, location, tags == null ? null : ImmutableMap.copyOf(tags), properties);
+   @SerializedNames({ "name", "id", "etag", "location", "tags", "properties" })
+   public static PublicIPAddress create(String name, String id, String etag, String location, Map<String, String> tags,
+         PublicIPAddressProperties properties) {
+      return builder().name(name).id(id).etag(etag).location(location).tags(tags).properties(properties).build();
+   }
+   
+   PublicIPAddress() {
+      
+   }
+   
+   public abstract Builder toBuilder();
+
+   public static Builder builder() {
+      return new AutoValue_PublicIPAddress.Builder();
+   }
+
+   @AutoValue.Builder
+   public abstract static class Builder {
+      public abstract Builder name(String name);
+      public abstract Builder id(String id);
+      public abstract Builder etag(String etag);
+      public abstract Builder location(String location);
+      public abstract Builder tags(Map<String, String> tags);
+      public abstract Builder properties(PublicIPAddressProperties properties);
+      
+      abstract Map<String, String> tags();
+      abstract PublicIPAddress autoBuild();
+
+      public PublicIPAddress build() {
+         tags(tags() != null ? ImmutableMap.copyOf(tags()) : null);
+         return autoBuild();
+      }
    }
 }
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/Subnet.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/Subnet.java
index 6830438..80460b8 100644
--- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/Subnet.java
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/Subnet.java
@@ -19,6 +19,8 @@
 import static com.google.common.collect.ImmutableList.copyOf;
 
 import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import com.google.common.collect.ImmutableList;
 
@@ -29,6 +31,8 @@
 @AutoValue
 public abstract class Subnet {
 
+   private static final Pattern NETWORK_PATTERN = Pattern.compile("^.*/virtualNetworks/([^/]+)(/.*)?$");
+   
    @AutoValue
    public abstract static class IpConfiguration {
 
@@ -38,19 +42,18 @@
       public static IpConfiguration create(final String id) {
          return new AutoValue_Subnet_IpConfiguration(id);
       }
+      
+      IpConfiguration() {
+         
+      }
    }
 
    @AutoValue
    public abstract static class SubnetProperties implements Provisionable {
 
-      @Nullable
-      public abstract String provisioningState();
-
-      @Nullable
-      public abstract String addressPrefix();
-
-      @Nullable
-      public abstract List<IpConfiguration> ipConfigurations();
+      @Nullable public abstract String provisioningState();
+      @Nullable public abstract String addressPrefix();
+      @Nullable public abstract List<IpConfiguration> ipConfigurations();
 
       @SerializedNames({"provisioningState", "addressPrefix", "ipConfigurations"})
       public static SubnetProperties create(final String provisioningState, final String addressPrefix, final List<IpConfiguration> ipConfigurations) {
@@ -61,6 +64,10 @@
                  .build();
       }
       
+      SubnetProperties() {
+         
+      }
+      
       public abstract Builder toBuilder();
 
       public static Builder builder() {
@@ -70,13 +77,10 @@
       @AutoValue.Builder
       public abstract static class Builder {
          public abstract Builder provisioningState(String provisioningState);
-
          public abstract Builder addressPrefix(String addressPrefix);
-
          public abstract Builder ipConfigurations(List<IpConfiguration> ipConfigurations);
 
          abstract List<IpConfiguration> ipConfigurations();
-
          abstract SubnetProperties autoBuild();
 
          public SubnetProperties build() {
@@ -86,23 +90,47 @@
       }
    }
 
-   @Nullable
-   public abstract String name();
-
-   @Nullable
-   public abstract String id();
-
-   @Nullable
-   public abstract String etag();
-
-   @Nullable
-   public abstract SubnetProperties properties();
+   @Nullable public abstract String name();
+   @Nullable public abstract String id();
+   @Nullable public abstract String etag();
+   @Nullable public abstract SubnetProperties properties();
+   
+   @Nullable public String virtualNetwork() {
+      return extractVirtualNetwork(id());
+   }
+   
+   public static String extractVirtualNetwork(String id) {
+      if (id == null)
+         return null;
+      Matcher m = NETWORK_PATTERN.matcher(id);
+      m.matches();
+      return m.group(1);
+   }
 
    @SerializedNames({"name", "id", "etag", "properties"})
    public static Subnet create(final String name,
                                final String id,
                                final String etag,
                                final SubnetProperties properties) {
-      return new AutoValue_Subnet(name, id, etag, properties);
+      return builder().name(name).id(id).etag(etag).properties(properties).build();
+   }
+   
+   Subnet() {
+      
+   }
+   
+   public abstract Builder toBuilder();
+
+   public static Builder builder() {
+      return new AutoValue_Subnet.Builder();
+   }
+
+   @AutoValue.Builder
+   public abstract static class Builder {
+      public abstract Builder name(String name);
+      public abstract Builder id(String id);
+      public abstract Builder etag(String etag);
+      public abstract Builder properties(SubnetProperties properties);
+      public abstract Subnet build();
    }
 }
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/features/PublicIPAddressApi.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/features/PublicIPAddressApi.java
index 2693753..75af4ff 100644
--- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/features/PublicIPAddressApi.java
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/features/PublicIPAddressApi.java
@@ -34,6 +34,7 @@
 import org.jclouds.azurecompute.arm.domain.PublicIPAddressProperties;
 import org.jclouds.azurecompute.arm.filters.ApiVersionFilter;
 import org.jclouds.azurecompute.arm.functions.FalseOn204;
+import org.jclouds.javax.annotation.Nullable;
 import org.jclouds.oauth.v2.filters.OAuthFilter;
 import org.jclouds.rest.annotations.Fallback;
 import org.jclouds.rest.annotations.MapBinder;
@@ -60,7 +61,7 @@
    @PUT
    PublicIPAddress createOrUpdate(@PathParam("publicipaddressname") String publicipaddressname,
                                                  @PayloadParam("location") String location,
-                                                 @PayloadParam("tags") Map<String, String> tags,
+                                                 @Nullable @PayloadParam("tags") Map<String, String> tags,
                                                  @PayloadParam("properties") PublicIPAddressProperties properties);
 
    @Named("publicipaddress:get")
diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/strategy/CreateResourcesThenCreateNodesTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/strategy/CreateResourcesThenCreateNodesTest.java
new file mode 100644
index 0000000..f95430c
--- /dev/null
+++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/strategy/CreateResourcesThenCreateNodesTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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 org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.testng.Assert.assertEquals;
+
+import org.jclouds.azurecompute.arm.AzureComputeApi;
+import org.jclouds.azurecompute.arm.compute.options.AzureTemplateOptions;
+import org.jclouds.azurecompute.arm.compute.options.IpOptions;
+import org.jclouds.azurecompute.arm.domain.PublicIPAddress;
+import org.jclouds.azurecompute.arm.domain.PublicIPAddressProperties;
+import org.jclouds.azurecompute.arm.domain.Subnet;
+import org.jclouds.azurecompute.arm.features.PublicIPAddressApi;
+import org.jclouds.azurecompute.arm.features.SubnetApi;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+@Test(groups = "unit", testName = "CreateResourcesThenCreateNodesTest")
+public class CreateResourcesThenCreateNodesTest {
+
+   @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "The options.networks and options.ipOptions are exclusive")
+   public void testNormalizeNetworkOptionsWithConflictingConfig() {
+      AzureTemplateOptions options = new AzureTemplateOptions();
+      options.ipOptions(IpOptions.builder().subnet(netResource("/virtualNetworks/vn/subnets/foo")).build());
+      options.networks(netResource("/virtualNetworks/vn/subnets/bar"));
+      strategy(null).normalizeNetworkOptions(options);
+   }
+   
+   @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "The allocateNewPublicIps and publicIpId are exclusive")
+   public void testNormalizeNetworkOptionsExclusivePublicIps() {
+      AzureTemplateOptions options = new AzureTemplateOptions();
+      options.ipOptions(IpOptions.builder().subnet(netResource("/virtualNetworks/vn/subnets/foo"))
+            .allocateNewPublicIp(true).publicIpId(netResource("/publicIPAddresses/pub")).build());
+      strategy(null).normalizeNetworkOptions(options);
+   }
+
+   public void testPortableNetworkOptions() {
+      AzureComputeApi api = createMock(AzureComputeApi.class);
+      SubnetApi subnetApi = createMock(SubnetApi.class);
+
+      expect(api.getSubnetApi(anyObject(String.class), anyObject(String.class))).andReturn(subnetApi).times(2);
+      expect(subnetApi.get(anyObject(String.class))).andReturn(Subnet.builder().build()).times(2);
+      replay(api, subnetApi);
+
+      AzureTemplateOptions options = new AzureTemplateOptions();
+      options.networks(netResource("/virtualNetworks/vn/subnets/foo"), netResource("/virtualNetworks/vn/subnets/bar"));
+      strategy(api).normalizeNetworkOptions(options);
+
+      assertEquals(options.getIpOptions(), ImmutableList.of(
+            IpOptions.builder().subnet(netResource("/virtualNetworks/vn/subnets/foo")).build(), IpOptions.builder()
+                  .subnet(netResource("/virtualNetworks/vn/subnets/bar")).build()));
+
+      // Verify that the code has validated that the subnets exist
+      verify(api, subnetApi);
+   }
+
+   public void testProviderSpecificNetworkOptions() {
+      AzureComputeApi api = createMock(AzureComputeApi.class);
+      SubnetApi subnetApi = createMock(SubnetApi.class);
+      PublicIPAddressApi publicIpApi = createMock(PublicIPAddressApi.class);
+
+      expect(api.getSubnetApi(anyObject(String.class), anyObject(String.class))).andReturn(subnetApi).times(2);
+      expect(api.getPublicIPAddressApi(anyObject(String.class))).andReturn(publicIpApi);
+      expect(subnetApi.get(anyObject(String.class))).andReturn(Subnet.builder().build()).times(2);
+      expect(publicIpApi.get(anyObject(String.class))).andReturn(mockAddress());
+      replay(api, subnetApi, publicIpApi);
+
+      IpOptions publicOpts = IpOptions.builder().subnet(netResource("/virtualNetworks/vn/subnets/foo"))
+            .publicIpId(netResource("/publicIPAddresses/pub")).address("10.0.0.2").build();
+      IpOptions privateOpts = IpOptions.builder().subnet(netResource("/virtualNetworks/vn/subnets/bar")).build();
+
+      AzureTemplateOptions options = new AzureTemplateOptions();
+      options.ipOptions(publicOpts, privateOpts);
+      strategy(api).normalizeNetworkOptions(options);
+
+      assertEquals(options.getIpOptions(), ImmutableList.of(publicOpts, privateOpts));
+
+      // Verify that the code has validated that the subnets exist
+      verify(api, subnetApi, publicIpApi);
+   }
+
+   private static CreateResourcesThenCreateNodes strategy(AzureComputeApi api) {
+      return new CreateResourcesThenCreateNodes(null, null, null, null, null, api, null, null, null, null);
+   }
+
+   private static String netResource(String resource) {
+      return "/subscriptions/subs/resourceGroups/rg/providers/Microsoft.Network" + resource;
+   }
+
+   private static PublicIPAddress mockAddress() {
+      return PublicIPAddress.builder().name("name").id("id").etag("etag").location("location")
+            .properties(PublicIPAddressProperties.builder().publicIPAllocationMethod("Dynamic").build()).build();
+   }
+}
diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/domain/IdReferenceTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/domain/IdReferenceTest.java
index e5426d7..e2a56e1 100644
--- a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/domain/IdReferenceTest.java
+++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/domain/IdReferenceTest.java
@@ -22,6 +22,7 @@
 
 import org.testng.annotations.Test;
 
+@Test(groups = "unit", testName = "IdReferenceTest")
 public class IdReferenceTest {
 
    @Test
diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/domain/SubnetTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/domain/SubnetTest.java
new file mode 100644
index 0000000..a5ef44c
--- /dev/null
+++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/domain/SubnetTest.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.azurecompute.arm.domain;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
+
+import org.testng.annotations.Test;
+
+@Test(groups = "unit", testName = "SubnetTest")
+public class SubnetTest {
+
+   @Test
+   public void testExtractVirtualNetwork() {
+
+      assertEquals(Subnet.builder().build().virtualNetwork(), null);
+      assertEquals(
+            Subnet.builder()
+                  .id("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/virtualNetworks/vn/subnets/subnet")
+                  .build().virtualNetwork(), "vn");
+      assertInvalidId("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/virtualNetworks");
+      assertInvalidId("virtualNetworks/vn");
+   }
+
+   private static void assertInvalidId(String id) {
+      try {
+         Subnet.builder().id(id).build().virtualNetwork();
+         fail("The given ID " + id + "should not match a valid virtual network");
+      } catch (IllegalStateException ex) {
+         // Expected
+      }
+   }
+}
diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/LoadBalancerApiLiveTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/LoadBalancerApiLiveTest.java
index c5b836b..5cf4239 100644
--- a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/LoadBalancerApiLiveTest.java
+++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/LoadBalancerApiLiveTest.java
@@ -23,6 +23,7 @@
 import static com.google.common.collect.Lists.newArrayList;
 import static org.jclouds.azurecompute.arm.compute.options.AzureTemplateOptions.Builder.availabilitySet;
 import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.TIMEOUT_RESOURCE_DELETED;
+import static org.jclouds.azurecompute.arm.domain.IdReference.extractName;
 import static org.jclouds.azurecompute.arm.domain.InboundNatRuleProperties.Protocol.Tcp;
 import static org.jclouds.compute.predicates.NodePredicates.inGroup;
 import static org.testng.Assert.assertEquals;
@@ -360,7 +361,7 @@
          VirtualMachine vm = api.getVirtualMachineApi(resourceGroupAndName.resourceGroup()).get(
                resourceGroupAndName.name());
 
-         String nicName = vm.properties().networkProfile().networkInterfaces().get(0).name();
+         String nicName = extractName(vm.properties().networkProfile().networkInterfaces().get(0).id());
          nicNames.add(nicName);
       }
 
diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/NetworkInterfaceCardApiMockTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/NetworkInterfaceCardApiMockTest.java
index e0f0ed4..dd10046 100644
--- a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/NetworkInterfaceCardApiMockTest.java
+++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/NetworkInterfaceCardApiMockTest.java
@@ -40,7 +40,7 @@
 
    private final String subscriptionid = "SUBSCRIPTIONID";
    private final String resourcegroup = "myresourcegroup";
-   private final String apiVersion = "api-version=2015-06-15";
+   private final String apiVersion = "api-version=2017-03-01";
    private final String location = "northeurope";
    private final String nicName = "myNic";
 
@@ -65,7 +65,8 @@
 
       assertNull(nicApi.get(nicName));
 
-      assertSent(server, "GET", "/subscriptions/SUBSCRIPTIONID/resourcegroups/myresourcegroup/providers/Microsoft.Network/networkInterfaces/myNic?api-version=2015-06-15");
+      String path = String.format("/subscriptions/%s/resourcegroups/%s/providers/Microsoft.Network/networkInterfaces/%s?%s", subscriptionid, resourcegroup, nicName, apiVersion);
+      assertSent(server, "GET", path);
    }
 
    public void listNetworkInterfaceCards() throws InterruptedException {
diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/SubnetApiMockTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/SubnetApiMockTest.java
index 0113201..3d58591 100644
--- a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/SubnetApiMockTest.java
+++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/SubnetApiMockTest.java
@@ -36,7 +36,7 @@
    private final String resourcegroup = "myresourcegroup";
    private final String virtualNetwork = "myvirtualnetwork";
    private final String subnetName = "mysubnet";
-   private final String apiVersion = "api-version=2015-06-15";
+   private final String apiVersion = "api-version=2017-03-01";
 
    public void createSubnet() throws InterruptedException {
 
diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VirtualMachineApiLiveTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VirtualMachineApiLiveTest.java
index cecdc01..f3f6aac 100644
--- a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VirtualMachineApiLiveTest.java
+++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VirtualMachineApiLiveTest.java
@@ -51,6 +51,8 @@
 import org.jclouds.azurecompute.arm.domain.VirtualMachineInstance;
 import org.jclouds.azurecompute.arm.domain.VirtualMachineInstance.PowerState;
 import org.jclouds.azurecompute.arm.domain.VirtualMachineProperties;
+import org.jclouds.azurecompute.arm.domain.NetworkProfile.NetworkInterface;
+import org.jclouds.azurecompute.arm.domain.NetworkProfile.NetworkInterface.NetworkInterfaceProperties;
 import org.jclouds.azurecompute.arm.functions.ParseJobStatus;
 import org.jclouds.azurecompute.arm.internal.BaseAzureComputeApiLiveTest;
 import org.testng.annotations.BeforeClass;
@@ -253,12 +255,11 @@
       OSProfile.WindowsConfiguration windowsConfig = OSProfile.WindowsConfiguration.create(false, null, null, true,
               null);
       OSProfile osProfile = OSProfile.create(vmName, "azureuser", "RFe3&432dg", null, null, windowsConfig);
-      IdReference networkInterface =
-              IdReference.create("/subscriptions/" + subscriptionid +
+      NetworkInterface networkInterface =
+            NetworkInterface.create("/subscriptions/" + subscriptionid +
                       "/resourceGroups/" + resourceGroupName + "/providers/Microsoft.Network/networkInterfaces/"
-                      + nic);
-      List<IdReference> networkInterfaces =
-              new ArrayList<IdReference>();
+                      + nic, NetworkInterfaceProperties.create(true));
+      List<NetworkInterface> networkInterfaces = new ArrayList<NetworkInterface>();
       networkInterfaces.add(networkInterface);
       NetworkProfile networkProfile = NetworkProfile.create(networkInterfaces);
       VirtualMachineProperties properties = VirtualMachineProperties.create(null,
diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VirtualMachineApiMockTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VirtualMachineApiMockTest.java
index 27e6f6b..95d967e 100644
--- a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VirtualMachineApiMockTest.java
+++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VirtualMachineApiMockTest.java
@@ -16,6 +16,12 @@
  */
 package org.jclouds.azurecompute.arm.features;
 
+import static com.google.common.collect.Iterables.isEmpty;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
 import java.net.URI;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
@@ -26,9 +32,9 @@
 import org.jclouds.azurecompute.arm.domain.DataDisk;
 import org.jclouds.azurecompute.arm.domain.DiagnosticsProfile;
 import org.jclouds.azurecompute.arm.domain.HardwareProfile;
-import org.jclouds.azurecompute.arm.domain.IdReference;
 import org.jclouds.azurecompute.arm.domain.ImageReference;
 import org.jclouds.azurecompute.arm.domain.NetworkProfile;
+import org.jclouds.azurecompute.arm.domain.NetworkProfile.NetworkInterface;
 import org.jclouds.azurecompute.arm.domain.OSDisk;
 import org.jclouds.azurecompute.arm.domain.OSProfile;
 import org.jclouds.azurecompute.arm.domain.Plan;
@@ -45,12 +51,6 @@
 import com.google.common.collect.ImmutableMap;
 import com.squareup.okhttp.mockwebserver.MockResponse;
 
-import static com.google.common.collect.Iterables.isEmpty;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertTrue;
-
 @Test(groups = "unit", testName = "VirtualMachineApiMockTest", singleThreaded = true)
 public class VirtualMachineApiMockTest extends BaseAzureComputeApiMockTest {
 
@@ -257,9 +257,9 @@
       OSProfile.WindowsConfiguration windowsConfig = OSProfile.WindowsConfiguration.create(false, null, null, true,
             null);
       OSProfile osProfile = OSProfile.create("windowsmachine", "azureuser", null, null, null, windowsConfig);
-      IdReference networkInterface = IdReference.create("/subscriptions/SUBSCRIPTIONID"
-            + "/resourceGroups/groupname/providers/Microsoft.Network/networkInterfaces/" + "windowsmachine167");
-      List<IdReference> networkInterfaces = new ArrayList<IdReference>();
+      NetworkInterface networkInterface = NetworkInterface.create("/subscriptions/SUBSCRIPTIONID"
+            + "/resourceGroups/groupname/providers/Microsoft.Network/networkInterfaces/" + "windowsmachine167", null);
+      List<NetworkInterface> networkInterfaces = new ArrayList<NetworkInterface>();
       networkInterfaces.add(networkInterface);
       NetworkProfile networkProfile = NetworkProfile.create(networkInterfaces);
       DiagnosticsProfile.BootDiagnostics bootDiagnostics = DiagnosticsProfile.BootDiagnostics.create(true,