You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by ad...@apache.org on 2014/11/22 17:09:47 UTC

[1/3] jclouds git commit: JCLOUDS-480 support version 4 signatures for aws-ec2.

Repository: jclouds
Updated Branches:
  refs/heads/master ba868af55 -> a449b24e7


http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/AWSAMIApiTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/AWSAMIApiTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/AWSAMIApiTest.java
deleted file mode 100644
index cc99fb2..0000000
--- a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/AWSAMIApiTest.java
+++ /dev/null
@@ -1,496 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.jclouds.aws.ec2.features;
-
-import static org.jclouds.ec2.options.DescribeImagesOptions.Builder.executableBy;
-import static org.jclouds.reflect.Reflection2.method;
-
-import java.io.IOException;
-
-import org.jclouds.Fallbacks.EmptySetOnNotFoundOr404;
-import org.jclouds.aws.ec2.xml.ProductCodesHandler;
-import org.jclouds.ec2.options.CreateImageOptions;
-import org.jclouds.ec2.options.DescribeImagesOptions;
-import org.jclouds.ec2.options.RegisterImageBackedByEbsOptions;
-import org.jclouds.ec2.options.RegisterImageOptions;
-import org.jclouds.ec2.xml.BlockDeviceMappingHandler;
-import org.jclouds.ec2.xml.DescribeImagesResponseHandler;
-import org.jclouds.ec2.xml.ImageIdHandler;
-import org.jclouds.ec2.xml.PermissionHandler;
-import org.jclouds.http.HttpRequest;
-import org.jclouds.http.functions.ParseSax;
-import org.jclouds.http.functions.ReleasePayloadAndReturn;
-import org.jclouds.rest.internal.GeneratedHttpRequest;
-import org.testng.annotations.Test;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.reflect.Invokable;
-/**
- * Tests behavior of {@code AWSAMIApi}
- */
-// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire
-@Test(groups = "unit", testName = "AWSAMIApiTest")
-public class AWSAMIApiTest extends BaseAWSEC2ApiTest<AWSAMIApi> {
-   public AWSAMIApiTest() {
-      provider = "aws-ec2";
-   }
-
-   HttpRequest createImage = HttpRequest.builder().method("POST")
-                                        .endpoint("https://ec2.us-east-1.amazonaws.com/")
-                                        .addHeader("Host", "ec2.us-east-1.amazonaws.com")
-                                        .addFormParam("Action", "CreateImage")
-                                        .addFormParam("InstanceId", "instanceId")
-                                        .addFormParam("Name", "name").build();
-
-   public void testCreateImage() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(AWSAMIApi.class, "createImageInRegion", String.class, String.class, String.class,
-               CreateImageOptions[].class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList(null, "name", "instanceId"));
-
-      request = (GeneratedHttpRequest) request.getFilters().get(0).filter(request);
-      
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(request, filter.filter(createImage).getPayload().getRawContent().toString(),
-            "application/x-www-form-urlencoded", false);
-      assertResponseParserClassEquals(method, request, ParseSax.class);
-      assertSaxResponseParserClassEquals(method, ImageIdHandler.class);
-      assertFallbackClassEquals(method, null);
-
-      checkFilters(request);
-   }
-
-   HttpRequest createImageOptions = HttpRequest.builder().method("POST")
-                                               .endpoint("https://ec2.us-east-1.amazonaws.com/")
-                                               .addHeader("Host", "ec2.us-east-1.amazonaws.com")
-                                               .addFormParam("Action", "CreateImage")
-                                               .addFormParam("Description", "description")
-                                               .addFormParam("InstanceId", "instanceId")
-                                               .addFormParam("Name", "name")
-                                               .addFormParam("NoReboot", "true").build();
-
-   public void testCreateImageOptions() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(AWSAMIApi.class, "createImageInRegion", String.class, String.class, String.class,
-               CreateImageOptions[].class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList(null, "name", "instanceId", new CreateImageOptions()
-               .withDescription("description").noReboot()));
-
-      request = (GeneratedHttpRequest) request.getFilters().get(0).filter(request);
-      
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(request, filter.filter(createImageOptions).getPayload().getRawContent().toString(),
-            "application/x-www-form-urlencoded", false);
-
-      assertResponseParserClassEquals(method, request, ParseSax.class);
-      assertSaxResponseParserClassEquals(method, ImageIdHandler.class);
-      assertFallbackClassEquals(method, null);
-
-      checkFilters(request);
-   }
-
-   HttpRequest describeImages = HttpRequest.builder().method("POST")
-                                           .endpoint("https://ec2.us-east-1.amazonaws.com/")
-                                           .addHeader("Host", "ec2.us-east-1.amazonaws.com")
-                                           .addFormParam("Action", "DescribeImages").build();
-
-   public void testDescribeImages() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(AWSAMIApi.class, "describeImagesInRegion", String.class,
-               DescribeImagesOptions[].class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList((String) null));
-
-      request = (GeneratedHttpRequest) request.getFilters().get(0).filter(request);
-      
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(request, filter.filter(describeImages).getPayload().getRawContent().toString(),
-            "application/x-www-form-urlencoded", false);
-
-      assertResponseParserClassEquals(method, request, ParseSax.class);
-      assertSaxResponseParserClassEquals(method, DescribeImagesResponseHandler.class);
-      assertFallbackClassEquals(method, EmptySetOnNotFoundOr404.class);
-
-      checkFilters(request);
-   }
-
-   HttpRequest describeImagesOptions = HttpRequest.builder().method("POST")
-                                                  .endpoint("https://ec2.us-east-1.amazonaws.com/")
-                                                  .addHeader("Host", "ec2.us-east-1.amazonaws.com")
-                                                  .addFormParam("Action", "DescribeImages")
-                                                  .addFormParam("ExecutableBy", "me")
-                                                  .addFormParam("ImageId.1", "1")
-                                                  .addFormParam("ImageId.2", "2")
-                                                  .addFormParam("Owner.1", "fred")
-                                                  .addFormParam("Owner.2", "nancy").build();
-
-   public void testDescribeImagesOptions() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(AWSAMIApi.class, "describeImagesInRegion", String.class,
-               DescribeImagesOptions[].class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList(null, executableBy("me").ownedBy("fred", "nancy").imageIds(
-               "1", "2")));
-
-      request = (GeneratedHttpRequest) request.getFilters().get(0).filter(request);
-      
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(request, filter.filter(describeImagesOptions).getPayload().getRawContent().toString(),
-            "application/x-www-form-urlencoded", false);
-
-      assertResponseParserClassEquals(method, request, ParseSax.class);
-      assertSaxResponseParserClassEquals(method, DescribeImagesResponseHandler.class);
-      assertFallbackClassEquals(method, EmptySetOnNotFoundOr404.class);
-
-      checkFilters(request);
-   }
-
-   HttpRequest deregisterImage = HttpRequest.builder().method("POST")
-                                            .endpoint("https://ec2.us-east-1.amazonaws.com/")
-                                            .addHeader("Host", "ec2.us-east-1.amazonaws.com")
-                                            .addFormParam("Action", "DeregisterImage")
-                                            .addFormParam("ImageId", "imageId").build();
-
-   public void testDeregisterImage() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(AWSAMIApi.class, "deregisterImageInRegion", String.class, String.class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList(null, "imageId"));
-
-      request = (GeneratedHttpRequest) request.getFilters().get(0).filter(request);
-      
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(request, filter.filter(deregisterImage).getPayload().getRawContent().toString(),
-            "application/x-www-form-urlencoded", false);
-
-      assertResponseParserClassEquals(method, request, ReleasePayloadAndReturn.class);
-      assertSaxResponseParserClassEquals(method, null);
-      assertFallbackClassEquals(method, null);
-
-      checkFilters(request);
-   }
-
-   HttpRequest registerImageFromManifest = HttpRequest.builder().method("POST")
-                                                      .endpoint("https://ec2.us-east-1.amazonaws.com/")
-                                                      .addHeader("Host", "ec2.us-east-1.amazonaws.com")
-                                                      .addFormParam("Action", "RegisterImage")
-                                                      .addFormParam("ImageLocation", "pathToManifest")
-                                                      .addFormParam("Name", "name").build();
-
-   public void testRegisterImageFromManifest() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(AWSAMIApi.class, "registerImageFromManifestInRegion", String.class, String.class,
-               String.class, RegisterImageOptions[].class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList(null, "name", "pathToManifest"));
-
-      request = (GeneratedHttpRequest) request.getFilters().get(0).filter(request);
-      
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(request, filter.filter(registerImageFromManifest).getPayload().getRawContent().toString(),
-            "application/x-www-form-urlencoded", false);
-      assertResponseParserClassEquals(method, request, ParseSax.class);
-      assertSaxResponseParserClassEquals(method, ImageIdHandler.class);
-      assertFallbackClassEquals(method, null);
-
-      checkFilters(request);
-   }
-
-   HttpRequest registerImageFromManifestOptions = HttpRequest.builder().method("POST")
-                                                             .endpoint("https://ec2.us-east-1.amazonaws.com/")
-                                                             .addHeader("Host", "ec2.us-east-1.amazonaws.com")
-                                                             .addFormParam("Action", "RegisterImage")
-                                                             .addFormParam("Description", "description")
-                                                             .addFormParam("ImageLocation", "pathToManifest")
-                                                             .addFormParam("Name", "name").build();
-
-   public void testRegisterImageFromManifestOptions() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(AWSAMIApi.class, "registerImageFromManifestInRegion", String.class, String.class,
-               String.class, RegisterImageOptions[].class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList(null, "name", "pathToManifest", new RegisterImageOptions()
-               .withDescription("description")));
-
-      request = (GeneratedHttpRequest) request.getFilters().get(0).filter(request);
-      
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(request, filter.filter(registerImageFromManifestOptions).getPayload().getRawContent()
-            .toString(), "application/x-www-form-urlencoded", false);
-
-      assertResponseParserClassEquals(method, request, ParseSax.class);
-      assertSaxResponseParserClassEquals(method, ImageIdHandler.class);
-      assertFallbackClassEquals(method, null);
-
-      checkFilters(request);
-   }
-
-   HttpRequest registerImageBackedByEBS = HttpRequest.builder().method("POST")
-                                                     .endpoint("https://ec2.us-east-1.amazonaws.com/")
-                                                     .addHeader("Host", "ec2.us-east-1.amazonaws.com")
-                                                     .addFormParam("Action", "RegisterImage")
-                                                     .addFormParam("BlockDeviceMapping.0.DeviceName", "/dev/sda1")
-                                                     .addFormParam("BlockDeviceMapping.0.Ebs.SnapshotId", "snapshotId")
-                                                     .addFormParam("Name", "imageName")
-                                                     .addFormParam("RootDeviceName", "/dev/sda1").build();
-
-   public void testRegisterImageBackedByEBS() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(AWSAMIApi.class, "registerUnixImageBackedByEbsInRegion", String.class,
-               String.class, String.class, RegisterImageBackedByEbsOptions[].class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList(null, "imageName", "snapshotId"));
-
-      request = (GeneratedHttpRequest) request.getFilters().get(0).filter(request);
-      
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(request, filter.filter(registerImageBackedByEBS).getPayload().getRawContent().toString(),
-            "application/x-www-form-urlencoded", false);
-      assertResponseParserClassEquals(method, request, ParseSax.class);
-      assertSaxResponseParserClassEquals(method, ImageIdHandler.class);
-      assertFallbackClassEquals(method, null);
-
-      checkFilters(request);
-   }
-
-   HttpRequest registerImageBackedByEBSOptions = HttpRequest.builder().method("POST")
-           .endpoint("https://ec2.us-east-1.amazonaws.com/")
-           .addHeader("Host", "ec2.us-east-1.amazonaws.com")
-           .addFormParam("Action", "RegisterImage")
-           .addFormParam("BlockDeviceMapping.0.DeviceName", "/dev/sda1")
-           .addFormParam("BlockDeviceMapping.0.Ebs.SnapshotId", "snapshotId")
-           .addFormParam("BlockDeviceMapping.1.DeviceName", "/dev/device")
-           .addFormParam("BlockDeviceMapping.1.Ebs.DeleteOnTermination", "false")
-           .addFormParam("BlockDeviceMapping.1.Ebs.SnapshotId", "snapshot")
-           .addFormParam("BlockDeviceMapping.1.Ebs.VolumeType", "gp2")
-           .addFormParam("BlockDeviceMapping.2.DeviceName", "/dev/newdevice")
-           .addFormParam("BlockDeviceMapping.2.Ebs.DeleteOnTermination", "false")
-           .addFormParam("BlockDeviceMapping.2.Ebs.VolumeSize", "100")
-           .addFormParam("BlockDeviceMapping.2.VirtualName", "newblock")
-           .addFormParam("Description", "description")
-           .addFormParam("Name", "imageName")
-           .addFormParam("RootDeviceName", "/dev/sda1").build();
-
-   public void testRegisterImageBackedByEBSOptions() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(AWSAMIApi.class, "registerUnixImageBackedByEbsInRegion", String.class,
-               String.class, String.class, RegisterImageBackedByEbsOptions[].class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList(null, "imageName", "snapshotId",
-               new RegisterImageBackedByEbsOptions().withDescription("description").addBlockDeviceFromSnapshot(
-                        "/dev/device", null, "snapshot", false, "gp2", null, false).addNewBlockDevice("/dev/newdevice", "newblock", 100)));
-
-      request = (GeneratedHttpRequest) request.getFilters().get(0).filter(request);
-      
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(request, filter.filter(registerImageBackedByEBSOptions).getPayload().getRawContent()
-            .toString(), "application/x-www-form-urlencoded", false);
-
-      assertResponseParserClassEquals(method, request, ParseSax.class);
-      assertSaxResponseParserClassEquals(method, ImageIdHandler.class);
-      assertFallbackClassEquals(method, null);
-
-      checkFilters(request);
-   }
-
-   HttpRequest getBlockDeviceMappingsForImage = HttpRequest.builder().method("POST")
-                                                           .endpoint("https://ec2.us-east-1.amazonaws.com/")
-                                                           .addHeader("Host", "ec2.us-east-1.amazonaws.com")
-                                                           .addFormParam("Action", "DescribeImageAttribute")
-                                                           .addFormParam("Attribute", "blockDeviceMapping")
-                                                           .addFormParam("ImageId", "imageId").build();
-
-   public void testGetBlockDeviceMappingsForImage() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(AWSAMIApi.class, "getBlockDeviceMappingsForImageInRegion", String.class,
-               String.class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList(null, "imageId"));
-
-      request = (GeneratedHttpRequest) request.getFilters().get(0).filter(request);
-      
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(request, filter.filter(getBlockDeviceMappingsForImage).getPayload().getRawContent()
-            .toString(), "application/x-www-form-urlencoded", false);
-
-      assertResponseParserClassEquals(method, request, ParseSax.class);
-      assertSaxResponseParserClassEquals(method, BlockDeviceMappingHandler.class);
-      assertFallbackClassEquals(method, null);
-
-      checkFilters(request);
-   }
-
-   HttpRequest getLaunchPermissionForImage = HttpRequest.builder().method("POST")
-                                                        .endpoint("https://ec2.us-east-1.amazonaws.com/")
-                                                        .addHeader("Host", "ec2.us-east-1.amazonaws.com")
-                                                        .addFormParam("Action", "DescribeImageAttribute")
-                                                        .addFormParam("Attribute", "launchPermission")
-                                                        .addFormParam("ImageId", "imageId").build();
-
-   public void testGetLaunchPermissionForImage() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(AWSAMIApi.class, "getLaunchPermissionForImageInRegion", String.class, String.class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList(null, "imageId"));
-
-      request = (GeneratedHttpRequest) request.getFilters().get(0).filter(request);
-      
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(request, filter.filter(getLaunchPermissionForImage).getPayload().getRawContent().toString(),
-            "application/x-www-form-urlencoded", false);
-
-      assertResponseParserClassEquals(method, request, ParseSax.class);
-      assertSaxResponseParserClassEquals(method, PermissionHandler.class);
-      assertFallbackClassEquals(method, null);
-
-      checkFilters(request);
-   }
-
-   HttpRequest addLaunchPermission = HttpRequest.builder().method("POST")
-                                                          .endpoint("https://ec2.us-east-1.amazonaws.com/")
-                                                          .addHeader("Host", "ec2.us-east-1.amazonaws.com")
-                                                          .addFormParam("Action", "ModifyImageAttribute")
-                                                          .addFormParam("Attribute", "launchPermission")
-                                                          .addFormParam("ImageId", "imageId")
-                                                          .addFormParam("OperationType", "add")
-                                                          .addFormParam("UserGroup.1", "all")
-                                                          .addFormParam("UserId.1", "bob")
-                                                          .addFormParam("UserId.2", "sue").build();
-
-   public void testAddLaunchPermissionsToImage() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(AWSAMIApi.class, "addLaunchPermissionsToImageInRegion", String.class,
-               Iterable.class, Iterable.class, String.class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList(null, ImmutableList.of("bob", "sue"), ImmutableList
-               .of("all"), "imageId"));
-
-      request = (GeneratedHttpRequest) request.getFilters().get(0).filter(request);
-      
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(request, filter.filter(addLaunchPermission).getPayload().getRawContent().toString(),
-            "application/x-www-form-urlencoded", false);
-
-      assertResponseParserClassEquals(method, request, ReleasePayloadAndReturn.class);
-      assertSaxResponseParserClassEquals(method, null);
-      assertFallbackClassEquals(method, null);
-
-      checkFilters(request);
-   }
-
-   HttpRequest removeLaunchPermission = HttpRequest.builder().method("POST")
-                                                   .endpoint("https://ec2.us-east-1.amazonaws.com/")
-                                                   .addHeader("Host", "ec2.us-east-1.amazonaws.com")
-                                                   .addFormParam("Action", "ModifyImageAttribute")
-                                                   .addFormParam("Attribute", "launchPermission")
-                                                   .addFormParam("ImageId", "imageId")
-                                                   .addFormParam("OperationType", "remove")
-                                                   .addFormParam("UserGroup.1", "all")
-                                                   .addFormParam("UserId.1", "bob")
-                                                   .addFormParam("UserId.2", "sue").build();
-
-   public void testRemoveLaunchPermissionsFromImage() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(AWSAMIApi.class, "removeLaunchPermissionsFromImageInRegion", String.class,
-               Iterable.class, Iterable.class, String.class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList(null, ImmutableList.of("bob", "sue"), ImmutableList
-               .of("all"), "imageId"));
-
-      request = (GeneratedHttpRequest) request.getFilters().get(0).filter(request);
-      
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(request, filter.filter(removeLaunchPermission).getPayload().getRawContent().toString(),
-            "application/x-www-form-urlencoded", false);
-      assertResponseParserClassEquals(method, request, ReleasePayloadAndReturn.class);
-      assertSaxResponseParserClassEquals(method, null);
-      assertFallbackClassEquals(method, null);
-
-      checkFilters(request);
-   }
-
-   HttpRequest resetLaunchPermissionsOnImage = HttpRequest.builder().method("POST")
-                                                          .endpoint("https://ec2.us-east-1.amazonaws.com/")
-                                                          .addHeader("Host", "ec2.us-east-1.amazonaws.com")
-                                                          .addFormParam("Action", "ResetImageAttribute")
-                                                          .addFormParam("Attribute", "launchPermission")
-                                                          .addFormParam("ImageId", "imageId").build();
-
-   public void testResetLaunchPermissionsOnImage() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(AWSAMIApi.class, "resetLaunchPermissionsOnImageInRegion", String.class,
-               String.class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList(null, "imageId"));
-
-      request = (GeneratedHttpRequest) request.getFilters().get(0).filter(request);
-      
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(request,
-            filter.filter(resetLaunchPermissionsOnImage).getPayload().getRawContent().toString(),
-            "application/x-www-form-urlencoded", false);
-      assertResponseParserClassEquals(method, request, ReleasePayloadAndReturn.class);
-      assertSaxResponseParserClassEquals(method, null);
-      assertFallbackClassEquals(method, null);
-
-      checkFilters(request);
-   }
-
-   public void testGetProductCodesForImage() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(AWSAMIApi.class, "getProductCodesForImageInRegion", String.class, String.class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList(null, "imageId"));
-
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(request,
-            "Action=DescribeImageAttribute&Attribute=productCodes&ImageId=imageId",
-            "application/x-www-form-urlencoded", false);
-
-      assertResponseParserClassEquals(method, request, ParseSax.class);
-      assertSaxResponseParserClassEquals(method, ProductCodesHandler.class);
-      assertFallbackClassEquals(method, null);
-
-      checkFilters(request);
-   }
-
-   public void testAddProductCodesToImage() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(AWSAMIApi.class, "addProductCodesToImageInRegion", String.class, Iterable.class,
-            String.class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList(null, ImmutableList.of("code1", "code2"), "imageId"));
-
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(
-            request,
-            "Action=ModifyImageAttribute&OperationType=add&Attribute=productCodes&ImageId=imageId&ProductCode.1=code1&ProductCode.2=code2",
-            "application/x-www-form-urlencoded", false);
-
-      assertResponseParserClassEquals(method, request, ReleasePayloadAndReturn.class);
-      assertSaxResponseParserClassEquals(method, null);
-      assertFallbackClassEquals(method, null);
-
-      checkFilters(request);
-   }
-
-   public void testRemoveProductCodesFromImage() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(AWSAMIApi.class, "removeProductCodesFromImageInRegion", String.class,
-            Iterable.class, String.class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList(null, ImmutableList.of("code1", "code2"), "imageId"));
-
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(
-            request,
-            "Action=ModifyImageAttribute&OperationType=remove&Attribute=productCodes&ImageId=imageId&ProductCode.1=code1&ProductCode.2=code2",
-            "application/x-www-form-urlencoded", false);
-
-      assertResponseParserClassEquals(method, request, ReleasePayloadAndReturn.class);
-      assertSaxResponseParserClassEquals(method, null);
-      assertFallbackClassEquals(method, null);
-
-      checkFilters(request);
-   }
-}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/AWSSecurityGroupApiTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/AWSSecurityGroupApiTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/AWSSecurityGroupApiTest.java
index 735af5b..2e73dfe 100644
--- a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/AWSSecurityGroupApiTest.java
+++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/AWSSecurityGroupApiTest.java
@@ -24,7 +24,6 @@ import org.jclouds.Fallbacks.EmptySetOnNotFoundOr404;
 import org.jclouds.Fallbacks.VoidOnNotFoundOr404;
 import org.jclouds.aws.ec2.xml.AWSEC2DescribeSecurityGroupsResponseHandler;
 import org.jclouds.ec2.util.IpPermissions;
-import org.jclouds.http.HttpRequest;
 import org.jclouds.http.functions.ParseSax;
 import org.jclouds.http.functions.ReleasePayloadAndReturn;
 import org.jclouds.net.domain.IpPermission;
@@ -97,13 +96,6 @@ public class AWSSecurityGroupApiTest extends BaseAWSEC2ApiTest<AWSSecurityGroupA
       checkFilters(request);
    }
 
-   HttpRequest createSecurityGroup = HttpRequest.builder().method("POST")
-                                                .endpoint("https://ec2.us-east-1.amazonaws.com/")
-                                                .addHeader("Host", "ec2.us-east-1.amazonaws.com")
-                                                .addFormParam("Action", "CreateSecurityGroup")
-                                                .addFormParam("GroupDescription", "description")
-                                                .addFormParam("GroupName", "name").build();
-
    public void testCreateSecurityGroup() throws SecurityException, NoSuchMethodException, IOException {
       Invokable<?, ?> method = method(AWSSecurityGroupApi.class, "createSecurityGroupInRegion", String.class,
             String.class, String.class);
@@ -113,7 +105,7 @@ public class AWSSecurityGroupApiTest extends BaseAWSEC2ApiTest<AWSSecurityGroupA
 
       assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
       assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(request, filter.filter(createSecurityGroup).getPayload().getRawContent().toString(),
+      assertPayloadEquals(request, "Action=CreateSecurityGroup&GroupName=name&GroupDescription=description&Version=2012-06-01",
             "application/x-www-form-urlencoded", false);
 
       assertResponseParserClassEquals(method, request, ReleasePayloadAndReturn.class);

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/BaseAWSEC2ApiTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/BaseAWSEC2ApiTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/BaseAWSEC2ApiTest.java
index caf6e48..349b780 100644
--- a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/BaseAWSEC2ApiTest.java
+++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/BaseAWSEC2ApiTest.java
@@ -17,7 +17,10 @@
 package org.jclouds.aws.ec2.features;
 
 import static com.google.common.collect.Maps.transformValues;
+import static com.google.common.net.HttpHeaders.AUTHORIZATION;
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
 
 import java.io.IOException;
 import java.net.URI;
@@ -29,7 +32,8 @@ import javax.inject.Singleton;
 import org.jclouds.aws.domain.Region;
 import org.jclouds.aws.ec2.AWSEC2ProviderMetadata;
 import org.jclouds.aws.ec2.config.AWSEC2HttpApiModule;
-import org.jclouds.aws.filters.FormSigner;
+import org.jclouds.aws.filters.FormSignerV4;
+import org.jclouds.aws.filters.FormSignerV4.ServiceAndRegion;
 import org.jclouds.compute.domain.Image;
 import org.jclouds.date.DateService;
 import org.jclouds.ec2.compute.domain.RegionAndName;
@@ -49,18 +53,20 @@ import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
 import com.google.inject.Module;
 import com.google.inject.Provides;
 
 @Test(groups = "unit")
 public abstract class BaseAWSEC2ApiTest<T> extends BaseRestAnnotationProcessingTest<T> {
 
-      @ConfiguresHttpApi
+   @ConfiguresHttpApi
    protected static class StubAWSEC2HttpApiModule extends AWSEC2HttpApiModule {
 
       @Override
       protected String provideTimeStamp(DateService dateService) {
-         return "2009-11-08T15:54:08.897Z";
+         return "20120416T155408Z";
       }
 
       @Provides
@@ -83,10 +89,10 @@ public abstract class BaseAWSEC2ApiTest<T> extends BaseRestAnnotationProcessingT
 
             @Override
             public Map<String, Supplier<URI>> get() {
-               return transformValues(ImmutableMap.<String, URI> of(Region.EU_WEST_1, URI
-                        .create("https://ec2.eu-west-1.amazonaws.com"), Region.US_EAST_1, URI
-                        .create("https://ec2.us-east-1.amazonaws.com"), Region.US_WEST_1, URI
-                        .create("https://ec2.us-west-1.amazonaws.com")), Suppliers2.<URI> ofInstanceFunction());
+               return transformValues(ImmutableMap
+                     .<String, URI>of(Region.EU_WEST_1, URI.create("https://ec2.eu-west-1.amazonaws.com"),
+                           Region.US_EAST_1, URI.create("https://ec2.us-east-1.amazonaws.com"), Region.US_WEST_1,
+                           URI.create("https://ec2.us-west-1.amazonaws.com")), Suppliers2.<URI>ofInstanceFunction());
             }
 
          });
@@ -100,21 +106,49 @@ public abstract class BaseAWSEC2ApiTest<T> extends BaseRestAnnotationProcessingT
 
          });
       }
+
+      @Provides ServiceAndRegion ServiceAndRegion(){
+         return new ServiceAndRegion() {
+            @Override public String service() {
+               return "ec2";
+            }
+
+            @Override public String region(String host) {
+               return "us-east-1";
+            }
+         };
+      }
+   }
+
+   @Override protected void assertNonPayloadHeadersEqual(HttpRequest request, String toMatch) {
+      Multimap<String, String> headersToCheck = LinkedHashMultimap.create();
+      for (String key : request.getHeaders().keySet()) {
+         if (key.equals("X-Amz-Date")) {
+            assertEquals(request.getFirstHeaderOrNull(key), "20120416T155408Z");
+         } else if (key.equals("Authorization")) {
+            assertThat(request.getFirstHeaderOrNull(AUTHORIZATION)).startsWith(
+                  "AWS4-HMAC-SHA256 Credential=identity/20120416/"
+                        + "us-east-1/ec2/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=");
+         } else {
+            headersToCheck.putAll(key, request.getHeaders().get(key));
+         }
+      }
+      assertEquals(sortAndConcatHeadersIntoString(headersToCheck), toMatch);
    }
 
-   protected FormSigner filter;
+   protected FormSignerV4 filter;
 
    @Override
    protected void checkFilters(HttpRequest request) {
       assertEquals(request.getFilters().size(), 1);
-      assertEquals(request.getFilters().get(0).getClass(), FormSigner.class);
+      assertTrue(request.getFilters().get(0) instanceof FormSignerV4);
    }
 
    @Override
    @BeforeTest
    protected void setupFactory() throws IOException {
       super.setupFactory();
-      this.filter = injector.getInstance(FormSigner.class);
+      this.filter = injector.getInstance(FormSignerV4.class);
    }
 
    @Override

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/MonitoringApiMockTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/MonitoringApiMockTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/MonitoringApiMockTest.java
new file mode 100644
index 0000000..cad674c
--- /dev/null
+++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/MonitoringApiMockTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.aws.ec2.features;
+
+import static org.testng.Assert.assertFalse;
+
+import java.util.Map;
+
+import org.jclouds.aws.ec2.domain.MonitoringState;
+import org.jclouds.aws.ec2.internal.BaseAWSEC2ApiMockTest;
+import org.testng.annotations.Test;
+
+@Test(groups = "unit", testName = "MonitoringApiMockTest", singleThreaded = true)
+public class MonitoringApiMockTest extends BaseAWSEC2ApiMockTest {
+
+   public void monitorInstancesInRegion() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/monitoring.xml");
+
+      Map<String, MonitoringState> result = monitoringApi()
+            .monitorInstancesInRegion(DEFAULT_REGION, "i-911444f0", "i-911444f1");
+
+      assertFalse(result.isEmpty());
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=MonitorInstances&InstanceId.0=i-911444f0&InstanceId.1=i-911444f1");
+   }
+
+   public void unmonitorInstancesInRegion() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/monitoring.xml");
+
+      Map<String, MonitoringState> result = monitoringApi()
+            .unmonitorInstancesInRegion(DEFAULT_REGION, "i-911444f0", "i-911444f1");
+
+      assertFalse(result.isEmpty());
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=UnmonitorInstances&InstanceId.0=i-911444f0&InstanceId.1=i-911444f1");
+   }
+
+   private MonitoringApi monitoringApi() {
+      return api().getMonitoringApi().get();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/MonitoringApiTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/MonitoringApiTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/MonitoringApiTest.java
deleted file mode 100644
index 23b9d62..0000000
--- a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/MonitoringApiTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.jclouds.aws.ec2.features;
-
-import static org.jclouds.reflect.Reflection2.method;
-
-import java.io.IOException;
-
-import org.jclouds.aws.ec2.xml.MonitoringStateHandler;
-import org.jclouds.http.functions.ParseSax;
-import org.jclouds.rest.internal.GeneratedHttpRequest;
-import org.testng.annotations.Test;
-
-import com.google.common.collect.Lists;
-import com.google.common.reflect.Invokable;
-/**
- * Tests behavior of {@code MonitoringApi}
- */
-// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire
-@Test(groups = "unit", testName = "MonitoringApiTest")
-public class MonitoringApiTest extends BaseAWSEC2ApiTest<MonitoringApi> {
-
-   public void testUnmonitorInstances() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(MonitoringApi.class, "unmonitorInstancesInRegion", String.class, String.class,
-            String[].class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList(null, "instance1", "instance2"));
-
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      String payload = "Action=UnmonitorInstances&InstanceId.0=instance1&InstanceId.1=instance2";
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(request, payload, "application/x-www-form-urlencoded", false);
-
-      assertResponseParserClassEquals(method, request, ParseSax.class);
-      assertSaxResponseParserClassEquals(method, MonitoringStateHandler.class);
-      assertFallbackClassEquals(method, null);
-
-      checkFilters(request);
-   }
-
-   public void testMonitorInstances() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(MonitoringApi.class, "monitorInstancesInRegion", String.class, String.class,
-            String[].class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList(null, "instance1", "instance2"));
-
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(request,
-            "Action=MonitorInstances&InstanceId.0=instance1&InstanceId.1=instance2",
-            "application/x-www-form-urlencoded", false);
-
-      assertResponseParserClassEquals(method, request, ParseSax.class);
-      assertSaxResponseParserClassEquals(method, MonitoringStateHandler.class);
-      assertFallbackClassEquals(method, null);
-
-      checkFilters(request);
-   }
-}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/PlacementGroupApiExpectTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/PlacementGroupApiExpectTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/PlacementGroupApiExpectTest.java
deleted file mode 100644
index 77d51de..0000000
--- a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/PlacementGroupApiExpectTest.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.jclouds.aws.ec2.features;
-
-import static com.google.common.collect.Iterables.getOnlyElement;
-import static org.testng.Assert.assertEquals;
-
-import org.jclouds.aws.ec2.AWSEC2Api;
-import org.jclouds.aws.ec2.compute.internal.BaseAWSEC2ComputeServiceExpectTest;
-import org.jclouds.aws.ec2.domain.PlacementGroup;
-import org.jclouds.http.HttpRequest;
-import org.jclouds.http.HttpResponse;
-import org.testng.annotations.Test;
-
-import com.google.common.collect.ImmutableMultimap;
-import com.google.common.collect.ImmutableSet;
-
-@Test(groups = "unit", testName = "PlacementGroupApiExpectTest")
-public class PlacementGroupApiExpectTest extends BaseAWSEC2ComputeServiceExpectTest {
-
-   HttpRequest filter = HttpRequest.builder().method("POST")
-           .endpoint("https://ec2.us-east-1.amazonaws.com/")
-           .addHeader("Host", "ec2.us-east-1.amazonaws.com")
-           .addFormParam("Action", "DescribePlacementGroups")
-           .addFormParam("Filter.1.Name", "strategy")
-           .addFormParam("Filter.1.Value.1", "cluster")
-           .addFormParam("Signature", "SaA7Un1BE3m9jIEKyjXNdQPzFh/QAJSCebvKXiwUEK0%3D")
-           .addFormParam("SignatureMethod", "HmacSHA256")
-           .addFormParam("SignatureVersion", "2")
-           .addFormParam("Timestamp", "2012-04-16T15%3A54%3A08.897Z")
-           .addFormParam("Version", "2012-06-01")
-           .addFormParam("AWSAccessKeyId", "identity").build();
-
-   public void testFilterWhenResponseIs2xx() {
-      HttpResponse filterResponse = HttpResponse.builder().statusCode(200)
-              .payload(payloadFromResourceWithContentType("/describe_placement_groups.xml", "text/xml")).build();
-
-      AWSEC2Api apiWhenExist = requestsSendResponses(describeRegionsRequest, describeRegionsResponse, filter, filterResponse)
-              .getContext().unwrapApi(AWSEC2Api.class);
-
-      PlacementGroup group = getOnlyElement(apiWhenExist.getPlacementGroupApi().get().describePlacementGroupsInRegionWithFilter("us-east-1",
-              ImmutableMultimap.<String, String>builder()
-                      .put("strategy", "cluster")
-                      .build()));
-
-      assertEquals(group.getName(), "XYZ-cluster");
-   }
-
-   public void testFilterWhenResponseIs404() {
-      HttpResponse filterResponse = HttpResponse.builder().statusCode(404).build();
-
-      AWSEC2Api apiWhenNotExist = requestsSendResponses(describeRegionsRequest, describeRegionsResponse, filter, filterResponse)
-              .getContext().unwrapApi(AWSEC2Api.class);
-
-      assertEquals(apiWhenNotExist.getPlacementGroupApi().get().describePlacementGroupsInRegionWithFilter("us-east-1",
-              ImmutableMultimap.<String, String>builder()
-                      .put("strategy", "cluster")
-                      .build()),
-              ImmutableSet.of());
-   }
-}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/PlacementGroupApiMockTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/PlacementGroupApiMockTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/PlacementGroupApiMockTest.java
new file mode 100644
index 0000000..b94c6c3
--- /dev/null
+++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/PlacementGroupApiMockTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.aws.ec2.features;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static org.testng.Assert.assertEquals;
+
+import org.jclouds.aws.ec2.domain.PlacementGroup;
+import org.jclouds.aws.ec2.internal.BaseAWSEC2ApiMockTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+
+@Test(groups = "unit", testName = "PlacementGroupApiMockTest", singleThreaded = true)
+public class PlacementGroupApiMockTest extends BaseAWSEC2ApiMockTest {
+
+   public void describePlacementGroupsInRegionWithFilter() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/describe_placement_groups.xml");
+
+      PlacementGroup result = getOnlyElement(placementApi()
+            .describePlacementGroupsInRegionWithFilter(DEFAULT_REGION, ImmutableMultimap.of("strategy", "cluster")));
+
+      assertEquals(result.getName(), "XYZ-cluster");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DescribePlacementGroups&Filter.1.Name=strategy&Filter.1.Value.1=cluster");
+   }
+
+   public void describePlacementGroupsInRegionWithFilter_404() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueue(DEFAULT_REGION, new MockResponse().setResponseCode(404));
+
+      assertEquals(placementApi().describePlacementGroupsInRegionWithFilter(DEFAULT_REGION,
+                  ImmutableMultimap.of("strategy", "cluster")), ImmutableSet.of());
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DescribePlacementGroups&Filter.1.Name=strategy&Filter.1.Value.1=cluster");
+   }
+
+   public void deletePlacementGroupInRegion() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueue(DEFAULT_REGION, new MockResponse());
+
+      placementApi().deletePlacementGroupInRegion(DEFAULT_REGION, "name");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DeletePlacementGroup&GroupName=name");
+   }
+
+   public void deletePlacementGroupInRegion_404() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueue(DEFAULT_REGION, new MockResponse().setResponseCode(404));
+
+      placementApi().deletePlacementGroupInRegion(DEFAULT_REGION, "name");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DeletePlacementGroup&GroupName=name");
+   }
+
+   public void createPlacementGroupInRegion() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueue(DEFAULT_REGION, new MockResponse());
+
+      placementApi().createPlacementGroupInRegion(DEFAULT_REGION, "name");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=CreatePlacementGroup&Strategy=cluster&GroupName=name");
+   }
+
+   public void createPlacementGroupInRegion_strategy() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueue(DEFAULT_REGION, new MockResponse());
+
+      placementApi().createPlacementGroupInRegion(DEFAULT_REGION, "name", "cluster");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=CreatePlacementGroup&GroupName=name&Strategy=cluster");
+   }
+
+   private PlacementGroupApi placementApi() {
+      return api().getPlacementGroupApi().get();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/PlacementGroupApiTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/PlacementGroupApiTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/PlacementGroupApiTest.java
deleted file mode 100644
index 76bd954..0000000
--- a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/PlacementGroupApiTest.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.jclouds.aws.ec2.features;
-
-import static org.jclouds.reflect.Reflection2.method;
-
-import java.io.IOException;
-
-import org.jclouds.Fallbacks.EmptySetOnNotFoundOr404;
-import org.jclouds.Fallbacks.VoidOnNotFoundOr404;
-import org.jclouds.aws.ec2.xml.DescribePlacementGroupsResponseHandler;
-import org.jclouds.http.HttpRequest;
-import org.jclouds.http.functions.ParseSax;
-import org.jclouds.http.functions.ReleasePayloadAndReturn;
-import org.jclouds.rest.internal.GeneratedHttpRequest;
-import org.testng.annotations.Test;
-
-import com.google.common.collect.Lists;
-import com.google.common.reflect.Invokable;
-/**
- * Tests behavior of {@code PlacementGroupApi}
- */
-// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire
-@Test(groups = "unit", testName = "PlacementGroupApiTest")
-public class PlacementGroupApiTest extends BaseAWSEC2ApiTest<PlacementGroupApi> {
-
-   public void testDeletePlacementGroup() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(PlacementGroupApi.class, "deletePlacementGroupInRegion", String.class,
-               String.class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList(null, "name"));
-
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(request, "Action=DeletePlacementGroup&GroupName=name",
-               "application/x-www-form-urlencoded", false);
-
-      assertResponseParserClassEquals(method, request, ReleasePayloadAndReturn.class);
-      assertSaxResponseParserClassEquals(method, null);
-      assertFallbackClassEquals(method, VoidOnNotFoundOr404.class);
-
-      checkFilters(request);
-   }
-
-   HttpRequest createPlacementGroup = HttpRequest.builder().method("POST")
-                                                 .endpoint("https://ec2.us-east-1.amazonaws.com/")
-                                                 .addHeader("Host", "ec2.us-east-1.amazonaws.com")
-                                                 .addFormParam("Action", "CreatePlacementGroup")
-                                                 .addFormParam("GroupName", "name")
-                                                 .addFormParam("Strategy", "cluster").build();
-
-   public void testCreatePlacementGroup() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(PlacementGroupApi.class, "createPlacementGroupInRegion", String.class,
-               String.class, String.class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList(null, "name", "cluster"));
-
-      request = (GeneratedHttpRequest) request.getFilters().get(0).filter(request);
-      
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(request, filter.filter(createPlacementGroup).getPayload().getRawContent().toString(),
-            "application/x-www-form-urlencoded", false);
-
-      assertResponseParserClassEquals(method, request, ReleasePayloadAndReturn.class);
-      assertSaxResponseParserClassEquals(method, null);
-      assertFallbackClassEquals(method, null);
-
-      checkFilters(request);
-   }
-
-   public void testCreatePlacementGroupDefault() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(PlacementGroupApi.class, "createPlacementGroupInRegion", String.class,
-               String.class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList(null, "name"));
-
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(request, "Action=CreatePlacementGroup&Strategy=cluster&GroupName=name",
-               "application/x-www-form-urlencoded", false);
-
-      assertResponseParserClassEquals(method, request, ReleasePayloadAndReturn.class);
-      assertSaxResponseParserClassEquals(method, null);
-      assertFallbackClassEquals(method, null);
-
-      checkFilters(request);
-   }
-
-   public void testDescribePlacementGroups() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(PlacementGroupApi.class, "describePlacementGroupsInRegion", String.class,
-               String[].class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList((String) null));
-
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(request, "Action=DescribePlacementGroups",
-               "application/x-www-form-urlencoded", false);
-
-      assertResponseParserClassEquals(method, request, ParseSax.class);
-      assertSaxResponseParserClassEquals(method, DescribePlacementGroupsResponseHandler.class);
-      assertFallbackClassEquals(method, EmptySetOnNotFoundOr404.class);
-
-      checkFilters(request);
-   }
-
-   public void testDescribePlacementGroupsArgs() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(PlacementGroupApi.class, "describePlacementGroupsInRegion", String.class,
-               String[].class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList(null, "1", "2"));
-
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(request, "Action=DescribePlacementGroups&GroupName.1=1&GroupName.2=2",
-               "application/x-www-form-urlencoded", false);
-
-      assertResponseParserClassEquals(method, request, ParseSax.class);
-      assertSaxResponseParserClassEquals(method, DescribePlacementGroupsResponseHandler.class);
-      assertFallbackClassEquals(method, EmptySetOnNotFoundOr404.class);
-
-      checkFilters(request);
-   }
-}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/SpotInstanceApiExpectTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/SpotInstanceApiExpectTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/SpotInstanceApiExpectTest.java
deleted file mode 100644
index 7460505..0000000
--- a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/SpotInstanceApiExpectTest.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.jclouds.aws.ec2.features;
-
-import static com.google.common.collect.Iterables.getOnlyElement;
-import static org.testng.Assert.assertEquals;
-
-import org.jclouds.aws.ec2.AWSEC2Api;
-import org.jclouds.aws.ec2.compute.internal.BaseAWSEC2ComputeServiceExpectTest;
-import org.jclouds.aws.ec2.domain.SpotInstanceRequest;
-import org.jclouds.http.HttpRequest;
-import org.jclouds.http.HttpResponse;
-import org.testng.annotations.Test;
-
-import com.google.common.collect.ImmutableMultimap;
-import com.google.common.collect.ImmutableSet;
-
-@Test(groups = "unit", testName = "SpotInstanceApiExpectTest")
-public class SpotInstanceApiExpectTest extends BaseAWSEC2ComputeServiceExpectTest {
-
-   HttpRequest filter = HttpRequest.builder().method("POST")
-           .endpoint("https://ec2.us-east-1.amazonaws.com/")
-           .addHeader("Host", "ec2.us-east-1.amazonaws.com")
-           .addFormParam("Action", "DescribeSpotInstanceRequests")
-           .addFormParam("Filter.1.Name", "instance-id")
-           .addFormParam("Filter.1.Value.1", "i-ef308e8e")
-           .addFormParam("Signature", "wQtGpumMCDEzvlldKepCKeEjD9iE7eAyiRBlQztcJMA%3D")
-           .addFormParam("SignatureMethod", "HmacSHA256")
-           .addFormParam("SignatureVersion", "2")
-           .addFormParam("Timestamp", "2012-04-16T15%3A54%3A08.897Z")
-           .addFormParam("Version", "2012-06-01")
-           .addFormParam("AWSAccessKeyId", "identity").build();
-
-   public void testFilterWhenResponseIs2xx() {
-      HttpResponse filterResponse = HttpResponse.builder().statusCode(200)
-              .payload(payloadFromResourceWithContentType("/describe_spot_instance.xml", "text/xml")).build();
-
-      AWSEC2Api apiWhenExist = requestsSendResponses(describeRegionsRequest, describeRegionsResponse, filter, filterResponse)
-              .getContext().unwrapApi(AWSEC2Api.class);
-
-      SpotInstanceRequest request = getOnlyElement(apiWhenExist.getSpotInstanceApi().get().describeSpotInstanceRequestsInRegionWithFilter("us-east-1",
-              ImmutableMultimap.<String, String>builder()
-                      .put("instance-id", "i-ef308e8e")
-                      .build()));
-
-      assertEquals(request.getId(), "sir-1ede0012");
-   }
-
-   public void testFilterWhenResponseIs404() {
-      HttpResponse filterResponse = HttpResponse.builder().statusCode(404).build();
-
-      AWSEC2Api apiWhenNotExist = requestsSendResponses(describeRegionsRequest, describeRegionsResponse, filter, filterResponse)
-              .getContext().unwrapApi(AWSEC2Api.class);
-
-      assertEquals(apiWhenNotExist.getSpotInstanceApi().get().describeSpotInstanceRequestsInRegionWithFilter("us-east-1",
-              ImmutableMultimap.<String, String>builder()
-                      .put("instance-id", "i-ef308e8e")
-                      .build()),
-              ImmutableSet.of());
-   }
-}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/SpotInstanceApiMockTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/SpotInstanceApiMockTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/SpotInstanceApiMockTest.java
new file mode 100644
index 0000000..5863059
--- /dev/null
+++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/SpotInstanceApiMockTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.aws.ec2.features;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static org.jclouds.aws.ec2.options.DescribeSpotPriceHistoryOptions.Builder.from;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Date;
+import java.util.Set;
+
+import org.jclouds.aws.ec2.domain.Spot;
+import org.jclouds.aws.ec2.domain.SpotInstanceRequest;
+import org.jclouds.aws.ec2.internal.BaseAWSEC2ApiMockTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+
+   @Test(groups = "unit", testName = "SpotInstanceApiMockTest", singleThreaded = true)
+   public class SpotInstanceApiMockTest extends BaseAWSEC2ApiMockTest {
+
+      public void describeSpotInstanceRequestsInRegionWithFilter() throws Exception {
+         enqueueRegions(DEFAULT_REGION);
+         enqueueXml(DEFAULT_REGION, "/describe_spot_instance.xml");
+
+         SpotInstanceRequest result = getOnlyElement(spotApi()
+               .describeSpotInstanceRequestsInRegionWithFilter(DEFAULT_REGION,
+                     ImmutableMultimap.of("instance-id", "i-ef308e8e")));
+
+         assertEquals(result.getId(), "sir-1ede0012");
+
+         assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+         assertPosted(DEFAULT_REGION, "Action=DescribeSpotInstanceRequests&Filter.1.Name=instance-id&Filter.1.Value.1=i-ef308e8e");
+      }
+
+      public void describeSpotInstanceRequestsInRegionWithFilter_404() throws Exception {
+         enqueueRegions(DEFAULT_REGION);
+         enqueue(DEFAULT_REGION, new MockResponse().setResponseCode(404));
+
+         assertEquals(spotApi().describeSpotInstanceRequestsInRegionWithFilter(DEFAULT_REGION,
+               ImmutableMultimap.of("instance-id", "i-ef308e8e")), ImmutableSet.of());
+
+         assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+         assertPosted(DEFAULT_REGION, "Action=DescribeSpotInstanceRequests&Filter.1.Name=instance-id&Filter.1.Value.1=i-ef308e8e");
+      }
+
+      public void cancelSpotInstanceRequestsInRegion() throws Exception {
+         enqueueRegions(DEFAULT_REGION);
+         enqueue(DEFAULT_REGION, new MockResponse());
+
+         spotApi().cancelSpotInstanceRequestsInRegion(DEFAULT_REGION, "sir-f4d44212");
+
+         assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+         assertPosted(DEFAULT_REGION, "Action=CancelSpotInstanceRequests&SpotInstanceRequestId.1=sir-f4d44212");
+      }
+
+      public void cancelSpotInstanceRequestsInRegion_404() throws Exception {
+         enqueueRegions(DEFAULT_REGION);
+         enqueue(DEFAULT_REGION, new MockResponse().setResponseCode(404));
+
+         spotApi().cancelSpotInstanceRequestsInRegion(DEFAULT_REGION, "sir-f4d44212");
+
+         assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+         assertPosted(DEFAULT_REGION, "Action=CancelSpotInstanceRequests&SpotInstanceRequestId.1=sir-f4d44212");
+      }
+
+      public void describeSpotPriceHistoryInRegion() throws Exception {
+         enqueueRegions(DEFAULT_REGION);
+         enqueueXml(DEFAULT_REGION, "/describe_spot_price_history.xml");
+
+         Set<Spot> result = spotApi().describeSpotPriceHistoryInRegion(DEFAULT_REGION);
+
+         assertEquals(result.size(), 3);
+
+         assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+         assertPosted(DEFAULT_REGION, "Action=DescribeSpotPriceHistory");
+      }
+
+      public void describeSpotPriceHistoryInRegion_404() throws Exception {
+         enqueueRegions(DEFAULT_REGION);
+         enqueue(DEFAULT_REGION, new MockResponse().setResponseCode(404));
+
+         Set<Spot> result = spotApi().describeSpotPriceHistoryInRegion(DEFAULT_REGION);
+
+         assertTrue(result.isEmpty());
+
+         assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+         assertPosted(DEFAULT_REGION, "Action=DescribeSpotPriceHistory");
+      }
+
+      public void describeSpotPriceHistoryInRegionOptions() throws Exception {
+         enqueueRegions(DEFAULT_REGION);
+         enqueueXml(DEFAULT_REGION, "/describe_spot_price_history.xml");
+
+         Date from = new Date(12345678910l);
+         Date to = new Date(1234567891011l);
+
+         Set<Spot> result = spotApi().describeSpotPriceHistoryInRegion(DEFAULT_REGION,
+               from(from).to(to).productDescription("description").instanceType("m1.small"));
+
+         assertEquals(result.size(), 3);
+
+         assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+         assertPosted(DEFAULT_REGION, "Action=DescribeSpotPriceHistory&StartTime=1970-05-23T21%3A21%3A18.910Z&EndTime=2009-02-13T23%3A31%3A31.011Z&ProductDescription=description&InstanceType.1=m1.small");
+      }
+
+      private SpotInstanceApi spotApi() {
+         return api().getSpotInstanceApi().get();
+      }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/SpotInstanceApiTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/SpotInstanceApiTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/SpotInstanceApiTest.java
deleted file mode 100644
index 5337ca8..0000000
--- a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/SpotInstanceApiTest.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.jclouds.aws.ec2.features;
-
-import static org.jclouds.reflect.Reflection2.method;
-
-import java.io.IOException;
-import java.util.Date;
-
-import org.jclouds.Fallbacks.EmptySetOnNotFoundOr404;
-import org.jclouds.Fallbacks.VoidOnNotFoundOr404;
-import org.jclouds.aws.ec2.options.DescribeSpotPriceHistoryOptions;
-import org.jclouds.aws.ec2.xml.DescribeSpotPriceHistoryResponseHandler;
-import org.jclouds.http.functions.ParseSax;
-import org.jclouds.http.functions.ReleasePayloadAndReturn;
-import org.jclouds.rest.internal.GeneratedHttpRequest;
-import org.testng.annotations.Test;
-
-import com.google.common.collect.Lists;
-import com.google.common.reflect.Invokable;
-/**
- * Tests behavior of {@code SpotInstanceApi}
- */
-// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire
-@Test(groups = "unit", testName = "SpotInstanceApiTest")
-public class SpotInstanceApiTest extends BaseAWSEC2ApiTest<SpotInstanceApi> {
-
-   public void testCancelSpotInstanceRequests() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(SpotInstanceApi.class, "cancelSpotInstanceRequestsInRegion", String.class,
-            String[].class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList(null, "id"));
-
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(request, "Action=CancelSpotInstanceRequests&SpotInstanceRequestId.1=id",
-            "application/x-www-form-urlencoded", false);
-
-      assertResponseParserClassEquals(method, request, ReleasePayloadAndReturn.class);
-      assertSaxResponseParserClassEquals(method, null);
-      assertFallbackClassEquals(method, VoidOnNotFoundOr404.class);
-
-      checkFilters(request);
-   }
-
-   public void testDescribeSpotPriceHistory() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(SpotInstanceApi.class, "describeSpotPriceHistoryInRegion", String.class,
-            DescribeSpotPriceHistoryOptions[].class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList((String) null));
-
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(request, "Action=DescribeSpotPriceHistory",
-            "application/x-www-form-urlencoded", false);
-
-      assertResponseParserClassEquals(method, request, ParseSax.class);
-      assertSaxResponseParserClassEquals(method, DescribeSpotPriceHistoryResponseHandler.class);
-      assertFallbackClassEquals(method, EmptySetOnNotFoundOr404.class);
-
-      checkFilters(request);
-   }
-
-   Date from = new Date(12345678910l);
-   Date to = new Date(1234567891011l);
-
-   public void testDescribeSpotPriceHistoryArgs() throws SecurityException, NoSuchMethodException, IOException {
-      Invokable<?, ?> method = method(SpotInstanceApi.class, "describeSpotPriceHistoryInRegion", String.class,
-            DescribeSpotPriceHistoryOptions[].class);
-      GeneratedHttpRequest request = processor.createRequest(method, Lists.<Object> newArrayList(null, DescribeSpotPriceHistoryOptions.Builder.from(from)
-            .to(to).productDescription("description").instanceType("m1.small")));
-
-      assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1");
-      assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n");
-      assertPayloadEquals(
-            request,
-            "Action=DescribeSpotPriceHistory&StartTime=1970-05-23T21%3A21%3A18.910Z&EndTime=2009-02-13T23%3A31%3A31.011Z&ProductDescription=description&InstanceType.1=m1.small",
-            "application/x-www-form-urlencoded", false);
-
-      assertResponseParserClassEquals(method, request, ParseSax.class);
-      assertSaxResponseParserClassEquals(method, DescribeSpotPriceHistoryResponseHandler.class);
-      assertFallbackClassEquals(method, EmptySetOnNotFoundOr404.class);
-
-      checkFilters(request);
-   }
-}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/internal/BaseAWSEC2ApiMockTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/internal/BaseAWSEC2ApiMockTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/internal/BaseAWSEC2ApiMockTest.java
new file mode 100644
index 0000000..bc21706
--- /dev/null
+++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/internal/BaseAWSEC2ApiMockTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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.aws.ec2.internal;
+
+import static com.google.common.base.Throwables.propagate;
+import static com.google.common.net.HttpHeaders.AUTHORIZATION;
+import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
+import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor;
+import static javax.ws.rs.core.MediaType.APPLICATION_XML;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.jclouds.aws.filters.FormSignerV4.ServiceAndRegion;
+import static org.jclouds.util.Strings2.toStringAndClose;
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import org.jclouds.Constants;
+import org.jclouds.ContextBuilder;
+import org.jclouds.aws.ec2.AWSEC2Api;
+import org.jclouds.aws.ec2.AWSEC2ProviderMetadata;
+import org.jclouds.aws.ec2.config.AWSEC2HttpApiModule;
+import org.jclouds.compute.ComputeService;
+import org.jclouds.compute.ComputeServiceContext;
+import org.jclouds.concurrent.config.ExecutorServiceModule;
+import org.jclouds.date.DateService;
+import org.jclouds.rest.ConfiguresHttpApi;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.inject.Module;
+import com.google.inject.Provides;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+
+/**
+ * Tests need to run {@code singleThreaded = true) as otherwise tests will clash on the regionToServers field.
+ * Sharing the regionToServers field means less code to write.
+ */
+public class BaseAWSEC2ApiMockTest {
+   protected static final String DEFAULT_REGION = "us-east-1";
+
+   // Example keys from http://docs.aws.amazon.com/general/latest/gr/signature-version-2.html
+   private static final String ACCESS_KEY = "AKIAIOSFODNN7EXAMPLE";
+   private static final String SECRET_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY";
+
+   private Map<String, MockWebServer> regionToServers = Maps.newLinkedHashMap();
+
+   protected AWSEC2Api api() {
+      return builder(new Properties()).buildApi(AWSEC2Api.class);
+   }
+
+   protected ComputeService computeService() {
+      return builder(new Properties()).buildView(ComputeServiceContext.class).getComputeService();
+   }
+
+   protected ContextBuilder builder(Properties overrides) {
+      overrides.setProperty(Constants.PROPERTY_MAX_RETRIES, "1");
+      return ContextBuilder.newBuilder(new AWSEC2ProviderMetadata())
+            .credentials(ACCESS_KEY, SECRET_KEY)
+            .endpoint("http://localhost:" + regionToServers.get(DEFAULT_REGION).getPort())
+            .overrides(overrides)
+            .modules(modules);
+   }
+
+   private final Set<Module> modules = ImmutableSet
+         .<Module>of(new MockAWSEC2HttpApiModule(), new ExecutorServiceModule(sameThreadExecutor()));
+
+   @ConfiguresHttpApi
+   class MockAWSEC2HttpApiModule extends AWSEC2HttpApiModule {
+
+      @Override
+      protected String provideTimeStamp(DateService dateService) {
+         return "20120416T155408Z";
+      }
+
+      @Provides ServiceAndRegion serviceAndRegion(){
+         return new ServiceAndRegion() {
+            @Override public String service() {
+               return "ec2";
+            }
+
+            @Override public String region(String host) {
+               for (Map.Entry<String, MockWebServer> regionToServer : regionToServers.entrySet()) {
+                  if (host.equals("localhost:" + regionToServer.getValue().getPort())) {
+                     return regionToServer.getKey();
+                  }
+               }
+               throw new IllegalStateException(host + " not found");
+            }
+         };
+      }
+   }
+
+   @BeforeMethod
+   public void start() throws IOException {
+      MockWebServer server = new MockWebServer();
+      server.play();
+      regionToServers.put(DEFAULT_REGION, server);
+   }
+
+   @AfterMethod(alwaysRun = true)
+   public void stop() throws IOException {
+      for (MockWebServer server : regionToServers.values()) {
+         server.shutdown();
+      }
+   }
+
+   protected void enqueue(String region, MockResponse response) {
+      regionToServers.get(region).enqueue(response);
+   }
+
+   protected void enqueueRegions(String... regions) throws IOException {
+      StringBuilder describeRegionsResponse = new StringBuilder();
+      describeRegionsResponse.append("<DescribeRegionsResponse>");
+      for (String region : regions) {
+         describeRegionsResponse.append("<item>");
+         describeRegionsResponse.append("<regionName>").append(region).append("</regionName>");
+         if (!regionToServers.containsKey(region)) {
+            MockWebServer server = new MockWebServer();
+            server.play();
+            regionToServers.put(region, server);
+         }
+         String regionEndpoint = "http://localhost:" + regionToServers.get(region).getPort();
+         describeRegionsResponse.append("<regionEndpoint>").append(regionEndpoint).append("</regionEndpoint>");
+         describeRegionsResponse.append("</item>");
+      }
+      describeRegionsResponse.append("</DescribeRegionsResponse>");
+      enqueue(DEFAULT_REGION,
+            new MockResponse().addHeader(CONTENT_TYPE, APPLICATION_XML).setBody(describeRegionsResponse.toString()));
+   }
+
+   protected void enqueueXml(String region, String resource) {
+      enqueue(region,
+            new MockResponse().addHeader(CONTENT_TYPE, APPLICATION_XML).setBody(stringFromResource(resource)));
+   }
+
+   protected String stringFromResource(String resourceName) {
+      try {
+         return toStringAndClose(getClass().getResourceAsStream(resourceName));
+      } catch (IOException e) {
+         throw propagate(e);
+      }
+   }
+
+   protected RecordedRequest assertPosted(String region, String postParams) throws InterruptedException {
+      RecordedRequest request = regionToServers.get(region).takeRequest();
+      assertEquals(request.getMethod(), "POST");
+      assertEquals(request.getPath(), "/");
+      assertEquals(request.getHeader("X-Amz-Date"), "20120416T155408Z");
+      assertThat(
+            request.getHeader(AUTHORIZATION)).startsWith("AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20120416/" +
+            region + "/ec2/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=");
+      String body = new String(request.getBody(), Charsets.UTF_8);
+      assertThat(body).contains("&Version=2012-06-01");
+      assertEquals(body.replace("&Version=2012-06-01", ""), postParams);
+      return request;
+   }
+}


[2/3] jclouds git commit: JCLOUDS-480 support version 4 signatures for aws-ec2.

Posted by ad...@apache.org.
http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/AWSEC2ComputeServiceExpectTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/AWSEC2ComputeServiceExpectTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/AWSEC2ComputeServiceExpectTest.java
deleted file mode 100644
index da2f898..0000000
--- a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/AWSEC2ComputeServiceExpectTest.java
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.jclouds.aws.ec2.compute;
-
-import static org.jclouds.aws.ec2.compute.AWSEC2TemplateOptions.Builder.blockUntilRunning;
-import static org.testng.Assert.assertEquals;
-
-import javax.ws.rs.core.MediaType;
-
-import org.jclouds.aws.ec2.compute.internal.BaseAWSEC2ComputeServiceExpectTest;
-import org.jclouds.compute.ComputeService;
-import org.jclouds.compute.domain.NodeMetadata;
-import org.jclouds.compute.domain.Template;
-import org.jclouds.compute.predicates.NodePredicates;
-import org.jclouds.http.HttpRequest;
-import org.jclouds.http.HttpResponse;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableMap.Builder;
-import com.google.common.collect.Iterables;
-
-/**
- * Tests the compute service abstraction of the EC2 api.
- */
-@Test(groups = "unit", testName = "AWSEC2ComputeServiceExpectTest")
-public class AWSEC2ComputeServiceExpectTest extends BaseAWSEC2ComputeServiceExpectTest {
-
-   private HttpResponse requestSpotInstancesResponse;
-   private HttpRequest describeSpotInstanceRequest;
-   private HttpResponse describeSpotInstanceResponse;
-
-   @BeforeClass
-   @Override
-   protected void setupDefaultRequests() {
-      super.setupDefaultRequests();
-      requestSpotInstancesResponse = HttpResponse.builder().statusCode(200)
-                       .payload(payloadFromResourceWithContentType(
-                             "/request_spot_instances-ebs.xml", MediaType.APPLICATION_XML)).build();
-   
-      describeSpotInstanceRequest = formSigner.filter(HttpRequest.builder().method("POST")
-                       .endpoint("https://ec2." + region + ".amazonaws.com/")
-                       .addHeader("Host", "ec2." + region + ".amazonaws.com")
-                       .addFormParam("Action", "DescribeSpotInstanceRequests")
-                       .addFormParam("SpotInstanceRequestId.1", "sir-228e6406").build());
-
-      describeSpotInstanceResponse = HttpResponse.builder().statusCode(200)
-                       .payload(payloadFromResourceWithContentType(
-                             "/request_spot_instances-ebs.xml", MediaType.APPLICATION_XML)).build();
-   }
-
-   public void testLaunchVPCSpotInstanceSubnetId() throws Exception {
-      HttpRequest requestSpotInstancesRequest = formSigner.filter(HttpRequest.builder().method("POST")
-                          .endpoint("https://ec2." + region + ".amazonaws.com/")
-                          .addHeader("Host", "ec2." + region + ".amazonaws.com")
-                          .addFormParam("Action", "RequestSpotInstances")
-                          .addFormParam("InstanceCount", "1")
-                          .addFormParam("LaunchSpecification.ImageId", "ami-be3adfd7")
-                          .addFormParam("LaunchSpecification.InstanceType", "m1.small")
-                          .addFormParam("LaunchSpecification.KeyName", "Demo")
-                          .addFormParam("LaunchSpecification.Placement.AvailabilityZone", "us-east-1a")
-                          .addFormParam("LaunchSpecification.SubnetId", "subnet-xyz")
-                          .addFormParam("LaunchSpecification.UserData", "I2Nsb3VkLWNvbmZpZwpyZXBvX3VwZ3JhZGU6IG5vbmUK")
-                          .addFormParam("SpotPrice", "1.0").build());
-
-      Builder<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder();
-      requestResponseMap.put(describeRegionsRequest, describeRegionsResponse);
-      requestResponseMap.put(describeAvailabilityZonesRequest, describeAvailabilityZonesResponse);
-      requestResponseMap.put(describeImagesRequest, describeImagesResponse);
-      requestResponseMap.put(createKeyPairRequest, createKeyPairResponse);
-      requestResponseMap.put(createSecurityGroupRequest, createSecurityGroupResponse);
-      requestResponseMap.put(describeSecurityGroupRequest, describeSecurityGroupResponse);
-      requestResponseMap.put(authorizeSecurityGroupIngressRequest22, authorizeSecurityGroupIngressResponse);
-      requestResponseMap.put(authorizeSecurityGroupIngressRequestGroup, authorizeSecurityGroupIngressResponse);
-      requestResponseMap.put(requestSpotInstancesRequest, requestSpotInstancesResponse);
-      requestResponseMap.put(describeSpotInstanceRequest, describeSpotInstanceResponse);
-
-      ComputeService createsVPCSpotInstance = requestsSendResponses(requestResponseMap.build());
-
-      Template template = createsVPCSpotInstance.templateBuilder().locationId("us-east-1a").build();
-
-      template.getOptions().as(AWSEC2TemplateOptions.class).spotPrice(1f).subnetId("subnet-xyz").keyPair("Demo").blockUntilRunning(false);
-
-      NodeMetadata node = Iterables.getOnlyElement(createsVPCSpotInstance.createNodesInGroup("test", 1, template));
-      assertEquals(node.getId(), "us-east-1/sir-228e6406");
-   }
-
-   String iamInstanceProfileArn = "arn:aws:iam::123456789012:instance-profile/application_abc/component_xyz/Webserver";
-
-   public void testLaunchSpotInstanceIAMInstanceProfileArn() throws Exception {
-      HttpRequest requestSpotInstancesRequest = formSigner.filter(HttpRequest.builder().method("POST")
-                          .endpoint("https://ec2." + region + ".amazonaws.com/")
-                          .addHeader("Host", "ec2." + region + ".amazonaws.com")
-                          .addFormParam("Action", "RequestSpotInstances")
-                          .addFormParam("InstanceCount", "1")
-                          .addFormParam("LaunchSpecification.IamInstanceProfile.Arn", iamInstanceProfileArn)
-                          .addFormParam("LaunchSpecification.ImageId", "ami-be3adfd7")
-                          .addFormParam("LaunchSpecification.InstanceType", "m1.small")
-                          .addFormParam("LaunchSpecification.Placement.AvailabilityZone", "us-east-1a")
-                          .addFormParam("LaunchSpecification.SecurityGroup.1", "jclouds#test")
-                          .addFormParam("LaunchSpecification.UserData", "I2Nsb3VkLWNvbmZpZwpyZXBvX3VwZ3JhZGU6IG5vbmUK")
-                          .addFormParam("SpotPrice", "1.0").build());
-
-      Builder<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder();
-      requestResponseMap.put(describeRegionsRequest, describeRegionsResponse);
-      requestResponseMap.put(describeAvailabilityZonesRequest, describeAvailabilityZonesResponse);
-      requestResponseMap.put(describeImagesRequest, describeImagesResponse);
-      requestResponseMap.put(createKeyPairRequest, createKeyPairResponse);
-      requestResponseMap.put(createSecurityGroupRequest, createSecurityGroupResponse);
-      requestResponseMap.put(describeSecurityGroupRequest, describeSecurityGroupResponse);
-      requestResponseMap.put(authorizeSecurityGroupIngressRequest22, authorizeSecurityGroupIngressResponse);
-      requestResponseMap.put(authorizeSecurityGroupIngressRequestGroup, authorizeSecurityGroupIngressResponse);
-      requestResponseMap.put(requestSpotInstancesRequest, requestSpotInstancesResponse);
-      requestResponseMap.put(describeSpotInstanceRequest, describeSpotInstanceResponse);
-
-      ComputeService createsSpotInstance = requestsSendResponses(requestResponseMap.build());
-
-      Template template = createsSpotInstance.templateBuilder().locationId("us-east-1a").build();
-
-      template.getOptions().as(AWSEC2TemplateOptions.class).spotPrice(1f).iamInstanceProfileArn(iamInstanceProfileArn)
-            .noKeyPair().blockUntilRunning(false);
-
-      NodeMetadata node = Iterables.getOnlyElement(createsSpotInstance.createNodesInGroup("test", 1, template));
-      assertEquals(node.getId(), "us-east-1/sir-228e6406");
-   }
-
-   public void testLaunchSpotInstanceIAMInstanceProfileName() throws Exception {
-      HttpRequest requestSpotInstancesRequest = formSigner.filter(HttpRequest.builder().method("POST")
-                          .endpoint("https://ec2." + region + ".amazonaws.com/")
-                          .addHeader("Host", "ec2." + region + ".amazonaws.com")
-                          .addFormParam("Action", "RequestSpotInstances")
-                          .addFormParam("InstanceCount", "1")
-                          .addFormParam("LaunchSpecification.IamInstanceProfile.Name", "Webserver")
-                          .addFormParam("LaunchSpecification.ImageId", "ami-be3adfd7")
-                          .addFormParam("LaunchSpecification.InstanceType", "m1.small")
-                          .addFormParam("LaunchSpecification.Placement.AvailabilityZone", "us-east-1a")
-                          .addFormParam("LaunchSpecification.SecurityGroup.1", "jclouds#test")
-                          .addFormParam("LaunchSpecification.UserData", "I2Nsb3VkLWNvbmZpZwpyZXBvX3VwZ3JhZGU6IG5vbmUK")
-                          .addFormParam("SpotPrice", "1.0").build());
-
-      Builder<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder();
-      requestResponseMap.put(describeRegionsRequest, describeRegionsResponse);
-      requestResponseMap.put(describeAvailabilityZonesRequest, describeAvailabilityZonesResponse);
-      requestResponseMap.put(describeImagesRequest, describeImagesResponse);
-      requestResponseMap.put(createKeyPairRequest, createKeyPairResponse);
-      requestResponseMap.put(createSecurityGroupRequest, createSecurityGroupResponse);
-      requestResponseMap.put(describeSecurityGroupRequest, describeSecurityGroupResponse);
-      requestResponseMap.put(authorizeSecurityGroupIngressRequest22, authorizeSecurityGroupIngressResponse);
-      requestResponseMap.put(authorizeSecurityGroupIngressRequestGroup, authorizeSecurityGroupIngressResponse);
-      requestResponseMap.put(requestSpotInstancesRequest, requestSpotInstancesResponse);
-      requestResponseMap.put(describeSpotInstanceRequest, describeSpotInstanceResponse);
-
-      ComputeService createsSpotInstance = requestsSendResponses(requestResponseMap.build());
-
-      Template template = createsSpotInstance.templateBuilder().locationId("us-east-1a").build();
-
-      template.getOptions().as(AWSEC2TemplateOptions.class).spotPrice(1f).iamInstanceProfileName("Webserver")
-            .noKeyPair().blockUntilRunning(false);
-
-      NodeMetadata node = Iterables.getOnlyElement(createsSpotInstance.createNodesInGroup("test", 1, template));
-      assertEquals(node.getId(), "us-east-1/sir-228e6406");
-   }
-
-   public void testCreateNodeWithIAMInstanceProfileArn() throws Exception {
-      HttpRequest runInstancesRequest = formSigner.filter(HttpRequest.builder().method("POST")
-            .endpoint("https://ec2." + region + ".amazonaws.com/")
-            .addHeader("Host", "ec2." + region + ".amazonaws.com")
-            .addFormParam("Action", "RunInstances")
-            .addFormParam("IamInstanceProfile.Arn", iamInstanceProfileArn)
-            .addFormParam("ImageId", "ami-be3adfd7")
-            .addFormParam("InstanceType", "m1.small")
-            .addFormParam("MaxCount", "1")
-            .addFormParam("MinCount", "1")
-            .addFormParam("SecurityGroup.1", "jclouds#test")
-            .addFormParam("UserData", "I2Nsb3VkLWNvbmZpZwpyZXBvX3VwZ3JhZGU6IG5vbmUK").build());
-
-      Builder<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder();
-      requestResponseMap.put(describeRegionsRequest, describeRegionsResponse);
-      requestResponseMap.put(describeAvailabilityZonesRequest, describeAvailabilityZonesResponse);
-      requestResponseMap.put(describeImagesRequest, describeImagesResponse);
-      requestResponseMap.put(createKeyPairRequest, createKeyPairResponse);
-      requestResponseMap.put(createSecurityGroupRequest, createSecurityGroupResponse);
-      requestResponseMap.put(describeSecurityGroupRequest, describeSecurityGroupResponse);
-      requestResponseMap.put(authorizeSecurityGroupIngressRequest22, authorizeSecurityGroupIngressResponse);
-      requestResponseMap.put(authorizeSecurityGroupIngressRequestGroup, authorizeSecurityGroupIngressResponse);
-      requestResponseMap.put(runInstancesRequest, runInstancesResponse);
-      requestResponseMap.put(describeInstanceRequest, describeInstanceResponse);
-      requestResponseMap.put(describeImageRequest, describeImagesResponse);
-
-      ComputeService apiThatCreatesNode = requestsSendResponses(requestResponseMap.build());
-
-      NodeMetadata node = Iterables.getOnlyElement(apiThatCreatesNode.createNodesInGroup("test", 1,
-            blockUntilRunning(false).iamInstanceProfileArn(iamInstanceProfileArn).noKeyPair()));
-      assertEquals(node.getId(), "us-east-1/i-2baa5550");
-   }
-
-   public void testCreateNodeWithIAMInstanceProfileName() throws Exception {
-      HttpRequest runInstancesRequest = formSigner.filter(HttpRequest.builder().method("POST")
-            .endpoint("https://ec2." + region + ".amazonaws.com/")
-            .addHeader("Host", "ec2." + region + ".amazonaws.com")
-            .addFormParam("Action", "RunInstances")
-            .addFormParam("IamInstanceProfile.Name", "Webserver")
-            .addFormParam("ImageId", "ami-be3adfd7")
-            .addFormParam("InstanceType", "m1.small")
-            .addFormParam("MaxCount", "1")
-            .addFormParam("MinCount", "1")
-            .addFormParam("SecurityGroup.1", "jclouds#test")
-            .addFormParam("UserData", "I2Nsb3VkLWNvbmZpZwpyZXBvX3VwZ3JhZGU6IG5vbmUK").build());
-
-      Builder<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder();
-      requestResponseMap.put(describeRegionsRequest, describeRegionsResponse);
-      requestResponseMap.put(describeAvailabilityZonesRequest, describeAvailabilityZonesResponse);
-      requestResponseMap.put(describeImagesRequest, describeImagesResponse);
-      requestResponseMap.put(createKeyPairRequest, createKeyPairResponse);
-      requestResponseMap.put(createSecurityGroupRequest, createSecurityGroupResponse);
-      requestResponseMap.put(describeSecurityGroupRequest, describeSecurityGroupResponse);
-      requestResponseMap.put(authorizeSecurityGroupIngressRequest22, authorizeSecurityGroupIngressResponse);
-      requestResponseMap.put(authorizeSecurityGroupIngressRequestGroup, authorizeSecurityGroupIngressResponse);
-      requestResponseMap.put(runInstancesRequest, runInstancesResponse);
-      requestResponseMap.put(describeInstanceRequest, describeInstanceResponse);
-      requestResponseMap.put(describeImageRequest, describeImagesResponse);
-
-      ComputeService apiThatCreatesNode = requestsSendResponses(requestResponseMap.build());
-
-      NodeMetadata node = Iterables.getOnlyElement(apiThatCreatesNode.createNodesInGroup("test", 1,
-            blockUntilRunning(false).iamInstanceProfileName("Webserver").noKeyPair()));
-      assertEquals(node.getId(), "us-east-1/i-2baa5550");
-   }
-
-   public void testListNodesWhereImageDoesntExist() throws Exception {
-      HttpRequest describeInstancesRequest = formSigner.filter(HttpRequest.builder().method("POST")
-                           .endpoint("https://ec2." + region + ".amazonaws.com/")
-                           .addHeader("Host", "ec2." + region + ".amazonaws.com")
-                           .addFormParam("Action", "DescribeInstances").build());
-
-      HttpRequest describeSpotInstancesRequest = formSigner.filter(HttpRequest.builder().method("POST")
-                          .endpoint("https://ec2." + region + ".amazonaws.com/")
-                          .addHeader("Host", "ec2." + region + ".amazonaws.com")
-                          .addFormParam("Action", "DescribeSpotInstanceRequests").build());
-
-      HttpResponse noSpotInstancesResponse = HttpResponse.builder().statusCode(200)
-            .payload(payloadFromStringWithContentType(
-                  "<DescribeSpotInstanceRequestsResponse><spotInstanceRequestSet></spotInstanceRequestSet></DescribeSpotInstanceRequestsResponse>",
-                  MediaType.APPLICATION_XML)).build();
-      
-      HttpResponse noImagesResponse = HttpResponse.builder().statusCode(200)
-                          .payload(payloadFromStringWithContentType(
-                                 "<DescribeImagesResponse><imagesSet></imagesSet></DescribeImagesResponse>",
-                                 MediaType.APPLICATION_XML)).build();
-      
-      Builder<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder();
-      requestResponseMap.put(describeRegionsRequest, describeRegionsResponse);
-      requestResponseMap.put(describeAvailabilityZonesRequest, describeAvailabilityZonesResponse);
-      requestResponseMap.put(describeImagesRequest, noImagesResponse);
-      requestResponseMap.put(describeInstancesRequest, describeInstanceResponse);
-      requestResponseMap.put(describeSpotInstancesRequest, noSpotInstancesResponse);
-
-      ComputeService listsWithoutImages = requestsSendResponses(requestResponseMap.build());
-
-      NodeMetadata node = Iterables.getOnlyElement(listsWithoutImages.listNodesDetailsMatching(NodePredicates.all()));
-      assertEquals(node.getId(), "us-east-1/i-2baa5550");
-   }
-}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/AWSEC2TemplateBuilderLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/AWSEC2TemplateBuilderLiveTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/AWSEC2TemplateBuilderLiveTest.java
index 65cbd60..050b9aa 100644
--- a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/AWSEC2TemplateBuilderLiveTest.java
+++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/AWSEC2TemplateBuilderLiveTest.java
@@ -29,6 +29,7 @@ import java.util.Set;
 
 import org.jclouds.aws.domain.Region;
 import org.jclouds.aws.ec2.AWSEC2Api;
+import org.jclouds.aws.ec2.AWSEC2ProviderMetadata;
 import org.jclouds.aws.ec2.reference.AWSEC2Constants;
 import org.jclouds.compute.ComputeServiceContext;
 import org.jclouds.compute.domain.OsFamily;
@@ -46,6 +47,7 @@ import org.jclouds.http.HttpCommand;
 import org.jclouds.http.internal.TrackingJavaUrlHttpCommandExecutorService;
 import org.jclouds.location.reference.LocationConstants;
 import org.jclouds.logging.log4j.config.Log4JLoggingModule;
+import org.jclouds.providers.ProviderMetadata;
 import org.testng.annotations.Test;
 
 import com.google.common.collect.ImmutableSet;
@@ -59,6 +61,10 @@ public class AWSEC2TemplateBuilderLiveTest extends EC2TemplateBuilderLiveTest {
       provider = "aws-ec2";
    }
 
+   @Override public ProviderMetadata createProviderMetadata() {
+      return new AWSEC2ProviderMetadata();
+   }
+
    @Test
    public void testTemplateBuilderM1MEDIUMWithNegativeLookaroundDoesntMatchTestImages() {
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/extensions/AWSEC2SecurityGroupExtensionApiMockTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/extensions/AWSEC2SecurityGroupExtensionApiMockTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/extensions/AWSEC2SecurityGroupExtensionApiMockTest.java
new file mode 100644
index 0000000..1bee5d5
--- /dev/null
+++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/extensions/AWSEC2SecurityGroupExtensionApiMockTest.java
@@ -0,0 +1,167 @@
+/*
+ * 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.aws.ec2.compute.extensions;
+
+import static org.jclouds.domain.LocationScope.REGION;
+import static org.testng.Assert.assertEquals;
+
+import org.jclouds.aws.ec2.internal.BaseAWSEC2ApiMockTest;
+import org.jclouds.compute.domain.SecurityGroup;
+import org.jclouds.compute.domain.SecurityGroupBuilder;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
+import org.jclouds.domain.LocationBuilder;
+import org.jclouds.net.domain.IpPermission;
+import org.jclouds.net.domain.IpProtocol;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.Iterables;
+
+@Test(groups = "unit", testName = "AWSEC2SecurityGroupExtensionMockTest", singleThreaded = true)
+public class AWSEC2SecurityGroupExtensionApiMockTest extends BaseAWSEC2ApiMockTest {
+
+   SecurityGroup group = new SecurityGroupBuilder() //
+         .id(DEFAULT_REGION + "/sg-3c6ef654") //
+         .providerId("sg-3c6ef654") //
+         .name("jclouds#some-group") //
+         .ownerId("993194456877")
+         .location(new LocationBuilder().scope(REGION).id(DEFAULT_REGION).description("").build()).build();
+
+   IpPermission permByCidrBlock = IpPermission.builder() //
+         .ipProtocol(IpProtocol.TCP) //
+         .fromPort(22) //
+         .toPort(40) //
+         .cidrBlock("0.0.0.0/0").build();
+
+   public void addIpPermissionCidrFromIpPermission() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/authorize_securitygroup_ingress_response.xml");
+      enqueueXml(DEFAULT_REGION, "/describe_securitygroups_extension_cidr.xml");
+      enqueueXml(DEFAULT_REGION, "/availabilityZones.xml");
+
+      SecurityGroup newGroup = extension().addIpPermission(permByCidrBlock, group);
+
+      assertEquals(1, newGroup.getIpPermissions().size());
+
+      IpPermission newPerm = Iterables.getOnlyElement(newGroup.getIpPermissions());
+      assertEquals(newPerm, permByCidrBlock);
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION,
+            "Action=AuthorizeSecurityGroupIngress&GroupId=sg-3c6ef654&IpPermissions.0.IpProtocol=tcp&IpPermissions.0.FromPort=22&IpPermissions.0.ToPort=40&IpPermissions.0.IpRanges.0.CidrIp=0.0.0.0/0");
+      assertPosted(DEFAULT_REGION, "Action=DescribeSecurityGroups&GroupId.1=sg-3c6ef654");
+      assertPosted(DEFAULT_REGION, "Action=DescribeAvailabilityZones");
+   }
+
+   public void addIpPermissionCidrFromParams() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/authorize_securitygroup_ingress_response.xml");
+      enqueueXml(DEFAULT_REGION, "/describe_securitygroups_extension_cidr.xml");
+      enqueueXml(DEFAULT_REGION, "/availabilityZones.xml");
+
+      SecurityGroup newGroup = extension()
+            .addIpPermission(permByCidrBlock.getIpProtocol(), permByCidrBlock.getFromPort(),
+                  permByCidrBlock.getToPort(), permByCidrBlock.getTenantIdGroupNamePairs(),
+                  permByCidrBlock.getCidrBlocks(), permByCidrBlock.getGroupIds(), group);
+
+      IpPermission newPerm = Iterables.getOnlyElement(newGroup.getIpPermissions());
+      assertEquals(newPerm, permByCidrBlock);
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION,
+            "Action=AuthorizeSecurityGroupIngress&GroupId=sg-3c6ef654&IpPermissions.0.IpProtocol=tcp&IpPermissions.0.FromPort=22&IpPermissions.0.ToPort=40&IpPermissions.0.IpRanges.0.CidrIp=0.0.0.0/0");
+      assertPosted(DEFAULT_REGION, "Action=DescribeSecurityGroups&GroupId.1=sg-3c6ef654");
+      assertPosted(DEFAULT_REGION, "Action=DescribeAvailabilityZones");
+   }
+
+   IpPermission permByGroup = IpPermission.builder() //
+         .ipProtocol(IpProtocol.TCP) //
+         .fromPort(22) //
+         .toPort(40) //
+         .tenantIdGroupNamePair(group.getOwnerId(), group.getProviderId()).build();
+
+   public void addIpPermissionGroupFromIpPermission() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/authorize_securitygroup_ingress_response.xml");
+      enqueueXml(DEFAULT_REGION, "/describe_securitygroups_extension_group.xml");
+      enqueueXml(DEFAULT_REGION, "/availabilityZones.xml");
+
+      SecurityGroup newGroup = extension().addIpPermission(permByGroup, group);
+
+      assertEquals(1, newGroup.getIpPermissions().size());
+
+      IpPermission newPerm = Iterables.getOnlyElement(newGroup.getIpPermissions());
+      assertEquals(newPerm, permByGroup);
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION,
+            "Action=AuthorizeSecurityGroupIngress&GroupId=sg-3c6ef654&IpPermissions.0.IpProtocol=tcp&IpPermissions.0.FromPort=22&IpPermissions.0.ToPort=40&IpPermissions.0.Groups.0.UserId=993194456877&IpPermissions.0.Groups.0.GroupId=sg-3c6ef654");
+      assertPosted(DEFAULT_REGION, "Action=DescribeSecurityGroups&GroupId.1=sg-3c6ef654");
+      assertPosted(DEFAULT_REGION, "Action=DescribeAvailabilityZones");
+   }
+
+   public void addIpPermissionGroupFromParams() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/authorize_securitygroup_ingress_response.xml");
+      enqueueXml(DEFAULT_REGION, "/describe_securitygroups_extension_group.xml");
+      enqueueXml(DEFAULT_REGION, "/availabilityZones.xml");
+
+      SecurityGroup newGroup = extension()
+            .addIpPermission(permByGroup.getIpProtocol(), permByGroup.getFromPort(), permByGroup.getToPort(),
+                  permByGroup.getTenantIdGroupNamePairs(), permByGroup.getCidrBlocks(), permByGroup.getGroupIds(),
+                  group);
+
+      IpPermission newPerm = Iterables.getOnlyElement(newGroup.getIpPermissions());
+      assertEquals(newPerm, permByGroup);
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION,
+            "Action=AuthorizeSecurityGroupIngress&GroupId=sg-3c6ef654&IpPermissions.0.IpProtocol=tcp&IpPermissions.0.FromPort=22&IpPermissions.0.ToPort=40&IpPermissions.0.Groups.0.UserId=993194456877&IpPermissions.0.Groups.0.GroupId=sg-3c6ef654");
+      assertPosted(DEFAULT_REGION, "Action=DescribeSecurityGroups&GroupId.1=sg-3c6ef654");
+      assertPosted(DEFAULT_REGION, "Action=DescribeAvailabilityZones");
+   }
+
+   public void createSecurityGroup() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/created_securitygroup.xml");
+      // TODO: ridiculously chatty
+      enqueueXml(DEFAULT_REGION, "/describe_securitygroups_extension_single.xml");
+      enqueueXml(DEFAULT_REGION, "/describe_securitygroups_extension_single.xml");
+      enqueueXml(DEFAULT_REGION, "/describe_securitygroups_extension_single.xml");
+      enqueueXml(DEFAULT_REGION, "/describe_securitygroups_extension_single.xml");
+      enqueueXml(DEFAULT_REGION, "/availabilityZones.xml");
+
+      SecurityGroup newGroup = extension()
+            .createSecurityGroup(group.getName().replace("jclouds#", ""), group.getLocation());
+      assertEquals(newGroup.getId(), group.getId());
+      assertEquals(newGroup.getProviderId(), group.getProviderId());
+      assertEquals(newGroup.getName(), group.getName());
+      assertEquals(newGroup.getLocation().getId(), group.getLocation().getId()); // One from response has a parent
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION,
+            "Action=CreateSecurityGroup&GroupName=jclouds%23some-group&GroupDescription=jclouds%23some-group");
+      assertPosted(DEFAULT_REGION, "Action=DescribeSecurityGroups&GroupName.1=jclouds%23some-group");
+      assertPosted(DEFAULT_REGION, "Action=DescribeSecurityGroups&GroupName.1=jclouds%23some-group");
+      assertPosted(DEFAULT_REGION, "Action=DescribeSecurityGroups&GroupName.1=jclouds%23some-group");
+      assertPosted(DEFAULT_REGION, "Action=DescribeSecurityGroups&GroupId.1=sg-3c6ef654");
+      assertPosted(DEFAULT_REGION, "Action=DescribeAvailabilityZones");
+   }
+
+   private SecurityGroupExtension extension() {
+      return computeService().getSecurityGroupExtension().get();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/extensions/AWSEC2SecurityGroupExtensionExpectTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/extensions/AWSEC2SecurityGroupExtensionExpectTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/extensions/AWSEC2SecurityGroupExtensionExpectTest.java
deleted file mode 100644
index 3e59794..0000000
--- a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/extensions/AWSEC2SecurityGroupExtensionExpectTest.java
+++ /dev/null
@@ -1,414 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.jclouds.aws.ec2.compute.extensions;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertTrue;
-
-import javax.ws.rs.core.MediaType;
-import java.util.Set;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableMap.Builder;
-import com.google.common.collect.ImmutableMultimap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.LinkedHashMultimap;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
-import org.jclouds.aws.ec2.compute.internal.BaseAWSEC2ComputeServiceExpectTest;
-import org.jclouds.compute.domain.SecurityGroup;
-import org.jclouds.compute.domain.SecurityGroupBuilder;
-import org.jclouds.compute.extensions.SecurityGroupExtension;
-import org.jclouds.domain.LocationBuilder;
-import org.jclouds.domain.LocationScope;
-import org.jclouds.http.HttpRequest;
-import org.jclouds.http.HttpResponse;
-import org.jclouds.net.domain.IpPermission;
-import org.jclouds.net.domain.IpProtocol;
-import org.testng.annotations.Test;
-
-@Test(groups = "unit", testName = "AWSEC2SecurityGroupExtensionExpectTest")
-public class AWSEC2SecurityGroupExtensionExpectTest extends BaseAWSEC2ComputeServiceExpectTest {
-
-   public void testAddIpPermissionCidrFromIpPermission() {
-      HttpRequest describeSecurityGroupsSingleRequest = 
-         formSigner.filter(HttpRequest.builder()
-                           .method("POST")
-                           .endpoint("https://ec2." + region + ".amazonaws.com/")
-                           .addHeader("Host", "ec2." + region + ".amazonaws.com")
-                           .addFormParam("Action", "DescribeSecurityGroups")
-                           .addFormParam("GroupId.1", "sg-3c6ef654").build());
-      
-      HttpResponse describeSecurityGroupsSingleResponse = 
-         HttpResponse.builder().statusCode(200)
-         .payload(payloadFromResourceWithContentType(
-                                                     "/describe_securitygroups_extension_cidr.xml", MediaType.APPLICATION_XML)).build();
-      
-
-      HttpRequest authorizeSecurityGroupIngressRequestRange = 
-               formSigner.filter(HttpRequest.builder()
-                                 .method("POST")
-                                 .endpoint("https://ec2." + region + ".amazonaws.com/")
-                                 .addHeader("Host", "ec2." + region + ".amazonaws.com")
-                                 .addFormParam("Action", "AuthorizeSecurityGroupIngress")
-                                 .addFormParam("GroupId", "sg-3c6ef654")
-                                 .addFormParam("IpPermissions.0.FromPort", "22")
-                                 .addFormParam("IpPermissions.0.IpProtocol", "tcp")
-                                 .addFormParam("IpPermissions.0.IpRanges.0.CidrIp", "0.0.0.0/0")
-                                 .addFormParam("IpPermissions.0.ToPort", "40")
-                                 .build());
-                                 
-      Builder<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder();
-      requestResponseMap.put(describeRegionsRequest, describeRegionsResponse);
-      requestResponseMap.put(describeAvailabilityZonesRequest, describeAvailabilityZonesResponse);
-      requestResponseMap.put(describeSecurityGroupsSingleRequest, describeSecurityGroupsSingleResponse);
-      requestResponseMap.put(createKeyPairRequest, createKeyPairResponse);
-      requestResponseMap.put(createSecurityGroupRequest, createSecurityGroupResponse);
-
-      requestResponseMap.put(authorizeSecurityGroupIngressRequestRange, authorizeSecurityGroupIngressResponse);
-
-      IpPermission.Builder builder = IpPermission.builder();
-
-      builder.ipProtocol(IpProtocol.TCP);
-      builder.fromPort(22);
-      builder.toPort(40);
-      builder.cidrBlock("0.0.0.0/0");
-
-      IpPermission perm = builder.build();
-
-      SecurityGroupExtension extension = requestsSendResponses(requestResponseMap.build()).getSecurityGroupExtension().get();
-
-      SecurityGroupBuilder groupBuilder = new SecurityGroupBuilder();
-      groupBuilder.id("us-east-1/sg-3c6ef654");
-      groupBuilder.providerId("sg-3c6ef654");
-      groupBuilder.name("jclouds#some-group");
-      groupBuilder.location(new LocationBuilder()
-                            .scope(LocationScope.REGION)
-                            .id(region)
-                            .description("region")
-                            .build());
-      
-      SecurityGroup origGroup = groupBuilder.build();
-
-      SecurityGroup newGroup = extension.addIpPermission(perm, origGroup);
-
-      assertEquals(1, newGroup.getIpPermissions().size());
-
-      IpPermission newPerm = Iterables.getOnlyElement(newGroup.getIpPermissions());
-
-      assertNotNull(newPerm);
-      assertEquals(IpProtocol.TCP, newPerm.getIpProtocol());
-      assertEquals(22, newPerm.getFromPort());
-      assertEquals(40, newPerm.getToPort());
-      assertEquals(1, newPerm.getCidrBlocks().size());
-      assertTrue(newPerm.getCidrBlocks().contains("0.0.0.0/0"));
-   }
-
-   public void testAddIpPermissionCidrFromParams() {
-      HttpRequest describeSecurityGroupsSingleRequest = 
-         formSigner.filter(HttpRequest.builder()
-                           .method("POST")
-                           .endpoint("https://ec2." + region + ".amazonaws.com/")
-                           .addHeader("Host", "ec2." + region + ".amazonaws.com")
-                           .addFormParam("Action", "DescribeSecurityGroups")
-                           .addFormParam("GroupId.1", "sg-3c6ef654").build());
-      
-      HttpResponse describeSecurityGroupsSingleResponse = 
-         HttpResponse.builder().statusCode(200)
-         .payload(payloadFromResourceWithContentType(
-                                                     "/describe_securitygroups_extension_cidr.xml", MediaType.APPLICATION_XML)).build();
-      
-
-      HttpRequest authorizeSecurityGroupIngressRequestRange = 
-               formSigner.filter(HttpRequest.builder()
-                                 .method("POST")
-                                 .endpoint("https://ec2." + region + ".amazonaws.com/")
-                                 .addHeader("Host", "ec2." + region + ".amazonaws.com")
-                                 .addFormParam("Action", "AuthorizeSecurityGroupIngress")
-                                 .addFormParam("GroupId", "sg-3c6ef654")
-                                 .addFormParam("IpPermissions.0.FromPort", "22")
-                                 .addFormParam("IpPermissions.0.IpProtocol", "tcp")
-                                 .addFormParam("IpPermissions.0.IpRanges.0.CidrIp", "0.0.0.0/0")
-                                 .addFormParam("IpPermissions.0.ToPort", "40")
-                                 .build());
-      
-      Builder<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder();
-      requestResponseMap.put(describeRegionsRequest, describeRegionsResponse);
-      requestResponseMap.put(describeAvailabilityZonesRequest, describeAvailabilityZonesResponse);
-      requestResponseMap.put(describeSecurityGroupsSingleRequest, describeSecurityGroupsSingleResponse);
-      requestResponseMap.put(createKeyPairRequest, createKeyPairResponse);
-      requestResponseMap.put(createSecurityGroupRequest, createSecurityGroupResponse);
-
-      requestResponseMap.put(authorizeSecurityGroupIngressRequestRange, authorizeSecurityGroupIngressResponse);
-
-      SecurityGroupExtension extension = requestsSendResponses(requestResponseMap.build()).getSecurityGroupExtension().get();
-
-      SecurityGroupBuilder groupBuilder = new SecurityGroupBuilder();
-      groupBuilder.id("us-east-1/sg-3c6ef654");
-      groupBuilder.providerId("sg-3c6ef654");
-      groupBuilder.name("jclouds#some-group");
-      groupBuilder.location(new LocationBuilder()
-                            .scope(LocationScope.REGION)
-                            .id(region)
-                            .description("region")
-                            .build());
-      
-      SecurityGroup origGroup = groupBuilder.build();
-
-      SecurityGroup newGroup = extension.addIpPermission(IpProtocol.TCP,
-                                                         22,
-                                                         40,
-                                                         emptyMultimap(),
-                                                         ImmutableSet.of("0.0.0.0/0"),
-                                                         emptyStringSet(),
-                                                         origGroup);
-
-      assertEquals(1, newGroup.getIpPermissions().size());
-
-      IpPermission newPerm = Iterables.getOnlyElement(newGroup.getIpPermissions());
-
-      assertNotNull(newPerm);
-      assertEquals(IpProtocol.TCP, newPerm.getIpProtocol());
-      assertEquals(22, newPerm.getFromPort());
-      assertEquals(40, newPerm.getToPort());
-      assertEquals(1, newPerm.getCidrBlocks().size());
-      assertTrue(newPerm.getCidrBlocks().contains("0.0.0.0/0"));
-   }
-
-   public void testAddIpPermissionGroupFromIpPermission() {
-      HttpRequest describeSecurityGroupsSingleRequest = 
-         formSigner.filter(HttpRequest.builder()
-                           .method("POST")
-                           .endpoint("https://ec2." + region + ".amazonaws.com/")
-                           .addHeader("Host", "ec2." + region + ".amazonaws.com")
-                           .addFormParam("Action", "DescribeSecurityGroups")
-                           .addFormParam("GroupId.1", "sg-3c6ef654").build());
-      
-      HttpResponse describeSecurityGroupsSingleResponse = 
-         HttpResponse.builder().statusCode(200)
-         .payload(payloadFromResourceWithContentType(
-                                                     "/describe_securitygroups_extension_group.xml", MediaType.APPLICATION_XML)).build();
-      
-
-      HttpRequest authorizeSecurityGroupIngressRequestGroupTenant = 
-               formSigner.filter(HttpRequest.builder()
-                                 .method("POST")
-                                 .endpoint("https://ec2." + region + ".amazonaws.com/")
-                                 .addHeader("Host", "ec2." + region + ".amazonaws.com")
-                                 .addFormParam("Action", "AuthorizeSecurityGroupIngress")
-                                 .addFormParam("GroupId", "sg-3c6ef654")
-                                 .addFormParam("IpPermissions.0.FromPort", "22")
-                                 .addFormParam("IpPermissions.0.Groups.0.GroupId", "sg-3c6ef654")
-                                 .addFormParam("IpPermissions.0.Groups.0.UserId", "993194456877")
-                                 .addFormParam("IpPermissions.0.IpProtocol", "tcp")
-                                 .addFormParam("IpPermissions.0.ToPort", "40")
-                                 .build());
-
-      Builder<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder();
-      requestResponseMap.put(describeRegionsRequest, describeRegionsResponse);
-      requestResponseMap.put(describeAvailabilityZonesRequest, describeAvailabilityZonesResponse);
-      requestResponseMap.put(describeSecurityGroupsSingleRequest, describeSecurityGroupsSingleResponse);
-      requestResponseMap.put(createKeyPairRequest, createKeyPairResponse);
-      requestResponseMap.put(createSecurityGroupRequest, createSecurityGroupResponse);
-
-      requestResponseMap.put(authorizeSecurityGroupIngressRequestGroupTenant, authorizeSecurityGroupIngressResponse);
-
-      IpPermission.Builder builder = IpPermission.builder();
-
-      builder.ipProtocol(IpProtocol.TCP);
-      builder.fromPort(22);
-      builder.toPort(40);
-      builder.tenantIdGroupNamePair("993194456877", "sg-3c6ef654");
-
-      IpPermission perm = builder.build();
-
-      SecurityGroupExtension extension = requestsSendResponses(requestResponseMap.build()).getSecurityGroupExtension().get();
-
-      SecurityGroupBuilder groupBuilder = new SecurityGroupBuilder();
-      groupBuilder.id("us-east-1/sg-3c6ef654");
-      groupBuilder.providerId("sg-3c6ef654");
-      groupBuilder.name("jclouds#some-group");
-      groupBuilder.location(new LocationBuilder()
-                            .scope(LocationScope.REGION)
-                            .id(region)
-                            .description("region")
-                            .build());
-      groupBuilder.ownerId("993194456877");
-      
-      SecurityGroup origGroup = groupBuilder.build();
-
-      SecurityGroup newGroup = extension.addIpPermission(perm, origGroup);
-
-      assertEquals(1, newGroup.getIpPermissions().size());
-
-      IpPermission newPerm = Iterables.getOnlyElement(newGroup.getIpPermissions());
-
-      assertNotNull(newPerm);
-      assertEquals(IpProtocol.TCP, newPerm.getIpProtocol());
-      assertEquals(22, newPerm.getFromPort());
-      assertEquals(40, newPerm.getToPort());
-      assertEquals(0, newPerm.getCidrBlocks().size());
-      assertEquals(1, newPerm.getTenantIdGroupNamePairs().size());
-      assertTrue(newPerm.getTenantIdGroupNamePairs().keySet().contains(origGroup.getOwnerId()));
-      assertTrue(newPerm.getTenantIdGroupNamePairs().values().contains(origGroup.getProviderId()));
-   }
-
-
-   public void testAddIpPermissionGroupFromParams() {
-      HttpRequest describeSecurityGroupsSingleRequest = 
-         formSigner.filter(HttpRequest.builder()
-                           .method("POST")
-                           .endpoint("https://ec2." + region + ".amazonaws.com/")
-                           .addHeader("Host", "ec2." + region + ".amazonaws.com")
-                           .addFormParam("Action", "DescribeSecurityGroups")
-                           .addFormParam("GroupId.1", "sg-3c6ef654").build());
-      
-      HttpResponse describeSecurityGroupsSingleResponse = 
-         HttpResponse.builder().statusCode(200)
-         .payload(payloadFromResourceWithContentType(
-                                                     "/describe_securitygroups_extension_group.xml", MediaType.APPLICATION_XML)).build();
-
-
-      HttpRequest authorizeSecurityGroupIngressRequestGroupTenant = 
-               formSigner.filter(HttpRequest.builder()
-                                 .method("POST")
-                                 .endpoint("https://ec2." + region + ".amazonaws.com/")
-                                 .addHeader("Host", "ec2." + region + ".amazonaws.com")
-                                 .addFormParam("Action", "AuthorizeSecurityGroupIngress")
-                                 .addFormParam("GroupId", "sg-3c6ef654")
-                                 .addFormParam("IpPermissions.0.FromPort", "22")
-                                 .addFormParam("IpPermissions.0.Groups.0.GroupId", "sg-3c6ef654")
-                                 .addFormParam("IpPermissions.0.Groups.0.UserId", "993194456877")
-                                 .addFormParam("IpPermissions.0.IpProtocol", "tcp")
-                                 .addFormParam("IpPermissions.0.ToPort", "40")
-                                 .build());
-
-      Builder<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder();
-      requestResponseMap.put(describeRegionsRequest, describeRegionsResponse);
-      requestResponseMap.put(describeAvailabilityZonesRequest, describeAvailabilityZonesResponse);
-      requestResponseMap.put(describeSecurityGroupsSingleRequest, describeSecurityGroupsSingleResponse);
-      requestResponseMap.put(createKeyPairRequest, createKeyPairResponse);
-      requestResponseMap.put(createSecurityGroupRequest, createSecurityGroupResponse);
-
-      requestResponseMap.put(authorizeSecurityGroupIngressRequestGroupTenant, authorizeSecurityGroupIngressResponse);
-
-      SecurityGroupExtension extension = requestsSendResponses(requestResponseMap.build()).getSecurityGroupExtension().get();
-
-      SecurityGroupBuilder groupBuilder = new SecurityGroupBuilder();
-      groupBuilder.id("us-east-1/sg-3c6ef654");
-      groupBuilder.providerId("sg-3c6ef654");
-      groupBuilder.name("jclouds#some-group");
-      groupBuilder.ownerId("993194456877");
-      groupBuilder.location(new LocationBuilder()
-                            .scope(LocationScope.REGION)
-                            .id(region)
-                            .description("region")
-                            .build());
-      
-      SecurityGroup origGroup = groupBuilder.build();
-
-      ImmutableMultimap.Builder<String, String> permBuilder = ImmutableMultimap.builder();
-      permBuilder.put(origGroup.getOwnerId(), origGroup.getId());
-      
-      SecurityGroup newGroup = extension.addIpPermission(IpProtocol.TCP,
-                                                         22,
-                                                         40,
-                                                         permBuilder.build(),
-                                                         emptyStringSet(),
-                                                         emptyStringSet(),
-                                                         origGroup);
-
-      assertEquals(1, newGroup.getIpPermissions().size());
-
-      IpPermission newPerm = Iterables.getOnlyElement(newGroup.getIpPermissions());
-
-      assertNotNull(newPerm);
-      assertEquals(IpProtocol.TCP, newPerm.getIpProtocol());
-      assertEquals(22, newPerm.getFromPort());
-      assertEquals(40, newPerm.getToPort());
-      assertEquals(0, newPerm.getCidrBlocks().size());
-      assertEquals(1, newPerm.getTenantIdGroupNamePairs().size());
-      assertTrue(newPerm.getTenantIdGroupNamePairs().keySet().contains(origGroup.getOwnerId()));
-      assertTrue(newPerm.getTenantIdGroupNamePairs().values().contains(origGroup.getProviderId()));
-   }
-
-   public void testCreateSecurityGroup() {
-      HttpRequest createSecurityGroupExtRequest =
-              formSigner.filter(HttpRequest.builder()
-                      .method("POST")
-                      .endpoint("https://ec2." + region + ".amazonaws.com/")
-                      .addHeader("Host", "ec2." + region + ".amazonaws.com")
-                      .addFormParam("Action", "CreateSecurityGroup")
-                      .addFormParam("GroupDescription", "jclouds#some-group")
-                      .addFormParam("GroupName", "jclouds#some-group").build());
-
-      HttpRequest describeSecurityGroupsSingleRequest =
-              formSigner.filter(HttpRequest.builder()
-                      .method("POST")
-                      .endpoint("https://ec2." + region + ".amazonaws.com/")
-                      .addHeader("Host", "ec2." + region + ".amazonaws.com")
-                      .addFormParam("Action", "DescribeSecurityGroups")
-                      .addFormParam("GroupName.1", "jclouds#some-group").build());
-
-      HttpRequest describeSecurityGroupsByIdRequest =
-              formSigner.filter(HttpRequest.builder()
-                      .method("POST")
-                      .endpoint("https://ec2." + region + ".amazonaws.com/")
-                      .addHeader("Host", "ec2." + region + ".amazonaws.com")
-                      .addFormParam("Action", "DescribeSecurityGroups")
-                      .addFormParam("GroupId.1", "sg-3c6ef654").build());
-
-      HttpResponse describeSecurityGroupsSingleResponse =
-              HttpResponse.builder().statusCode(200)
-                      .payload(payloadFromResourceWithContentType(
-                              "/describe_securitygroups_extension_single.xml", MediaType.APPLICATION_XML)).build();
-
-
-      Builder<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder();
-      requestResponseMap.put(describeRegionsRequest, describeRegionsResponse);
-      requestResponseMap.put(describeAvailabilityZonesRequest, describeAvailabilityZonesResponse);
-      requestResponseMap.put(describeSecurityGroupsSingleRequest, describeSecurityGroupsSingleResponse);
-      requestResponseMap.put(describeSecurityGroupsByIdRequest, describeSecurityGroupsSingleResponse);
-      requestResponseMap.put(createKeyPairRequest, createKeyPairResponse);
-      requestResponseMap.put(createSecurityGroupExtRequest, createSecurityGroupResponse);
-
-      requestResponseMap.put(authorizeSecurityGroupIngressRequest22, authorizeSecurityGroupIngressResponse);
-      requestResponseMap.put(authorizeSecurityGroupIngressRequestGroup, authorizeSecurityGroupIngressResponse);
-
-
-      SecurityGroupExtension extension = requestsSendResponses(requestResponseMap.build()).getSecurityGroupExtension().get();
-
-      SecurityGroup group = extension.createSecurityGroup("some-group", new LocationBuilder()
-              .scope(LocationScope.REGION)
-              .id(region)
-              .description("region")
-              .build());
-
-      assertEquals("sg-3c6ef654", group.getProviderId());
-      assertEquals(region + "/sg-3c6ef654", group.getId());
-   }
-   
-   private Multimap<String, String> emptyMultimap() {
-      return LinkedHashMultimap.create();
-   }
-
-   private Set<String> emptyStringSet() {
-      return Sets.newLinkedHashSet();
-   }
-}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/internal/BaseAWSEC2ComputeServiceExpectTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/internal/BaseAWSEC2ComputeServiceExpectTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/internal/BaseAWSEC2ComputeServiceExpectTest.java
deleted file mode 100644
index 376b169..0000000
--- a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/internal/BaseAWSEC2ComputeServiceExpectTest.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.jclouds.aws.ec2.compute.internal;
-
-import static org.jclouds.aws.ec2.reference.AWSEC2Constants.PROPERTY_EC2_CC_AMI_QUERY;
-import static org.jclouds.ec2.reference.EC2Constants.PROPERTY_EC2_GENERATE_INSTANCE_NAMES;
-
-import java.util.Properties;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.jclouds.aws.ec2.config.AWSEC2HttpApiModule;
-import org.jclouds.date.DateService;
-import org.jclouds.ec2.compute.internal.BaseEC2ComputeServiceExpectTest;
-import org.jclouds.http.HttpRequest;
-import org.jclouds.rest.ConfiguresHttpApi;
-import org.testng.annotations.BeforeClass;
-
-import com.google.common.base.Supplier;
-import com.google.inject.Module;
-import com.google.inject.Provides;
-import com.google.inject.TypeLiteral;
-
-/**
- * Tests the compute service abstraction of the EC2 api.
- */
-public abstract class BaseAWSEC2ComputeServiceExpectTest extends BaseEC2ComputeServiceExpectTest {
-
-   protected HttpRequest describeSecurityGroupByIdRequest;
-
-   public BaseAWSEC2ComputeServiceExpectTest() {
-      provider = "aws-ec2";
-   }
-
-   @Override
-   protected Properties setupProperties() {
-      Properties properties = super.setupProperties();
-      // zero out cluster image query for now
-      properties.setProperty(PROPERTY_EC2_CC_AMI_QUERY, "");
-      properties.setProperty(PROPERTY_EC2_GENERATE_INSTANCE_NAMES, "false");
-      return properties;
-   }
-
-   @BeforeClass
-   @Override
-   protected void setupDefaultRequests() {
-      super.setupDefaultRequests();
-
-      describeSecurityGroupByIdRequest =
-              formSigner.filter(HttpRequest.builder()
-                      .method("POST")
-                      .endpoint("https://ec2." + region + ".amazonaws.com/")
-                      .addHeader("Host", "ec2." + region + ".amazonaws.com")
-                      .addFormParam("Action", "DescribeSecurityGroups")
-                      .addFormParam("GroupId.1", "sg-3c6ef654").build());
-
-      authorizeSecurityGroupIngressRequestGroup =
-              formSigner.filter(HttpRequest.builder()
-                      .method("POST")
-                      .endpoint("https://ec2." + region + ".amazonaws.com/")
-                      .addHeader("Host", "ec2." + region + ".amazonaws.com")
-                      .addFormParam("Action", "AuthorizeSecurityGroupIngress")
-                      .addFormParam("SourceSecurityGroupId", "sg-3c6ef654")
-                      .addFormParam("SourceSecurityGroupOwnerId", "993194456877")
-                      .addFormParam("GroupName", "jclouds#test").build());
-
-      authorizeSecurityGroupIngressRequest22 = 
-         formSigner.filter(HttpRequest.builder()
-                           .method("POST")
-                           .endpoint("https://ec2." + region + ".amazonaws.com/")
-                           .addHeader("Host", "ec2." + region + ".amazonaws.com")
-                           .addFormParam("Action", "AuthorizeSecurityGroupIngress")
-                           .addFormParam("GroupId", "sg-3c6ef654")
-                           .addFormParam("IpPermissions.0.FromPort", "22")
-                           .addFormParam("IpPermissions.0.ToPort", "22")
-                           .addFormParam("IpPermissions.0.IpRanges.0.CidrIp", "0.0.0.0/0")
-                           .addFormParam("IpPermissions.0.IpProtocol", "tcp")
-                           .addFormParam("IpPermissions.1.FromPort", "0")
-                           .addFormParam("IpPermissions.1.ToPort", "65535")
-                           .addFormParam("IpPermissions.1.Groups.0.GroupId", "sg-3c6ef654")
-                           .addFormParam("IpPermissions.1.Groups.0.UserId", "993194456877")
-                           .addFormParam("IpPermissions.1.IpProtocol", "tcp")
-                           .addFormParam("IpPermissions.2.FromPort", "0")
-                           .addFormParam("IpPermissions.2.ToPort", "65535")
-                           .addFormParam("IpPermissions.2.Groups.0.GroupId", "sg-3c6ef654")
-                           .addFormParam("IpPermissions.2.Groups.0.UserId", "993194456877")
-                           .addFormParam("IpPermissions.2.IpProtocol", "udp")
-                           .build());
-
-      describeImagesRequest = 
-               formSigner.filter(HttpRequest.builder()
-                          .method("POST")
-                          .endpoint("https://ec2." + region + ".amazonaws.com/")
-                          .addHeader("Host", "ec2." + region + ".amazonaws.com")
-                          .addFormParam("Action", "DescribeImages")
-                          .addFormParam("Filter.1.Name", "owner-id")
-                          .addFormParam("Filter.1.Value.1", "137112412989")
-                          .addFormParam("Filter.1.Value.2", "801119661308")
-                          .addFormParam("Filter.1.Value.3", "063491364108")
-                          .addFormParam("Filter.1.Value.4", "099720109477")
-                          .addFormParam("Filter.1.Value.5", "411009282317")
-                          .addFormParam("Filter.2.Name", "state")
-                          .addFormParam("Filter.2.Value.1", "available")
-                          .addFormParam("Filter.3.Name", "image-type")
-                          .addFormParam("Filter.3.Value.1", "machine").build());
-   }
-
-   @ConfiguresHttpApi
-   protected static class TestAWSEC2HttpApiModule extends AWSEC2HttpApiModule {
-
-      @Override
-      protected void configure() {
-         super.configure();
-         // predicatable node names
-         final AtomicInteger suffix = new AtomicInteger();
-         bind(new TypeLiteral<Supplier<String>>() {
-         }).toInstance(new Supplier<String>() {
-
-            @Override
-            public String get() {
-               return suffix.getAndIncrement() + "";
-            }
-
-         });
-      }
-
-      @Override
-      @Provides
-      protected String provideTimeStamp(DateService dateService) {
-         return CONSTANT_DATE;
-      }
-   }
-
-   @Override
-   protected Module createModule() {
-      return new TestAWSEC2HttpApiModule();
-   }
-}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/AWSAMIApiMockTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/AWSAMIApiMockTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/AWSAMIApiMockTest.java
new file mode 100644
index 0000000..283ea2d
--- /dev/null
+++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/AWSAMIApiMockTest.java
@@ -0,0 +1,254 @@
+/*
+ * 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.aws.ec2.features;
+
+import static org.jclouds.ec2.options.DescribeImagesOptions.Builder.executableBy;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.jclouds.aws.ec2.internal.BaseAWSEC2ApiMockTest;
+import org.jclouds.ec2.domain.Image;
+import org.jclouds.ec2.domain.Permission;
+import org.jclouds.ec2.options.CreateImageOptions;
+import org.jclouds.ec2.options.RegisterImageBackedByEbsOptions;
+import org.jclouds.ec2.options.RegisterImageOptions;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+
+@Test(groups = "unit", testName = "AWSAMIApiMockTest", singleThreaded = true)
+public class AWSAMIApiMockTest extends BaseAWSEC2ApiMockTest {
+
+   public void describeImagesInRegion() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/amzn_images.xml");
+
+      Set<? extends Image> result = amiApi().describeImagesInRegion(DEFAULT_REGION);
+
+      assertFalse(result.isEmpty());
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DescribeImages");
+   }
+
+   public void describeImagesInRegion_options() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/amzn_images.xml");
+
+      Set<? extends Image> result = amiApi()
+            .describeImagesInRegion(DEFAULT_REGION, executableBy("me").ownedBy("fred", "nancy").imageIds("1", "2"));
+
+      assertFalse(result.isEmpty());
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DescribeImages&ExecutableBy=me&Owner.1=fred&Owner.2=nancy&ImageId.1=1&ImageId.2=2");
+   }
+
+   public void describeImagesInRegion_404() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueue(DEFAULT_REGION, new MockResponse().setResponseCode(404));
+
+      Set<? extends Image> result = amiApi().describeImagesInRegion(DEFAULT_REGION);
+
+      assertTrue(result.isEmpty());
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DescribeImages");
+   }
+
+   public void createImageInRegion() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueue(DEFAULT_REGION, new MockResponse().setBody("<CreateImageResponse><ImageId>ami-246f8d4d</ImageId></CreateImageResponse>"));
+
+      String result = amiApi().createImageInRegion(DEFAULT_REGION, "name", "instanceId");
+      assertEquals(result, "ami-246f8d4d");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=CreateImage&Name=name&InstanceId=instanceId");
+   }
+
+   public void createImageInRegion_options() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueue(DEFAULT_REGION,
+            new MockResponse().setBody("<CreateImageResponse><ImageId>ami-246f8d4d</ImageId></CreateImageResponse>"));
+
+      String result = amiApi().createImageInRegion(DEFAULT_REGION, "name", "instanceId",
+            new CreateImageOptions().withDescription("description").noReboot());
+      assertEquals(result, "ami-246f8d4d");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=CreateImage&Name=name&InstanceId=instanceId&Description=description&NoReboot=true");
+   }
+
+   public void registerImageFromManifestInRegion() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueue(DEFAULT_REGION, new MockResponse().setBody("<RegisterImageResponse><ImageId>ami-246f8d4d</ImageId></RegisterImageResponse>"));
+
+      String result = amiApi().registerImageFromManifestInRegion(DEFAULT_REGION, "name", "pathToManifest");
+      assertEquals(result, "ami-246f8d4d");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=RegisterImage&Name=name&ImageLocation=pathToManifest");
+   }
+
+   public void registerImageFromManifestInRegion_options() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueue(DEFAULT_REGION, new MockResponse().setBody("<RegisterImageResponse><ImageId>ami-246f8d4d</ImageId></RegisterImageResponse>"));
+
+      String result = amiApi().registerImageFromManifestInRegion(DEFAULT_REGION, "name", "pathToManifest",
+            new RegisterImageOptions().withDescription("description"));
+      assertEquals(result, "ami-246f8d4d");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=RegisterImage&Name=name&ImageLocation=pathToManifest&Description=description");
+   }
+
+   public void registerUnixImageBackedByEbsInRegion() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueue(DEFAULT_REGION, new MockResponse()
+            .setBody("<RegisterImageResponse><ImageId>ami-246f8d4d</ImageId></RegisterImageResponse>"));
+
+      String result = amiApi().registerUnixImageBackedByEbsInRegion(DEFAULT_REGION, "imageName", "snapshotId");
+      assertEquals(result, "ami-246f8d4d");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=RegisterImage&RootDeviceName=/dev/sda1&BlockDeviceMapping.0.DeviceName=/dev/sda1&Name=imageName&BlockDeviceMapping.0.Ebs.SnapshotId=snapshotId");
+   }
+
+   public void registerUnixImageBackedByEbsInRegion_options() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueue(DEFAULT_REGION, new MockResponse().setBody("<RegisterImageResponse><ImageId>ami-246f8d4d</ImageId></RegisterImageResponse>"));
+
+      String result = amiApi().registerUnixImageBackedByEbsInRegion(DEFAULT_REGION, "imageName", "snapshotId",
+            new RegisterImageBackedByEbsOptions().withDescription("description")
+                  .addBlockDeviceFromSnapshot("/dev/device", null, "snapshot", false, "gp2", null, false)
+                  .addNewBlockDevice("/dev/newdevice", "newblock", 100));
+      assertEquals(result, "ami-246f8d4d");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=RegisterImage&RootDeviceName=/dev/sda1&BlockDeviceMapping.0.DeviceName=/dev/sda1&Name=imageName&BlockDeviceMapping.0.Ebs.SnapshotId=snapshotId&Description=description&BlockDeviceMapping.1.Ebs.DeleteOnTermination=false&BlockDeviceMapping.1.Ebs.VolumeType=gp2&BlockDeviceMapping.1.DeviceName=/dev/device&BlockDeviceMapping.1.Ebs.SnapshotId=snapshot&BlockDeviceMapping.2.Ebs.DeleteOnTermination=false&BlockDeviceMapping.2.DeviceName=/dev/newdevice&BlockDeviceMapping.2.VirtualName=newblock&BlockDeviceMapping.2.Ebs.VolumeSize=100");
+   }
+
+   public void deregisterImageInRegion() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueue(DEFAULT_REGION, new MockResponse());
+
+      amiApi().deregisterImageInRegion(DEFAULT_REGION, "ami-246f8d4d");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DeregisterImage&ImageId=ami-246f8d4d");
+   }
+
+   public void getBlockDeviceMappingsForImageInRegion() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/describe_image_attribute_blockDeviceMapping.xml");
+
+      Map<String, Image.EbsBlockDevice> result = amiApi()
+            .getBlockDeviceMappingsForImageInRegion(DEFAULT_REGION, "ami-246f8d4d");
+
+      assertFalse(result.isEmpty());
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DescribeImageAttribute&Attribute=blockDeviceMapping&ImageId=ami-246f8d4d");
+   }
+
+   public void getLaunchPermissionForImageInRegion() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/describe_image_attribute_launchPermission.xml");
+
+      Permission result = amiApi().getLaunchPermissionForImageInRegion(DEFAULT_REGION, "ami-246f8d4d");
+
+      assertNotNull(result);
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DescribeImageAttribute&Attribute=launchPermission&ImageId=ami-246f8d4d");
+   }
+
+   public void addLaunchPermissionsToImageInRegion() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueue(DEFAULT_REGION, new MockResponse());
+
+      amiApi().addLaunchPermissionsToImageInRegion(DEFAULT_REGION, ImmutableList.of("bob", "sue"),
+            ImmutableList.of("all"), "imageId");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=ModifyImageAttribute&OperationType=add&Attribute=launchPermission&ImageId=imageId&UserId.1=bob&UserId.2=sue&UserGroup.1=all");
+   }
+
+   public void removeLaunchPermissionsFromImageInRegion() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueue(DEFAULT_REGION, new MockResponse());
+
+      amiApi().removeLaunchPermissionsFromImageInRegion(DEFAULT_REGION, ImmutableList.of("bob", "sue"),
+            ImmutableList.of("all"), "imageId");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=ModifyImageAttribute&OperationType=remove&Attribute=launchPermission&ImageId=imageId&UserId.1=bob&UserId.2=sue&UserGroup.1=all");
+   }
+
+   public void resetLaunchPermissionsOnImageInRegion() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueue(DEFAULT_REGION, new MockResponse());
+
+      amiApi().resetLaunchPermissionsOnImageInRegion(DEFAULT_REGION, "imageId");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=ResetImageAttribute&Attribute=launchPermission&ImageId=imageId");
+   }
+
+   public void getProductCodesForImageInRegion() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/describe_image_attribute_productCodes.xml");
+
+      Set<String> result = amiApi().getProductCodesForImageInRegion(DEFAULT_REGION, "ami-246f8d4d");
+
+      assertFalse(result.isEmpty());
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DescribeImageAttribute&Attribute=productCodes&ImageId=ami-246f8d4d");
+   }
+
+   public void addProductCodesToImageInRegion() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueue(DEFAULT_REGION, new MockResponse());
+
+      amiApi().addProductCodesToImageInRegion(DEFAULT_REGION, ImmutableList.of("code1", "code2"), "imageId");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=ModifyImageAttribute&OperationType=add&Attribute=productCodes&ImageId=imageId&ProductCode.1=code1&ProductCode.2=code2");
+   }
+
+   public void removeProductCodesFromImageInRegion() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueue(DEFAULT_REGION, new MockResponse());
+
+      amiApi().removeProductCodesFromImageInRegion(DEFAULT_REGION, ImmutableList.of("code1", "code2"), "imageId");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=ModifyImageAttribute&OperationType=remove&Attribute=productCodes&ImageId=imageId&ProductCode.1=code1&ProductCode.2=code2");
+   }
+
+   private AWSAMIApi amiApi() {
+      return api().getAMIApi().get();
+   }
+}


[3/3] jclouds git commit: JCLOUDS-480 support version 4 signatures for aws-ec2.

Posted by ad...@apache.org.
JCLOUDS-480 support version 4 signatures for aws-ec2.


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

Branch: refs/heads/master
Commit: a449b24e7dd9d4872b3d729a3c63add5b6033318
Parents: ba868af
Author: Adrian Cole <ac...@twitter.com>
Authored: Wed Nov 19 10:52:36 2014 -0800
Committer: Adrian Cole <ad...@gmail.com>
Committed: Sat Nov 22 08:08:37 2014 -0800

----------------------------------------------------------------------
 .../binders/BindS3UploadPolicyAndSignature.java |  10 +-
 .../jclouds/ec2/features/BaseEC2ApiTest.java    |   8 +-
 .../aws/config/FormSigningHttpApiModule.java    |  14 +-
 .../org/jclouds/aws/filters/FormSigner.java     | 258 +++++-----
 .../org/jclouds/aws/filters/FormSignerV4.java   | 219 ++++++++
 .../org/jclouds/aws/filters/FormSignerTest.java | 102 ----
 .../jclouds/aws/filters/FormSignerV2Test.java   |  98 ++++
 .../aws/filters/FormSignerV4LiveTest.java       |  91 ++++
 .../jclouds/aws/filters/FormSignerV4Test.java   | 102 ++++
 .../java/org/jclouds/aws/util/AWSUtilsTest.java |   4 +-
 .../aws/xml/SessionCredentialsHandlerTest.java  |   1 -
 .../java/org/jclouds/rest/RequestSigner.java    |   5 +
 .../org/jclouds/rest/config/RestModule.java     |   5 -
 providers/aws-ec2/pom.xml                       |  12 +
 .../aws/ec2/config/AWSEC2HttpApiModule.java     |  17 +-
 .../AWSEC2ComputeServiceApiMockTest.java        | 237 +++++++++
 .../compute/AWSEC2ComputeServiceExpectTest.java | 278 -----------
 .../compute/AWSEC2TemplateBuilderLiveTest.java  |   6 +
 ...AWSEC2SecurityGroupExtensionApiMockTest.java | 167 +++++++
 .../AWSEC2SecurityGroupExtensionExpectTest.java | 414 ----------------
 .../BaseAWSEC2ComputeServiceExpectTest.java     | 151 ------
 .../aws/ec2/features/AWSAMIApiMockTest.java     | 254 ++++++++++
 .../jclouds/aws/ec2/features/AWSAMIApiTest.java | 496 -------------------
 .../ec2/features/AWSSecurityGroupApiTest.java   |  10 +-
 .../aws/ec2/features/BaseAWSEC2ApiTest.java     |  54 +-
 .../aws/ec2/features/MonitoringApiMockTest.java |  58 +++
 .../aws/ec2/features/MonitoringApiTest.java     |  71 ---
 .../features/PlacementGroupApiExpectTest.java   |  75 ---
 .../ec2/features/PlacementGroupApiMockTest.java | 100 ++++
 .../aws/ec2/features/PlacementGroupApiTest.java | 134 -----
 .../ec2/features/SpotInstanceApiExpectTest.java |  75 ---
 .../ec2/features/SpotInstanceApiMockTest.java   | 127 +++++
 .../aws/ec2/features/SpotInstanceApiTest.java   |  98 ----
 .../aws/ec2/internal/BaseAWSEC2ApiMockTest.java | 179 +++++++
 34 files changed, 1863 insertions(+), 2067 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/apis/ec2/src/main/java/org/jclouds/ec2/binders/BindS3UploadPolicyAndSignature.java
----------------------------------------------------------------------
diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/binders/BindS3UploadPolicyAndSignature.java b/apis/ec2/src/main/java/org/jclouds/ec2/binders/BindS3UploadPolicyAndSignature.java
index bc5b9fd..b49c9c4 100644
--- a/apis/ec2/src/main/java/org/jclouds/ec2/binders/BindS3UploadPolicyAndSignature.java
+++ b/apis/ec2/src/main/java/org/jclouds/ec2/binders/BindS3UploadPolicyAndSignature.java
@@ -21,21 +21,20 @@ import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.io.BaseEncoding.base64;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
-import org.jclouds.aws.filters.FormSigner;
+import org.jclouds.aws.filters.FormSigner.FormSignerV2;
 import org.jclouds.http.HttpRequest;
 import org.jclouds.rest.Binder;
 
 import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.ImmutableMultimap.Builder;
 
-@Singleton
+// TODO: see if this still exists in V4
 public class BindS3UploadPolicyAndSignature implements Binder {
-   private final FormSigner signer;
+   private final FormSignerV2 signer;
 
    @Inject
-   BindS3UploadPolicyAndSignature(FormSigner signer) {
+   BindS3UploadPolicyAndSignature(FormSignerV2 signer) {
       this.signer = signer;
    }
 
@@ -49,5 +48,4 @@ public class BindS3UploadPolicyAndSignature implements Binder {
       builder.put("Storage.S3.UploadPolicySignature", signature);
       return (R) request.toBuilder().replaceFormParams(builder.build()).build();
    }
-
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/apis/ec2/src/test/java/org/jclouds/ec2/features/BaseEC2ApiTest.java
----------------------------------------------------------------------
diff --git a/apis/ec2/src/test/java/org/jclouds/ec2/features/BaseEC2ApiTest.java b/apis/ec2/src/test/java/org/jclouds/ec2/features/BaseEC2ApiTest.java
index e70f760..be2a732 100644
--- a/apis/ec2/src/test/java/org/jclouds/ec2/features/BaseEC2ApiTest.java
+++ b/apis/ec2/src/test/java/org/jclouds/ec2/features/BaseEC2ApiTest.java
@@ -28,7 +28,7 @@ import javax.inject.Singleton;
 
 import org.jclouds.apis.ApiMetadata;
 import org.jclouds.aws.domain.Region;
-import org.jclouds.aws.filters.FormSigner;
+import org.jclouds.aws.filters.FormSigner.FormSignerV2;
 import org.jclouds.compute.domain.Image;
 import org.jclouds.date.DateService;
 import org.jclouds.ec2.EC2ApiMetadata;
@@ -115,19 +115,19 @@ public abstract class BaseEC2ApiTest<T> extends BaseRestAnnotationProcessingTest
       }
    }
 
-   protected FormSigner filter;
+   protected FormSignerV2 filter;
 
    @Override
    protected void checkFilters(HttpRequest request) {
       assertEquals(request.getFilters().size(), 1);
-      assertEquals(request.getFilters().get(0).getClass(), FormSigner.class);
+      assertEquals(request.getFilters().get(0).getClass(), FormSignerV2.class);
    }
 
    @Override
    @BeforeTest
    protected void setupFactory() throws IOException {
       super.setupFactory();
-      this.filter = injector.getInstance(FormSigner.class);
+      this.filter = injector.getInstance(FormSignerV2.class);
    }
 
    @Override

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/apis/sts/src/main/java/org/jclouds/aws/config/FormSigningHttpApiModule.java
----------------------------------------------------------------------
diff --git a/apis/sts/src/main/java/org/jclouds/aws/config/FormSigningHttpApiModule.java b/apis/sts/src/main/java/org/jclouds/aws/config/FormSigningHttpApiModule.java
index 57d695a..5989a29 100644
--- a/apis/sts/src/main/java/org/jclouds/aws/config/FormSigningHttpApiModule.java
+++ b/apis/sts/src/main/java/org/jclouds/aws/config/FormSigningHttpApiModule.java
@@ -23,6 +23,7 @@ import javax.inject.Singleton;
 import org.jclouds.aws.filters.FormSigner;
 import org.jclouds.date.DateService;
 import org.jclouds.date.TimeStamp;
+import org.jclouds.http.HttpRequest;
 import org.jclouds.rest.ConfiguresHttpApi;
 import org.jclouds.rest.RequestSigner;
 
@@ -51,7 +52,18 @@ public abstract class FormSigningHttpApiModule<A> extends AWSHttpApiModule<A> {
    @Provides
    @Singleton
    RequestSigner provideRequestSigner(FormSigner in) {
-      return in;
+      if (in instanceof RequestSigner) {
+         return (RequestSigner) in;
+      }
+      return new RequestSigner() {
+         @Override public String createStringToSign(HttpRequest input) {
+            return null;
+         }
+
+         @Override public String sign(String toSign) {
+            return null;
+         }
+      };
    }
 
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/apis/sts/src/main/java/org/jclouds/aws/filters/FormSigner.java
----------------------------------------------------------------------
diff --git a/apis/sts/src/main/java/org/jclouds/aws/filters/FormSigner.java b/apis/sts/src/main/java/org/jclouds/aws/filters/FormSigner.java
index f9aee46..64c3842 100644
--- a/apis/sts/src/main/java/org/jclouds/aws/filters/FormSigner.java
+++ b/apis/sts/src/main/java/org/jclouds/aws/filters/FormSigner.java
@@ -42,7 +42,6 @@ import javax.annotation.Resource;
 import javax.inject.Inject;
 import javax.inject.Named;
 import javax.inject.Provider;
-import javax.inject.Singleton;
 
 import org.jclouds.Constants;
 import org.jclouds.aws.domain.SessionCredentials;
@@ -66,154 +65,145 @@ import com.google.common.collect.Multimap;
 import com.google.common.collect.TreeMultimap;
 import com.google.common.io.ByteProcessor;
 import com.google.common.net.HttpHeaders;
+import com.google.inject.ImplementedBy;
+
+@ImplementedBy(FormSigner.FormSignerV2.class)
+public interface FormSigner extends HttpRequestFilter {
+
+   static final class FormSignerV2 implements FormSigner, RequestSigner {
+
+      public static final Set<String> mandatoryParametersForSignature = ImmutableSet
+            .of(ACTION, SIGNATURE_METHOD, SIGNATURE_VERSION, VERSION);
+
+      private final SignatureWire signatureWire;
+      private final String apiVersion;
+      private final Supplier<Credentials> creds;
+      private final Provider<String> dateService;
+      private final Crypto crypto;
+      private final HttpUtils utils;
+
+      @Resource @Named(Constants.LOGGER_SIGNATURE)
+      private Logger signatureLog = Logger.NULL;
+
+      @Inject FormSignerV2(SignatureWire signatureWire, @ApiVersion String apiVersion,
+            @org.jclouds.location.Provider Supplier<Credentials> creds, @TimeStamp Provider<String> dateService,
+            Crypto crypto, HttpUtils utils) {
+         this.signatureWire = signatureWire;
+         this.apiVersion = apiVersion;
+         this.creds = creds;
+         this.dateService = dateService;
+         this.crypto = crypto;
+         this.utils = utils;
+      }
 
-/**
- * 
- * @see <a href=
- *      "http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/Query-Common-Parameters.html"
- *      />
- */
-@Singleton
-public class FormSigner implements HttpRequestFilter, RequestSigner {
-
-   public static final Set<String> mandatoryParametersForSignature = ImmutableSet.of(ACTION, SIGNATURE_METHOD,
-         SIGNATURE_VERSION, VERSION);
-
-   private final SignatureWire signatureWire;
-   private final String apiVersion;
-   private final Supplier<Credentials> creds;
-   private final Provider<String> dateService;
-   private final Crypto crypto;
-   private final HttpUtils utils;
-
-   @Resource
-   @Named(Constants.LOGGER_SIGNATURE)
-   private Logger signatureLog = Logger.NULL;
-
-   @Inject
-   public FormSigner(SignatureWire signatureWire, @ApiVersion String apiVersion,
-         @org.jclouds.location.Provider Supplier<Credentials> creds, @TimeStamp Provider<String> dateService,
-         Crypto crypto, HttpUtils utils) {
-      this.signatureWire = signatureWire;
-      this.apiVersion = apiVersion;
-      this.creds = creds;
-      this.dateService = dateService;
-      this.crypto = crypto;
-      this.utils = utils;
-   }
-
-   public HttpRequest filter(HttpRequest request) throws HttpException {
-      checkNotNull(request.getFirstHeaderOrNull(HttpHeaders.HOST), "request is not ready to sign; host not present");
-      Multimap<String, String> decodedParams = queryParser().apply(request.getPayload().getRawContent().toString()); 
-      decodedParams.replaceValues(VERSION, ImmutableSet.of(apiVersion));
-      addSigningParams(decodedParams);
-      validateParams(decodedParams);
-      String stringToSign = createStringToSign(request, decodedParams);
-      String signature = sign(stringToSign);
-      addSignature(decodedParams, signature);
-      request = setPayload(request, decodedParams);
-      utils.logRequest(signatureLog, request, "<<");
-      return request;
-   }
-   
-   HttpRequest setPayload(HttpRequest request, Multimap<String, String> decodedParams) {
-      String queryLine = buildQueryLine(decodedParams);
-      request.setPayload(queryLine);
-      request.getPayload().getContentMetadata().setContentType("application/x-www-form-urlencoded");
-      return request;
-   }
+      public HttpRequest filter(HttpRequest request) throws HttpException {
+         checkNotNull(request.getFirstHeaderOrNull(HttpHeaders.HOST), "request is not ready to sign; host not present");
+         Multimap<String, String> decodedParams = queryParser().apply(request.getPayload().getRawContent().toString());
+         decodedParams.replaceValues(VERSION, ImmutableSet.of(apiVersion));
+         addSigningParams(decodedParams);
+         validateParams(decodedParams);
+         String stringToSign = createStringToSign(request, decodedParams);
+         String signature = sign(stringToSign);
+         addSignature(decodedParams, signature);
+         request = setPayload(request, decodedParams);
+         utils.logRequest(signatureLog, request, "<<");
+         return request;
+      }
 
-   private static final Comparator<String> actionFirstAccessKeyLast = new Comparator<String>() {
-      static final int LEFT_IS_GREATER = 1;
-      static final int RIGHT_IS_GREATER = -1;
+      HttpRequest setPayload(HttpRequest request, Multimap<String, String> decodedParams) {
+         String queryLine = buildQueryLine(decodedParams);
+         request.setPayload(queryLine);
+         request.getPayload().getContentMetadata().setContentType("application/x-www-form-urlencoded");
+         return request;
+      }
 
-      @Override
-      public int compare(String left, String right) {
-         if (left == right) {
-            return 0;
-         }
-         if ("Action".equals(right) || "AWSAccessKeyId".equals(left)) {
-            return LEFT_IS_GREATER;
+      private static final Comparator<String> actionFirstAccessKeyLast = new Comparator<String>() {
+         static final int LEFT_IS_GREATER = 1;
+         static final int RIGHT_IS_GREATER = -1;
+
+         @Override
+         public int compare(String left, String right) {
+            if (left == right) {
+               return 0;
+            }
+            if ("Action".equals(right) || "AWSAccessKeyId".equals(left)) {
+               return LEFT_IS_GREATER;
+            }
+            if ("Action".equals(left) || "AWSAccessKeyId".equals(right)) {
+               return RIGHT_IS_GREATER;
+            }
+            return natural().compare(left, right);
          }
-         if ("Action".equals(left) || "AWSAccessKeyId".equals(right)) {
-            return RIGHT_IS_GREATER;
-         }
-         return natural().compare(left, right);
+      };
+
+      private static String buildQueryLine(Multimap<String, String> decodedParams) {
+         Multimap<String, String> sortedParams = TreeMultimap.create(actionFirstAccessKeyLast, natural());
+         sortedParams.putAll(decodedParams);
+         return encodeQueryLine(sortedParams);
       }
-   };
 
-   private static String buildQueryLine(Multimap<String, String> decodedParams) {
-      Multimap<String, String> sortedParams = TreeMultimap.create(actionFirstAccessKeyLast, natural());
-      sortedParams.putAll(decodedParams);
-      return encodeQueryLine(sortedParams);
-   }
+      @VisibleForTesting void validateParams(Multimap<String, String> params) {
+         for (String parameter : mandatoryParametersForSignature) {
+            checkState(params.containsKey(parameter), "parameter " + parameter + " is required for signature");
+         }
+      }
 
-   @VisibleForTesting
-   void validateParams(Multimap<String, String> params) {
-      for (String parameter : mandatoryParametersForSignature) {
-         checkState(params.containsKey(parameter), "parameter " + parameter + " is required for signature");
+      @VisibleForTesting void addSignature(Multimap<String, String> params, String signature) {
+         params.replaceValues(SIGNATURE, ImmutableList.of(signature));
       }
-   }
 
-   @VisibleForTesting
-   void addSignature(Multimap<String, String> params, String signature) {
-      params.replaceValues(SIGNATURE, ImmutableList.of(signature));
-   }
+      @VisibleForTesting
+      public String sign(String toSign) {
+         String signature;
+         try {
+            ByteProcessor<byte[]> hmacSHA256 = asByteProcessor(
+                  crypto.hmacSHA256(creds.get().credential.getBytes(UTF_8)));
+            signature = base64().encode(readBytes(toInputStream(toSign), hmacSHA256));
+            if (signatureWire.enabled())
+               signatureWire.input(toInputStream(signature));
+         } catch (Exception e) {
+            throw new HttpException("error signing request", e);
+         }
+         return signature;
+      }
 
-   @VisibleForTesting
-   public String sign(String toSign) {
-      String signature;
-      try {
-         ByteProcessor<byte[]> hmacSHA256 = asByteProcessor(crypto.hmacSHA256(creds.get().credential.getBytes(UTF_8)));
-         signature = base64().encode(readBytes(toInputStream(toSign), hmacSHA256));
+      @VisibleForTesting
+      public String createStringToSign(HttpRequest request, Multimap<String, String> decodedParams) {
+         utils.logRequest(signatureLog, request, ">>");
+         StringBuilder stringToSign = new StringBuilder();
+         // StringToSign = HTTPVerb + "\n" +
+         stringToSign.append(request.getMethod()).append("\n");
+         // ValueOfHostHeaderInLowercase + "\n" +
+         stringToSign.append(request.getFirstHeaderOrNull(HttpHeaders.HOST).toLowerCase()).append("\n");
+         // HTTPRequestURI + "\n" +
+         stringToSign.append(request.getEndpoint().getPath()).append("\n");
+         // CanonicalizedFormString <from the preceding step>
+         stringToSign.append(buildCanonicalizedString(decodedParams));
          if (signatureWire.enabled())
-            signatureWire.input(toInputStream(signature));
-      } catch (Exception e) {
-         throw new HttpException("error signing request", e);
+            signatureWire.output(stringToSign.toString());
+         return stringToSign.toString();
       }
-      return signature;
-   }
-
-   @VisibleForTesting
-   public String createStringToSign(HttpRequest request, Multimap<String, String> decodedParams) {
-      utils.logRequest(signatureLog, request, ">>");
-      StringBuilder stringToSign = new StringBuilder();
-      // StringToSign = HTTPVerb + "\n" +
-      stringToSign.append(request.getMethod()).append("\n");
-      // ValueOfHostHeaderInLowercase + "\n" +
-      stringToSign.append(request.getFirstHeaderOrNull(HttpHeaders.HOST).toLowerCase()).append("\n");
-      // HTTPRequestURI + "\n" +
-      stringToSign.append(request.getEndpoint().getPath()).append("\n");
-      // CanonicalizedFormString <from the preceding step>
-      stringToSign.append(buildCanonicalizedString(decodedParams));
-      if (signatureWire.enabled())
-         signatureWire.output(stringToSign.toString());
-      return stringToSign.toString();
-   }
-
-   @VisibleForTesting
-   String buildCanonicalizedString(Multimap<String, String> decodedParams) {
-      // note that aws wants to percent encode the canonicalized string without skipping '/' and '?'
-      return encodeQueryLine(TreeMultimap.create(decodedParams), ImmutableList.<Character> of());
-   }
 
+      @VisibleForTesting String buildCanonicalizedString(Multimap<String, String> decodedParams) {
+         // note that aws wants to percent encode the canonicalized string without skipping '/' and '?'
+         return encodeQueryLine(TreeMultimap.create(decodedParams), ImmutableList.<Character>of());
+      }
 
-   @VisibleForTesting
-   void addSigningParams(Multimap<String, String> params) {
-      params.removeAll(SIGNATURE);
-      params.removeAll(SECURITY_TOKEN);
-      Credentials current = creds.get();
-      if (current instanceof SessionCredentials) {
-         params.put(SECURITY_TOKEN, SessionCredentials.class.cast(current).getSessionToken());
+      @VisibleForTesting void addSigningParams(Multimap<String, String> params) {
+         params.removeAll(SIGNATURE);
+         params.removeAll(SECURITY_TOKEN);
+         Credentials current = creds.get();
+         if (current instanceof SessionCredentials) {
+            params.put(SECURITY_TOKEN, SessionCredentials.class.cast(current).getSessionToken());
+         }
+         params.replaceValues(SIGNATURE_METHOD, ImmutableList.of("HmacSHA256"));
+         params.replaceValues(SIGNATURE_VERSION, ImmutableList.of("2"));
+         params.replaceValues(TIMESTAMP, ImmutableList.of(dateService.get()));
+         params.replaceValues(AWS_ACCESS_KEY_ID, ImmutableList.of(creds.get().identity));
       }
-      params.replaceValues(SIGNATURE_METHOD, ImmutableList.of("HmacSHA256"));
-      params.replaceValues(SIGNATURE_VERSION, ImmutableList.of("2"));
-      params.replaceValues(TIMESTAMP, ImmutableList.of(dateService.get()));
-      params.replaceValues(AWS_ACCESS_KEY_ID, ImmutableList.of(creds.get().identity));
-   }
 
-   public String createStringToSign(HttpRequest input) {
-      return createStringToSign(input, queryParser().apply(input.getPayload().getRawContent().toString()));
+      public String createStringToSign(HttpRequest input) {
+         return createStringToSign(input, queryParser().apply(input.getPayload().getRawContent().toString()));
+      }
    }
-
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/apis/sts/src/main/java/org/jclouds/aws/filters/FormSignerV4.java
----------------------------------------------------------------------
diff --git a/apis/sts/src/main/java/org/jclouds/aws/filters/FormSignerV4.java b/apis/sts/src/main/java/org/jclouds/aws/filters/FormSignerV4.java
new file mode 100644
index 0000000..2743c3d
--- /dev/null
+++ b/apis/sts/src/main/java/org/jclouds/aws/filters/FormSignerV4.java
@@ -0,0 +1,219 @@
+/*
+ * 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.aws.filters;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.hash.Hashing.sha256;
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.net.HttpHeaders.AUTHORIZATION;
+import static com.google.common.net.HttpHeaders.HOST;
+import static javax.ws.rs.core.MediaType.APPLICATION_FORM_URLENCODED;
+import static org.jclouds.aws.reference.FormParameters.ACTION;
+import static org.jclouds.aws.reference.FormParameters.VERSION;
+
+import java.net.URI;
+import java.security.GeneralSecurityException;
+import java.util.List;
+import java.util.Map;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import javax.inject.Inject;
+
+import org.jclouds.aws.domain.SessionCredentials;
+import org.jclouds.date.TimeStamp;
+import org.jclouds.domain.Credentials;
+import org.jclouds.http.HttpException;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.io.Payload;
+import org.jclouds.io.Payloads;
+import org.jclouds.location.Provider;
+import org.jclouds.providers.ProviderMetadata;
+import org.jclouds.rest.annotations.ApiVersion;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.ImplementedBy;
+
+public final class FormSignerV4 implements FormSigner {
+
+   // Specifying a default for how to parse the service and region in this way allows
+   // tests or other downstream services to not have to use guice overrides.
+   @ImplementedBy(ServiceAndRegion.AWSServiceAndRegion.class)
+   public interface ServiceAndRegion {
+      String service();
+      String region(String host);
+
+      static final class AWSServiceAndRegion implements ServiceAndRegion {
+         private final String service;
+
+         @Inject AWSServiceAndRegion(ProviderMetadata provider) {
+            this(provider.getEndpoint());
+         }
+
+         AWSServiceAndRegion(String endpoint) {
+            this.service = parseServiceAndRegion(URI.create(checkNotNull(endpoint, "endpoint")).getHost()).get(0);
+         }
+
+         @Override public String service() {
+            return service;
+         }
+
+         @Override public String region(String host) {
+            return parseServiceAndRegion(host).get(1);
+         }
+
+         /** This will only work for amazon deployments, and perhaps not all of them. */
+         private static List<String> parseServiceAndRegion(String host) {
+            checkArgument(host.endsWith(".amazonaws.com"), "Only AWS endpoints currently supported %s", host);
+            return Splitter.on('.').splitToList(host);
+         }
+      }
+   }
+
+   private final String apiVersion;
+   private final Supplier<Credentials> creds;
+   private final javax.inject.Provider<String> iso8601Timestamp;
+   private final ServiceAndRegion serviceAndRegion;
+
+   @Inject FormSignerV4(@ApiVersion String apiVersion, @Provider Supplier<Credentials> creds,
+         @TimeStamp javax.inject.Provider<String> iso8601Timestamp, ServiceAndRegion serviceAndRegion) {
+      this.apiVersion = apiVersion;
+      this.creds = creds;
+      this.iso8601Timestamp = iso8601Timestamp;
+      this.serviceAndRegion = serviceAndRegion;
+   }
+
+   @Override public HttpRequest filter(HttpRequest request) throws HttpException {
+      checkArgument(request.getHeaders().containsKey(HOST), "request is not ready to sign; host not present");
+      String host = request.getFirstHeaderOrNull(HOST);
+      String form = request.getPayload().getRawContent().toString();
+      checkArgument(form.indexOf(ACTION) != -1, "request is not ready to sign; Action not present %s", form);
+
+      String timestamp = iso8601Timestamp.get();
+      String datestamp = timestamp.substring(0, 8);
+
+      String service = serviceAndRegion.service();
+      String region = serviceAndRegion.region(host);
+      String credentialScope = Joiner.on('/').join(datestamp, region, service, "aws4_request");
+
+      // content-type is not a required signing param. However, examples use this, so we include it to ease testing.
+      ImmutableMap.Builder<String, String> signedHeadersBuilder = ImmutableMap.<String, String> builder() //
+            .put("content-type", request.getPayload().getContentMetadata().getContentType()) //
+            .put("host", host) //
+            .put("x-amz-date", timestamp);
+
+      HttpRequest.Builder<?> requestBuilder = request.toBuilder() //
+            .removeHeader(AUTHORIZATION) //
+            .replaceHeader("X-Amz-Date", timestamp);
+
+      if (form.indexOf(VERSION) == -1) {
+         requestBuilder.addFormParam("Version", apiVersion);
+      }
+
+      Credentials credentials = creds.get();
+
+      if (credentials instanceof SessionCredentials) {
+         String token = SessionCredentials.class.cast(credentials).getSessionToken();
+         requestBuilder.replaceHeader("X-Amz-Security-Token", token);
+         signedHeadersBuilder.put("x-amz-security-token", token);
+      }
+
+      ImmutableMap<String, String> signedHeaders = signedHeadersBuilder.build();
+
+      String stringToSign = createStringToSign(requestBuilder.build(), signedHeaders, credentialScope);
+      byte[] signatureKey = signatureKey(credentials.credential, datestamp, region, service);
+      String signature = base16().lowerCase().encode(hmacSHA256(stringToSign, signatureKey));
+
+      StringBuilder authorization = new StringBuilder("AWS4-HMAC-SHA256 ");
+      authorization.append("Credential=").append(credentials.identity).append('/').append(credentialScope).append(", ");
+      authorization.append("SignedHeaders=").append(Joiner.on(';').join(signedHeaders.keySet())).append(", ");
+      authorization.append("Signature=").append(signature);
+
+      return requestBuilder.addHeader(AUTHORIZATION, authorization.toString()).build();
+   }
+
+   // TODO: change EC2 apis to add this themselves with @FormParams
+   private Payload addVersionIfNecessary(Payload payload, String form) {
+      if (form.indexOf(VERSION) == -1) {
+         form += "&Version=" + apiVersion;
+         payload = Payloads.newStringPayload(form);
+         payload.getContentMetadata().setContentType(APPLICATION_FORM_URLENCODED);
+         payload.getContentMetadata().setContentLength((long) form.length());
+      }
+      return payload;
+   }
+
+   static byte[] signatureKey(String secretKey, String datestamp, String region, String service) {
+      byte[] kSecret = ("AWS4" + secretKey).getBytes(UTF_8);
+      byte[] kDate = hmacSHA256(datestamp, kSecret);
+      byte[] kRegion = hmacSHA256(region, kDate);
+      byte[] kService = hmacSHA256(service, kRegion);
+      byte[] kSigning = hmacSHA256("aws4_request", kService);
+      return kSigning;
+   }
+
+   static byte[] hmacSHA256(String data, byte[] key) {
+      try {
+         String algorithm = "HmacSHA256";
+         Mac mac = Mac.getInstance(algorithm);
+         mac.init(new SecretKeySpec(key, algorithm));
+         return mac.doFinal(data.getBytes(UTF_8));
+      } catch (GeneralSecurityException e) {
+         throw new HttpException(e);
+      }
+   }
+
+   static String createStringToSign(HttpRequest request, Map<String, String> signedHeaders, String credentialScope) {
+      StringBuilder canonicalRequest = new StringBuilder();
+      // HTTPRequestMethod + '\n' +
+      canonicalRequest.append(request.getMethod()).append("\n");
+      // CanonicalURI + '\n' +
+      canonicalRequest.append(request.getEndpoint().getPath()).append("\n");
+      // CanonicalQueryString + '\n' +
+      checkArgument(request.getEndpoint().getQuery() == null, "Query parameters not yet supported %s", request);
+      canonicalRequest.append("\n");
+      // CanonicalHeaders + '\n' +
+      for (Map.Entry<String, String> entry : signedHeaders.entrySet()) {
+         canonicalRequest.append(entry.getKey()).append(':').append(entry.getValue()).append('\n');
+      }
+      canonicalRequest.append("\n");
+
+      // SignedHeaders + '\n' +
+      canonicalRequest.append(Joiner.on(';').join(signedHeaders.keySet())).append('\n');
+
+      // HexEncode(Hash(Payload))
+      String payload = request.getPayload().getRawContent().toString();
+      canonicalRequest.append(base16().lowerCase().encode(sha256().hashString(payload, UTF_8).asBytes()));
+
+      StringBuilder toSign = new StringBuilder();
+      // Algorithm + '\n' +
+      toSign.append("AWS4-HMAC-SHA256").append('\n');
+      // RequestDate + '\n' +
+      toSign.append(signedHeaders.get("x-amz-date")).append('\n');
+      // CredentialScope + '\n' +
+      toSign.append(credentialScope).append('\n');
+      // HexEncode(Hash(CanonicalRequest))
+      toSign.append(base16().lowerCase().encode(sha256().hashString(canonicalRequest.toString(), UTF_8).asBytes()));
+
+      return toSign.toString();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/apis/sts/src/test/java/org/jclouds/aws/filters/FormSignerTest.java
----------------------------------------------------------------------
diff --git a/apis/sts/src/test/java/org/jclouds/aws/filters/FormSignerTest.java b/apis/sts/src/test/java/org/jclouds/aws/filters/FormSignerTest.java
deleted file mode 100644
index 2325751..0000000
--- a/apis/sts/src/test/java/org/jclouds/aws/filters/FormSignerTest.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.jclouds.aws.filters;
-
-import static javax.ws.rs.HttpMethod.GET;
-import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG;
-import static org.jclouds.providers.AnonymousProviderMetadata.forApiOnEndpoint;
-import static org.testng.Assert.assertEquals;
-
-import org.jclouds.ContextBuilder;
-import org.jclouds.aws.xml.SessionCredentialsHandlerTest;
-import org.jclouds.date.TimeStamp;
-import org.jclouds.domain.Credentials;
-import org.jclouds.http.HttpRequest;
-import org.jclouds.http.IntegrationTestClient;
-import org.jclouds.logging.config.NullLoggingModule;
-import org.jclouds.rest.RequestSigner;
-import org.jclouds.rest.internal.BaseRestApiTest.MockModule;
-import org.testng.annotations.Test;
-
-import com.google.common.base.Suppliers;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMultimap;
-import com.google.common.net.HttpHeaders;
-import com.google.inject.AbstractModule;
-import com.google.inject.Injector;
-import com.google.inject.Module;
-import com.google.inject.name.Names;
-/**
- * Tests behavior of {@code FormSigner}
- */
-// NOTE:without testName, this will not call @Before* and fail w/NPE during
-// surefire
-@Test(groups = "unit", singleThreaded = true, testName = "FormSignerTest")
-public class FormSignerTest {
-   public static Injector injector(Credentials creds) {
-      return ContextBuilder
-            .newBuilder(forApiOnEndpoint(IntegrationTestClient.class, "http://localhost"))
-            .credentialsSupplier(Suppliers.<Credentials> ofInstance(creds)).apiVersion("apiVersion")
-            .modules(ImmutableList.<Module> of(new MockModule(), new NullLoggingModule(), new AbstractModule() {
-               @Override
-               protected void configure() {
-                  bind(RequestSigner.class).to(FormSigner.class);
-                  bind(String.class).annotatedWith(Names.named(PROPERTY_HEADER_TAG)).toInstance("amz");
-                  bind(String.class).annotatedWith(TimeStamp.class).toInstance("2009-11-08T15:54:08.897Z");
-               }
-
-            })).buildInjector();
-   }
-
-   public static FormSigner filter(Credentials creds) {
-      return injector(creds).getInstance(FormSigner.class);
-   }
-
-   public static FormSigner staticCredentialsFilter = filter(new Credentials("identity", "credential"));
-
-   HttpRequest request = HttpRequest.builder().method(GET)
-         .endpoint("http://localhost")
-         .addHeader(HttpHeaders.HOST, "localhost")
-         .addFormParam("Action", "DescribeImages")
-         .addFormParam("ImageId.1", "ami-2bb65342").build();
-
-   @Test
-   void testAddsSecurityToken() {
-      HttpRequest filtered = filter(new SessionCredentialsHandlerTest().expected()).filter(request);
-      assertEquals(
-            filtered.getPayload().getRawContent(),
-            "Action=DescribeImages&ImageId.1=ami-2bb65342&SecurityToken=AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT&Signature=/8ReFVH1tvyNORsJb%2BSBieT9zvdqREQQr/olwmxC7VY%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2009-11-08T15%3A54%3A08.897Z&Version=apiVersion&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE");
-   }
-
-   @Test
-   void testBuildCanonicalizedStringSetsVersion() {
-      HttpRequest filtered = staticCredentialsFilter.filter(request);
-      assertEquals(filtered.getPayload().getRawContent(),
-            "Action=DescribeImages&ImageId.1=ami-2bb65342&Signature=ugnt4m2eHE7Ka/vXTr9EhKZq7bhxOfvW0y4pAEqF97w%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2009-11-08T15%3A54%3A08.897Z&Version=apiVersion&AWSAccessKeyId=identity");
-   }
-
-   @Test
-   void testBuildCanonicalizedString() {
-      assertEquals(
-            staticCredentialsFilter.buildCanonicalizedString(new ImmutableMultimap.Builder<String, String>()
-                  .put("AWSAccessKeyId", "foo").put("Action", "DescribeImages").put("Expires", "2008-02-10T12:00:00Z")
-                  .put("ImageId.1", "ami-2bb65342").put("SignatureMethod", "HmacSHA256").put("SignatureVersion", "2")
-                  .put("Version", "2010-06-15").build()),
-            "AWSAccessKeyId=foo&Action=DescribeImages&Expires=2008-02-10T12%3A00%3A00Z&ImageId.1=ami-2bb65342&SignatureMethod=HmacSHA256&SignatureVersion=2&Version=2010-06-15");
-   }
-
-}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/apis/sts/src/test/java/org/jclouds/aws/filters/FormSignerV2Test.java
----------------------------------------------------------------------
diff --git a/apis/sts/src/test/java/org/jclouds/aws/filters/FormSignerV2Test.java b/apis/sts/src/test/java/org/jclouds/aws/filters/FormSignerV2Test.java
new file mode 100644
index 0000000..71fa8f9
--- /dev/null
+++ b/apis/sts/src/test/java/org/jclouds/aws/filters/FormSignerV2Test.java
@@ -0,0 +1,98 @@
+/*
+ * 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.aws.filters;
+
+import static javax.ws.rs.HttpMethod.GET;
+import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG;
+import static org.jclouds.providers.AnonymousProviderMetadata.forApiOnEndpoint;
+import static org.testng.Assert.assertEquals;
+
+import org.jclouds.ContextBuilder;
+import org.jclouds.aws.filters.FormSigner.FormSignerV2;
+import org.jclouds.aws.xml.SessionCredentialsHandlerTest;
+import org.jclouds.date.TimeStamp;
+import org.jclouds.domain.Credentials;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.IntegrationTestClient;
+import org.jclouds.logging.config.NullLoggingModule;
+import org.jclouds.rest.RequestSigner;
+import org.jclouds.rest.internal.BaseRestApiTest.MockModule;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.net.HttpHeaders;
+import com.google.inject.AbstractModule;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+import com.google.inject.name.Names;
+
+@Test(groups = "unit", singleThreaded = true, testName = "FormSignerV2Test")
+public class FormSignerV2Test {
+   public static Injector injector(Credentials creds) {
+      return ContextBuilder
+            .newBuilder(forApiOnEndpoint(IntegrationTestClient.class, "http://localhost"))
+            .credentialsSupplier(Suppliers.<Credentials> ofInstance(creds)).apiVersion("apiVersion")
+            .modules(ImmutableList.<Module> of(new MockModule(), new NullLoggingModule(), new AbstractModule() {
+               @Override
+               protected void configure() {
+                  bind(RequestSigner.class).to(FormSignerV2.class);
+                  bind(String.class).annotatedWith(Names.named(PROPERTY_HEADER_TAG)).toInstance("amz");
+                  bind(String.class).annotatedWith(TimeStamp.class).toInstance("2009-11-08T15:54:08.897Z");
+               }
+
+            })).buildInjector();
+   }
+
+   public static FormSignerV2 filter(Credentials creds) {
+      return injector(creds).getInstance(FormSignerV2.class);
+   }
+
+   public static FormSignerV2 staticCredentialsFilter = filter(new Credentials("identity", "credential"));
+
+   HttpRequest request = HttpRequest.builder().method(GET)
+         .endpoint("http://localhost")
+         .addHeader(HttpHeaders.HOST, "localhost")
+         .addFormParam("Action", "DescribeImages")
+         .addFormParam("ImageId.1", "ami-2bb65342").build();
+
+   @Test
+   void testAddsSecurityToken() {
+      HttpRequest filtered = filter(new SessionCredentialsHandlerTest().expected()).filter(request);
+      assertEquals(
+            filtered.getPayload().getRawContent(),
+            "Action=DescribeImages&ImageId.1=ami-2bb65342&SecurityToken=AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT&Signature=/8ReFVH1tvyNORsJb%2BSBieT9zvdqREQQr/olwmxC7VY%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2009-11-08T15%3A54%3A08.897Z&Version=apiVersion&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE");
+   }
+
+   @Test
+   void testBuildCanonicalizedStringSetsVersion() {
+      HttpRequest filtered = staticCredentialsFilter.filter(request);
+      assertEquals(filtered.getPayload().getRawContent(),
+            "Action=DescribeImages&ImageId.1=ami-2bb65342&Signature=ugnt4m2eHE7Ka/vXTr9EhKZq7bhxOfvW0y4pAEqF97w%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2009-11-08T15%3A54%3A08.897Z&Version=apiVersion&AWSAccessKeyId=identity");
+   }
+
+   @Test
+   void testBuildCanonicalizedString() {
+      assertEquals(
+            staticCredentialsFilter.buildCanonicalizedString(new ImmutableMultimap.Builder<String, String>()
+                  .put("AWSAccessKeyId", "foo").put("Action", "DescribeImages").put("Expires", "2008-02-10T12:00:00Z")
+                  .put("ImageId.1", "ami-2bb65342").put("SignatureMethod", "HmacSHA256").put("SignatureVersion", "2")
+                  .put("Version", "2010-06-15").build()),
+            "AWSAccessKeyId=foo&Action=DescribeImages&Expires=2008-02-10T12%3A00%3A00Z&ImageId.1=ami-2bb65342&SignatureMethod=HmacSHA256&SignatureVersion=2&Version=2010-06-15");
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/apis/sts/src/test/java/org/jclouds/aws/filters/FormSignerV4LiveTest.java
----------------------------------------------------------------------
diff --git a/apis/sts/src/test/java/org/jclouds/aws/filters/FormSignerV4LiveTest.java b/apis/sts/src/test/java/org/jclouds/aws/filters/FormSignerV4LiveTest.java
new file mode 100644
index 0000000..bd697bd
--- /dev/null
+++ b/apis/sts/src/test/java/org/jclouds/aws/filters/FormSignerV4LiveTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.aws.filters;
+
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static org.jclouds.aws.filters.FormSignerV4.ServiceAndRegion;
+import static org.jclouds.aws.filters.FormSignerV4.ServiceAndRegion.AWSServiceAndRegion;
+import static org.jclouds.sts.options.SessionCredentialsOptions.Builder.durationSeconds;
+import static org.testng.Assert.assertEquals;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+import javax.inject.Provider;
+
+import org.jclouds.apis.BaseApiLiveTest;
+import org.jclouds.aws.domain.SessionCredentials;
+import org.jclouds.domain.Credentials;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.rest.ApiContext;
+import org.jclouds.sts.STSApi;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+
+@Test(groups = "live", singleThreaded = true, testName = "FormSignerV4LiveTest")
+public class FormSignerV4LiveTest extends BaseApiLiveTest<ApiContext<STSApi>> {
+
+   /** Example request, which hopefully the test user's account has access to! */
+   private final HttpRequest sampleRequest = HttpRequest.builder() //
+         .method("POST") //
+         .endpoint("https://ec2.us-east-1.amazonaws.com/") //
+         .addHeader("Host", "ec2.us-east-1.amazonaws.com") //
+         .addFormParam("Action", "DescribeRegions") //
+         .addFormParam("Version", "2010-08-31") //
+         .build();
+
+   /** Provides the expected iso8601 timestamp format for signature v4. */
+   private final Provider<String> timestamp = new Provider<String>() {
+      SimpleDateFormat iso8601 = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
+
+      @Override public String get() {
+         iso8601.setTimeZone(TimeZone.getTimeZone("GMT"));
+         return iso8601.format(new Date());
+      }
+   };
+
+   /** Test how we parse the AWS service and region based on an endpoint. */
+   private final ServiceAndRegion serviceAndRegion = new AWSServiceAndRegion(sampleRequest.getEndpoint().toString());
+
+   public FormSignerV4LiveTest() {
+      provider = "sts";
+   }
+
+   public void signatureV4() {
+      Supplier<Credentials> accessAndSecretKey = Suppliers.ofInstance(new Credentials(identity, credential));
+
+      FormSignerV4 filter = new FormSignerV4(apiVersion, accessAndSecretKey, timestamp, serviceAndRegion);
+
+      HttpRequest request = filter.filter(sampleRequest);
+
+      assertEquals(api.utils().http().invoke(request).getStatusCode(), 200);
+   }
+
+   public void signatureV4_session() {
+      SessionCredentials creds = api.getApi().createTemporaryCredentials(durationSeconds(MINUTES.toSeconds(15)));
+      Supplier<Credentials> sessionToken = Suppliers.<Credentials>ofInstance(creds);
+
+      FormSignerV4 filter = new FormSignerV4(apiVersion, sessionToken, timestamp, serviceAndRegion);
+
+      HttpRequest request = filter.filter(sampleRequest);
+
+      assertEquals(api.utils().http().invoke(request).getStatusCode(), 200);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/apis/sts/src/test/java/org/jclouds/aws/filters/FormSignerV4Test.java
----------------------------------------------------------------------
diff --git a/apis/sts/src/test/java/org/jclouds/aws/filters/FormSignerV4Test.java b/apis/sts/src/test/java/org/jclouds/aws/filters/FormSignerV4Test.java
new file mode 100644
index 0000000..2f94c26
--- /dev/null
+++ b/apis/sts/src/test/java/org/jclouds/aws/filters/FormSignerV4Test.java
@@ -0,0 +1,102 @@
+/*
+ * 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.aws.filters;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.testng.Assert.assertEquals;
+
+import javax.inject.Provider;
+
+import org.jclouds.aws.domain.SessionCredentials;
+import org.jclouds.aws.filters.FormSignerV4.ServiceAndRegion;
+import org.jclouds.aws.xml.SessionCredentialsHandlerTest;
+import org.jclouds.domain.Credentials;
+import org.jclouds.http.HttpRequest;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+
+/**
+ * Using samples from <a href="http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html">Amazon
+ * docs</a>
+ */
+@Test
+public class FormSignerV4Test {
+
+   String apiVersion = "2010-05-08";
+
+   Supplier<Credentials> accessAndSecretKey = Suppliers
+         .ofInstance(new Credentials("AKIAIOSFODNN7EXAMPLE", "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY"));
+
+   Provider<String> timestamp = new Provider<String>() {
+      @Override public String get() {
+         return "20110909T233600Z";
+      }
+   };
+
+   ServiceAndRegion serviceAndRegion = new ServiceAndRegion() {
+      @Override public String service() {
+         return "iam";
+      }
+
+      @Override public String region(String host) {
+         return "us-east-1";
+      }
+   };
+
+   public void signSampleRequest() {
+      HttpRequest request = HttpRequest.builder() //
+            .method("POST") //
+            .endpoint("https://iam.amazonaws.com/") //
+            .addHeader("Host", "iam.amazonaws.com") //
+            .payload("Action=ListUsers&Version=2010-05-08")
+            .build();
+
+      request.getPayload().getContentMetadata().setContentType("application/x-www-form-urlencoded; charset=utf-8");
+
+      FormSignerV4 filter = new FormSignerV4(apiVersion, accessAndSecretKey, timestamp, serviceAndRegion);
+
+      HttpRequest filtered = filter.filter(request);
+
+      assertEquals(filtered.getFirstHeaderOrNull("X-Amz-Date"), timestamp.get());
+
+      String sampleSignature = "ced6826de92d2bdeed8f846f0bf508e8559e98e4b0199114b84c54174deb456c";
+
+      assertThat(filtered.getFirstHeaderOrNull("Authorization")).endsWith("Signature=" + sampleSignature);
+   }
+
+   public void sessionTokenRequest() {
+      HttpRequest request = HttpRequest.builder() //
+            .method("POST") //
+            .endpoint("https://iam.amazonaws.com/") //
+            .addHeader("Host", "iam.amazonaws.com") //
+            .payload("Action=ListUsers&Version=2010-05-08").build();
+
+      request.getPayload().getContentMetadata().setContentType("application/x-www-form-urlencoded; charset=utf-8");
+
+      SessionCredentials sessionCredentials = new SessionCredentialsHandlerTest().expected();
+
+      FormSignerV4 filter = new FormSignerV4(apiVersion, Suppliers.<Credentials>ofInstance(sessionCredentials),
+            timestamp, serviceAndRegion);
+
+      HttpRequest filtered = filter.filter(request);
+
+      assertEquals(filtered.getFirstHeaderOrNull("X-Amz-Date"), timestamp.get());
+      assertEquals(filtered.getFirstHeaderOrNull("X-Amz-Security-Token"), sessionCredentials.getSessionToken());
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/apis/sts/src/test/java/org/jclouds/aws/util/AWSUtilsTest.java
----------------------------------------------------------------------
diff --git a/apis/sts/src/test/java/org/jclouds/aws/util/AWSUtilsTest.java b/apis/sts/src/test/java/org/jclouds/aws/util/AWSUtilsTest.java
index 8c80c32..33014d4 100644
--- a/apis/sts/src/test/java/org/jclouds/aws/util/AWSUtilsTest.java
+++ b/apis/sts/src/test/java/org/jclouds/aws/util/AWSUtilsTest.java
@@ -27,7 +27,7 @@ import java.io.IOException;
 import java.io.InputStream;
 
 import org.jclouds.aws.domain.AWSError;
-import org.jclouds.aws.filters.FormSignerTest;
+import org.jclouds.aws.filters.FormSignerV2Test;
 import org.jclouds.domain.Credentials;
 import org.jclouds.http.HttpCommand;
 import org.jclouds.http.HttpRequest;
@@ -47,7 +47,7 @@ public class AWSUtilsTest {
    @BeforeTest
    protected void setUpInjector() throws IOException {
 
-      utils = FormSignerTest.injector(new Credentials("identity", "credential")).getInstance(AWSUtils.class);
+      utils = FormSignerV2Test.injector(new Credentials("identity", "credential")).getInstance(AWSUtils.class);
 
       command = createMock(HttpCommand.class);
       expect(command.getCurrentRequest()).andReturn(createMock(HttpRequest.class)).atLeastOnce();

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/apis/sts/src/test/java/org/jclouds/aws/xml/SessionCredentialsHandlerTest.java
----------------------------------------------------------------------
diff --git a/apis/sts/src/test/java/org/jclouds/aws/xml/SessionCredentialsHandlerTest.java b/apis/sts/src/test/java/org/jclouds/aws/xml/SessionCredentialsHandlerTest.java
index a615046..cac60a4 100644
--- a/apis/sts/src/test/java/org/jclouds/aws/xml/SessionCredentialsHandlerTest.java
+++ b/apis/sts/src/test/java/org/jclouds/aws/xml/SessionCredentialsHandlerTest.java
@@ -51,5 +51,4 @@ public class SessionCredentialsHandlerTest extends BaseHandlerTest {
             .sessionToken("AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT")
             .expiration(new SimpleDateFormatDateService().iso8601DateParse("2011-07-11T19:55:29.611Z")).build();
    }
-
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/core/src/main/java/org/jclouds/rest/RequestSigner.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/jclouds/rest/RequestSigner.java b/core/src/main/java/org/jclouds/rest/RequestSigner.java
index 5673684..7bff9e0 100644
--- a/core/src/main/java/org/jclouds/rest/RequestSigner.java
+++ b/core/src/main/java/org/jclouds/rest/RequestSigner.java
@@ -18,6 +18,11 @@ package org.jclouds.rest;
 
 import org.jclouds.http.HttpRequest;
 
+/**
+ * @deprecated This is an internal interface historically used to debug signature logic. It currently is a broken
+ * abstraction as AWS Signature v4 requires multiple parameters to sign a request.
+ */
+@Deprecated
 public interface RequestSigner {
 
    String createStringToSign(HttpRequest input);

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/core/src/main/java/org/jclouds/rest/config/RestModule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/jclouds/rest/config/RestModule.java b/core/src/main/java/org/jclouds/rest/config/RestModule.java
index 3544300..f2b428e 100644
--- a/core/src/main/java/org/jclouds/rest/config/RestModule.java
+++ b/core/src/main/java/org/jclouds/rest/config/RestModule.java
@@ -40,16 +40,11 @@ import org.jclouds.rest.internal.TransformerForRequest;
 
 import com.google.common.base.Function;
 import com.google.common.base.Predicate;
-import com.google.common.base.Supplier;
 import com.google.inject.AbstractModule;
 import com.google.inject.TypeLiteral;
 import com.google.inject.assistedinject.FactoryModuleBuilder;
 
 public class RestModule extends AbstractModule {
-
-   public static final TypeLiteral<Supplier<URI>> URI_SUPPLIER_TYPE = new TypeLiteral<Supplier<URI>>() {
-   };
-
    protected final AtomicReference<AuthorizationException> authException = newReference();
 
    protected void installLocations() {

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/providers/aws-ec2/pom.xml
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/pom.xml b/providers/aws-ec2/pom.xml
index 780c193..c15b190 100644
--- a/providers/aws-ec2/pom.xml
+++ b/providers/aws-ec2/pom.xml
@@ -101,6 +101,18 @@
       <version>${project.version}</version>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>com.squareup.okhttp</groupId>
+      <artifactId>mockwebserver</artifactId>
+      <scope>test</scope>
+      <exclusions>
+        <!-- Already provided by jclouds-sshj -->
+        <exclusion>
+          <groupId>org.bouncycastle</groupId>
+          <artifactId>bcprov-jdk15on</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
   </dependencies>
 
   <build>

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/config/AWSEC2HttpApiModule.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/config/AWSEC2HttpApiModule.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/config/AWSEC2HttpApiModule.java
index 9ba5af8..d025fc7 100644
--- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/config/AWSEC2HttpApiModule.java
+++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/config/AWSEC2HttpApiModule.java
@@ -16,6 +16,9 @@
  */
 package org.jclouds.aws.ec2.config;
 
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
 
 import javax.inject.Singleton;
 
@@ -24,12 +27,15 @@ import org.jclouds.aws.ec2.domain.AWSRunningInstance;
 import org.jclouds.aws.ec2.domain.SpotInstanceRequest;
 import org.jclouds.aws.ec2.functions.SpotInstanceRequestToAWSRunningInstance;
 import org.jclouds.aws.ec2.options.AWSRunInstancesOptions;
+import org.jclouds.aws.filters.FormSigner;
+import org.jclouds.aws.filters.FormSignerV4;
+import org.jclouds.date.DateService;
 import org.jclouds.ec2.EC2Api;
 import org.jclouds.ec2.config.BaseEC2HttpApiModule;
-import org.jclouds.ec2.options.RunInstancesOptions;
 import org.jclouds.ec2.features.AMIApi;
 import org.jclouds.ec2.features.InstanceApi;
 import org.jclouds.ec2.features.SecurityGroupApi;
+import org.jclouds.ec2.options.RunInstancesOptions;
 import org.jclouds.rest.ConfiguresHttpApi;
 
 import com.google.common.base.Function;
@@ -42,8 +48,11 @@ import com.google.inject.TypeLiteral;
 @ConfiguresHttpApi
 public class AWSEC2HttpApiModule extends BaseEC2HttpApiModule<AWSEC2Api> {
 
+   private final SimpleDateFormat iso8601 = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
+
    public AWSEC2HttpApiModule() {
       super(AWSEC2Api.class);
+      iso8601.setTimeZone(TimeZone.getTimeZone("GMT"));
    }
 
    @Singleton
@@ -72,9 +81,15 @@ public class AWSEC2HttpApiModule extends BaseEC2HttpApiModule<AWSEC2Api> {
 
    @Override
    protected void configure() {
+      bind(FormSigner.class).to(FormSignerV4.class);
       bind(RunInstancesOptions.class).to(AWSRunInstancesOptions.class);
       bind(new TypeLiteral<Function<SpotInstanceRequest, AWSRunningInstance>>() {
       }).to(SpotInstanceRequestToAWSRunningInstance.class);
       super.configure();
    }
+
+   @Override protected String provideTimeStamp(DateService dateService) {
+      // 20120416T155408Z not 2012-04-16T15:54:08Z
+      return iso8601.format(new Date());
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a449b24e/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/AWSEC2ComputeServiceApiMockTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/AWSEC2ComputeServiceApiMockTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/AWSEC2ComputeServiceApiMockTest.java
new file mode 100644
index 0000000..6782373
--- /dev/null
+++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/AWSEC2ComputeServiceApiMockTest.java
@@ -0,0 +1,237 @@
+/*
+ * 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.aws.ec2.compute;
+
+import static org.jclouds.aws.ec2.compute.AWSEC2TemplateOptions.Builder.blockUntilRunning;
+import static org.testng.Assert.assertEquals;
+
+import org.jclouds.aws.ec2.internal.BaseAWSEC2ApiMockTest;
+import org.jclouds.compute.ComputeService;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.predicates.NodePredicates;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.Iterables;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+
+@Test(groups = "unit", testName = "AWSEC2ComputeServiceMockTest", singleThreaded = true)
+public class AWSEC2ComputeServiceApiMockTest extends BaseAWSEC2ApiMockTest {
+
+   public void launchVPCSpotInstanceSubnetId() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/availabilityZones.xml");
+      enqueueXml(DEFAULT_REGION, "/describe_images.xml");
+      enqueueXml(DEFAULT_REGION, "/describe_images_cc.xml");
+      enqueueXml(DEFAULT_REGION, "/request_spot_instances-ebs.xml");
+      enqueueXml(DEFAULT_REGION, "/request_spot_instances-ebs.xml");
+      enqueueXml(DEFAULT_REGION, "/describe_images_ebs.xml");
+      enqueue(DEFAULT_REGION, new MockResponse()); // create tags
+
+      ComputeService computeService = computeService();
+
+      Template template = computeService.templateBuilder().locationId("us-east-1a").build();
+
+      template.getOptions().as(AWSEC2TemplateOptions.class)
+            .spotPrice(1f).subnetId("subnet-xyz").keyPair("Demo").blockUntilRunning(false);
+
+      NodeMetadata node = Iterables.getOnlyElement(computeService.createNodesInGroup("test", 1, template));
+      assertEquals(node.getId(), "us-east-1/sir-228e6406");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DescribeAvailabilityZones");
+      assertPosted(DEFAULT_REGION, "Action=DescribeImages&Filter.1.Name=owner-id&Filter.1.Value.1=137112412989&Filter.1.Value.2=801119661308&Filter.1.Value.3=063491364108&Filter.1.Value.4=099720109477&Filter.1.Value.5=411009282317&Filter.2.Name=state&Filter.2.Value.1=available&Filter.3.Name=image-type&Filter.3.Value.1=machine");
+      assertPosted(DEFAULT_REGION, "Action=DescribeImages&Filter.1.Name=virtualization-type&Filter.1.Value.1=hvm&Filter.2.Name=architecture&Filter.2.Value.1=x86_64&Filter.3.Name=owner-id&Filter.3.Value.1=137112412989&Filter.3.Value.2=099720109477&Filter.4.Name=hypervisor&Filter.4.Value.1=xen&Filter.5.Name=state&Filter.5.Value.1=available&Filter.6.Name=image-type&Filter.6.Value.1=machine&Filter.7.Name=root-device-type&Filter.7.Value.1=ebs");
+      assertPosted(DEFAULT_REGION, "Action=RequestSpotInstances&SpotPrice=1.0&InstanceCount=1&LaunchSpecification.ImageId=ami-be3adfd7&LaunchSpecification.Placement.AvailabilityZone=us-east-1a&LaunchSpecification.InstanceType=m1.small&LaunchSpecification.SubnetId=subnet-xyz&LaunchSpecification.KeyName=Demo&LaunchSpecification.UserData=I2Nsb3VkLWNvbmZpZwpyZXBvX3VwZ3JhZGU6IG5vbmUK");
+      assertPosted(DEFAULT_REGION, "Action=DescribeSpotInstanceRequests&SpotInstanceRequestId.1=sir-228e6406");
+      assertPosted(DEFAULT_REGION, "Action=DescribeImages&ImageId.1=ami-595a0a1c");
+      assertPosted(DEFAULT_REGION, "Action=CreateTags&Tag.1.Key=Name&Tag.1.Value=test-228e6406&ResourceId.1=sir-228e6406");
+   }
+
+   String iamInstanceProfileArn = "arn:aws:iam::123456789012:instance-profile/application_abc/component_xyz/Webserver";
+
+   public void launchSpotInstanceIAMInstanceProfileArn() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/availabilityZones.xml");
+      enqueueXml(DEFAULT_REGION, "/describe_images.xml");
+      enqueueXml(DEFAULT_REGION, "/describe_images_cc.xml");
+      enqueueXml(DEFAULT_REGION, "/created_securitygroup.xml");
+      enqueueXml(DEFAULT_REGION, "/new_securitygroup.xml");
+      enqueueXml(DEFAULT_REGION, "/new_securitygroup.xml");
+      enqueueXml(DEFAULT_REGION, "/new_securitygroup.xml");
+      enqueueXml(DEFAULT_REGION, "/authorize_securitygroup_ingress_response.xml");
+      enqueueXml(DEFAULT_REGION, "/request_spot_instances-ebs.xml");
+      enqueueXml(DEFAULT_REGION, "/request_spot_instances-ebs.xml");
+      enqueueXml(DEFAULT_REGION, "/describe_images_ebs.xml");
+      enqueue(DEFAULT_REGION, new MockResponse()); // create tags
+
+      ComputeService computeService = computeService();
+
+      Template template = computeService.templateBuilder().locationId("us-east-1a").build();
+
+      template.getOptions().as(AWSEC2TemplateOptions.class).spotPrice(1f).iamInstanceProfileArn(iamInstanceProfileArn)
+            .noKeyPair().blockUntilRunning(false);
+
+      NodeMetadata node = Iterables.getOnlyElement(computeService.createNodesInGroup("test", 1, template));
+      assertEquals(node.getId(), "us-east-1/sir-228e6406");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DescribeAvailabilityZones");
+      assertPosted(DEFAULT_REGION, "Action=DescribeImages&Filter.1.Name=owner-id&Filter.1.Value.1=137112412989&Filter.1.Value.2=801119661308&Filter.1.Value.3=063491364108&Filter.1.Value.4=099720109477&Filter.1.Value.5=411009282317&Filter.2.Name=state&Filter.2.Value.1=available&Filter.3.Name=image-type&Filter.3.Value.1=machine");
+      assertPosted(DEFAULT_REGION, "Action=DescribeImages&Filter.1.Name=virtualization-type&Filter.1.Value.1=hvm&Filter.2.Name=architecture&Filter.2.Value.1=x86_64&Filter.3.Name=owner-id&Filter.3.Value.1=137112412989&Filter.3.Value.2=099720109477&Filter.4.Name=hypervisor&Filter.4.Value.1=xen&Filter.5.Name=state&Filter.5.Value.1=available&Filter.6.Name=image-type&Filter.6.Value.1=machine&Filter.7.Name=root-device-type&Filter.7.Value.1=ebs");
+      assertPosted(DEFAULT_REGION, "Action=CreateSecurityGroup&GroupName=jclouds%23test&GroupDescription=jclouds%23test");
+      assertPosted(DEFAULT_REGION, "Action=DescribeSecurityGroups&GroupName.1=jclouds%23test");
+      assertPosted(DEFAULT_REGION, "Action=DescribeSecurityGroups&GroupName.1=jclouds%23test");
+      assertPosted(DEFAULT_REGION, "Action=DescribeSecurityGroups&GroupName.1=jclouds%23test");
+      assertPosted(DEFAULT_REGION, "Action=AuthorizeSecurityGroupIngress&GroupId=sg-3c6ef654&IpPermissions.0.IpProtocol=tcp&IpPermissions.0.FromPort=22&IpPermissions.0.ToPort=22&IpPermissions.0.IpRanges.0.CidrIp=0.0.0.0/0&IpPermissions.1.IpProtocol=tcp&IpPermissions.1.FromPort=0&IpPermissions.1.ToPort=65535&IpPermissions.1.Groups.0.UserId=993194456877&IpPermissions.1.Groups.0.GroupId=sg-3c6ef654&IpPermissions.2.IpProtocol=udp&IpPermissions.2.FromPort=0&IpPermissions.2.ToPort=65535&IpPermissions.2.Groups.0.UserId=993194456877&IpPermissions.2.Groups.0.GroupId=sg-3c6ef654");
+      assertPosted(DEFAULT_REGION, "Action=RequestSpotInstances&SpotPrice=1.0&InstanceCount=1&LaunchSpecification.ImageId=ami-be3adfd7&LaunchSpecification.Placement.AvailabilityZone=us-east-1a&LaunchSpecification.SecurityGroup.1=jclouds%23test&LaunchSpecification.InstanceType=m1.small&LaunchSpecification.UserData=I2Nsb3VkLWNvbmZpZwpyZXBvX3VwZ3JhZGU6IG5vbmUK&LaunchSpecification.IamInstanceProfile.Arn=arn%3Aaws%3Aiam%3A%3A123456789012%3Ainstance-profile/application_abc/component_xyz/Webserver");
+      assertPosted(DEFAULT_REGION, "Action=DescribeSpotInstanceRequests&SpotInstanceRequestId.1=sir-228e6406");
+      assertPosted(DEFAULT_REGION, "Action=DescribeImages&ImageId.1=ami-595a0a1c");
+      assertPosted(DEFAULT_REGION, "Action=CreateTags&Tag.1.Key=Name&Tag.1.Value=test-228e6406&ResourceId.1=sir-228e6406");
+   }
+
+   public void launchSpotInstanceIAMInstanceProfileName() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/availabilityZones.xml");
+      enqueueXml(DEFAULT_REGION, "/describe_images.xml");
+      enqueueXml(DEFAULT_REGION, "/describe_images_cc.xml");
+      enqueueXml(DEFAULT_REGION, "/created_securitygroup.xml");
+      enqueueXml(DEFAULT_REGION, "/new_securitygroup.xml");
+      enqueueXml(DEFAULT_REGION, "/new_securitygroup.xml");
+      enqueueXml(DEFAULT_REGION, "/new_securitygroup.xml");
+      enqueueXml(DEFAULT_REGION, "/authorize_securitygroup_ingress_response.xml");
+      enqueueXml(DEFAULT_REGION, "/request_spot_instances-ebs.xml");
+      enqueueXml(DEFAULT_REGION, "/request_spot_instances-ebs.xml");
+      enqueueXml(DEFAULT_REGION, "/describe_images_ebs.xml");
+      enqueue(DEFAULT_REGION, new MockResponse()); // create tags
+
+      ComputeService computeService = computeService();
+
+      Template template = computeService.templateBuilder().locationId("us-east-1a").build();
+
+      template.getOptions().as(AWSEC2TemplateOptions.class).spotPrice(1f).iamInstanceProfileName("Webserver")
+            .noKeyPair().blockUntilRunning(false);
+
+      NodeMetadata node = Iterables.getOnlyElement(computeService.createNodesInGroup("test", 1, template));
+      assertEquals(node.getId(), "us-east-1/sir-228e6406");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DescribeAvailabilityZones");
+      assertPosted(DEFAULT_REGION, "Action=DescribeImages&Filter.1.Name=owner-id&Filter.1.Value.1=137112412989&Filter.1.Value.2=801119661308&Filter.1.Value.3=063491364108&Filter.1.Value.4=099720109477&Filter.1.Value.5=411009282317&Filter.2.Name=state&Filter.2.Value.1=available&Filter.3.Name=image-type&Filter.3.Value.1=machine");
+      assertPosted(DEFAULT_REGION, "Action=DescribeImages&Filter.1.Name=virtualization-type&Filter.1.Value.1=hvm&Filter.2.Name=architecture&Filter.2.Value.1=x86_64&Filter.3.Name=owner-id&Filter.3.Value.1=137112412989&Filter.3.Value.2=099720109477&Filter.4.Name=hypervisor&Filter.4.Value.1=xen&Filter.5.Name=state&Filter.5.Value.1=available&Filter.6.Name=image-type&Filter.6.Value.1=machine&Filter.7.Name=root-device-type&Filter.7.Value.1=ebs");
+      assertPosted(DEFAULT_REGION, "Action=CreateSecurityGroup&GroupName=jclouds%23test&GroupDescription=jclouds%23test");
+      assertPosted(DEFAULT_REGION, "Action=DescribeSecurityGroups&GroupName.1=jclouds%23test");
+      assertPosted(DEFAULT_REGION, "Action=DescribeSecurityGroups&GroupName.1=jclouds%23test");
+      assertPosted(DEFAULT_REGION, "Action=DescribeSecurityGroups&GroupName.1=jclouds%23test");
+      assertPosted(DEFAULT_REGION, "Action=AuthorizeSecurityGroupIngress&GroupId=sg-3c6ef654&IpPermissions.0.IpProtocol=tcp&IpPermissions.0.FromPort=22&IpPermissions.0.ToPort=22&IpPermissions.0.IpRanges.0.CidrIp=0.0.0.0/0&IpPermissions.1.IpProtocol=tcp&IpPermissions.1.FromPort=0&IpPermissions.1.ToPort=65535&IpPermissions.1.Groups.0.UserId=993194456877&IpPermissions.1.Groups.0.GroupId=sg-3c6ef654&IpPermissions.2.IpProtocol=udp&IpPermissions.2.FromPort=0&IpPermissions.2.ToPort=65535&IpPermissions.2.Groups.0.UserId=993194456877&IpPermissions.2.Groups.0.GroupId=sg-3c6ef654");
+      assertPosted(DEFAULT_REGION, "Action=RequestSpotInstances&SpotPrice=1.0&InstanceCount=1&LaunchSpecification.ImageId=ami-be3adfd7&LaunchSpecification.Placement.AvailabilityZone=us-east-1a&LaunchSpecification.SecurityGroup.1=jclouds%23test&LaunchSpecification.InstanceType=m1.small&LaunchSpecification.UserData=I2Nsb3VkLWNvbmZpZwpyZXBvX3VwZ3JhZGU6IG5vbmUK&LaunchSpecification.IamInstanceProfile.Name=Webserver");
+      assertPosted(DEFAULT_REGION, "Action=DescribeSpotInstanceRequests&SpotInstanceRequestId.1=sir-228e6406");
+      assertPosted(DEFAULT_REGION, "Action=DescribeImages&ImageId.1=ami-595a0a1c");
+      assertPosted(DEFAULT_REGION, "Action=CreateTags&Tag.1.Key=Name&Tag.1.Value=test-228e6406&ResourceId.1=sir-228e6406");
+   }
+
+   public void createNodeWithIAMInstanceProfileArn() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/amzn_images.xml");
+      enqueueXml(DEFAULT_REGION, "/describe_images_cc.xml");
+      enqueueXml(DEFAULT_REGION, "/availabilityZones.xml");
+      enqueueXml(DEFAULT_REGION, "/created_securitygroup.xml");
+      enqueueXml(DEFAULT_REGION, "/new_securitygroup.xml");
+      enqueueXml(DEFAULT_REGION, "/new_securitygroup.xml");
+      enqueueXml(DEFAULT_REGION, "/new_securitygroup.xml");
+      enqueueXml(DEFAULT_REGION, "/authorize_securitygroup_ingress_response.xml");
+      enqueueXml(DEFAULT_REGION, "/new_instance.xml");
+      enqueueXml(DEFAULT_REGION, "/describe_instances_running-1.xml");
+      enqueueXml(DEFAULT_REGION, "/describe_images.xml");
+      enqueue(DEFAULT_REGION, new MockResponse()); // create tags
+
+      ComputeService computeService = computeService();
+
+      NodeMetadata node = Iterables.getOnlyElement(computeService.createNodesInGroup("test", 1,
+            blockUntilRunning(false).iamInstanceProfileArn(iamInstanceProfileArn).noKeyPair()));
+      assertEquals(node.getId(), "us-east-1/i-2baa5550");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DescribeImages&Filter.1.Name=owner-id&Filter.1.Value.1=137112412989&Filter.1.Value.2=801119661308&Filter.1.Value.3=063491364108&Filter.1.Value.4=099720109477&Filter.1.Value.5=411009282317&Filter.2.Name=state&Filter.2.Value.1=available&Filter.3.Name=image-type&Filter.3.Value.1=machine");
+      assertPosted(DEFAULT_REGION, "Action=DescribeImages&Filter.1.Name=virtualization-type&Filter.1.Value.1=hvm&Filter.2.Name=architecture&Filter.2.Value.1=x86_64&Filter.3.Name=owner-id&Filter.3.Value.1=137112412989&Filter.3.Value.2=099720109477&Filter.4.Name=hypervisor&Filter.4.Value.1=xen&Filter.5.Name=state&Filter.5.Value.1=available&Filter.6.Name=image-type&Filter.6.Value.1=machine&Filter.7.Name=root-device-type&Filter.7.Value.1=ebs");
+      assertPosted(DEFAULT_REGION, "Action=DescribeAvailabilityZones");
+      assertPosted(DEFAULT_REGION, "Action=CreateSecurityGroup&GroupName=jclouds%23test&GroupDescription=jclouds%23test");
+      assertPosted(DEFAULT_REGION, "Action=DescribeSecurityGroups&GroupName.1=jclouds%23test");
+      assertPosted(DEFAULT_REGION, "Action=DescribeSecurityGroups&GroupName.1=jclouds%23test");
+      assertPosted(DEFAULT_REGION, "Action=DescribeSecurityGroups&GroupName.1=jclouds%23test");
+      assertPosted(DEFAULT_REGION, "Action=AuthorizeSecurityGroupIngress&GroupId=sg-3c6ef654&IpPermissions.0.IpProtocol=tcp&IpPermissions.0.FromPort=22&IpPermissions.0.ToPort=22&IpPermissions.0.IpRanges.0.CidrIp=0.0.0.0/0&IpPermissions.1.IpProtocol=tcp&IpPermissions.1.FromPort=0&IpPermissions.1.ToPort=65535&IpPermissions.1.Groups.0.UserId=993194456877&IpPermissions.1.Groups.0.GroupId=sg-3c6ef654&IpPermissions.2.IpProtocol=udp&IpPermissions.2.FromPort=0&IpPermissions.2.ToPort=65535&IpPermissions.2.Groups.0.UserId=993194456877&IpPermissions.2.Groups.0.GroupId=sg-3c6ef654");
+      assertPosted(DEFAULT_REGION, "Action=RunInstances&ImageId=ami-8ce4b5c9&MinCount=1&MaxCount=1&InstanceType=t1.micro&SecurityGroup.1=jclouds%23test&UserData=I2Nsb3VkLWNvbmZpZwpyZXBvX3VwZ3JhZGU6IG5vbmUK&IamInstanceProfile.Arn=arn%3Aaws%3Aiam%3A%3A123456789012%3Ainstance-profile/application_abc/component_xyz/Webserver");
+      assertPosted(DEFAULT_REGION, "Action=DescribeInstances&InstanceId.1=i-2baa5550");
+      assertPosted(DEFAULT_REGION, "Action=DescribeImages&ImageId.1=ami-aecd60c7");
+      assertPosted(DEFAULT_REGION, "Action=CreateTags&Tag.1.Key=Name&Tag.1.Value=test-2baa5550&ResourceId.1=i-2baa5550");
+   }
+
+   public void createNodeWithIAMInstanceProfileName() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/amzn_images.xml");
+      enqueueXml(DEFAULT_REGION, "/describe_images_cc.xml");
+      enqueueXml(DEFAULT_REGION, "/availabilityZones.xml");
+      enqueueXml(DEFAULT_REGION, "/created_securitygroup.xml");
+      enqueueXml(DEFAULT_REGION, "/new_securitygroup.xml");
+      enqueueXml(DEFAULT_REGION, "/new_securitygroup.xml");
+      enqueueXml(DEFAULT_REGION, "/new_securitygroup.xml");
+      enqueueXml(DEFAULT_REGION, "/authorize_securitygroup_ingress_response.xml");
+      enqueueXml(DEFAULT_REGION, "/new_instance.xml");
+      enqueueXml(DEFAULT_REGION, "/describe_instances_running-1.xml");
+      enqueueXml(DEFAULT_REGION, "/describe_images.xml");
+      enqueue(DEFAULT_REGION, new MockResponse()); // create tags
+
+      ComputeService computeService = computeService();
+
+      NodeMetadata node = Iterables.getOnlyElement(computeService.createNodesInGroup("test", 1,
+            blockUntilRunning(false).iamInstanceProfileName("Webserver").noKeyPair()));
+      assertEquals(node.getId(), "us-east-1/i-2baa5550");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DescribeImages&Filter.1.Name=owner-id&Filter.1.Value.1=137112412989&Filter.1.Value.2=801119661308&Filter.1.Value.3=063491364108&Filter.1.Value.4=099720109477&Filter.1.Value.5=411009282317&Filter.2.Name=state&Filter.2.Value.1=available&Filter.3.Name=image-type&Filter.3.Value.1=machine");
+      assertPosted(DEFAULT_REGION, "Action=DescribeImages&Filter.1.Name=virtualization-type&Filter.1.Value.1=hvm&Filter.2.Name=architecture&Filter.2.Value.1=x86_64&Filter.3.Name=owner-id&Filter.3.Value.1=137112412989&Filter.3.Value.2=099720109477&Filter.4.Name=hypervisor&Filter.4.Value.1=xen&Filter.5.Name=state&Filter.5.Value.1=available&Filter.6.Name=image-type&Filter.6.Value.1=machine&Filter.7.Name=root-device-type&Filter.7.Value.1=ebs");
+      assertPosted(DEFAULT_REGION, "Action=DescribeAvailabilityZones");
+      assertPosted(DEFAULT_REGION, "Action=CreateSecurityGroup&GroupName=jclouds%23test&GroupDescription=jclouds%23test");
+      assertPosted(DEFAULT_REGION, "Action=DescribeSecurityGroups&GroupName.1=jclouds%23test");
+      assertPosted(DEFAULT_REGION, "Action=DescribeSecurityGroups&GroupName.1=jclouds%23test");
+      assertPosted(DEFAULT_REGION, "Action=DescribeSecurityGroups&GroupName.1=jclouds%23test");
+      assertPosted(DEFAULT_REGION, "Action=AuthorizeSecurityGroupIngress&GroupId=sg-3c6ef654&IpPermissions.0.IpProtocol=tcp&IpPermissions.0.FromPort=22&IpPermissions.0.ToPort=22&IpPermissions.0.IpRanges.0.CidrIp=0.0.0.0/0&IpPermissions.1.IpProtocol=tcp&IpPermissions.1.FromPort=0&IpPermissions.1.ToPort=65535&IpPermissions.1.Groups.0.UserId=993194456877&IpPermissions.1.Groups.0.GroupId=sg-3c6ef654&IpPermissions.2.IpProtocol=udp&IpPermissions.2.FromPort=0&IpPermissions.2.ToPort=65535&IpPermissions.2.Groups.0.UserId=993194456877&IpPermissions.2.Groups.0.GroupId=sg-3c6ef654");
+      assertPosted(DEFAULT_REGION, "Action=RunInstances&ImageId=ami-8ce4b5c9&MinCount=1&MaxCount=1&InstanceType=t1.micro&SecurityGroup.1=jclouds%23test&UserData=I2Nsb3VkLWNvbmZpZwpyZXBvX3VwZ3JhZGU6IG5vbmUK&IamInstanceProfile.Name=Webserver");
+      assertPosted(DEFAULT_REGION, "Action=DescribeInstances&InstanceId.1=i-2baa5550");
+      assertPosted(DEFAULT_REGION, "Action=DescribeImages&ImageId.1=ami-aecd60c7");
+      assertPosted(DEFAULT_REGION, "Action=CreateTags&Tag.1.Key=Name&Tag.1.Value=test-2baa5550&ResourceId.1=i-2baa5550");
+   }
+
+   public void listNodesWhereImageDoesntExist() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/describe_instances_running-1.xml");
+      enqueueXml(DEFAULT_REGION, "/availabilityZones.xml");
+      enqueue(DEFAULT_REGION, new MockResponse().setBody("<DescribeImagesResponse><imagesSet></imagesSet></DescribeImagesResponse>"));
+      enqueue(DEFAULT_REGION, new MockResponse().setBody("<DescribeSpotInstanceRequestsResponse><spotInstanceRequestSet></spotInstanceRequestSet></DescribeSpotInstanceRequestsResponse>"));
+
+      ComputeService computeService = computeService();
+
+      NodeMetadata node = Iterables.getOnlyElement(computeService.listNodesDetailsMatching(NodePredicates.all()));
+      assertEquals(node.getId(), "us-east-1/i-2baa5550");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DescribeInstances");
+      assertPosted(DEFAULT_REGION, "Action=DescribeAvailabilityZones");
+      assertPosted(DEFAULT_REGION, "Action=DescribeImages&ImageId.1=ami-aecd60c7");
+      assertPosted(DEFAULT_REGION, "Action=DescribeSpotInstanceRequests");
+   }
+}