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 2015/04/30 16:48:01 UTC

jclouds-labs git commit: JCLOUDS-852: Added VMImageOpertaions to Azure Compute

Repository: jclouds-labs
Updated Branches:
  refs/heads/1.9.x e7fb50988 -> 6b4de9e73


JCLOUDS-852: Added VMImageOpertaions to Azure Compute


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

Branch: refs/heads/1.9.x
Commit: 6b4de9e73209970cb8f678fca53a26018d9dc0b5
Parents: e7fb509
Author: hsbhathiya <hs...@gmail.com>
Authored: Sun Mar 1 17:14:49 2015 +0530
Committer: Ignasi Barrera <na...@apache.org>
Committed: Thu Apr 30 16:45:38 2015 +0200

----------------------------------------------------------------------
 .../jclouds/azurecompute/AzureComputeApi.java   |   6 +
 .../binders/CaptureVMImageParamsToXML.java      |  68 +++++
 .../binders/VMImageParamsToXML.java             |  97 ++++++++
 .../domain/CaptureVMImageParams.java            | 117 +++++++++
 .../jclouds/azurecompute/domain/VMImage.java    | 166 +++++++++++++
 .../azurecompute/domain/VMImageParams.java      | 246 +++++++++++++++++++
 .../azurecompute/features/VMImageApi.java       |  95 +++++++
 .../features/VirtualMachineApi.java             |  13 +
 .../xml/DataVirtualHardDiskHandler.java         |   2 +-
 .../azurecompute/xml/ListVMImagesHandler.java   |  70 ++++++
 .../azurecompute/xml/OSConfigHandler.java       | 106 ++++++++
 .../azurecompute/xml/VMImageHandler.java        | 207 ++++++++++++++++
 .../features/VMImageApiLiveTest.java            | 215 ++++++++++++++++
 .../features/VMImageApiMockTest.java            | 198 +++++++++++++++
 .../features/VirtualMachineApiLiveTest.java     |   3 +
 .../features/VirtualMachineApiMockTest.java     |   3 +
 .../xml/ListVMImagesHandlerTest.java            | 106 ++++++++
 .../src/test/resources/vmimageparams.xml        |   1 +
 .../src/test/resources/vmimageparams_mock.xml   |   1 +
 azurecompute/src/test/resources/vmimages.xml    |  56 +++++
 20 files changed, 1775 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6b4de9e7/azurecompute/src/main/java/org/jclouds/azurecompute/AzureComputeApi.java
----------------------------------------------------------------------
diff --git a/azurecompute/src/main/java/org/jclouds/azurecompute/AzureComputeApi.java b/azurecompute/src/main/java/org/jclouds/azurecompute/AzureComputeApi.java
index 93187c4..d7e78f2 100644
--- a/azurecompute/src/main/java/org/jclouds/azurecompute/AzureComputeApi.java
+++ b/azurecompute/src/main/java/org/jclouds/azurecompute/AzureComputeApi.java
@@ -35,6 +35,7 @@ import org.jclouds.azurecompute.features.SubscriptionApi;
 import org.jclouds.azurecompute.features.TrafficManagerApi;
 import org.jclouds.azurecompute.features.VirtualMachineApi;
 import org.jclouds.azurecompute.features.VirtualNetworkApi;
+import org.jclouds.azurecompute.features.VMImageApi;
 import org.jclouds.rest.annotations.Delegate;
 
 /**
@@ -167,4 +168,9 @@ public interface AzureComputeApi extends Closeable {
     */
    @Delegate
    ReservedIPAddressApi getReservedIPAddressApi();
+   /*
+   * The Service Management API includes operations for managing the VM Images in your subscription.
+   */
+   @Delegate
+   VMImageApi getVMImageApi();
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6b4de9e7/azurecompute/src/main/java/org/jclouds/azurecompute/binders/CaptureVMImageParamsToXML.java
----------------------------------------------------------------------
diff --git a/azurecompute/src/main/java/org/jclouds/azurecompute/binders/CaptureVMImageParamsToXML.java b/azurecompute/src/main/java/org/jclouds/azurecompute/binders/CaptureVMImageParamsToXML.java
new file mode 100644
index 0000000..b8ac478
--- /dev/null
+++ b/azurecompute/src/main/java/org/jclouds/azurecompute/binders/CaptureVMImageParamsToXML.java
@@ -0,0 +1,68 @@
+/*
+ * 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.binders;
+
+import com.jamesmurty.utils.XMLBuilder;
+import org.jclouds.azurecompute.domain.CaptureVMImageParams;
+import org.jclouds.azurecompute.domain.RoleSize;
+
+import static org.jclouds.azurecompute.domain.VMImage.OSDiskConfiguration.OSState.GENERALIZED;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.rest.Binder;
+
+import static com.google.common.base.Throwables.propagate;
+
+public final class CaptureVMImageParamsToXML implements Binder {
+
+    @Override
+    public <R extends HttpRequest> R bindToRequest(R request, Object input) {
+        CaptureVMImageParams params = CaptureVMImageParams.class.cast(input);
+
+        try {
+            XMLBuilder builder = XMLBuilder.create("CaptureRoleAsVMImageOperation", "http://schemas.microsoft.com/windowsazure")
+                    .namespace("i", "http://www.w3.org/2001/XMLSchema-instance")
+                    .e("OperationType").t("CaptureRoleAsVMImageOperation").up()
+                    .e("OSState").t(params.osState() == GENERALIZED ? "Generalized" : "Specialized").up()
+                    .e("VMImageName").t(params.name()).up()
+                    .e("VMImageLabel").t(params.label()).up();
+            add(builder, "Description", params.description());
+            add(builder, "Language", params.language());
+            add(builder, "ImageFamily", params.imageFamily());
+
+            RoleSize.Type vmSize = params.recommendedVMSize();
+            if (vmSize != null) {
+                String vmSizeText = params.recommendedVMSize().getText();
+                builder.e("RecommendedVMSize").t(vmSizeText).up();
+            }
+
+            builder.up();
+
+            return (R) request.toBuilder().payload(builder.asString()).build();
+        } catch (Exception e) {
+            throw propagate(e);
+        }
+    }
+
+    private XMLBuilder add(XMLBuilder xmlBuilder, String entity, String text) {
+        if (text != null) {
+            return xmlBuilder.e(entity).t(text).up();
+        } else {
+            return xmlBuilder.e(entity).up();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6b4de9e7/azurecompute/src/main/java/org/jclouds/azurecompute/binders/VMImageParamsToXML.java
----------------------------------------------------------------------
diff --git a/azurecompute/src/main/java/org/jclouds/azurecompute/binders/VMImageParamsToXML.java b/azurecompute/src/main/java/org/jclouds/azurecompute/binders/VMImageParamsToXML.java
new file mode 100644
index 0000000..540faa8
--- /dev/null
+++ b/azurecompute/src/main/java/org/jclouds/azurecompute/binders/VMImageParamsToXML.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.binders;
+
+import com.google.common.base.CaseFormat;
+import com.jamesmurty.utils.XMLBuilder;
+import org.jclouds.azurecompute.domain.RoleSize;
+import org.jclouds.azurecompute.domain.VMImageParams;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.rest.Binder;
+
+import java.net.URI;
+
+import static com.google.common.base.CaseFormat.UPPER_CAMEL;
+import static com.google.common.base.Throwables.propagate;
+
+public final class VMImageParamsToXML implements Binder {
+
+    @Override
+    public <R extends HttpRequest> R bindToRequest(R request, Object input) {
+        VMImageParams params = VMImageParams.class.cast(input);
+
+        try {
+            XMLBuilder builder = XMLBuilder.create("VMImage", "http://schemas.microsoft.com/windowsazure");
+            add(builder, "Name", params.name());
+            add(builder, "Label", params.label());
+            add(builder, "Description", params.description());
+            //OSConfig
+            VMImageParams.OSDiskConfigurationParams osDiskConfig = params.osDiskConfiguration();
+            if (osDiskConfig != null) {
+                String cache = CaseFormat.UPPER_UNDERSCORE.to(UPPER_CAMEL, osDiskConfig.hostCaching().toString());
+                XMLBuilder osConfigBuilder = builder.e("OSDiskConfiguration");
+                osConfigBuilder
+                        .e("HostCaching").t(cache).up()
+                        .e("OSState").t(osDiskConfig.osState().toString()).up()
+                        .e("OS").t(osDiskConfig.os().toString()).up()
+                        .e("MediaLink").t(osDiskConfig.mediaLink().toASCIIString()).up()
+                        .up(); //OSDiskConfiguration
+            }
+            builder.up();
+            builder.e("DataDiskConfigurations").up();
+            add(builder, "Language", params.language());
+            add(builder, "ImageFamily", params.imageFamily());
+
+            RoleSize.Type vmSize = params.recommendedVMSize();
+            if (vmSize != null) {
+                String vmSizeText = params.recommendedVMSize().getText();
+                builder.e("RecommendedVMSize").t(vmSizeText).up();
+            }
+
+            add(builder, "Eula", params.eula());
+            add(builder, "IconUri", params.iconUri());
+            add(builder, "SmallIconUri", params.smallIconUri());
+            add(builder, "PrivacyUri", params.privacyUri());
+
+            if (params.showGui() != null) {
+                String showGuiText = params.showGui().toString();
+                builder.e("ShowGui").t(showGuiText).up();
+            }
+            builder.up();
+
+            return (R) request.toBuilder().payload(builder.asString()).build();
+        } catch (Exception e) {
+            throw propagate(e);
+        }
+    }
+
+    private XMLBuilder add(XMLBuilder xmlBuilder, String entity, String text) {
+        if (text != null) {
+            return xmlBuilder.e(entity).t(text).up();
+        } else {
+            return xmlBuilder.e(entity).up();
+        }
+    }
+
+    private XMLBuilder add(XMLBuilder xmlBuilder, String entity, URI text) {
+        if (text != null) {
+            return xmlBuilder.e(entity).t(text.toASCIIString()).up();
+        } else {
+            return xmlBuilder.e(entity).up();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6b4de9e7/azurecompute/src/main/java/org/jclouds/azurecompute/domain/CaptureVMImageParams.java
----------------------------------------------------------------------
diff --git a/azurecompute/src/main/java/org/jclouds/azurecompute/domain/CaptureVMImageParams.java b/azurecompute/src/main/java/org/jclouds/azurecompute/domain/CaptureVMImageParams.java
new file mode 100644
index 0000000..a68625e
--- /dev/null
+++ b/azurecompute/src/main/java/org/jclouds/azurecompute/domain/CaptureVMImageParams.java
@@ -0,0 +1,117 @@
+/*
+ * 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.domain;
+
+import com.google.auto.value.AutoValue;
+import org.jclouds.javax.annotation.Nullable;
+
+/**
+ * @see <a href="https://msdn.microsoft.com/en-us/library/azure/dn499768.aspx" >api</a>
+ */
+@AutoValue
+public abstract class CaptureVMImageParams {
+
+   public abstract VMImage.OSDiskConfiguration.OSState osState();
+
+   public abstract String name();
+
+   public abstract String label();
+
+   @Nullable public abstract String description();
+
+   @Nullable public abstract String language();
+
+   @Nullable public abstract String imageFamily();
+
+   @Nullable public abstract RoleSize.Type recommendedVMSize();
+
+   public Builder toBuilder() {
+      return builder().fromVMImageParams(this);
+   }
+
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public static final class Builder {
+      private VMImage.OSDiskConfiguration.OSState osState;
+      private String name;
+      private String label;
+      private String description;
+      private String language;
+      private String imageFamily;
+      private RoleSize.Type recommendedVMSize;
+
+      public Builder osState(VMImage.OSDiskConfiguration.OSState osState) {
+         this.osState = osState;
+         return this;
+      }
+
+      public Builder name(String name) {
+         this.name = name;
+         return this;
+      }
+
+      public Builder label(String label) {
+         this.label = label;
+         return this;
+      }
+
+      public Builder description(String description) {
+         this.description = description;
+         return this;
+      }
+
+      public Builder language(String language) {
+         this.language = language;
+         return this;
+      }
+
+      public Builder imageFamily(String imageFamily) {
+         this.imageFamily = imageFamily;
+         return this;
+      }
+
+      public Builder recommendedVMSize(RoleSize.Type recommendedRoleSize) {
+         this.recommendedVMSize = recommendedRoleSize;
+         return this;
+      }
+
+      public Builder fromVMImageParams(CaptureVMImageParams in) {
+         return name(in.name())
+               .label(in.label())
+               .osState(in.osState())
+               .description(in.description())
+               .language(in.language())
+               .imageFamily(in.imageFamily())
+               .recommendedVMSize(in.recommendedVMSize());
+
+      }
+
+      public CaptureVMImageParams build() {
+         return CaptureVMImageParams.create(osState, name, label, description, language,
+               imageFamily, recommendedVMSize);
+      }
+
+   }
+
+   public static CaptureVMImageParams create(VMImage.OSDiskConfiguration.OSState osState, String name, String label,
+         String description, String language, String imageFamily, RoleSize.Type recommendedVMSize) {
+      return new AutoValue_CaptureVMImageParams(osState, name, label, description, language, imageFamily,
+            recommendedVMSize);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6b4de9e7/azurecompute/src/main/java/org/jclouds/azurecompute/domain/VMImage.java
----------------------------------------------------------------------
diff --git a/azurecompute/src/main/java/org/jclouds/azurecompute/domain/VMImage.java b/azurecompute/src/main/java/org/jclouds/azurecompute/domain/VMImage.java
new file mode 100644
index 0000000..f7f5edf
--- /dev/null
+++ b/azurecompute/src/main/java/org/jclouds/azurecompute/domain/VMImage.java
@@ -0,0 +1,166 @@
+/*
+ * 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.domain;
+
+import com.google.auto.value.AutoValue;
+import org.jclouds.javax.annotation.Nullable;
+
+import java.net.URI;
+import java.util.Date;
+import java.util.List;
+
+import static com.google.common.collect.ImmutableList.copyOf;
+/**
+ * OS image from the image repository
+ *
+ * @see <a href="https://msdn.microsoft.com/en-us/library/azure/dn499770.aspx" >api</a>
+ */
+@AutoValue
+public abstract class VMImage {
+
+   @AutoValue
+   public abstract static class OSDiskConfiguration {
+
+      public enum Caching {
+         READ_ONLY,
+         READ_WRITE,
+         NONE
+      }
+
+      public enum OSState {
+         GENERALIZED,
+         SPECIALIZED
+      }
+
+      /**
+       * Specifies the name of the operating system disk.
+       */
+      public abstract String name();
+
+      /**
+       * Specifies the caching behavior of the operating system disk.
+       * This setting impacts the consistency and performance of the disk.
+       * The default value is ReadWrite
+       */
+      @Nullable public abstract Caching hostCaching();
+
+      /**
+       * Specifies the state of the operating system in the image.
+       * A Virtual Machine that is fully configured and running contains a Specialized operating system.
+       * A Virtual Machine on which the Sysprep command has been run with the generalize option contains
+       * a Generalized operating system.
+       */
+      @Nullable public abstract OSState osState();
+
+      /**
+       * Specifies the operating system type of the image.
+       */
+      public abstract OSImage.Type os();
+
+      /**
+       * Specifies the location of the blob in Azure storage. The blob location belongs to a storage account in the
+       * subscription specified by the <subscription-id> value in the operation call.
+       */
+      @Nullable public abstract URI mediaLink();
+
+      /**
+       * Specifies the size, in GB, of the operating system disk
+       */
+      @Nullable public abstract Integer logicalSizeInGB();
+
+      /**
+       * This property identifies the type of the storage account for the backing VHD.
+       */
+      @Nullable public abstract String ioType();
+
+      public static OSDiskConfiguration create(String name, Caching hostCaching, OSState osState, OSImage.Type os,
+            URI mediaLink, Integer logicalDiskSizeInGB, String ioType) {
+         return new AutoValue_VMImage_OSDiskConfiguration(name, hostCaching, osState, os, mediaLink,
+               logicalDiskSizeInGB, ioType);
+      }
+
+   }
+
+   public abstract String name();
+
+   /**
+    * The name can be up to 100 characters in length. The name can be used identify the storage account for your
+    * tracking purposes.
+    */
+   public abstract String label();
+
+   /**
+    * The repository classification of image. All user images have the category "User", but
+    * categories for other images could be, for example "Canonical"
+    */
+   @Nullable public abstract String category();
+
+   @Nullable public abstract String description();
+
+   @Nullable public abstract OSDiskConfiguration osDiskConfiguration();
+
+   public abstract List<DataVirtualHardDisk> dataDiskConfiguration();
+
+   @Nullable public abstract String serviceName();
+
+   @Nullable public abstract String deploymentName();
+
+   @Nullable public abstract String roleName();
+
+   /**
+    * The geo-locations of the image, if the image is not associated with an affinity group.
+    */
+   @Nullable public abstract String location();
+
+   /**
+    * The affinity group with which this image is associated, if any.
+    */
+   @Nullable public abstract String affinityGroup();
+
+   @Nullable public abstract Date createdTime();
+
+   @Nullable public abstract Date modifiedTime();
+
+   @Nullable public abstract String language();
+
+   @Nullable public abstract String imageFamily();
+
+   @Nullable public abstract RoleSize.Type recommendedVMSize();
+
+   @Nullable public abstract Boolean isPremium();
+
+   @Nullable public abstract String eula();
+
+   @Nullable public abstract URI iconUri();
+
+   @Nullable public abstract URI smallIconUri();
+
+   @Nullable public abstract URI privacyUri();
+
+   @Nullable public abstract Date publishedDate();
+
+   public static VMImage create(String name, String label, String category, String description,
+         OSDiskConfiguration osDiskConfiguration, List<DataVirtualHardDisk> dataDiskConfiguration, String serviceName,
+         String deploymentName, String roleName, String location, String affinityGroup, Date createdTime,
+         Date modifiedTime, String language, String imageFamily, RoleSize.Type recommendedVMSize, Boolean isPremium,
+         String eula, URI iconUri, URI smallIconUri,
+         URI privacyUri, Date publishedDate) {
+      return new AutoValue_VMImage(name, label, category, description, osDiskConfiguration, copyOf(dataDiskConfiguration),
+            serviceName, deploymentName, roleName, location, affinityGroup, createdTime, modifiedTime, language,
+            imageFamily, recommendedVMSize, isPremium, eula, iconUri, smallIconUri, privacyUri, publishedDate);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6b4de9e7/azurecompute/src/main/java/org/jclouds/azurecompute/domain/VMImageParams.java
----------------------------------------------------------------------
diff --git a/azurecompute/src/main/java/org/jclouds/azurecompute/domain/VMImageParams.java b/azurecompute/src/main/java/org/jclouds/azurecompute/domain/VMImageParams.java
new file mode 100644
index 0000000..7fda79d
--- /dev/null
+++ b/azurecompute/src/main/java/org/jclouds/azurecompute/domain/VMImageParams.java
@@ -0,0 +1,246 @@
+/*
+ * 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.domain;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.Lists;
+import org.jclouds.javax.annotation.Nullable;
+
+import java.net.URI;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * OS image from the image repository
+ *
+ * @see <a href="https://msdn.microsoft.com/en-us/library/azure/dn499770.aspx" >api</a>
+ */
+@AutoValue
+public abstract class VMImageParams {
+
+   @AutoValue
+   public abstract static class OSDiskConfigurationParams {
+
+      public enum Caching {
+         READ_ONLY,
+         READ_WRITE,
+         NONE
+      }
+
+      public enum OSState {
+         GENERALIZED,
+         SPECIALIZED
+      }
+
+      /**
+       * Specifies the name of the operating system disk.
+       */
+      @Nullable public abstract String name();
+
+      /**
+       * Specifies the caching behavior of the operating system disk.
+       * This setting impacts the consistency and performance of the disk.
+       * The default value is ReadWrite
+       */
+      @Nullable public abstract Caching hostCaching();
+
+      /**
+       * Specifies the state of the operating system in the image.
+       * A Virtual Machine that is fully configured and running contains a Specialized operating system.
+       * A Virtual Machine on which the Sysprep command has been run with the generalize option contains
+       * a Generalized operating system.
+       */
+
+      @Nullable public abstract OSState osState();
+
+      /**
+       * Specifies the operating system type of the image.
+       */
+      public abstract OSImage.Type os();
+
+      /**
+       * Specifies the location of the blob in Azure storage. The blob location belongs to a storage account in the
+       * subscription specified by the <subscription-id> value in the operation call.
+       */
+      @Nullable public abstract URI mediaLink();
+
+      /**
+       * Specifies the size, in GB, of the operating system disk
+       */
+      @Nullable public abstract Integer logicalSizeInGB();
+
+      /**
+       * This property identifies the type of the storage account for the backing VHD.
+       */
+      @Nullable public abstract String ioType();
+
+      public static OSDiskConfigurationParams OSDiskConfiguration(String name, Caching hostCaching, OSState osState,
+            OSImage.Type os, URI mediaLink, Integer logicalDiskSizeInGB, String ioType) {
+         return new AutoValue_VMImageParams_OSDiskConfigurationParams(name, hostCaching, osState, os, mediaLink,
+               logicalDiskSizeInGB, ioType);
+      }
+
+   }
+
+   @Nullable public abstract String name();
+
+   /**
+    * The name can be up to 100 characters in length. The name can be used identify the storage account for your
+    * tracking purposes.
+    */
+   @Nullable public abstract String label();
+
+   @Nullable public abstract String description();
+
+   @Nullable public abstract OSDiskConfigurationParams osDiskConfiguration();
+
+   public abstract List<DataVirtualHardDisk> dataDiskConfiguration();
+
+   @Nullable public abstract String language();
+
+   @Nullable public abstract String imageFamily();
+
+   @Nullable public abstract RoleSize.Type recommendedVMSize();
+
+   @Nullable public abstract String eula();
+
+   @Nullable public abstract URI iconUri();
+
+   @Nullable public abstract URI smallIconUri();
+
+   @Nullable public abstract URI privacyUri();
+
+   @Nullable public abstract Boolean showGui();
+
+   public Builder toBuilder() {
+      return builder().fromVMImageParams(this);
+   }
+
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public static final class Builder {
+      private String name;
+      private String label;
+      private String description;
+      private OSDiskConfigurationParams osDiskConfiguration;
+      private List<DataVirtualHardDisk> dataDiskConfiguration = Lists.newArrayList();
+      private String language;
+      private String imageFamily;
+      private RoleSize.Type recommendedVMSize;
+      private String eula;
+      private URI iconUri;
+      private URI smallIconUri;
+      private URI privacyUri;
+      private Boolean showGui;
+
+      public Builder name(String name) {
+         this.name = name;
+         return this;
+      }
+
+      public Builder label(String label) {
+         this.label = label;
+         return this;
+      }
+
+      public Builder description(String description) {
+         this.description = description;
+         return this;
+      }
+
+      public Builder osDiskConfiguration(OSDiskConfigurationParams osDiskConfig) {
+         this.osDiskConfiguration = osDiskConfig;
+         return this;
+      }
+
+      public Builder language(String language) {
+         this.language = language;
+         return this;
+      }
+
+      public Builder imageFamily(String imageFamily) {
+         this.imageFamily = imageFamily;
+         return this;
+      }
+
+      public Builder recommendedVMSize(RoleSize.Type recommendedRoleSize) {
+         this.recommendedVMSize = recommendedRoleSize;
+         return this;
+      }
+
+      public Builder showGui(Boolean showGui) {
+         this.showGui = showGui;
+         return this;
+      }
+
+      public Builder eula(String eula) {
+         this.eula = eula;
+         return this;
+      }
+
+      public Builder iconUri(URI iconUri) {
+         this.iconUri = iconUri;
+         return this;
+      }
+
+      public Builder smallIconUri(URI smallIconUri) {
+         this.smallIconUri = smallIconUri;
+         return this;
+      }
+
+      public Builder privacyUri(URI privacyUri) {
+         this.privacyUri = smallIconUri;
+         return this;
+      }
+
+      public Builder dataDiskConfiguration(DataVirtualHardDisk dataDiskConfiguration) {
+         this.dataDiskConfiguration.add(dataDiskConfiguration);
+         return this;
+      }
+
+      public Builder dataDiskConfigurations(Collection<DataVirtualHardDisk> dataDiskConfiguration) {
+         this.dataDiskConfiguration.addAll(dataDiskConfiguration);
+         return this;
+      }
+
+      public VMImageParams build() {
+         return VMImageParams
+               .create(name, label, description, osDiskConfiguration, dataDiskConfiguration, language,
+                     imageFamily, recommendedVMSize, eula, iconUri, smallIconUri, privacyUri,
+                     showGui);
+      }
+
+      public Builder fromVMImageParams(VMImageParams in) {
+         return name(in.name()).label(in.label()).description(in.description())
+               .osDiskConfiguration(in.osDiskConfiguration()).dataDiskConfigurations(in.dataDiskConfiguration())
+               .language(in.language()).imageFamily(in.imageFamily()).recommendedVMSize(in.recommendedVMSize())
+               .eula(in.eula()).iconUri(in.iconUri()).smallIconUri(in.smallIconUri()).privacyUri(in.privacyUri());
+      }
+
+   }
+
+   public static VMImageParams create(String name, String label, String description,
+         OSDiskConfigurationParams osDiskConfiguration, List<DataVirtualHardDisk> dataDiskConfiguration,
+         String language, String imageFamily, RoleSize.Type recommendedVMSize, String eula, URI iconUri,
+         URI smallIconUri, URI privacyUri, Boolean showGui) {
+      return new AutoValue_VMImageParams(name, label, description, osDiskConfiguration, dataDiskConfiguration,
+            language, imageFamily, recommendedVMSize, eula, iconUri, smallIconUri, privacyUri,
+            showGui);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6b4de9e7/azurecompute/src/main/java/org/jclouds/azurecompute/features/VMImageApi.java
----------------------------------------------------------------------
diff --git a/azurecompute/src/main/java/org/jclouds/azurecompute/features/VMImageApi.java b/azurecompute/src/main/java/org/jclouds/azurecompute/features/VMImageApi.java
new file mode 100644
index 0000000..ecca539
--- /dev/null
+++ b/azurecompute/src/main/java/org/jclouds/azurecompute/features/VMImageApi.java
@@ -0,0 +1,95 @@
+/*
+ * 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.features;
+
+import org.jclouds.azurecompute.binders.VMImageParamsToXML;
+import org.jclouds.azurecompute.domain.VMImage;
+import org.jclouds.azurecompute.domain.VMImageParams;
+import org.jclouds.azurecompute.functions.ParseRequestIdHeader;
+import org.jclouds.azurecompute.xml.ListVMImagesHandler;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.Headers;
+import org.jclouds.rest.annotations.ResponseParser;
+import org.jclouds.rest.annotations.XMLResponseParser;
+import org.jclouds.rest.annotations.BinderParam;
+
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import java.util.List;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_XML;
+import static org.jclouds.Fallbacks.EmptyListOnNotFoundOr404;
+import static org.jclouds.Fallbacks.NullOnNotFoundOr404;
+
+/**
+ * The Service Management API includes operations for managing the VM Images in your subscription.
+ *
+ * @see <a href="https://msdn.microsoft.com/en-us/library/azure/dn499771.aspx">docs</a>
+ */
+@Path("/services/vmimages")
+@Consumes(APPLICATION_XML)
+@Headers(keys = "x-ms-version", values = "{jclouds.api-version}")
+public interface VMImageApi {
+
+
+   /**
+    * The List VM Images operation retrieves a list of the VM Images from the image repository that is associated with
+    * the specified subscription.
+    */
+   @Named("ListVMImages")
+   @GET
+   @XMLResponseParser(ListVMImagesHandler.class)
+   @Fallback(EmptyListOnNotFoundOr404.class) List<VMImage> list();
+
+   /**
+    * The Create VM Image operation creates a VM Image in the image repository that is associated with the specified
+    * subscription using a specified set of virtual hard disks.
+    */
+   @Named("CreateVMImage")
+   @POST
+   @Produces(APPLICATION_XML)
+   @ResponseParser(ParseRequestIdHeader.class) String create(
+         @BinderParam(VMImageParamsToXML.class) VMImageParams params);
+
+   /**
+    * The Create VM Image operation creates a VM Image in the image repository that is associated with the specified
+    * subscription using a specified set of virtual hard disks.
+    */
+   @Named("UpdateVMImage")
+   @PUT
+   @Path("/{imageName}")
+   @Produces(APPLICATION_XML)
+   @ResponseParser(ParseRequestIdHeader.class) String update(@PathParam("imageName") String imageName,
+         @BinderParam(VMImageParamsToXML.class) VMImageParams params);
+
+   /**
+    * The Delete VM Image operation deletes the specified VM Image from the image repository that is associated with
+    * the specified subscription.
+    */
+   @Named("DeleteImage")
+   @DELETE
+   @Path("/{imageName}")
+   @Fallback(NullOnNotFoundOr404.class)
+   @ResponseParser(ParseRequestIdHeader.class) String delete(@PathParam("imageName") String imageName);
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6b4de9e7/azurecompute/src/main/java/org/jclouds/azurecompute/features/VirtualMachineApi.java
----------------------------------------------------------------------
diff --git a/azurecompute/src/main/java/org/jclouds/azurecompute/features/VirtualMachineApi.java b/azurecompute/src/main/java/org/jclouds/azurecompute/features/VirtualMachineApi.java
index 1ee93e0..968b406 100644
--- a/azurecompute/src/main/java/org/jclouds/azurecompute/features/VirtualMachineApi.java
+++ b/azurecompute/src/main/java/org/jclouds/azurecompute/features/VirtualMachineApi.java
@@ -28,7 +28,9 @@ import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
 
+import org.jclouds.azurecompute.binders.CaptureVMImageParamsToXML;
 import org.jclouds.azurecompute.binders.RoleToXML;
+import org.jclouds.azurecompute.domain.CaptureVMImageParams;
 import org.jclouds.azurecompute.domain.Role;
 import org.jclouds.azurecompute.functions.ParseRequestIdHeader;
 import org.jclouds.azurecompute.xml.RoleHandler;
@@ -124,4 +126,15 @@ public interface VirtualMachineApi {
    @ResponseParser(ParseRequestIdHeader.class)
    String updateRole(@PathParam("roleName") String roleName, @BinderParam(RoleToXML.class) Role role);
 
+   /**
+    * The Capture VM Image operation creates a copy of the operating system virtual hard disk (VHD) and all of the data
+    * VHDs that are associated with the Virtual Machine, saves the VHD copies in the same storage location as the original
+    * VHDs, and registers the copies as a VM Image in the image repository that is associated with the specified subscription.
+    */
+   @Named("CaptureVMImage")
+   @POST
+   @Produces(MediaType.APPLICATION_XML)
+   @Path("/roleinstances/{name}/Operations")
+   @ResponseParser(ParseRequestIdHeader.class) String capture(@PathParam("name") String name,
+         @BinderParam(CaptureVMImageParamsToXML.class) CaptureVMImageParams params);
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6b4de9e7/azurecompute/src/main/java/org/jclouds/azurecompute/xml/DataVirtualHardDiskHandler.java
----------------------------------------------------------------------
diff --git a/azurecompute/src/main/java/org/jclouds/azurecompute/xml/DataVirtualHardDiskHandler.java b/azurecompute/src/main/java/org/jclouds/azurecompute/xml/DataVirtualHardDiskHandler.java
index 17ac28a..e6178a1 100644
--- a/azurecompute/src/main/java/org/jclouds/azurecompute/xml/DataVirtualHardDiskHandler.java
+++ b/azurecompute/src/main/java/org/jclouds/azurecompute/xml/DataVirtualHardDiskHandler.java
@@ -61,7 +61,7 @@ final class DataVirtualHardDiskHandler extends ParseSax.HandlerForGeneratedReque
       if (qName.equals("HostCaching")) {
          String hostCachingText = currentOrNull(currentText);
          hostCaching = DataVirtualHardDisk.Caching.fromString(UPPER_CAMEL.to(UPPER_UNDERSCORE, hostCachingText));
-      } else if (qName.equals("DiskName")) {
+      } else if (qName.equals("DiskName") || qName.equals("Name")) {
          diskName = currentOrNull(currentText);
       } else if (qName.equals("Lun")) {
          String lunText = currentOrNull(currentText);

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6b4de9e7/azurecompute/src/main/java/org/jclouds/azurecompute/xml/ListVMImagesHandler.java
----------------------------------------------------------------------
diff --git a/azurecompute/src/main/java/org/jclouds/azurecompute/xml/ListVMImagesHandler.java b/azurecompute/src/main/java/org/jclouds/azurecompute/xml/ListVMImagesHandler.java
new file mode 100644
index 0000000..a4e9e08
--- /dev/null
+++ b/azurecompute/src/main/java/org/jclouds/azurecompute/xml/ListVMImagesHandler.java
@@ -0,0 +1,70 @@
+/*
+ * 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.xml;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+import com.google.inject.Inject;
+import org.jclouds.azurecompute.domain.VMImage;
+import org.jclouds.http.functions.ParseSax;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+import java.util.List;
+
+public final class ListVMImagesHandler extends ParseSax.HandlerForGeneratedRequestWithResult<List<VMImage>> {
+   private boolean inVMImage;
+   private final VMImageHandler VMImageHandler;
+   private final Builder<VMImage> images = ImmutableList.builder();
+
+   @Inject ListVMImagesHandler(VMImageHandler vmImageHandler) {
+      this.VMImageHandler = vmImageHandler;
+   }
+
+   @Override
+   public List<VMImage> getResult() {
+      return images.build();
+   }
+
+   @Override
+   public void startElement(String url, String name, String qName, Attributes attributes) throws SAXException {
+      if (qName.equals("VMImage")) {
+         inVMImage = true;
+      }
+      if (inVMImage) {
+         VMImageHandler.startElement(url, name, qName, attributes);
+      }
+   }
+
+   @Override
+   public void endElement(String uri, String name, String qName) {
+      if (qName.equals("VMImage")) {
+         inVMImage = false;
+         images.add(VMImageHandler.getResult());
+      }
+      if (inVMImage) {
+         VMImageHandler.endElement(uri, name, qName);
+      }
+   }
+
+   @Override
+   public void characters(char ch[], int start, int length) {
+      if (inVMImage) {
+         VMImageHandler.characters(ch, start, length);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6b4de9e7/azurecompute/src/main/java/org/jclouds/azurecompute/xml/OSConfigHandler.java
----------------------------------------------------------------------
diff --git a/azurecompute/src/main/java/org/jclouds/azurecompute/xml/OSConfigHandler.java b/azurecompute/src/main/java/org/jclouds/azurecompute/xml/OSConfigHandler.java
new file mode 100644
index 0000000..8144c31
--- /dev/null
+++ b/azurecompute/src/main/java/org/jclouds/azurecompute/xml/OSConfigHandler.java
@@ -0,0 +1,106 @@
+/*
+ * 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.xml;
+
+import org.jclouds.azurecompute.domain.OSImage;
+import org.jclouds.azurecompute.domain.VMImage;
+import org.jclouds.http.functions.ParseSax;
+
+import java.net.URI;
+
+import static com.google.common.base.CaseFormat.UPPER_CAMEL;
+import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE;
+import static org.jclouds.util.SaxUtils.currentOrNull;
+
+/**
+ * @see <a href="https://msdn.microsoft.com/en-us/library/azure/jj157193.aspx#DataVirtualHardDisks" >api</a>
+ */
+final class OSConfigHandler extends ParseSax.HandlerForGeneratedRequestWithResult<VMImage.OSDiskConfiguration> {
+
+    private VMImage.OSDiskConfiguration.Caching hostCaching;
+    private String name;
+    private VMImage.OSDiskConfiguration.OSState osState;
+    private OSImage.Type os;
+    private Integer logicalDiskSizeInGB;
+    private URI mediaLink;
+    private String ioType;
+
+    private final StringBuilder currentText = new StringBuilder();
+
+    @Override
+    public VMImage.OSDiskConfiguration getResult() {
+        return VMImage.OSDiskConfiguration
+                .create(name, hostCaching, osState, os, mediaLink, logicalDiskSizeInGB, ioType);
+    }
+
+    @Override
+    public void endElement(String ignoredUri, String ignoredName, String qName) {
+
+        if (qName.equals("HostCaching")) {
+            String hostCachingText = currentOrNull(currentText);
+            if (hostCachingText != null) {
+                hostCaching = parseHostCache(hostCachingText);
+            }
+        } else if (qName.equals("OSState")) {
+            String osStateText = currentOrNull(currentText);
+            if (osStateText != null) {
+                osState = VMImage.OSDiskConfiguration.OSState.valueOf(osStateText.toUpperCase());
+            }
+        } else if (qName.equals("OS")) {
+            String osText = currentOrNull(currentText);
+            if (osText != null) {
+                os = OSImage.Type.valueOf(osText.toUpperCase());
+            }
+        } else if (qName.equals("Name")) {
+            name = currentOrNull(currentText);
+        } else if (qName.equals("LogicalDiskSizeInGB")) {
+            String gb = currentOrNull(currentText);
+            if (gb != null) {
+                logicalDiskSizeInGB = Integer.parseInt(gb);
+            }
+        } else if (qName.equals("MediaLink")) {
+            String link = currentOrNull(currentText);
+            if (link != null) {
+                mediaLink = URI.create(link);
+            }
+        } else if (qName.equals("IOType")) {
+            ioType = currentOrNull(currentText);
+        }
+        currentText.setLength(0);
+    }
+
+    private static VMImage.OSDiskConfiguration.Caching parseHostCache(String hostCaching) {
+        try {
+            return VMImage.OSDiskConfiguration.Caching.valueOf(UPPER_CAMEL.to(UPPER_UNDERSCORE, hostCaching));
+        } catch (IllegalArgumentException e) {
+            return VMImage.OSDiskConfiguration.Caching.NONE;
+        }
+    }
+
+    private static VMImage.OSDiskConfiguration.OSState parseOSState(String osState) {
+        try {
+            return VMImage.OSDiskConfiguration.OSState.valueOf(UPPER_CAMEL.to(UPPER_UNDERSCORE, osState));
+        } catch (IllegalArgumentException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    @Override
+    public void characters(char ch[], int start, int length) {
+        currentText.append(ch, start, length);
+    }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6b4de9e7/azurecompute/src/main/java/org/jclouds/azurecompute/xml/VMImageHandler.java
----------------------------------------------------------------------
diff --git a/azurecompute/src/main/java/org/jclouds/azurecompute/xml/VMImageHandler.java b/azurecompute/src/main/java/org/jclouds/azurecompute/xml/VMImageHandler.java
new file mode 100644
index 0000000..4c3ee57
--- /dev/null
+++ b/azurecompute/src/main/java/org/jclouds/azurecompute/xml/VMImageHandler.java
@@ -0,0 +1,207 @@
+/*
+ * 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.xml;
+
+import com.google.common.collect.Lists;
+import com.google.inject.Inject;
+import org.jclouds.azurecompute.domain.DataVirtualHardDisk;
+import org.jclouds.azurecompute.domain.RoleSize;
+import org.jclouds.azurecompute.domain.VMImage;
+import org.jclouds.date.DateService;
+import org.jclouds.date.internal.SimpleDateFormatDateService;
+import org.jclouds.http.functions.ParseSax;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+import java.net.URI;
+import java.util.Date;
+import java.util.List;
+
+import static org.jclouds.util.SaxUtils.currentOrNull;
+
+/**
+ * @see <a href="https://msdn.microsoft.com/en-us/library/azure/dn499770.aspx" >api</a>
+ */
+final class VMImageHandler extends ParseSax.HandlerForGeneratedRequestWithResult<VMImage> {
+   private String name;
+   private String label;
+   private String category;
+   private String description;
+   private VMImage.OSDiskConfiguration osDiskConfiguration;
+   private String serviceName;
+   private String deploymentName;
+   private String roleName;
+   private String location;
+   private String affinityGroup;
+   private Date createdTime;
+   private Date modifiedTime;
+   private String language;
+   private String imageFamily;
+   private RoleSize.Type recommendedVMSize;
+   private Boolean isPremium;
+   private String eula;
+   private URI iconUri;
+   private URI smallIconUri;
+   private URI privacyUri;
+   private Date publishedDate;
+
+   private final StringBuilder currentText = new StringBuilder();
+
+   private final DataVirtualHardDiskHandler dataVirtualHardDiskHandler;
+   private final OSConfigHandler osConfigHandler;
+   private List<DataVirtualHardDisk> dataDiskConfigurations = Lists.newArrayList();
+
+   private boolean inOSConfig;
+   private boolean inDataConfig;
+   private final DateService dateService = new SimpleDateFormatDateService();
+
+   @Inject VMImageHandler(DataVirtualHardDiskHandler dataVirtualHardDiskHandler, OSConfigHandler osConfigHandler) {
+      this.dataVirtualHardDiskHandler = dataVirtualHardDiskHandler;
+      this.osConfigHandler = osConfigHandler;
+   }
+
+   @Override
+   public VMImage getResult() {
+      VMImage result = VMImage
+            .create(name, label, category, description, osDiskConfiguration, dataDiskConfigurations,
+                  serviceName, deploymentName, roleName, location, affinityGroup, createdTime, modifiedTime, language,
+                  imageFamily, recommendedVMSize, isPremium, eula, iconUri, smallIconUri, privacyUri, publishedDate);
+      resetState(); // handler is called in a loop.
+      return result;
+   }
+
+   @Override
+   public void startElement(String ignoredUri, String ignoredLocalName, String qName,
+         Attributes ignoredAttributes) throws SAXException {
+
+      if (qName.equals("OSDiskConfiguration")) {
+         inOSConfig = true;
+      }
+      if (inOSConfig) {
+         osConfigHandler.endElement(ignoredUri, ignoredLocalName, qName);
+      }
+      if (qName.equals("DataDiskConfiguration")) {
+         inDataConfig = true;
+      }
+      if (inDataConfig) {
+         dataVirtualHardDiskHandler.startElement(ignoredUri, ignoredLocalName, qName, ignoredAttributes);
+      }
+   }
+
+   private void resetState() {
+      name = affinityGroup = label = description = category = null;
+      serviceName = deploymentName = roleName = location = affinityGroup = null;
+      createdTime = modifiedTime = null;
+      language = imageFamily = eula = null;
+      recommendedVMSize = null;
+      isPremium = Boolean.FALSE;
+      iconUri = smallIconUri = privacyUri = null;
+      publishedDate = null;
+      osDiskConfiguration = null;
+      dataDiskConfigurations = Lists.newArrayList();
+   }
+
+   @Override public void endElement(String ignoredUri, String ignoredName, String qName) {
+      if (qName.equals("Name") && !inDataConfig && !inOSConfig) {
+         name = currentOrNull(currentText);
+      } else if (qName.equals("Label")) {
+         label = currentOrNull(currentText);
+      } else if (qName.equals("Category")) {
+         category = currentOrNull(currentText);
+      } else if (qName.equals("Description")) {
+         description = currentOrNull(currentText);
+      } else if (qName.equals("OSDiskConfiguration")) {
+         osDiskConfiguration = osConfigHandler.getResult();
+         inOSConfig = false;
+      } else if (inOSConfig) {
+         osConfigHandler.endElement(ignoredUri, ignoredName, qName);
+      } else if (qName.equals("DataDiskConfiguration") && !inOSConfig) {
+         dataDiskConfigurations.add(dataVirtualHardDiskHandler.getResult());
+         inDataConfig = false;
+      } else if (inDataConfig) {
+         dataVirtualHardDiskHandler.endElement(ignoredUri, ignoredName, qName);
+      } else if (qName.equals("ServiceName")) {
+         serviceName = currentOrNull(currentText);
+      } else if (qName.equals("DeploymentName")) {
+         deploymentName = currentOrNull(currentText);
+      } else if (qName.equals("RoleName")) {
+         roleName = currentOrNull(currentText);
+      } else if (qName.equals("Location")) {
+         location = currentOrNull(currentText);
+      } else if (qName.equals("AffinityGroup")) {
+         affinityGroup = currentOrNull(currentText);
+      } else if (qName.equals("CreatedTime")) {
+         createdTime = dateService.iso8601DateOrSecondsDateParse(currentOrNull(currentText));
+      } else if (qName.equals("ModifiedTime")) {
+         modifiedTime = dateService.iso8601DateOrSecondsDateParse(currentOrNull(currentText));
+      } else if (qName.equals("Language")) {
+         language = currentOrNull(currentText);
+      } else if (qName.equals("ImageFamily")) {
+         imageFamily = currentOrNull(currentText);
+      } else if (qName.equals("Label")) {
+         label = currentOrNull(currentText);
+      } else if (qName.equals("RecommendedVMSize")) {
+         String vmSizeText = currentOrNull(currentText);
+         if (vmSizeText != null) {
+            recommendedVMSize = parseRoleSize(vmSizeText);
+         }
+      } else if (qName.equals("IconUri")) {
+         String uri = currentOrNull(currentText);
+         if (uri != null) {
+            iconUri = URI.create(uri);
+         }
+      } else if (qName.equals("SmallIconUri")) {
+         String uri = currentOrNull(currentText);
+         if (uri != null) {
+            smallIconUri = URI.create(uri);
+         }
+      } else if (qName.equals("PrivacyUri")) {
+         String uri = currentOrNull(currentText);
+         if (uri != null) {
+            privacyUri = URI.create(uri);
+         }
+      } else if (qName.equals("IsPremium")) {
+         String isPremiumText = currentOrNull(currentText);
+         if (isPremiumText != null) {
+            isPremium = Boolean.parseBoolean(isPremiumText);
+         }
+      } else if (qName.equals("Eula")) {
+         eula = currentOrNull(currentText);
+      } else if (qName.equals("PublishedDate")) {
+         publishedDate = dateService.iso8601SecondsDateParse(currentOrNull(currentText));
+      }
+      currentText.setLength(0);
+   }
+
+   @Override public void characters(char ch[], int start, int length) {
+      if (inDataConfig) {
+         dataVirtualHardDiskHandler.characters(ch, start, length);
+      } else if (inOSConfig) {
+         osConfigHandler.characters(ch, start, length);
+      } else {
+         currentText.append(ch, start, length);
+      }
+   }
+
+   private static RoleSize.Type parseRoleSize(String roleSize) {
+      try {
+         return RoleSize.Type.valueOf(roleSize.toUpperCase().replace(" ", ""));
+      } catch (IllegalArgumentException e) {
+         return RoleSize.Type.UNRECOGNIZED;
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6b4de9e7/azurecompute/src/test/java/org/jclouds/azurecompute/features/VMImageApiLiveTest.java
----------------------------------------------------------------------
diff --git a/azurecompute/src/test/java/org/jclouds/azurecompute/features/VMImageApiLiveTest.java b/azurecompute/src/test/java/org/jclouds/azurecompute/features/VMImageApiLiveTest.java
new file mode 100644
index 0000000..2b074fd
--- /dev/null
+++ b/azurecompute/src/test/java/org/jclouds/azurecompute/features/VMImageApiLiveTest.java
@@ -0,0 +1,215 @@
+/*
+ * 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.features;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.jclouds.azurecompute.domain.Deployment.InstanceStatus.READY_ROLE;
+import static org.jclouds.util.Predicates2.retry;
+import static org.testng.Assert.assertNotEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableSet;
+
+import com.google.common.collect.Iterables;
+import org.jclouds.azurecompute.compute.AzureComputeServiceAdapter;
+import org.jclouds.azurecompute.domain.Deployment;
+import org.jclouds.azurecompute.domain.DeploymentParams;
+import org.jclouds.azurecompute.domain.VMImage;
+import org.jclouds.azurecompute.domain.RoleSize;
+import org.jclouds.azurecompute.domain.OSImage;
+import org.jclouds.azurecompute.domain.CloudService;
+import org.jclouds.azurecompute.domain.CaptureVMImageParams;
+import org.jclouds.azurecompute.domain.VMImageParams;
+import org.jclouds.azurecompute.internal.BaseAzureComputeApiLiveTest;
+import org.jclouds.azurecompute.util.ConflictManagementPredicate;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Date;
+import java.text.SimpleDateFormat;
+
+@Test(groups = "live", testName = "VMImageApiLiveTest")
+public class VMImageApiLiveTest extends BaseAzureComputeApiLiveTest {
+
+    private static final String CLOUD_SERVICE = String.format("%s%d-%s",
+            System.getProperty("user.name"), RAND, VMImageApiLiveTest.class.getSimpleName()).toLowerCase();
+
+    private static final String DEPLOYMENT = String.format("%s%d-%s",
+            System.getProperty("user.name"), RAND, VMImageApiLiveTest.class.getSimpleName()).toLowerCase();
+
+    private static final String CAPTURED_IMAGE_NAME = "captured-image";
+
+    private static final String CREATE_IMAGE_NAME = "create-image";
+
+    private String roleName;
+
+    private String diskName;
+
+    private Predicate<String> roleInstanceReady;
+
+    private CloudService cloudService;
+
+    @BeforeClass(groups = {"integration", "live"})
+    @Override
+    public void setup() {
+        super.setup();
+        cloudService = getOrCreateCloudService(CLOUD_SERVICE, LOCATION);
+
+        roleInstanceReady = retry(new Predicate<String>() {
+
+            @Override
+            public boolean apply(String input) {
+                Deployment.RoleInstance roleInstance = getFirstRoleInstanceInDeployment(input);
+                return roleInstance != null && roleInstance.instanceStatus() == READY_ROLE;
+            }
+        }, 600, 5, 5, SECONDS);
+
+        final DeploymentParams params = DeploymentParams.builder()
+                .name(DEPLOYMENT)
+                .os(OSImage.Type.LINUX)
+                .sourceImageName(BaseAzureComputeApiLiveTest.IMAGE_NAME)
+                .mediaLink(AzureComputeServiceAdapter.createMediaLink(storageService.serviceName(), DEPLOYMENT))
+                .username("test")
+                .password("supersecurePassword1!")
+                .size(RoleSize.Type.BASIC_A2)
+                .subnetName(Iterables.get(virtualNetworkSite.subnets(), 0).name())
+                .virtualNetworkName(virtualNetworkSite.name())
+                .externalEndpoint(DeploymentParams.ExternalEndpoint.inboundTcpToLocalPort(22, 22))
+                .build();
+        Deployment deployment = getOrCreateDeployment(cloudService.name(), params);
+        Deployment.RoleInstance roleInstance = getFirstRoleInstanceInDeployment(DEPLOYMENT);
+        assertTrue(roleInstanceReady.apply(DEPLOYMENT), roleInstance.toString());
+        roleName = roleInstance.roleName();
+        diskName = deployment.roleList().get(0).osVirtualHardDisk().diskName();
+    }
+
+    @Test(dependsOnMethods = "testCaptureVMImage")
+    public void testCreate() {
+        Date date = new Date();
+        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+        VMImage image = api().list().get(5);
+        VMImageParams.OSDiskConfigurationParams osParams = VMImageParams.OSDiskConfigurationParams
+                .OSDiskConfiguration(CREATE_IMAGE_NAME + "osdisk",
+                        VMImageParams.OSDiskConfigurationParams.Caching.READ_ONLY,
+                        VMImageParams.OSDiskConfigurationParams.OSState.SPECIALIZED,
+                        image.osDiskConfiguration().os(),
+                        URI.create(
+                                "https://" + storageService.serviceName()
+                                        + ".blob.core.windows.net/vhds/" + CAPTURED_IMAGE_NAME + "-os-" + dateFormat.format(date) + ".vhd"),
+                        30,
+                        "Standard");
+        VMImageParams params = VMImageParams.builder().name(CREATE_IMAGE_NAME).label(CREATE_IMAGE_NAME)
+                .description(image.description()).recommendedVMSize(image.recommendedVMSize())
+                .osDiskConfiguration(osParams).imageFamily(image.imageFamily())
+                .build();
+
+        String requestId = api().create(params);
+        assertNotNull(requestId);
+        operationSucceeded.apply(requestId);
+    }
+
+    @Test
+    public void testCaptureVMImage() {
+        String shutdownRequest = api.getVirtualMachineApiForDeploymentInService(DEPLOYMENT, CLOUD_SERVICE).shutdown(roleName);
+        assertTrue(operationSucceeded.apply(shutdownRequest), shutdownRequest);
+
+        CaptureVMImageParams captureParams = CaptureVMImageParams.builder()
+                .osState(VMImage.OSDiskConfiguration.OSState.GENERALIZED).name(CAPTURED_IMAGE_NAME)
+                .label(CAPTURED_IMAGE_NAME).recommendedVMSize(RoleSize.Type.MEDIUM).build();
+
+        String requestId = api.getVirtualMachineApiForDeploymentInService(DEPLOYMENT, CLOUD_SERVICE)
+                .capture(roleName, captureParams);
+        assertNotNull(requestId);
+        operationSucceeded.apply(requestId);
+    }
+
+    @Test(dependsOnMethods = "testCreate")
+    public void testUpdateVMImage() {
+        VMImage image = api().list().get(5);
+        VMImageParams params = VMImageParams.builder()
+                .label("UpdatedLabel")
+                .description(image.description()).recommendedVMSize(RoleSize.Type.A7)
+                .build();
+
+        String requestId = api().update(CAPTURED_IMAGE_NAME, params);
+        assertNotNull(requestId);
+        operationSucceeded.apply(requestId);
+    }
+
+    @Test
+    public void testList() {
+        List<VMImage> vmImageList = api().list();
+        assertTrue(vmImageList.size() > 0);
+        for (VMImage VMImage : vmImageList) {
+            checkVMImage(VMImage);
+        }
+    }
+
+    @Test(dependsOnMethods = {"testList", "testUpdateVMImage"})
+    public void testDelete() {
+        String requestId = api().delete(CAPTURED_IMAGE_NAME);
+        assertNotNull(requestId);
+        assertTrue(operationSucceeded.apply(requestId), requestId);
+    }
+
+    private void checkVMImage(VMImage image) {
+        assertNotNull(image.label(), "Label cannot be null for " + image);
+        assertNotNull(image.name(), "Name cannot be null for " + image);
+        assertNotNull(image.location(), "Location cannot be null for " + image);
+
+        //OSImage
+        VMImage.OSDiskConfiguration osDiskConfiguration = image.osDiskConfiguration();
+        assertNotNull(osDiskConfiguration);
+        assertNotNull(osDiskConfiguration.name());
+        assertTrue(osDiskConfiguration.logicalSizeInGB() > 0);
+
+        if (osDiskConfiguration.mediaLink() != null) {
+            assertTrue(ImmutableSet.of("http", "https").contains(osDiskConfiguration.mediaLink().getScheme()),
+                    "MediaLink should be an http(s) url" + image);
+        }
+
+        if (image.category() != null) {
+            assertNotEquals("", image.category().trim(), "Invalid Category for " + image);
+        }
+    }
+
+    @AfterClass
+    @Override
+    protected void tearDown() {
+        assertTrue(new ConflictManagementPredicate(api) {
+            @Override
+            protected String operation() {
+                return api.getDiskApi().delete(diskName);
+            }
+        }.apply(diskName));
+        super.tearDown();
+    }
+
+    private VMImageApi api() {
+        return api.getVMImageApi();
+    }
+
+    private Deployment.RoleInstance getFirstRoleInstanceInDeployment(String deployment) {
+        return Iterables.getOnlyElement(api.getDeploymentApiForService(cloudService.name()).get(deployment).
+                roleInstanceList());
+    }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6b4de9e7/azurecompute/src/test/java/org/jclouds/azurecompute/features/VMImageApiMockTest.java
----------------------------------------------------------------------
diff --git a/azurecompute/src/test/java/org/jclouds/azurecompute/features/VMImageApiMockTest.java b/azurecompute/src/test/java/org/jclouds/azurecompute/features/VMImageApiMockTest.java
new file mode 100644
index 0000000..2396bb0
--- /dev/null
+++ b/azurecompute/src/test/java/org/jclouds/azurecompute/features/VMImageApiMockTest.java
@@ -0,0 +1,198 @@
+/*
+ * 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.features;
+
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.assertEquals;
+
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+import org.jclouds.azurecompute.domain.OSImage;
+import org.jclouds.azurecompute.domain.RoleSize;
+import org.jclouds.azurecompute.domain.VMImageParams;
+import org.jclouds.azurecompute.domain.CaptureVMImageParams;
+import org.jclouds.azurecompute.domain.VMImage;
+import org.jclouds.azurecompute.internal.BaseAzureComputeApiMockTest;
+import org.jclouds.azurecompute.xml.ListVMImagesHandlerTest;
+import org.testng.annotations.Test;
+
+import java.net.URI;
+
+@Test(groups = "unit", testName = "VMImageApiMockTest")
+public class VMImageApiMockTest extends BaseAzureComputeApiMockTest {
+
+    public void listWhenFound() throws Exception {
+        MockWebServer server = mockAzureManagementServer();
+        server.enqueue(xmlResponse("/vmimages.xml"));
+
+        try {
+            VMImageApi api = api(server.getUrl("/")).getVMImageApi();
+
+            assertEquals(api.list(), ListVMImagesHandlerTest.expected());
+
+            assertSent(server, "GET", "/services/vmimages");
+        } finally {
+            server.shutdown();
+        }
+    }
+
+    public void listWhenNotFound() throws Exception {
+        MockWebServer server = mockAzureManagementServer();
+        server.enqueue(new MockResponse().setResponseCode(404));
+
+        try {
+            VMImageApi api = api(server.getUrl("/")).getVMImageApi();
+
+            assertTrue(api.list().isEmpty());
+
+            assertSent(server, "GET", "/services/vmimages");
+        } finally {
+            server.shutdown();
+        }
+    }
+
+    public void create() throws Exception {
+        MockWebServer server = mockAzureManagementServer();
+        server.enqueue(requestIdResponse("request-1"));
+
+        try {
+            VMImageApi api = api(server.getUrl("/")).getVMImageApi();
+
+            VMImageParams.OSDiskConfigurationParams osParams = VMImageParams.OSDiskConfigurationParams
+                    .OSDiskConfiguration("ClouderaGolden-os_disk",
+                            VMImageParams.OSDiskConfigurationParams.Caching.READ_ONLY,
+                            VMImageParams.OSDiskConfigurationParams.OSState.SPECIALIZED,
+                            OSImage.Type.LINUX,
+                            URI.create("http://blobs/disks/neotysss/MSFT__Win2K8R2SP1-ABCD-en-us-30GB.vhd"),
+                            30,
+                            "Standard");
+            VMImageParams params = VMImageParams.builder()
+                    .name("ClouderaGolden")
+                    .label("CDH 5.1 Evaluation")
+                    .description("Single click deployment")
+                    .recommendedVMSize(RoleSize.Type.LARGE)
+                    .osDiskConfiguration(osParams)
+                    .imageFamily("Ubuntu")
+                    .language("en")
+                    .eula("http://www.gnu.org/copyleft/gpl.html")
+                    .iconUri(URI.create("http://www.cloudera.com/content/cloudera/en/privacy-policy.html"))
+                    .smallIconUri(URI.create("http://www.cloudera.com/content/cloudera/en/privacy-policy.html"))
+                    .privacyUri(URI.create("http://www.cloudera.com/content/cloudera/en/privacy-policy.html"))
+                    .showGui(Boolean.TRUE)
+                    .build();
+
+            assertEquals(api.create(params), "request-1");
+
+            assertSent(server, "POST", "/services/vmimages", "/vmimageparams.xml");
+        } finally {
+            server.shutdown();
+        }
+    }
+
+    public void testUpdate() throws Exception {
+        MockWebServer server = mockAzureManagementServer();
+        server.enqueue(requestIdResponse("request-1"));
+
+        try {
+            VMImageApi api = api(server.getUrl("/")).getVMImageApi();
+
+            VMImageParams.OSDiskConfigurationParams osParams = VMImageParams.OSDiskConfigurationParams
+                    .OSDiskConfiguration("ClouderaGolden-os_disk",
+                            VMImageParams.OSDiskConfigurationParams.Caching.READ_ONLY,
+                            VMImageParams.OSDiskConfigurationParams.OSState.SPECIALIZED,
+                            OSImage.Type.LINUX,
+                            URI.create("http://blobs/disks/neotysss/MSFT__Win2K8R2SP1-ABCD-en-us-30GB.vhd"),
+                            30,
+                            "Standard");
+            VMImageParams params = VMImageParams.builder()
+                    .name("ClouderaGolden")
+                    .label("CDH 5.1 Evaluation")
+                    .description("Single click deployment")
+                    .recommendedVMSize(RoleSize.Type.LARGE)
+                    .osDiskConfiguration(osParams)
+                    .imageFamily("Ubuntu")
+                    .language("en")
+                    .eula("http://www.gnu.org/copyleft/gpl.html")
+                    .iconUri(URI.create("http://www.cloudera.com/content/cloudera/en/privacy-policy.html"))
+                    .smallIconUri(URI.create("http://www.cloudera.com/content/cloudera/en/privacy-policy.html"))
+                    .privacyUri(URI.create("http://www.cloudera.com/content/cloudera/en/privacy-policy.html"))
+                    .showGui(Boolean.TRUE)
+                    .build();
+
+            assertEquals(api.update("myvmimage", params), "request-1");
+
+            assertSent(server, "PUT", "/services/vmimages/myvmimage", "/vmimageparams.xml");
+        } finally {
+            server.shutdown();
+        }
+    }
+
+    public void deleteWhenFound() throws Exception {
+        MockWebServer server = mockAzureManagementServer();
+        server.enqueue(requestIdResponse("request-1"));
+
+        try {
+            VMImageApi api = api(server.getUrl("/")).getVMImageApi();
+
+            assertEquals(api.delete("myvmimage"), "request-1");
+
+            assertSent(server, "DELETE", "/services/vmimages/myvmimage");
+        } finally {
+            server.shutdown();
+        }
+    }
+
+    public void deleteWhenNotFound() throws Exception {
+        MockWebServer server = mockAzureManagementServer();
+        server.enqueue(new MockResponse().setResponseCode(404));
+
+        try {
+            VMImageApi api = api(server.getUrl("/")).getVMImageApi();
+
+            assertNull(api.delete("myvmimage"));
+
+            assertSent(server, "DELETE", "/services/vmimages/myvmimage");
+        } finally {
+            server.shutdown();
+        }
+    }
+
+    @Test
+    public void testCaptureVMImage() throws Exception {
+
+        MockWebServer server = mockAzureManagementServer();
+        server.enqueue(requestIdResponse("request-1"));
+
+        try {
+            CaptureVMImageParams captureParams = CaptureVMImageParams.builder()
+                    .osState(VMImage.OSDiskConfiguration.OSState.GENERALIZED).name("capturedimage")
+                    .label("CapturedImage").recommendedVMSize(RoleSize.Type.MEDIUM).build();
+
+            VirtualMachineApi api = api(server.getUrl("/")).
+                    getVirtualMachineApiForDeploymentInService("mydeployment", "myservice");
+
+            assertEquals(api.capture("myvirtualmachine", captureParams), "request-1");
+            assertSent(server, "POST",
+                    "/services/hostedservices/myservice/deployments/mydeployment/roleinstances/" +
+                            "myvirtualmachine/Operations",
+                    "/vmimageparams_mock.xml");
+        } finally {
+            server.shutdown();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6b4de9e7/azurecompute/src/test/java/org/jclouds/azurecompute/features/VirtualMachineApiLiveTest.java
----------------------------------------------------------------------
diff --git a/azurecompute/src/test/java/org/jclouds/azurecompute/features/VirtualMachineApiLiveTest.java b/azurecompute/src/test/java/org/jclouds/azurecompute/features/VirtualMachineApiLiveTest.java
index 1d20841..7b67b85 100644
--- a/azurecompute/src/test/java/org/jclouds/azurecompute/features/VirtualMachineApiLiveTest.java
+++ b/azurecompute/src/test/java/org/jclouds/azurecompute/features/VirtualMachineApiLiveTest.java
@@ -42,6 +42,9 @@ import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
+/*
+ * Note: Live test for CaptureVMImage method is in VMImageApiLiveTest class
+ */
 @Test(groups = "live", testName = "VirtualMachineApiLiveTest", singleThreaded = true)
 public class VirtualMachineApiLiveTest extends BaseAzureComputeApiLiveTest {
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6b4de9e7/azurecompute/src/test/java/org/jclouds/azurecompute/features/VirtualMachineApiMockTest.java
----------------------------------------------------------------------
diff --git a/azurecompute/src/test/java/org/jclouds/azurecompute/features/VirtualMachineApiMockTest.java b/azurecompute/src/test/java/org/jclouds/azurecompute/features/VirtualMachineApiMockTest.java
index f6529d4..5bdd3a8 100644
--- a/azurecompute/src/test/java/org/jclouds/azurecompute/features/VirtualMachineApiMockTest.java
+++ b/azurecompute/src/test/java/org/jclouds/azurecompute/features/VirtualMachineApiMockTest.java
@@ -25,6 +25,9 @@ import org.testng.annotations.Test;
 
 import com.squareup.okhttp.mockwebserver.MockWebServer;
 
+/*
+ * Note: Mock test for CaptureVMImage method is in VMImageApiMockTest class
+ */
 @Test(groups = "unit", testName = "VirtualMachineApiMockTest")
 public class VirtualMachineApiMockTest extends BaseAzureComputeApiMockTest {
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6b4de9e7/azurecompute/src/test/java/org/jclouds/azurecompute/xml/ListVMImagesHandlerTest.java
----------------------------------------------------------------------
diff --git a/azurecompute/src/test/java/org/jclouds/azurecompute/xml/ListVMImagesHandlerTest.java b/azurecompute/src/test/java/org/jclouds/azurecompute/xml/ListVMImagesHandlerTest.java
new file mode 100644
index 0000000..4befa28
--- /dev/null
+++ b/azurecompute/src/test/java/org/jclouds/azurecompute/xml/ListVMImagesHandlerTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.xml;
+
+import static org.testng.Assert.assertEquals;
+
+import com.google.common.collect.ImmutableList;
+import org.jclouds.azurecompute.domain.DataVirtualHardDisk;
+import org.jclouds.azurecompute.domain.OSImage;
+import org.jclouds.azurecompute.domain.VMImage;
+import org.jclouds.date.DateService;
+import org.jclouds.date.internal.SimpleDateFormatDateService;
+import org.jclouds.http.functions.BaseHandlerTest;
+import org.testng.annotations.Test;
+
+import java.io.InputStream;
+import java.net.URI;
+import java.util.List;
+
+@Test(groups = "unit", testName = "ListVMImagesHandlerTest")
+public class ListVMImagesHandlerTest extends BaseHandlerTest {
+
+   public void test() {
+      InputStream is = getClass().getResourceAsStream("/vmimages.xml");
+      List<VMImage> result = factory.create(new ListVMImagesHandler(new VMImageHandler(
+            new DataVirtualHardDiskHandler(),
+            new OSConfigHandler()
+      ))).parse(is);
+
+      assertEquals(result, expected());
+   }
+
+   public static List<VMImage> expected() {
+      DateService dateService = new SimpleDateFormatDateService();
+      return ImmutableList.of(
+            VMImage.create("1acf693f34c74e86a50be61cb631ddfe__ClouderaGolden-202406-699696",
+                  "CDH 5.1 Evaluation",
+                  "Public",
+                  "Single click deployment of CDH 5.1 Evaluation for MR, HDFS and HIVE",
+                  OSDiskConfig(),
+                  dataDiskConfig(),
+                  null,
+                  null,
+                  null,
+                  "East Asia;Southeast Asia;Australia East;Australia Southeast;Brazil South;North Europe",
+                  null,
+                  dateService.iso8601DateOrSecondsDateParse("2014-07-05T14:55:17Z"),
+                  dateService.iso8601DateOrSecondsDateParse("2014-09-06T22:58:11Z"),
+                  null,
+                  "CDH 5.1 Evaluation",
+                  null,
+                  false,
+                  "http://www.gnu.org/copyleft/gpl.html",
+                  null,
+                  null,
+                  URI.create("http://www.cloudera.com/content/cloudera/en/privacy-policy.html"),
+                  dateService.iso8601DateOrSecondsDateParse("2012-07-05T14:55:17Z")
+            )
+      );
+   }
+
+   public static VMImage.OSDiskConfiguration OSDiskConfig() {
+      return VMImage.OSDiskConfiguration.create("ClouderaGolden-202406-699696-os-2014-10-06",
+            VMImage.OSDiskConfiguration.Caching.READ_WRITE,
+            VMImage.OSDiskConfiguration.OSState.SPECIALIZED,
+            OSImage.Type.LINUX,
+            null,
+            30,
+            null);
+   }
+
+   public static List<DataVirtualHardDisk> dataDiskConfig() {
+      return ImmutableList.of(
+            DataVirtualHardDisk.create(
+                  DataVirtualHardDisk.Caching.READ_ONLY,
+                  "testimage1-testimage1-0-20120817095145",
+                  10,
+                  30,
+                  URI.create("http://blobs/disks/neotysss/MSFT__Win2K8R2SP1-ABCD-en-us-30GB.vhd"),
+                  "Standard"
+            ),
+            DataVirtualHardDisk.create(
+                  DataVirtualHardDisk.Caching.READ_WRITE,
+                  "testimage2-testimage2-0-20120817095145",
+                  20,
+                  30,
+                  URI.create("http://blobs/disks/neotysss/MSFT__Win2K8R2SP1-ABCD-en-us-30GB.vhd"),
+                  "Standard"
+            )
+      );
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6b4de9e7/azurecompute/src/test/resources/vmimageparams.xml
----------------------------------------------------------------------
diff --git a/azurecompute/src/test/resources/vmimageparams.xml b/azurecompute/src/test/resources/vmimageparams.xml
new file mode 100644
index 0000000..43a54bf
--- /dev/null
+++ b/azurecompute/src/test/resources/vmimageparams.xml
@@ -0,0 +1 @@
+<VMImage xmlns="http://schemas.microsoft.com/windowsazure"><Name>ClouderaGolden</Name><Label>CDH 5.1 Evaluation</Label><Description>Single click deployment</Description><OSDiskConfiguration><HostCaching>ReadOnly</HostCaching><OSState>SPECIALIZED</OSState><OS>LINUX</OS><MediaLink>http://blobs/disks/neotysss/MSFT__Win2K8R2SP1-ABCD-en-us-30GB.vhd</MediaLink></OSDiskConfiguration><DataDiskConfigurations/><Language>en</Language><ImageFamily>Ubuntu</ImageFamily><RecommendedVMSize>Large</RecommendedVMSize><Eula>http://www.gnu.org/copyleft/gpl.html</Eula><IconUri>http://www.cloudera.com/content/cloudera/en/privacy-policy.html</IconUri><SmallIconUri>http://www.cloudera.com/content/cloudera/en/privacy-policy.html</SmallIconUri><PrivacyUri>http://www.cloudera.com/content/cloudera/en/privacy-policy.html</PrivacyUri><ShowGui>true</ShowGui></VMImage>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6b4de9e7/azurecompute/src/test/resources/vmimageparams_mock.xml
----------------------------------------------------------------------
diff --git a/azurecompute/src/test/resources/vmimageparams_mock.xml b/azurecompute/src/test/resources/vmimageparams_mock.xml
new file mode 100644
index 0000000..bd62d9c
--- /dev/null
+++ b/azurecompute/src/test/resources/vmimageparams_mock.xml
@@ -0,0 +1 @@
+<CaptureRoleAsVMImageOperation xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><OperationType>CaptureRoleAsVMImageOperation</OperationType><OSState>Generalized</OSState><VMImageName>capturedimage</VMImageName><VMImageLabel>CapturedImage</VMImageLabel><Description/><Language/><ImageFamily/><RecommendedVMSize>Medium</RecommendedVMSize></CaptureRoleAsVMImageOperation>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6b4de9e7/azurecompute/src/test/resources/vmimages.xml
----------------------------------------------------------------------
diff --git a/azurecompute/src/test/resources/vmimages.xml b/azurecompute/src/test/resources/vmimages.xml
new file mode 100644
index 0000000..f92e74b
--- /dev/null
+++ b/azurecompute/src/test/resources/vmimages.xml
@@ -0,0 +1,56 @@
+<VMImages xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
+    <VMImage>
+        <Name>1acf693f34c74e86a50be61cb631ddfe__ClouderaGolden-202406-699696</Name>
+        <Label>CDH 5.1 Evaluation</Label>
+        <Category>Public</Category>
+        <Description>Single click deployment of CDH 5.1 Evaluation for MR, HDFS and HIVE</Description>
+        <OSDiskConfiguration>
+            <Name>ClouderaGolden-202406-699696-os-2014-10-06</Name>
+            <HostCaching>ReadWrite</HostCaching>
+            <OSState>SPECIALIZED</OSState>
+            <OS>Linux</OS>
+            <MediaLink/>
+            <LogicalDiskSizeInGB>30</LogicalDiskSizeInGB>
+            <IOType/>
+        </OSDiskConfiguration>
+        <DataDiskConfigurations>
+            <DataDiskConfiguration>
+                <Name>testimage1-testimage1-0-20120817095145</Name>
+                <HostCaching>ReadOnly</HostCaching>
+                <Lun>10</Lun>
+                <MediaLink>http://blobs/disks/neotysss/MSFT__Win2K8R2SP1-ABCD-en-us-30GB.vhd</MediaLink>
+                <LogicalDiskSizeInGB>30</LogicalDiskSizeInGB>
+                <IOType>Standard</IOType>
+            </DataDiskConfiguration>
+            <DataDiskConfiguration>
+                <Name>testimage2-testimage2-0-20120817095145</Name>
+                <HostCaching>ReadWrite</HostCaching>
+                <Lun>20</Lun>
+                <MediaLink>http://blobs/disks/neotysss/MSFT__Win2K8R2SP1-ABCD-en-us-30GB.vhd</MediaLink>
+                <LogicalDiskSizeInGB>30</LogicalDiskSizeInGB>
+                <IOType>Standard</IOType>
+            </DataDiskConfiguration>
+        </DataDiskConfigurations>
+        <ServiceName/>
+        <DeploymentName/>
+        <RoleName/>
+        <Location>
+            East Asia;Southeast Asia;Australia East;Australia Southeast;Brazil South;North Europe
+        </Location>
+        <AffinityGroup/>
+        <CreatedTime>2014-07-05T14:55:17Z</CreatedTime>
+        <ModifiedTime>2014-09-06T22:58:11Z</ModifiedTime>
+        <Language/>
+        <ImageFamily>CDH 5.1 Evaluation</ImageFamily>
+        <RecommendedVMSize/>
+        <IsPremium>False</IsPremium>
+        <Eula>http://www.gnu.org/copyleft/gpl.html</Eula>
+        <IconUri/>
+        <SmallIconUri></SmallIconUri>
+        <PrivacyUri>http://www.cloudera.com/content/cloudera/en/privacy-policy.html</PrivacyUri>
+        <PublisherName>publisher-identifier</PublisherName>
+        <PublishedDate>2012-07-05T14:55:17Z</PublishedDate>
+        <ShowInGui>indicator-of-availability</ShowInGui>
+        <PricingDetailLink>pricing-details</PricingDetailLink>
+    </VMImage>
+</VMImages>