You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by na...@apache.org on 2017/05/29 08:31:28 UTC

[1/4] jclouds git commit: Add RouteTable API.

Repository: jclouds
Updated Branches:
  refs/heads/master a9006288e -> 28c3c33bf


http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/RouteTableApiMockTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/RouteTableApiMockTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/RouteTableApiMockTest.java
new file mode 100644
index 0000000..2541a93
--- /dev/null
+++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/RouteTableApiMockTest.java
@@ -0,0 +1,301 @@
+/*
+ * 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 javax.ws.rs.core.Response.Status.PRECONDITION_FAILED;
+import static org.jclouds.aws.ec2.options.RouteOptions.Builder.destinationCidrBlock;
+import static org.jclouds.aws.ec2.options.RouteOptions.Builder.gatewayId;
+import static org.jclouds.aws.ec2.options.RouteTableOptions.Builder.dryRun;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import org.jclouds.aws.AWSResponseException;
+import org.jclouds.aws.ec2.domain.Route;
+import org.jclouds.aws.ec2.domain.RouteTable;
+import org.jclouds.aws.ec2.internal.BaseAWSEC2ApiMockTest;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+
+@Test(groups = "unit", testName = "RouteTableApiMockTest", singleThreaded = true)
+public class RouteTableApiMockTest extends BaseAWSEC2ApiMockTest {
+
+   public void describeRouteTables() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueue(DEFAULT_REGION, new MockResponse().setResponseCode(404));
+      final ImmutableList<RouteTable> routeTables = routeTableApi().describeRouteTables(DEFAULT_REGION).toList();
+
+      assertTrue(routeTables.isEmpty(), "Returned " + routeTables.size() + " results for 404 response: " + routeTables);
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DescribeRouteTables");
+   }
+
+   public void describeRouteTablesNotFound() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/describe_route_tables.xml");
+      final ImmutableList<RouteTable> routeTables = routeTableApi().describeRouteTables(DEFAULT_REGION).toList();
+
+      assertNotNull(routeTables, "Failed to create route table description object");
+      assertEquals(routeTables.size(), 3, "Failed to return all entries from test data, returned: " + routeTables);
+
+      for (RouteTable table : routeTables) {
+         if (ImmutableList.of("rtb-80a3fae4", "rtb-d4605bb0").contains(table.id())) {
+            assertRoutesForNormalVpc(table, table.id());
+         } else if (table.id().equals("rtb-e6c98381")) {
+            assertRoutesForTestVpc(table, table.id());
+         }
+      }
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DescribeRouteTables");
+   }
+
+   public void describeRouteTablesWithInvalidStateValue() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/describe_route_tables_invalid.xml");
+      final ImmutableList<RouteTable> routeTables = routeTableApi().describeRouteTables(DEFAULT_REGION).toList();
+
+      assertNotNull(routeTables, "Failed to create route table description object");
+      assertEquals(routeTables.size(), 1, "Failed to return expected entry from test data, returned: " + routeTables);
+
+      assertEquals(routeTables.get(0).routeSet().get(0).state(), Route.RouteState.UNRECOGNIZED);
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DescribeRouteTables");
+   }
+
+   private void assertRoutesForNormalVpc(RouteTable table, String id) {
+      assertEquals(table.routeSet().size(), 2, "Failed to match test data route set size for " + id);
+      final String actual = table.associationSet().get(0).routeTableId();
+      assertEquals(actual, id, "Test data mismatch in " + id + " association set routeTableId(): " + actual);
+      assertTrue(table.associationSet().get(0).main(), "Test data mismatch in " + id + " association 'main'");
+   }
+
+   private void assertRoutesForTestVpc(RouteTable table, String id) {
+      assertEquals(table.routeSet().size(), 1, "Failed to match test data route set size for " + id);
+
+      assertEquals(table.routeSet().get(0).destinationCidrBlock(), "10.20.30.0/24",
+         "Mismatch in test data for " + id + " route set destinationCidrBlock");
+      assertEquals(table.routeSet().get(0).gatewayId(), "local",
+         "Mismatch in test data for " + id + " route set gatewayId");
+      assertEquals(table.routeSet().get(0).state(), Route.RouteState.ACTIVE,
+         "Mismatch in test data for " + id + " route set state");
+
+      final String actual = table.associationSet().get(0).routeTableId();
+      assertEquals(actual, id, "Test data mismatch in " + id + " association set routeTableId(): " + actual);
+      assertTrue(table.associationSet().get(0).main(), "Test data mismatch in " + id + " association 'main'");
+   }
+
+   public void createRouteTable() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/create_route_table.xml");
+      RouteTable result = routeTableApi().createRouteTable(DEFAULT_REGION, "vpc-1a2b3c4d");
+
+      assertNotNull(result, "Failed to create RouteTable object");
+      assertEquals(result.id(), "rtb-8bda6cef", "Gateway id does not match mock data: " + result.id());
+      assertEquals(result.routeSet().size(), 2, "Should have 2 routes");
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=CreateRouteTable&VpcId=vpc-1a2b3c4d");
+   }
+
+   public void createRouteTableWithOptions() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(PRECONDITION_FAILED, DEFAULT_REGION, "/dry_run.xml");
+      try {
+         routeTableApi().createRouteTable(DEFAULT_REGION, "vpc-1a2b3c4d", dryRun());
+         Assert.fail("Expected 'DryRunOperation' exception was not thrown");
+      } catch (AWSResponseException e) {
+         assertDryRun(e);
+      }
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=CreateRouteTable&VpcId=vpc-1a2b3c4d&DryRun=true");
+   }
+
+   public void deleteRouteTable() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/delete_route_table.xml");
+      final boolean deleted = routeTableApi().deleteRouteTable(DEFAULT_REGION, "rtb-8bda6cef");
+      assertTrue(deleted, "Failed to match 'true' data in test response");
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DeleteRouteTable&RouteTableId=rtb-8bda6cef");
+   }
+
+   public void deleteRouteTableWithOptions() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(PRECONDITION_FAILED, DEFAULT_REGION, "/dry_run.xml");
+      try {
+         routeTableApi().deleteRouteTable(DEFAULT_REGION, "rtb-8bda6cef", dryRun());
+         Assert.fail("Expected 'DryRunOperation' exception was not thrown");
+      } catch (AWSResponseException e) {
+         assertDryRun(e);
+      }
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DeleteRouteTable&RouteTableId=rtb-8bda6cef&DryRun=true");
+   }
+
+   public void deleteRouteTableNotFound() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueue(DEFAULT_REGION, new MockResponse().setResponseCode(404));
+      final boolean deleted = routeTableApi().deleteRouteTable(DEFAULT_REGION, "rtb-8bda6cef");
+      assertFalse(deleted, "Non-existent table reported as successfully deleted");
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DeleteRouteTable&RouteTableId=rtb-8bda6cef");
+   }
+
+   public void associateRouteTable() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/associate_route_table.xml");
+      final String associationId = routeTableApi().associateRouteTable(DEFAULT_REGION, "rtb-8c95c0eb", "subnet-6986410e");
+      assertEquals(associationId, "rtbassoc-fb7fed9d", "Failed to associate route");
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=AssociateRouteTable&RouteTableId=rtb-8c95c0eb&SubnetId=subnet-6986410e");
+   }
+
+   public void associateRouteTableNotFound() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueue(DEFAULT_REGION, new MockResponse().setResponseCode(404));
+      final String associationId = routeTableApi().associateRouteTable(DEFAULT_REGION, "rtb-8c95c0eb", "subnet-6986410e");
+      assertNull(associationId, "Returned id for non-existent route table: " + associationId);
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=AssociateRouteTable&RouteTableId=rtb-8c95c0eb&SubnetId=subnet-6986410e");
+   }
+
+   public void associateRouteTableWithOptions() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(PRECONDITION_FAILED, DEFAULT_REGION, "/dry_run.xml");
+      try {
+         routeTableApi().associateRouteTable(DEFAULT_REGION, "rtb-8c95c0eb", "subnet-6986410e", dryRun());
+      } catch (AWSResponseException e) {
+         assertDryRun(e);
+      }
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION,
+         "Action=AssociateRouteTable&RouteTableId=rtb-8c95c0eb&SubnetId=subnet-6986410e&DryRun=true");
+   }
+
+   public void disassociateRouteTable() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/disassociate_route_table.xml");
+      final boolean result = routeTableApi().disassociateRouteTable(DEFAULT_REGION, "rtbassoc-fb7fed9d");
+      assertTrue(result, "Failed to disassociate route");
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DisassociateRouteTable&AssociationId=rtbassoc-fb7fed9d");
+   }
+
+   public void disassociateRouteTableNotFound() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueue(DEFAULT_REGION, new MockResponse().setResponseCode(404));
+      final boolean result = routeTableApi().disassociateRouteTable(DEFAULT_REGION, "rtbassoc-fb7fed9d");
+      assertFalse(result, "Non-existent table reported as successfully disassociated");
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DisassociateRouteTable&AssociationId=rtbassoc-fb7fed9d");
+   }
+
+   public void disassociateRouteTablewithOptions() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(PRECONDITION_FAILED, DEFAULT_REGION, "/dry_run.xml");
+      try {
+         routeTableApi().disassociateRouteTable(DEFAULT_REGION, "rtbassoc-fb7fed9d", dryRun());
+      } catch (AWSResponseException e) {
+         assertDryRun(e);
+      }
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DisassociateRouteTable&AssociationId=rtbassoc-fb7fed9d&DryRun=true");
+   }
+
+   public void createRoute() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/create_route.xml");
+      final boolean created = routeTableApi().createRoute(DEFAULT_REGION, "rtb-a77f2ac0",
+            gatewayId("igw-97e68af3").destinationCidrBlock("172.18.19.0/24"));
+      assertTrue(created, "Failed to match 'true' in test data response");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION,
+         "Action=CreateRoute&RouteTableId=rtb-a77f2ac0&GatewayId=igw-97e68af3&DestinationCidrBlock=172.18.19.0/24");
+   }
+
+   public void createRouteNotFound() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/create_route.xml");
+      final boolean created = routeTableApi().createRoute(DEFAULT_REGION, "rtb-a77f2ac0",
+            gatewayId("igw-97e68af3").destinationCidrBlock("172.18.19.0/24"));
+      assertTrue(created, "Failed to match 'true' in test data response");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION,
+         "Action=CreateRoute&RouteTableId=rtb-a77f2ac0&GatewayId=igw-97e68af3&DestinationCidrBlock=172.18.19.0/24");
+   }
+
+   public void replaceRoute() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueue(DEFAULT_REGION, new MockResponse().setResponseCode(404));
+      final boolean created = routeTableApi().replaceRoute(DEFAULT_REGION, "rtb-a77f2ac0",
+         gatewayId("vgw-1d00376e").destinationCidrBlock("172.18.19.0/24"));
+      assertFalse(created, "Reported successful replace of route in non-existent route table");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION,
+         "Action=ReplaceRoute&RouteTableId=rtb-a77f2ac0&GatewayId=vgw-1d00376e&DestinationCidrBlock=172.18.19.0/24");
+   }
+
+   public void replaceRouteNotFound() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/replace_route.xml");
+      final boolean created = routeTableApi().replaceRoute(DEFAULT_REGION, "rtb-a77f2ac0",
+         gatewayId("vgw-1d00376e").destinationCidrBlock("172.18.19.0/24"));
+      assertTrue(created, "Failed to match 'true' in test data response");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION,
+         "Action=ReplaceRoute&RouteTableId=rtb-a77f2ac0&GatewayId=vgw-1d00376e&DestinationCidrBlock=172.18.19.0/24");
+   }
+
+   public void deleteRoute() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueueXml(DEFAULT_REGION, "/delete_route.xml");
+      final boolean deleted = routeTableApi().deleteRoute(DEFAULT_REGION, "rtb-a77f2ac0",
+         destinationCidrBlock("172.18.19.0/24"));
+      assertTrue(deleted, "Failed to match 'true' in test data response");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DeleteRoute&RouteTableId=rtb-a77f2ac0&DestinationCidrBlock=172.18.19.0/24");
+   }
+
+   public void deleteRouteNotFound() throws Exception {
+      enqueueRegions(DEFAULT_REGION);
+      enqueue(DEFAULT_REGION, new MockResponse().setResponseCode(404));
+      final boolean deleted = routeTableApi().deleteRoute(DEFAULT_REGION, "rtb-a77f2ac0",
+         destinationCidrBlock("172.18.19.0/24"));
+      assertFalse(deleted, "Reported successful delete of route in non-existent route table");
+
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION, "Action=DeleteRoute&RouteTableId=rtb-a77f2ac0&DestinationCidrBlock=172.18.19.0/24");
+   }
+
+   private void assertDryRun(AWSResponseException e) {
+      assertEquals(e.getError().getCode(), "DryRunOperation", "Expected DryRunOperation but got " + e.getError());
+   }
+
+   private RouteTableApi routeTableApi() {
+      return api().getRouteTableApi().get();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/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
index 060e577..d528fe5 100644
--- 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
@@ -26,10 +26,13 @@ import static org.jclouds.util.Strings2.toStringAndClose;
 import static org.testng.Assert.assertEquals;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
 
+import javax.ws.rs.core.Response;
+
 import org.jclouds.Constants;
 import org.jclouds.ContextBuilder;
 import org.jclouds.aws.ec2.AWSEC2Api;
@@ -155,6 +158,13 @@ public class BaseAWSEC2ApiMockTest {
             new MockResponse().addHeader(CONTENT_TYPE, APPLICATION_XML).setBody(describeRegionsResponse.toString()));
    }
 
+   protected void enqueueXml(Response.Status status, String region, String resource) {
+      enqueue(region, new MockResponse()
+         .setStatus("HTTP/1.1 " + status.getStatusCode() + " " + status.getReasonPhrase())
+         .addHeader(CONTENT_TYPE, APPLICATION_XML)
+         .setBody(stringFromResource(resource)));
+   }
+
    protected void enqueueXml(String region, String resource) {
       enqueue(region,
             new MockResponse().addHeader(CONTENT_TYPE, APPLICATION_XML).setBody(stringFromResource(resource)));
@@ -162,7 +172,12 @@ public class BaseAWSEC2ApiMockTest {
 
    protected String stringFromResource(String resourceName) {
       try {
-         return toStringAndClose(getClass().getResourceAsStream(resourceName));
+         final InputStream resourceAsStream = getClass().getResourceAsStream(resourceName);
+         if (resourceAsStream == null) {
+            throw new IllegalArgumentException(
+               "Could not find resource '" + resourceName + "' in class " + getClass().getSimpleName());
+         }
+         return toStringAndClose(resourceAsStream);
       } catch (IOException e) {
          throw propagate(e);
       }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/test/resources/associate_route_table.xml
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/resources/associate_route_table.xml b/providers/aws-ec2/src/test/resources/associate_route_table.xml
new file mode 100644
index 0000000..88e2db5
--- /dev/null
+++ b/providers/aws-ec2/src/test/resources/associate_route_table.xml
@@ -0,0 +1,4 @@
+<AssociateRouteTableResponse xmlns="http://ec2.amazonaws.com/doc/2012-06-01/">
+    <requestId>f87d1645-3919-4e26-b98f-852c47def4ee</requestId>
+    <associationId>rtbassoc-fb7fed9d</associationId>
+</AssociateRouteTableResponse>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/test/resources/create_internet_gateway_dry_run.xml
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/resources/create_internet_gateway_dry_run.xml b/providers/aws-ec2/src/test/resources/create_internet_gateway_dry_run.xml
deleted file mode 100644
index 037d770..0000000
--- a/providers/aws-ec2/src/test/resources/create_internet_gateway_dry_run.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<Response>
-    <Errors>
-        <Error>
-            <Code>DryRunOperation</Code>
-            <Message>Request would have succeeded, but DryRun flag is set.</Message>
-        </Error>
-    </Errors>
-    <RequestID>344ef005-e34b-42fb-a334-1180fe317e7c</RequestID>
-</Response>
-

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/test/resources/create_route.xml
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/resources/create_route.xml b/providers/aws-ec2/src/test/resources/create_route.xml
new file mode 100644
index 0000000..e2e31ca
--- /dev/null
+++ b/providers/aws-ec2/src/test/resources/create_route.xml
@@ -0,0 +1,4 @@
+<CreateRouteResponse xmlns="http://ec2.amazonaws.com/doc/2012-06-01/">
+    <requestId>3b1d5abc-0d50-4367-87c1-a4dcc2f8b928</requestId>
+    <return>true</return>
+</CreateRouteResponse>

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/test/resources/create_route_table.xml
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/resources/create_route_table.xml b/providers/aws-ec2/src/test/resources/create_route_table.xml
new file mode 100644
index 0000000..8234a4f
--- /dev/null
+++ b/providers/aws-ec2/src/test/resources/create_route_table.xml
@@ -0,0 +1,24 @@
+<CreateRouteTableResponse xmlns="http://ec2.amazonaws.com/doc/2012-06-01/">
+    <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+    <routeTable>
+        <routeTableId>rtb-8bda6cef</routeTableId>
+        <vpcId>vpc-1a2b3c4d</vpcId>
+        <routeSet>
+            <item>
+                <destinationCidrBlock>10.0.0.0/16</destinationCidrBlock>
+                <gatewayId>local</gatewayId>
+                <state>active</state>
+                <origin>CreateRouteTable</origin>
+            </item>
+            <item>
+                <destinationIpv6CidrBlock>2001:db8:1234:1a00::/56</destinationIpv6CidrBlock>
+                <gatewayId>local</gatewayId>
+                <state>active</state>
+                <origin>CreateRouteTable</origin>
+            </item>
+        </routeSet>
+        <associationSet/>
+        <propagatingVgwSet/>
+        <tagSet/>
+    </routeTable>
+</CreateRouteTableResponse>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/test/resources/delete_route.xml
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/resources/delete_route.xml b/providers/aws-ec2/src/test/resources/delete_route.xml
new file mode 100644
index 0000000..4dce471
--- /dev/null
+++ b/providers/aws-ec2/src/test/resources/delete_route.xml
@@ -0,0 +1,4 @@
+<DeleteRouteResponse xmlns="http://ec2.amazonaws.com/doc/2012-06-01/">
+    <requestId>6d6d0513-5af2-46ae-8f8a-24c8c3ab0f70</requestId>
+    <return>true</return>
+</DeleteRouteResponse>

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/test/resources/delete_route_table.xml
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/resources/delete_route_table.xml b/providers/aws-ec2/src/test/resources/delete_route_table.xml
new file mode 100644
index 0000000..8ea49fb
--- /dev/null
+++ b/providers/aws-ec2/src/test/resources/delete_route_table.xml
@@ -0,0 +1,4 @@
+<DeleteRouteTableResponse xmlns="http://ec2.amazonaws.com/doc/2012-06-01/">
+    <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+    <return>true</return>
+</DeleteRouteTableResponse>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/test/resources/describe_route_tables.xml
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/resources/describe_route_tables.xml b/providers/aws-ec2/src/test/resources/describe_route_tables.xml
new file mode 100644
index 0000000..a23a3ca
--- /dev/null
+++ b/providers/aws-ec2/src/test/resources/describe_route_tables.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<DescribeRouteTablesResponse xmlns="http://ec2.amazonaws.com/doc/2012-06-01/">
+    <requestId>0fe3e6ca-5b09-4722-9d81-1f341376c830</requestId>
+    <routeTableSet>
+        <item>
+            <routeTableId>rtb-80a3fae4</routeTableId>
+            <vpcId>vpc-6dcb5609</vpcId>
+            <routeSet>
+                <item>
+                    <destinationCidrBlock>172.31.0.0/16</destinationCidrBlock>
+                    <gatewayId>local</gatewayId>
+                    <state>active</state>
+                </item>
+                <item>
+                    <destinationCidrBlock>0.0.0.0/0</destinationCidrBlock>
+                    <gatewayId>igw-dcdcc7b9</gatewayId>
+                    <state>active</state>
+                </item>
+            </routeSet>
+            <associationSet>
+                <item>
+                    <routeTableAssociationId>rtbassoc-f173c296</routeTableAssociationId>
+                    <routeTableId>rtb-80a3fae4</routeTableId>
+                    <main>true</main>
+                </item>
+            </associationSet>
+            <tagSet/>
+        </item>
+        <item>
+            <routeTableId>rtb-d4605bb0</routeTableId>
+            <vpcId>vpc-924731f6</vpcId>
+            <routeSet>
+                <item>
+                    <destinationCidrBlock>10.0.0.0/16</destinationCidrBlock>
+                    <gatewayId>local</gatewayId>
+                    <state>active</state>
+                </item>
+                <item>
+                    <destinationCidrBlock>0.0.0.0/0</destinationCidrBlock>
+                    <gatewayId>igw-5af2023e</gatewayId>
+                    <state>active</state>
+                </item>
+            </routeSet>
+            <associationSet>
+                <item>
+                    <routeTableAssociationId>rtbassoc-a08aefc7</routeTableAssociationId>
+                    <routeTableId>rtb-d4605bb0</routeTableId>
+                    <main>true</main>
+                </item>
+            </associationSet>
+            <tagSet/>
+        </item>
+        <item>
+            <routeTableId>rtb-e6c98381</routeTableId>
+            <vpcId>vpc-6fa76308</vpcId>
+            <routeSet>
+                <item>
+                    <destinationCidrBlock>10.20.30.0/24</destinationCidrBlock>
+                    <gatewayId>local</gatewayId>
+                    <state>active</state>
+                </item>
+            </routeSet>
+            <associationSet>
+                <item>
+                    <routeTableAssociationId>rtbassoc-2d2dbe4b</routeTableAssociationId>
+                    <routeTableId>rtb-e6c98381</routeTableId>
+                    <main>true</main>
+                </item>
+            </associationSet>
+            <tagSet/>
+        </item>
+    </routeTableSet>
+</DescribeRouteTablesResponse>
+

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/test/resources/describe_route_tables_invalid.xml
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/resources/describe_route_tables_invalid.xml b/providers/aws-ec2/src/test/resources/describe_route_tables_invalid.xml
new file mode 100644
index 0000000..a7109fa
--- /dev/null
+++ b/providers/aws-ec2/src/test/resources/describe_route_tables_invalid.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<DescribeRouteTablesResponse xmlns="http://ec2.amazonaws.com/doc/2012-06-01/">
+    <requestId>0fe3e6ca-5b09-4722-9d81-1f341376c830</requestId>
+    <routeTableSet>
+        <item>
+            <routeTableId>rtb-80a3fae4</routeTableId>
+            <vpcId>vpc-6dcb5609</vpcId>
+            <routeSet>
+                <item>
+                    <destinationCidrBlock>172.31.0.0/16</destinationCidrBlock>
+                    <gatewayId>local</gatewayId>
+                    <state>bogus</state>
+                </item>
+            </routeSet>
+            <associationSet/>
+            <tagSet/>
+        </item>
+    </routeTableSet>
+</DescribeRouteTablesResponse>
+

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/test/resources/disassociate_route_table.xml
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/resources/disassociate_route_table.xml b/providers/aws-ec2/src/test/resources/disassociate_route_table.xml
new file mode 100644
index 0000000..1a05743
--- /dev/null
+++ b/providers/aws-ec2/src/test/resources/disassociate_route_table.xml
@@ -0,0 +1,4 @@
+<DisassociateRouteTableResponse xmlns="http://ec2.amazonaws.com/doc/2012-06-01/">
+    <requestId>36e6a0e6-cd7a-45fc-8539-ba05cc070a3f</requestId>
+    <return>true</return>
+</DisassociateRouteTableResponse>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/test/resources/dry_run.xml
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/resources/dry_run.xml b/providers/aws-ec2/src/test/resources/dry_run.xml
new file mode 100644
index 0000000..037d770
--- /dev/null
+++ b/providers/aws-ec2/src/test/resources/dry_run.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Response>
+    <Errors>
+        <Error>
+            <Code>DryRunOperation</Code>
+            <Message>Request would have succeeded, but DryRun flag is set.</Message>
+        </Error>
+    </Errors>
+    <RequestID>344ef005-e34b-42fb-a334-1180fe317e7c</RequestID>
+</Response>
+

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/test/resources/replace_route.xml
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/resources/replace_route.xml b/providers/aws-ec2/src/test/resources/replace_route.xml
new file mode 100644
index 0000000..dbbca61
--- /dev/null
+++ b/providers/aws-ec2/src/test/resources/replace_route.xml
@@ -0,0 +1,4 @@
+<ReplaceRouteResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
+    <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+    <return>true</return>
+</ReplaceRouteResponse>
\ No newline at end of file


[4/4] jclouds git commit: Temporarily use a custom annotation instead of @SinceApiVersion.

Posted by na...@apache.org.
Temporarily use a custom annotation instead of @SinceApiVersion.

The intention is to use @SinceApiVersion for this purpose, but that
would affect a number of APIs, and we would want to have good test
coverage before merging that change (in
FormSignerUtils#getAnnotatedApiVersion). However, there is some issue
wth certain tests at resent that means we cannot successfully test
all APIs that make use of @SinceApiVersion in order to assure
ourselves that FormSignerUtils will not introduce some problem.

See https://github.com/jclouds/jclouds/pull/1102#issuecomment-302682049
for details.

This annotation is introduced as a temporary measure in order to
decouple the functionality of FormSignerUtils#getAnnotatedApiVersion
from @SinceApiVersion and the tests in question. It can be removed and
replaced by @SinceApiVersion when those tests are fixed.

Designates that a method overrides the {@link ApiVersion} on the class
with a specific value.


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

Branch: refs/heads/master
Commit: 28c3c33bf07b4d4ddbd5516f952992088d09e76b
Parents: ce0a0ad
Author: Geoff Macartney <ge...@cloudsoftcorp.com>
Authored: Fri May 26 15:22:21 2017 +0100
Committer: Ignasi Barrera <na...@apache.org>
Committed: Mon May 29 10:24:52 2017 +0200

----------------------------------------------------------------------
 .../jclouds/aws/filters/FormSignerUtils.java    | 12 ++---
 .../rest/annotations/ApiVersionOverride.java    | 56 ++++++++++++++++++++
 .../jclouds/aws/ec2/features/AWSSubnetApi.java  |  3 +-
 3 files changed, 64 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/28c3c33b/apis/sts/src/main/java/org/jclouds/aws/filters/FormSignerUtils.java
----------------------------------------------------------------------
diff --git a/apis/sts/src/main/java/org/jclouds/aws/filters/FormSignerUtils.java b/apis/sts/src/main/java/org/jclouds/aws/filters/FormSignerUtils.java
index 84ff04b..4a5d6d6 100644
--- a/apis/sts/src/main/java/org/jclouds/aws/filters/FormSignerUtils.java
+++ b/apis/sts/src/main/java/org/jclouds/aws/filters/FormSignerUtils.java
@@ -18,7 +18,7 @@ package org.jclouds.aws.filters;
 
 import org.jclouds.http.HttpRequest;
 import org.jclouds.reflect.Invocation;
-import org.jclouds.rest.annotations.SinceApiVersion;
+import org.jclouds.rest.annotations.ApiVersionOverride;
 import org.jclouds.rest.internal.GeneratedHttpRequest;
 
 import com.google.common.base.Optional;
@@ -32,7 +32,7 @@ public final class FormSignerUtils {
    private FormSignerUtils() {}
 
    /**
-    * Get the version from a @SinceApiVersion() annotation on an API method or its owning class.
+    * Get the version from a @ApiVersionOverride() annotation on an API method or its owning class.
     * @param request The API request for the method.
     * @return An optional of the value of the annotation.
     */
@@ -47,12 +47,12 @@ public final class FormSignerUtils {
 
    private static Optional<String> getAnnotatedApiVersion(Invocation invocation) {
       final Invokable<?, ?> invokable = invocation.getInvokable();
-      if (invokable.isAnnotationPresent(SinceApiVersion.class)) {
-         return Optional.fromNullable(invokable.getAnnotation(SinceApiVersion.class).value());
+      if (invokable.isAnnotationPresent(ApiVersionOverride.class)) {
+         return Optional.fromNullable(invokable.getAnnotation(ApiVersionOverride.class).value());
       } else {
          final Class<?> owner = invokable.getOwnerType().getRawType();
-         if (owner.isAnnotationPresent(SinceApiVersion.class)) {
-            return Optional.fromNullable(owner.getAnnotation(SinceApiVersion.class).value());
+         if (owner.isAnnotationPresent(ApiVersionOverride.class)) {
+            return Optional.fromNullable(owner.getAnnotation(ApiVersionOverride.class).value());
          }
       }
       return Optional.absent();

http://git-wip-us.apache.org/repos/asf/jclouds/blob/28c3c33b/core/src/main/java/org/jclouds/rest/annotations/ApiVersionOverride.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/jclouds/rest/annotations/ApiVersionOverride.java b/core/src/main/java/org/jclouds/rest/annotations/ApiVersionOverride.java
new file mode 100644
index 0000000..0307806
--- /dev/null
+++ b/core/src/main/java/org/jclouds/rest/annotations/ApiVersionOverride.java
@@ -0,0 +1,56 @@
+/*
+ * 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.rest.annotations;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+
+/**
+ * @Deprecated The intention is to use @SinceApiVersion for this purpose, but that would affect
+ * a number of APIs, and we would want to have good test coverage before merging that change
+ * (in {@link FormSignerUtils#getAnnotatedApiVersion}). However, there is some issue with certain tests at
+ * present that means we cannot successfully test all APIs that make use of @SinceApiVersion in order
+ * to assure ourselves that FormSignerUtils will not introduce some problem. See
+ * <a href="https://github.com/jclouds/jclouds/pull/1102#issuecomment-302682049">
+ *    comments on github</a> for details
+ * This annotation is introduced as a temporary measure in order to decouple the functionality of
+ * {@link FormSignerUtils#getAnnotatedApiVersion} from @SinceApiVersion and the tests in question.
+ * It can be removed and replaced by @SinceApiVersion when those tests are fixed.
+ *
+ * Designates that a method overrides the {@link ApiVersion} on the class with a specific value.
+ *
+ * @see ApiVersion
+ */
+@Deprecated
+@Target({ METHOD })
+@Retention(RUNTIME)
+@Qualifier
+public @interface ApiVersionOverride {
+
+   /**
+    * Value to override the default {@link ApiVersion}.
+    *
+    */
+   String value();
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/28c3c33b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/features/AWSSubnetApi.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/features/AWSSubnetApi.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/features/AWSSubnetApi.java
index b0f245a..f11699b 100644
--- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/features/AWSSubnetApi.java
+++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/features/AWSSubnetApi.java
@@ -36,6 +36,7 @@ import org.jclouds.ec2.xml.DescribeSubnetsResponseHandler;
 import org.jclouds.ec2.xml.SubnetHandler;
 import org.jclouds.javax.annotation.Nullable;
 import org.jclouds.location.functions.RegionToEndpointOrProviderIfNull;
+import org.jclouds.rest.annotations.ApiVersionOverride;
 import org.jclouds.rest.annotations.BinderParam;
 import org.jclouds.rest.annotations.EndpointParam;
 import org.jclouds.rest.annotations.Fallback;
@@ -141,7 +142,7 @@ public interface AWSSubnetApi extends SubnetApi {
     * @param options The options containing the attribute to modify. You can only modify one attribute at a time.
     * @return true if the modification was successful
     */
-   @SinceApiVersion("2014-06-15")
+   @ApiVersionOverride("2014-06-15")
    @Named("ModifySubnetAttribute")
    @POST
    @Path("/")


[3/4] jclouds git commit: Add ModifySubnetAttribute

Posted by na...@apache.org.
Add ModifySubnetAttribute


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

Branch: refs/heads/master
Commit: ce0a0ad213a331cec53fae6309b32d73388adf7e
Parents: b3d21f9
Author: Geoff Macartney <ge...@cloudsoftcorp.com>
Authored: Wed May 17 16:19:52 2017 +0100
Committer: Ignasi Barrera <na...@apache.org>
Committed: Mon May 29 10:24:52 2017 +0200

----------------------------------------------------------------------
 .../org/jclouds/aws/filters/FormSigner.java     | 12 ++-
 .../jclouds/aws/filters/FormSignerUtils.java    | 61 ++++++++++++++
 .../org/jclouds/aws/filters/FormSignerV4.java   | 25 +++++-
 .../jclouds/aws/ec2/features/AWSSubnetApi.java  | 22 +++++
 .../options/ModifySubnetAttributeOptions.java   | 86 ++++++++++++++++++++
 .../aws/ec2/features/AWSSubnetApiLiveTest.java  | 24 +++++-
 .../aws/ec2/features/AWSSubnetApiMockTest.java  | 20 +++++
 .../aws/ec2/internal/BaseAWSEC2ApiMockTest.java |  9 +-
 8 files changed, 250 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/ce0a0ad2/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 aeb3647..b63f5a9 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
@@ -22,6 +22,7 @@ import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.collect.Ordering.natural;
 import static com.google.common.io.BaseEncoding.base64;
 import static com.google.common.io.ByteStreams.readBytes;
+import static org.jclouds.aws.filters.FormSignerUtils.getAnnotatedApiVersion;
 import static org.jclouds.aws.reference.FormParameters.ACTION;
 import static org.jclouds.aws.reference.FormParameters.AWS_ACCESS_KEY_ID;
 import static org.jclouds.aws.reference.FormParameters.SECURITY_TOKEN;
@@ -59,6 +60,7 @@ import org.jclouds.rest.annotations.ApiVersion;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Objects;
+import com.google.common.base.Optional;
 import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -100,7 +102,15 @@ public interface FormSigner extends HttpRequestFilter {
       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));
+         Optional<String> optAnnotatedVersion = getAnnotatedApiVersion(request);
+         String version;
+         if (optAnnotatedVersion.isPresent()) {
+            String annotatedVersion = optAnnotatedVersion.get();
+            version = annotatedVersion.compareTo(apiVersion) > 0 ? annotatedVersion : apiVersion;
+         } else {
+            version = apiVersion;
+         }
+         decodedParams.replaceValues(VERSION, ImmutableSet.of(version));
          addSigningParams(decodedParams);
          validateParams(decodedParams);
          String stringToSign = createStringToSign(request, decodedParams);

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ce0a0ad2/apis/sts/src/main/java/org/jclouds/aws/filters/FormSignerUtils.java
----------------------------------------------------------------------
diff --git a/apis/sts/src/main/java/org/jclouds/aws/filters/FormSignerUtils.java b/apis/sts/src/main/java/org/jclouds/aws/filters/FormSignerUtils.java
new file mode 100644
index 0000000..84ff04b
--- /dev/null
+++ b/apis/sts/src/main/java/org/jclouds/aws/filters/FormSignerUtils.java
@@ -0,0 +1,61 @@
+/*
+ * 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 org.jclouds.http.HttpRequest;
+import org.jclouds.reflect.Invocation;
+import org.jclouds.rest.annotations.SinceApiVersion;
+import org.jclouds.rest.internal.GeneratedHttpRequest;
+
+import com.google.common.base.Optional;
+import com.google.common.reflect.Invokable;
+
+/**
+ * Utilities for FormSigner implementations.
+ */
+public final class FormSignerUtils {
+
+   private FormSignerUtils() {}
+
+   /**
+    * Get the version from a @SinceApiVersion() annotation on an API method or its owning class.
+    * @param request The API request for the method.
+    * @return An optional of the value of the annotation.
+    */
+   public static Optional<String> getAnnotatedApiVersion(HttpRequest request) {
+      if (request instanceof GeneratedHttpRequest) {
+         GeneratedHttpRequest generatedRequest = (GeneratedHttpRequest) request;
+         return getAnnotatedApiVersion(generatedRequest.getInvocation());
+      } else {
+         return Optional.absent();
+      }
+   }
+
+   private static Optional<String> getAnnotatedApiVersion(Invocation invocation) {
+      final Invokable<?, ?> invokable = invocation.getInvokable();
+      if (invokable.isAnnotationPresent(SinceApiVersion.class)) {
+         return Optional.fromNullable(invokable.getAnnotation(SinceApiVersion.class).value());
+      } else {
+         final Class<?> owner = invokable.getOwnerType().getRawType();
+         if (owner.isAnnotationPresent(SinceApiVersion.class)) {
+            return Optional.fromNullable(owner.getAnnotation(SinceApiVersion.class).value());
+         }
+      }
+      return Optional.absent();
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ce0a0ad2/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
index a1359bb..7dfbd12 100644
--- a/apis/sts/src/main/java/org/jclouds/aws/filters/FormSignerV4.java
+++ b/apis/sts/src/main/java/org/jclouds/aws/filters/FormSignerV4.java
@@ -23,6 +23,7 @@ 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 org.jclouds.aws.filters.FormSignerUtils.getAnnotatedApiVersion;
 import static org.jclouds.aws.reference.FormParameters.ACTION;
 import static org.jclouds.aws.reference.FormParameters.VERSION;
 import static org.jclouds.http.utils.Queries.queryParser;
@@ -46,6 +47,7 @@ import org.jclouds.providers.ProviderMetadata;
 import org.jclouds.rest.annotations.ApiVersion;
 
 import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
 import com.google.common.base.Splitter;
 import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableMap;
@@ -101,7 +103,18 @@ public final class FormSignerV4 implements FormSigner {
       this.serviceAndRegion = serviceAndRegion;
    }
 
-   @Override public HttpRequest filter(HttpRequest request) throws HttpException {
+   /**
+    * Adds the Authorization header to the request.
+    *
+    * Also if the method for the operation (or its class) is annotated with a version that is higher than the
+    * default (apiVersion), then the Version parameter of the request is set to be the value from the annotation.
+    *
+    * @param request The HTTP request for the API call.
+    * @return The request
+    * @throws HttpException
+    */
+   @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();
@@ -126,7 +139,15 @@ public final class FormSignerV4 implements FormSigner {
             .replaceHeader("X-Amz-Date", timestamp);
 
       if (!decodedParams.containsKey(VERSION)) {
-         requestBuilder.addFormParam(VERSION, apiVersion);
+         Optional<String> optAnnotatedVersion = getAnnotatedApiVersion(request);
+         if (optAnnotatedVersion.isPresent()) {
+            String annotatedVersion = optAnnotatedVersion.get();
+            // allow an explicit version annotation to _upgrade_ the version past apiVersion (but not downgrade)
+            String greater = annotatedVersion.compareTo(apiVersion) > 0 ? annotatedVersion : apiVersion;
+            requestBuilder.addFormParam(VERSION, greater);
+         } else {
+            requestBuilder.addFormParam(VERSION, apiVersion);
+         }
       }
 
       Credentials credentials = creds.get();

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ce0a0ad2/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/features/AWSSubnetApi.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/features/AWSSubnetApi.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/features/AWSSubnetApi.java
index ccafebe..b0f245a 100644
--- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/features/AWSSubnetApi.java
+++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/features/AWSSubnetApi.java
@@ -25,6 +25,8 @@ import javax.ws.rs.Path;
 
 import org.jclouds.Fallbacks;
 import org.jclouds.aws.ec2.options.CreateSubnetOptions;
+import org.jclouds.aws.ec2.options.ModifySubnetAttributeOptions;
+import org.jclouds.aws.ec2.xml.ReturnValueHandler;
 import org.jclouds.aws.filters.FormSigner;
 import org.jclouds.ec2.binders.BindFiltersToIndexedFormParams;
 import org.jclouds.ec2.binders.BindSubnetIdsToIndexedFormParams;
@@ -130,4 +132,24 @@ public interface AWSSubnetApi extends SubnetApi {
    FluentIterable<Subnet> describeSubnetsInRegionWithFilter(
            @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region,
            @BinderParam(BindFiltersToIndexedFormParams.class) Multimap<String, String> filter);
+
+   /**
+    * Modifies a subnet attribute. You can only modify one attribute at a time.
+    *
+    * @param region The region for the subnet
+    * @param subnetId The ID of the subnet
+    * @param options The options containing the attribute to modify. You can only modify one attribute at a time.
+    * @return true if the modification was successful
+    */
+   @SinceApiVersion("2014-06-15")
+   @Named("ModifySubnetAttribute")
+   @POST
+   @Path("/")
+   @FormParams(keys = ACTION, values = "ModifySubnetAttribute")
+   @XMLResponseParser(ReturnValueHandler.class)
+   @Fallback(Fallbacks.FalseOnNotFoundOr404.class)
+   boolean modifySubnetAttribute(
+      @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region,
+      @FormParam("SubnetId") String subnetId,
+      ModifySubnetAttributeOptions options);
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ce0a0ad2/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/ModifySubnetAttributeOptions.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/ModifySubnetAttributeOptions.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/ModifySubnetAttributeOptions.java
new file mode 100644
index 0000000..b18b850
--- /dev/null
+++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/ModifySubnetAttributeOptions.java
@@ -0,0 +1,86 @@
+/*
+ * 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.options;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.jclouds.ec2.options.internal.BaseEC2RequestOptions;
+import org.jclouds.rest.annotations.SinceApiVersion;
+
+/**
+ * Contains options supported in the Form API for the ModifySubnetAttribute
+ * operation. <h2>
+ * Usage</h2> The recommended way to instantiate a ModifySubnetAttributeOptions
+ * object is to statically import ModifySubnetAttributeOptions.Builder.* and
+ * invoke a static creation method followed by an instance mutator (if needed):
+ * <p/>
+ * <code>
+ * import static org.jclouds.aws.ec2.options.ModifySubnetAttributeOptions.Builder.*
+ * <p/>
+ * group = connection.getAWSSubnetApi().modifySubnetAttribute(region, subnetId, mapPublicIpOnLaunch(true));
+ * <code>
+ * 
+ * @see <a href=
+ *      "http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_ModifySubnetAttribute.html"
+ *      />
+ */
+@SinceApiVersion("2014-06-15")
+public class ModifySubnetAttributeOptions extends BaseEC2RequestOptions {
+
+   /**
+    * The Availability Zone for the subnet.
+    */
+   public ModifySubnetAttributeOptions assignIpv6AddressOnCreation(Boolean assignIpv6AddressOnCreation) {
+      formParameters.put("AssignIpv6AddressOnCreation.Value",
+         checkNotNull(assignIpv6AddressOnCreation, "assignIpv6AddressOnCreation").toString());
+      return this;
+   }
+
+   public Boolean isAssignIpv6AddressOnCreation() {
+      return Boolean.parseBoolean("AssignIpv6AddressOnCreation.Value");
+   }
+
+   public ModifySubnetAttributeOptions mapPublicIpOnLaunch(Boolean mapPublicIpOnLaunch) {
+      formParameters.put("MapPublicIpOnLaunch.Value",
+         checkNotNull(mapPublicIpOnLaunch, "mapPublicIpOnLaunch").toString());
+      return this;
+   }
+
+   public Boolean isMapPublicIpOnLaunch() {
+      return Boolean.parseBoolean(getFirstFormOrNull("MapPublicIpOnLaunch.Value"));
+   }
+
+   public static class Builder {
+
+      /**
+       * @see ModifySubnetAttributeOptions#assignIpv6AddressOnCreation(Boolean )
+       */
+      public static ModifySubnetAttributeOptions assignIpv6AddressOnCreation(Boolean assignIpv6AddressOnCreation) {
+         ModifySubnetAttributeOptions options = new ModifySubnetAttributeOptions();
+         return options.assignIpv6AddressOnCreation(assignIpv6AddressOnCreation);
+      }
+
+      /**
+       * @see ModifySubnetAttributeOptions#mapPublicIpOnLaunch(Boolean)
+       */
+      public static ModifySubnetAttributeOptions mapPublicIpOnLaunch(Boolean mapPublicIpOnLaunch) {
+         ModifySubnetAttributeOptions options = new ModifySubnetAttributeOptions();
+         return options.mapPublicIpOnLaunch(mapPublicIpOnLaunch);
+      }
+
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ce0a0ad2/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/AWSSubnetApiLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/AWSSubnetApiLiveTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/AWSSubnetApiLiveTest.java
index b851f3a..e06ae6e 100644
--- a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/AWSSubnetApiLiveTest.java
+++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/AWSSubnetApiLiveTest.java
@@ -16,15 +16,19 @@
  */
 package org.jclouds.aws.ec2.features;
 
+import static org.jclouds.aws.ec2.options.ModifySubnetAttributeOptions.Builder.mapPublicIpOnLaunch;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertTrue;
 
+import java.util.Random;
+
 import org.jclouds.aws.ec2.AWSEC2Api;
 import org.jclouds.aws.ec2.domain.VPC;
 import org.jclouds.aws.ec2.options.CreateVpcOptions;
 import org.jclouds.compute.internal.BaseComputeServiceContextLiveTest;
 import org.jclouds.ec2.domain.Subnet;
+import org.jclouds.ec2.features.TagApi;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
@@ -33,6 +37,7 @@ import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.Iterables;
 
@@ -47,13 +52,15 @@ public class AWSSubnetApiLiveTest extends BaseComputeServiceContextLiveTest {
    private AWSEC2Api api;
    private AWSSubnetApi subnetClient;
    private VPCApi vpcClient;
+   private TagApi tagApi;
+   private String simpleName = getClass().getSimpleName() + new Random().nextInt(10000);
 
    private Subnet subnet;
    private VPC vpc;
 
    public AWSSubnetApiLiveTest() {
       provider = "aws-ec2";
-      region = "us-west-2";
+      region = "eu-west-1";
    }
 
    @Override
@@ -63,6 +70,7 @@ public class AWSSubnetApiLiveTest extends BaseComputeServiceContextLiveTest {
       api = view.unwrapApi(AWSEC2Api.class);
       subnetClient = api.getAWSSubnetApi().get();
       vpcClient = view.unwrapApi(AWSEC2Api.class).getVPCApi().get();
+      tagApi = api.getTagApiForRegion(region).get();
    }
 
    @Override
@@ -85,10 +93,12 @@ public class AWSSubnetApiLiveTest extends BaseComputeServiceContextLiveTest {
 
    @Test
    public void testCreateSubnetInRegion() {
-      vpc = vpcClient.createVpc(region, "10.0.0.0/16", CreateVpcOptions.NONE);
-      subnet = subnetClient.createSubnetInRegion(region, vpc.id(), "10.0.0.0/20");
+      vpc = vpcClient.createVpc(region, "10.21.0.0/16", CreateVpcOptions.NONE);
+      // tag the VPC for ease of identification in console if things go wrong
+      tagApi.applyToResources(ImmutableMap.of("Name", simpleName), ImmutableList.of(vpc.id()));
+      subnet = subnetClient.createSubnetInRegion(region, vpc.id(), "10.21.0.0/20");
       assertNotNull(subnet);
-      assertEquals(subnet.getCidrBlock(), "10.0.0.0/20");
+      assertEquals(subnet.getCidrBlock(), "10.21.0.0/20");
    }
 
    @Test(dependsOnMethods = "testCreateSubnetInRegion")
@@ -107,6 +117,12 @@ public class AWSSubnetApiLiveTest extends BaseComputeServiceContextLiveTest {
    }
 
    @Test(dependsOnMethods = "testCreateSubnetInRegion")
+   public void testModifySubnetAttribute() {
+      final boolean result = subnetClient.modifySubnetAttribute(region, subnet.getSubnetId(), mapPublicIpOnLaunch(true));
+      assertTrue(result, "Failed to modify subnet attribute");
+   }
+
+   @Test(dependsOnMethods = "testCreateSubnetInRegion")
    public void testList() {
       FluentIterable<Subnet> subnets = subnetClient.describeSubnetsInRegionWithFilter(region, 
             ImmutableMultimap.<String, String>of());

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ce0a0ad2/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/AWSSubnetApiMockTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/AWSSubnetApiMockTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/AWSSubnetApiMockTest.java
index b8f81ca..191a51b 100644
--- a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/AWSSubnetApiMockTest.java
+++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/AWSSubnetApiMockTest.java
@@ -16,6 +16,7 @@
  */
 package org.jclouds.aws.ec2.features;
 
+import static org.jclouds.aws.ec2.options.ModifySubnetAttributeOptions.Builder.mapPublicIpOnLaunch;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
 
@@ -203,6 +204,25 @@ public class AWSSubnetApiMockTest extends BaseAWSEC2ApiMockTest {
       assertPosted(region, "Action=DescribeSubnets");
    }
 
+   public void modifySubnetAttribute() throws Exception {
+      String region = "us-west-2";
+      enqueueRegions(DEFAULT_REGION);
+      enqueue(DEFAULT_REGION,
+         new MockResponse().setBody("<ModifySubnetAttributeResponse xmlns=\"http://ec2.amazonaws.com/doc/2016-09-15/\">\n" +
+            "  <requestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</requestId>\n" +
+            "  <return>true</return>\n" +
+            "</ModifySubnetAttributeResponse>"));
+
+      final boolean result =
+         subnetApiForRegion(DEFAULT_REGION).modifySubnetAttribute(DEFAULT_REGION, "subnet-9d4a7b6c", mapPublicIpOnLaunch(true));
+      assertTrue(result, "Failed to match expected test result of 'true'");
+      assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
+      assertPosted(DEFAULT_REGION,
+         "Action=ModifySubnetAttribute&SubnetId=subnet-9d4a7b6c&MapPublicIpOnLaunch.Value=true",
+         "2014-06-15");
+
+   }
+
    private AWSSubnetApi subnetApi() {
       return api().getAWSSubnetApi().get();
    }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/ce0a0ad2/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
index d528fe5..7ad46c8 100644
--- 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
@@ -183,7 +183,12 @@ public class BaseAWSEC2ApiMockTest {
       }
    }
 
+
    protected RecordedRequest assertPosted(String region, String postParams) throws InterruptedException {
+      return assertPosted(region, postParams, "2012-06-01");
+   }
+
+   protected RecordedRequest assertPosted(String region, String postParams, String apiVersion) throws InterruptedException {
       RecordedRequest request = regionToServers.get(region).takeRequest();
       assertEquals(request.getMethod(), "POST");
       assertEquals(request.getPath(), "/");
@@ -192,8 +197,8 @@ public class BaseAWSEC2ApiMockTest {
             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);
+      assertThat(body).contains("&Version=" + apiVersion);
+      assertEquals(body.replace("&Version=" + apiVersion, ""), postParams);
       return request;
    }
 }


[2/4] jclouds git commit: Add RouteTable API.

Posted by na...@apache.org.
Add RouteTable API.

Limitations:
Does not contain support for VgwRoutePropagation.


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

Branch: refs/heads/master
Commit: b3d21f965288b44cadc750ff1dde2ec7ac45fff6
Parents: a900628
Author: Geoff Macartney <ge...@cloudsoftcorp.com>
Authored: Fri May 12 16:44:30 2017 +0100
Committer: Ignasi Barrera <na...@apache.org>
Committed: Mon May 29 10:22:46 2017 +0200

----------------------------------------------------------------------
 .../java/org/jclouds/aws/ec2/AWSEC2Api.java     |  17 +-
 .../BindRouteTableIdsToIndexedFormParams.java   |  32 ++
 .../java/org/jclouds/aws/ec2/domain/Route.java  | 107 +++++++
 .../org/jclouds/aws/ec2/domain/RouteTable.java  |  99 ++++++
 .../aws/ec2/domain/RouteTableAssociation.java   |  71 +++++
 .../jclouds/aws/ec2/features/RouteTableApi.java | 279 +++++++++++++++++
 .../aws/ec2/options/InternetGatewayOptions.java |   5 +-
 .../jclouds/aws/ec2/options/RouteOptions.java   | 254 ++++++++++++++++
 .../aws/ec2/options/RouteTableOptions.java      |  75 +++++
 .../xml/AssociateRouteTableResponseHandler.java |  40 +++
 .../xml/CreateRouteTableResponseHandler.java    |  55 ++++
 .../xml/DescribeRouteTablesResponseHandler.java | 100 ++++++
 .../org/jclouds/aws/ec2/xml/RouteHandler.java   |  48 +++
 .../jclouds/aws/ec2/xml/RouteSetHandler.java    |  77 +++++
 .../xml/RouteTableAssociationSetHandler.java    |  79 +++++
 .../jclouds/aws/ec2/xml/RouteTableHandler.java  | 116 +++++++
 .../features/InternetGatewayApiLiveTest.java    |   8 +-
 .../features/InternetGatewayApiMockTest.java    |   8 +-
 .../aws/ec2/features/RouteTableApiLiveTest.java | 293 ++++++++++++++++++
 .../aws/ec2/features/RouteTableApiMockTest.java | 301 +++++++++++++++++++
 .../aws/ec2/internal/BaseAWSEC2ApiMockTest.java |  17 +-
 .../test/resources/associate_route_table.xml    |   4 +
 .../create_internet_gateway_dry_run.xml         |  11 -
 .../aws-ec2/src/test/resources/create_route.xml |   4 +
 .../src/test/resources/create_route_table.xml   |  24 ++
 .../aws-ec2/src/test/resources/delete_route.xml |   4 +
 .../src/test/resources/delete_route_table.xml   |   4 +
 .../test/resources/describe_route_tables.xml    |  74 +++++
 .../resources/describe_route_tables_invalid.xml |  20 ++
 .../test/resources/disassociate_route_table.xml |   4 +
 .../aws-ec2/src/test/resources/dry_run.xml      |  11 +
 .../src/test/resources/replace_route.xml        |   4 +
 32 files changed, 2225 insertions(+), 20 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/AWSEC2Api.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/AWSEC2Api.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/AWSEC2Api.java
index a094ce6..d2808af 100644
--- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/AWSEC2Api.java
+++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/AWSEC2Api.java
@@ -24,6 +24,7 @@ import org.jclouds.aws.ec2.features.AWSSubnetApi;
 import org.jclouds.aws.ec2.features.InternetGatewayApi;
 import org.jclouds.aws.ec2.features.MonitoringApi;
 import org.jclouds.aws.ec2.features.PlacementGroupApi;
+import org.jclouds.aws.ec2.features.RouteTableApi;
 import org.jclouds.aws.ec2.features.SpotInstanceApi;
 import org.jclouds.aws.ec2.features.VPCApi;
 import org.jclouds.ec2.EC2Api;
@@ -142,10 +143,24 @@ public interface AWSEC2Api extends EC2Api {
    Optional<? extends InternetGatewayApi> getInternetGatewayApi();
 
    /**
-    * Provides synchronous access to InternetGateway services in a given region.
+    * Provides synchronous access to Internet Gateway services in a given region.
     */
    @Delegate
    Optional<? extends InternetGatewayApi> getInternetGatewayApiForRegion(
       @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region
    );
+
+   /**
+    * Provides synchronous access to Route Table services.
+    */
+   @Delegate
+   Optional<? extends RouteTableApi> getRouteTableApi();
+
+   /**
+    * Provides synchronous access to Route Table services in a given region.
+    */
+   @Delegate
+   Optional<? extends RouteTableApi> getRouteTableApiForRegion(
+      @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region
+   );
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/BindRouteTableIdsToIndexedFormParams.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/BindRouteTableIdsToIndexedFormParams.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/BindRouteTableIdsToIndexedFormParams.java
new file mode 100644
index 0000000..4c2ec42
--- /dev/null
+++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/BindRouteTableIdsToIndexedFormParams.java
@@ -0,0 +1,32 @@
+/*
+ * 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.binders;
+
+import org.jclouds.aws.util.AWSUtils;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.rest.Binder;
+
+/**
+ * Binds the String [] to form parameters named with RouteTableId.index
+ */
+public class BindRouteTableIdsToIndexedFormParams implements Binder {
+   @Override
+   public <R extends HttpRequest> R bindToRequest(R request, Object input) {
+      return AWSUtils.indexStringArrayToFormValuesWithPrefix(request, "RouteTableId", input);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/domain/Route.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/domain/Route.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/domain/Route.java
new file mode 100644
index 0000000..39d2d92
--- /dev/null
+++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/domain/Route.java
@@ -0,0 +1,107 @@
+/*
+ * 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.domain;
+
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+
+/**
+ * A route in an Amazon EC2 Route Table.
+ *
+ * @see <a href="http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_Route.html" >doc</a>
+ */
+@AutoValue
+public abstract class Route {
+
+   public enum RouteState {
+
+      /**
+       * An active route.
+       */
+      ACTIVE,
+
+      /**
+       * Indicates that the route's target isn't available (for example, the specified gateway isn't attached
+       * to the VPC, or the specified NAT instance has been terminated).
+       */
+      BLACKHOLE,
+
+      /**
+       * Value supplied was not valid.
+       */
+      UNRECOGNIZED;
+
+      public String value() {
+         return name().toLowerCase();
+      }
+
+      public static RouteState fromValue(String v) {
+         if (v == null || v.isEmpty()) {
+            throw new IllegalArgumentException("Value cannot be null or empty");
+         }
+         try {
+            return valueOf(v.toUpperCase());
+         } catch (IllegalArgumentException e) {
+            return UNRECOGNIZED;
+         }
+      }
+   }
+
+
+   @Nullable
+   public abstract String destinationCidrBlock();
+
+   @Nullable
+   public abstract String gatewayId();
+
+   @Nullable
+   public abstract RouteState state();
+
+   @Nullable
+   public abstract String origin();
+
+   @SerializedNames({"destinationCidrBlock", "gatewayId", "state", "origin"})
+   public static Route create(String destinationCidrBlock, String gatewayId, RouteState state, String origin) {
+      return builder()
+         .destinationCidrBlock(destinationCidrBlock)
+         .gatewayId(gatewayId)
+         .state(state)
+         .origin(origin)
+         .build();
+   }
+
+   Route() {}
+
+   public static Builder builder() {
+      return new AutoValue_Route.Builder();
+   }
+
+   @AutoValue.Builder
+   public abstract static class Builder {
+      public abstract Builder destinationCidrBlock(String destinationCidrBlock);
+
+      public abstract Builder gatewayId(String gatewayId);
+
+      public abstract Builder state(RouteState state);
+
+      public abstract Builder origin(String origin);
+
+      public abstract Route build();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/domain/RouteTable.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/domain/RouteTable.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/domain/RouteTable.java
new file mode 100644
index 0000000..b4eb182
--- /dev/null
+++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/domain/RouteTable.java
@@ -0,0 +1,99 @@
+/*
+ * 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.domain;
+
+import java.util.List;
+import java.util.Map;
+
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Amazon EC2 Route Table.
+ *
+ * @see <a href="http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RouteTable.html" >doc</a>
+ */
+@AutoValue
+public abstract class RouteTable {
+
+   @Nullable
+   public abstract String id();
+
+   @Nullable
+   public abstract String vpcId();
+
+   @Nullable
+   public abstract List<Route> routeSet();
+
+   @Nullable
+   public abstract List<RouteTableAssociation> associationSet();
+
+   @Nullable
+   public abstract Map<String, String> tags();
+
+   @SerializedNames({"routeTableId", "vpcId", "routeSet", "associationSet", "tagSet"})
+   public static RouteTable create(String id,
+                                   String vpcId,
+                                   List<Route> routeSet,
+                                   List<RouteTableAssociation> associationSet,
+                                   Map<String, String> tags) {
+      return builder()
+         .id(id)
+         .vpcId(vpcId)
+         .routeSet(routeSet)
+         .associationSet(associationSet)
+         .tags(tags)
+         .build();
+   }
+
+   RouteTable() {}
+
+   public static Builder builder() {
+      return new AutoValue_RouteTable.Builder();
+   }
+
+   @AutoValue.Builder
+   public abstract static class Builder {
+
+      public abstract Builder id(String id);
+      public abstract Builder vpcId(String vpcId);
+      public abstract Builder routeSet(List<Route> routeSet);
+      public abstract Builder associationSet(List<RouteTableAssociation> associationSet);
+      public abstract Builder tags(Map<String, String> tags);
+
+      @Nullable abstract List<Route> routeSet();
+      @Nullable abstract List<RouteTableAssociation> associationSet();
+      @Nullable abstract Map<String, String> tags();
+
+      abstract RouteTable autoBuild();
+
+      public RouteTable build() {
+         routeSet(routeSet() != null ? ImmutableList.copyOf(routeSet()) : ImmutableList.<Route>of());
+         associationSet(associationSet() != null
+            ? ImmutableList.copyOf(associationSet())
+            : ImmutableList.<RouteTableAssociation>of());
+         tags(tags() != null ? ImmutableMap.copyOf(tags()) : ImmutableMap.<String, String>of());
+         return autoBuild();
+      }
+
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/domain/RouteTableAssociation.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/domain/RouteTableAssociation.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/domain/RouteTableAssociation.java
new file mode 100644
index 0000000..2b25afa
--- /dev/null
+++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/domain/RouteTableAssociation.java
@@ -0,0 +1,71 @@
+/*
+ * 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.domain;
+
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+
+/**
+ * An association of a route to a subnet.
+ *
+ * @see <a href="http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RouteTableAssociation.html">AWS docs</a>
+ */
+@AutoValue
+public abstract class RouteTableAssociation {
+
+
+   @Nullable
+   public abstract String id();
+
+   @Nullable
+   public abstract String routeTableId();
+
+   @Nullable
+   public abstract String subnetId();
+
+   @Nullable
+   public abstract Boolean main();
+
+   @SerializedNames({"routeTableAssociationId", "routeTableId", "subnetId", "main"})
+   public static RouteTableAssociation create(String id, String routeTableId, String subnetId, Boolean main) {
+      return builder()
+         .id(id)
+         .routeTableId(routeTableId)
+         .subnetId(subnetId)
+         .main(main)
+         .build();
+   }
+
+   RouteTableAssociation() {}
+
+   public static Builder builder() {
+      return new AutoValue_RouteTableAssociation.Builder();
+   }
+
+   @AutoValue.Builder
+   public abstract static class Builder {
+      public abstract Builder id(String id);
+      public abstract Builder routeTableId(String routeTableId);
+      public abstract Builder subnetId(String subnetId);
+      public abstract Builder main(Boolean main);
+
+      public abstract RouteTableAssociation build();
+
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/features/RouteTableApi.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/features/RouteTableApi.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/features/RouteTableApi.java
new file mode 100644
index 0000000..3190e60
--- /dev/null
+++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/features/RouteTableApi.java
@@ -0,0 +1,279 @@
+/*
+ * 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.aws.reference.FormParameters.ACTION;
+
+import javax.inject.Named;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+
+import org.jclouds.Fallbacks;
+import org.jclouds.aws.ec2.binders.BindRouteTableIdsToIndexedFormParams;
+import org.jclouds.aws.ec2.domain.RouteTable;
+import org.jclouds.aws.ec2.options.RouteOptions;
+import org.jclouds.aws.ec2.options.RouteTableOptions;
+import org.jclouds.aws.ec2.xml.AssociateRouteTableResponseHandler;
+import org.jclouds.aws.ec2.xml.CreateRouteTableResponseHandler;
+import org.jclouds.aws.ec2.xml.DescribeRouteTablesResponseHandler;
+import org.jclouds.aws.ec2.xml.ReturnValueHandler;
+import org.jclouds.aws.filters.FormSigner;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.location.functions.RegionToEndpointOrProviderIfNull;
+import org.jclouds.rest.annotations.BinderParam;
+import org.jclouds.rest.annotations.EndpointParam;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.FormParams;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.VirtualHost;
+import org.jclouds.rest.annotations.XMLResponseParser;
+
+import com.google.common.collect.FluentIterable;
+
+/**
+ * Provides access to AWS Route Table services.
+ *
+ * @see <a href="http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RouteTable.html">RouteTable docs</a>
+ */
+@RequestFilters(FormSigner.class)
+@VirtualHost
+@Path("/")
+public interface RouteTableApi {
+
+   /**
+    * Creates a {@link RouteTable}
+    *
+    * @param region The region to create the table in.
+    * @param vpcId The ID of the VPC
+    * @return The route table
+    */
+   @Named("CreateRouteTable")
+   @POST
+   @FormParams(keys = ACTION, values = "CreateRouteTable")
+   @XMLResponseParser(CreateRouteTableResponseHandler.class)
+   RouteTable createRouteTable(
+      @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region,
+      @FormParam("VpcId") String vpcId);
+
+   /**
+    * Creates a {@link RouteTable}, supplying options.
+    *
+    * @param region  The region to create the table in
+    * @param vpcId The ID of the VPC
+    * @param options Options for the request
+    * @return The route table
+    */
+   @Named("CreateRouteTable")
+   @POST
+   @FormParams(keys = ACTION, values = "CreateRouteTable")
+   @XMLResponseParser(CreateRouteTableResponseHandler.class)
+   RouteTable createRouteTable(
+      @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region,
+      @FormParam("VpcId") String vpcId,
+      RouteTableOptions options);
+
+   /**
+    * Deletes a {@link RouteTable}
+    *
+    * @param region The region to delete the table from
+    * @param routeTableId The ID of the table to delete
+    * @return true if the route table was found and deleted
+    */
+   @Named("DeleteRouteTable")
+   @POST
+   @FormParams(keys = ACTION, values = "DeleteRouteTable")
+   @XMLResponseParser(ReturnValueHandler.class)
+   @Fallback(Fallbacks.FalseOnNotFoundOr404.class)
+   boolean deleteRouteTable(
+      @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region,
+      @FormParam("RouteTableId") String routeTableId);
+
+   /**
+    * Delete a {@link RouteTable}, supplying options.
+    *
+    * @param region  The region to delete the table from
+    * @param routeTableId The ID of the table to delete
+    * @param options Options for the request
+    * @return true if the route table was found and deleted
+    */
+   @Named("DeleteRouteTable")
+   @POST
+   @FormParams(keys = ACTION, values = "DeleteRouteTable")
+   @XMLResponseParser(ReturnValueHandler.class)
+   @Fallback(Fallbacks.FalseOnNotFoundOr404.class)
+   boolean deleteRouteTable(
+      @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region,
+      @FormParam("RouteTableId") String routeTableId,
+      RouteTableOptions options);
+
+   /**
+    * Associates a subnet with a route table. The subnet and route table must be in the same VPC.
+    * This association causes traffic originating from the subnet to be routed according to the routes in the route table.
+    * The action returns an association ID, which you need in order to disassociate the route table from the subnet later.
+    * A route table can be associated with multiple subnets.
+    *
+    * @param region Region of the VPC for the route table
+    * @param routeTableId ID of the route table
+    * @param subnetId ID of the subnet to associate
+    *
+    * @return The association ID which you need in order to disassociate the route table from the subnet later.
+    */
+   @Named("AssociateRouteTable")
+   @POST
+   @FormParams(keys = ACTION, values = "AssociateRouteTable")
+   @XMLResponseParser(AssociateRouteTableResponseHandler.class)
+   @Fallback(Fallbacks.NullOnNotFoundOr404.class)
+   String associateRouteTable(
+      @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region,
+      @FormParam("RouteTableId") String routeTableId,
+      @FormParam("SubnetId") String subnetId);
+
+   /**
+    * @see #associateRouteTable(java.lang.String, java.lang.String, java.lang.String)
+    *
+    * @param region Region of the VPC for the route table
+    * @param routeTableId ID of the route table
+    * @param subnetId ID of the subnet to associate
+    * @param options Options for the request
+    *
+    * @return The association ID which you need in order to disassociate the route table from the subnet later.
+    */
+   @Named("AssociateRouteTable")
+   @POST
+   @FormParams(keys = ACTION, values = "AssociateRouteTable")
+   @XMLResponseParser(AssociateRouteTableResponseHandler.class)
+   @Fallback(Fallbacks.NullOnNotFoundOr404.class)
+   String associateRouteTable(
+      @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region,
+      @FormParam("RouteTableId") String routeTableId,
+      @FormParam("SubnetId") String subnetId,
+      RouteTableOptions options);
+
+   /**
+    * Disassociates a subnet from a route table.
+    * After you perform this action, the subnet no longer uses the routes in the route table.
+    * Instead, it uses the routes in the VPC's main route table.
+    * @param region Region of the route table
+    * @param associationId association id returned by {@link #associateRouteTable(String, String, String)}
+    * @return true if the subnet was found and disassociated.
+    */
+   @Named("DisassociateRouteTable")
+   @POST
+   @FormParams(keys = ACTION, values = "DisassociateRouteTable")
+   @XMLResponseParser(ReturnValueHandler.class)
+   @Fallback(Fallbacks.FalseOnNotFoundOr404.class)
+   boolean disassociateRouteTable(
+      @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region,
+      @FormParam("AssociationId") String associationId);
+
+   /**
+    * @see #disassociateRouteTable(String, String)
+    * @param region Region of the route table
+    * @param associationId association id returned by {@link #associateRouteTable(String, String, String)}
+    * @param options Options for the request
+    * @return true if the subnet was found and disassociated.
+    */
+   @Named("DisassociateRouteTable")
+   @POST
+   @FormParams(keys = ACTION, values = "DisassociateRouteTable")
+   @XMLResponseParser(ReturnValueHandler.class)
+   @Fallback(Fallbacks.FalseOnNotFoundOr404.class)
+   boolean disassociateRouteTable(
+      @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region,
+      @FormParam("AssociationId") String associationId,
+      RouteTableOptions options);
+
+   /**
+    * Creates a route in a route table within a VPC.
+    *
+    * @param region region of the VPC
+    * @param routeTableId ID of the route table to put the route in
+    * @param options You must specify one of the following targets: Internet gateway or virtual
+    *                private gateway, NAT instance, NAT gateway, VPC peering connection,
+    *                network interface, or egress-only Internet gateway.
+    * @return true if the route was created
+    */
+   @Named("CreateRoute")
+   @POST
+   @FormParams(keys = ACTION, values = "CreateRoute")
+   @XMLResponseParser(ReturnValueHandler.class)
+   @Fallback(Fallbacks.FalseOnNotFoundOr404.class)
+   boolean createRoute(
+      @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region,
+      @FormParam("RouteTableId") String routeTableId,
+      RouteOptions options);
+
+   /**
+    * Replaces a route in a route table within a VPC.
+    *
+    * @param region region of the VPC
+    * @param routeTableId ID of the route table containing the route to replace
+    * @param options You must specify only one of the following targets: Internet gateway or virtual
+    *                private gateway, NAT instance, NAT gateway, VPC peering connection,
+    *                network interface, or egress-only Internet gateway.
+    * @return true if the route was found and replaced
+    */
+   @Named("ReplaceRoute")
+   @POST
+   @FormParams(keys = ACTION, values = "ReplaceRoute")
+   @XMLResponseParser(ReturnValueHandler.class)
+   @Fallback(Fallbacks.FalseOnNotFoundOr404.class)
+   boolean replaceRoute(
+      @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region,
+      @FormParam("RouteTableId") String routeTableId,
+      RouteOptions options);
+
+   /**
+    * Delete a route from a route table.
+    *
+    * @param region region of the VPC
+    * @param routeTableId ID of the route table owning the route
+    * @param options This should include the destination CIDR block of the route to delete
+    *
+    * @return true if the route was found and deleted
+    *
+    * <p>
+    * <b>Example:</b>
+    * <pre>
+    *    api.deleteRoute(region, routeTable.id(), destinationCidrBlock("10.20.30.0/24"))
+    * </pre>
+    * </p>
+    */
+   @Named("DeleteRoute")
+   @POST
+   @FormParams(keys = ACTION, values = "DeleteRoute")
+   @XMLResponseParser(ReturnValueHandler.class)
+   @Fallback(Fallbacks.FalseOnNotFoundOr404.class)
+   boolean deleteRoute(
+      @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region,
+      @FormParam("RouteTableId") String routeTableId,
+      RouteOptions options);
+
+   /**
+    * Describes route tables.
+    * @param region The region to search for route tables.
+    */
+   @Named("DescribeRouteTables")
+   @POST
+   @FormParams(keys = ACTION, values = "DescribeRouteTables")
+   @XMLResponseParser(DescribeRouteTablesResponseHandler.class)
+   @Fallback(Fallbacks.EmptyFluentIterableOnNotFoundOr404.class)
+   FluentIterable<RouteTable> describeRouteTables(
+      @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region,
+      @BinderParam(BindRouteTableIdsToIndexedFormParams.class) String... routeTableIds);
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/InternetGatewayOptions.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/InternetGatewayOptions.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/InternetGatewayOptions.java
index 6449ae4..cff7311 100644
--- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/InternetGatewayOptions.java
+++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/InternetGatewayOptions.java
@@ -28,7 +28,7 @@ import org.jclouds.ec2.options.internal.BaseEC2RequestOptions;
  * import static org.jclouds.ec2.options.InternetGatewayOptions.Builder.*
  * <p/>
  * EC2Api connection = // get connection
- * Future<Set<ImageMetadata>> images =
+ * InternetGateway gw =
  * connection.getInternetGatewayApi().get().createInternetGateway(region, dryRun());
  * <code>
  *
@@ -41,7 +41,8 @@ public class InternetGatewayOptions extends BaseEC2RequestOptions {
    public static final InternetGatewayOptions NONE = new InternetGatewayOptions();
 
    /**
-    * Checks whether you have the required permissions for the action, without actually making the request, and provides an error response.
+    * Checks whether you have the required permissions for the action, without actually making the request,
+    * and provides an error response.
     */
    public InternetGatewayOptions dryRun() {
       formParameters.put("DryRun", "true");

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/RouteOptions.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/RouteOptions.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/RouteOptions.java
new file mode 100644
index 0000000..751bb29
--- /dev/null
+++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/RouteOptions.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.options;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.jclouds.ec2.options.internal.BaseEC2RequestOptions;
+
+/**
+ * Contains options supported in the Form API for the Route operations. <h2>
+ * Usage</h2> The recommended way to instantiate such an object is to statically import
+ * RouteOptions.Builder.* and invoke a static creation method followed by an instance mutator
+ * (if needed):
+ * <p/>
+ * <code>
+ * import static org.jclouds.ec2.options.RouteOptions.Builder.*
+ * <p/>
+ * EC2Api connection = // get connection
+ * Route r = connection.getRouteTableApi().get()
+ *   .createRoute(region, routeTableId, gatewayId("igw-97e68af3").destinationCidrBlock("172.18.19.0/24"));
+ * <code>
+ *
+ * @see <a
+ * href="http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CreateRoute.html"
+ * />
+ */
+public class RouteOptions extends BaseEC2RequestOptions {
+
+   /**
+    * Checks whether you have the required permissions for the action, without actually making the request,
+    * and provides an error response.
+    */
+   public RouteOptions dryRun() {
+      formParameters.put("DryRun", "true");
+      return this;
+   }
+
+   public boolean isDryRun() {
+      return getFirstFormOrNull("DryRun") != null;
+   }
+
+   /**
+    * The IPv4 CIDR address block used for the destination match.
+    * Routing decisions are based on the most specific match.
+    */
+   public RouteOptions destinationCidrBlock(String destinationCidrBlock) {
+      formParameters.put("DestinationCidrBlock", checkNotNull(destinationCidrBlock, "destinationCidrBlock"));
+      return this;
+   }
+
+   /**
+    * @see RouteOptions#destinationCidrBlock(java.lang.String)
+    */
+   public String getDestinationCidrBlock() {
+      return getFirstFormOrNull("DestinationCidrBlock");
+   }
+
+   /**
+    * The IPv6 CIDR block used for the destination match. Routing decisions are based on the most specific match.
+    */
+   public RouteOptions destinationIpv6CidrBlock(String destinationIpv6CidrBlock) {
+      formParameters.put("DestinationIpv6CidrBlock", checkNotNull(destinationIpv6CidrBlock, "destinationIpv6CidrBlock"));
+      return this;
+   }
+
+   /**
+    * @see RouteOptions#destinationIpv6CidrBlock(java.lang.String)
+    */
+   public String getDestinationIpv6CidrBlock() {
+      return getFirstFormOrNull("DestinationIpv6CidrBlock");
+   }
+
+   /**
+    * The ID of an Internet gateway or virtual private gateway attached to your VPC.
+    */
+   public RouteOptions gatewayId(String gatewayId) {
+      formParameters.put("GatewayId", checkNotNull(gatewayId, "gatewayId"));
+      return this;
+   }
+
+   /**
+    * @see RouteOptions#gatewayId(java.lang.String)
+    */
+   public String getGatewayId() {
+      return getFirstFormOrNull("GatewayId");
+   }
+
+   /**
+    * [IPv6 traffic only] The ID of an egress-only Internet gateway.
+    */
+   public RouteOptions egressOnlyInternetGatewayId(String egressOnlyInternetGatewayId) {
+      formParameters.put("EgressOnlyInternetGatewayId",
+         checkNotNull(egressOnlyInternetGatewayId, "egressOnlyInternetGatewayId"));
+      return this;
+   }
+
+   /**
+    * @see RouteOptions#egressOnlyInternetGatewayId(java.lang.String)
+    */
+   public String getEgressOnlyInternetGatewayId() {
+      return getFirstFormOrNull("EgressOnlyInternetGatewayId");
+   }
+
+   /**
+    * [IPv4 traffic only] The ID of a NAT gateway.
+    */
+   public RouteOptions natGatewayId(String natGatewayId) {
+      formParameters.put("NatGatewayId", checkNotNull(natGatewayId, "natGatewayId"));
+      return this;
+   }
+
+   /**
+    * @see RouteOptions#natGatewayId(String)
+    */
+   public String getNatGatewayId() {
+      return getFirstFormOrNull("NatGatewayId");
+   }
+
+   /**
+    * The ID of a network interface.
+    */
+   public RouteOptions networkInterfaceId(String networkInterfaceId) {
+      formParameters.put("NetworkInterfaceId", checkNotNull(networkInterfaceId, "networkInterfaceId"));
+      return this;
+   }
+
+   /**
+    * @see RouteOptions#networkInterfaceId(String)
+    */
+   public String getNetworkInterfaceId() {
+      return getFirstFormOrNull("NetworkInterfaceId");
+   }
+
+   /**
+    * The ID of a NAT instance in your VPC. The operation fails if you specify an instance ID unless
+    * exactly one network interface is attached.
+    */
+   public RouteOptions instanceId(String instanceId) {
+      formParameters.put("InstanceId", checkNotNull(instanceId, "instanceId"));
+      return this;
+   }
+
+   /**
+    * @see RouteOptions#instanceId(String)
+    */
+   public String getInstanceId() {
+      return getFirstFormOrNull("InstanceId");
+   }
+
+   /**
+    * The ID of a VPC peering connection.
+    */
+   public RouteOptions vpcPeeringConnectionId(String vpcPeeringConnectionId) {
+      formParameters.put("VpcPeeringConnectionId", checkNotNull(vpcPeeringConnectionId, "vpcPeeringConnectionId"));
+      return this;
+   }
+
+   /**
+    * @see RouteOptions#vpcPeeringConnectionId(String)
+    */
+   public String getVpcPeeringConnectionId() {
+      return getFirstFormOrNull("VpcPeeringConnectionId");
+   }
+
+
+   public static class Builder {
+      /**
+       * @see RouteOptions#dryRun()
+       */
+      public static RouteOptions dryRun() {
+         RouteOptions options = new RouteOptions();
+         return options.dryRun();
+      }
+
+      /**
+       * @see RouteOptions#destinationCidrBlock(java.lang.String)
+       */
+      public static RouteOptions destinationCidrBlock(String destinationCidrBlock) {
+         RouteOptions options = new RouteOptions();
+         return options.destinationCidrBlock(destinationCidrBlock);
+      }
+
+      /**
+       * @see RouteOptions#destinationIpv6CidrBlock(java.lang.String)
+       */
+      public static RouteOptions destinationIpv6CidrBlock(String destinationIpv6CidrBlock) {
+         RouteOptions options = new RouteOptions();
+         return options.destinationIpv6CidrBlock(destinationIpv6CidrBlock);
+      }
+
+      /**
+       * @see RouteOptions#gatewayId(java.lang.String)
+       */
+      public static RouteOptions gatewayId(String gatewayId) {
+         RouteOptions options = new RouteOptions();
+         return options.gatewayId(gatewayId);
+      }
+
+      /**
+       * @see RouteOptions#egressOnlyInternetGatewayId(java.lang.String)
+       */
+      public static RouteOptions egressOnlyInternetGatewayId(String egressOnlyInternetGatewayId) {
+         RouteOptions options = new RouteOptions();
+         return options.egressOnlyInternetGatewayId(egressOnlyInternetGatewayId);
+      }
+
+      /**
+       * @see RouteOptions#natGatewayId(String)
+       */
+      public static RouteOptions natGatewayId(String natGatewayId) {
+         RouteOptions options = new RouteOptions();
+         return options.natGatewayId(natGatewayId);
+      }
+
+      /**
+       * @see RouteOptions#networkInterfaceId(String)
+       */
+      public static RouteOptions networkInterfaceId(String networkInterfaceId) {
+         RouteOptions options = new RouteOptions();
+         return options.networkInterfaceId(networkInterfaceId);
+      }
+
+      /**
+       * @see RouteOptions#vpcPeeringConnectionId(String)
+       */
+      public static RouteOptions vpcPeeringConnectionId(String vpcPeeringConnectionId) {
+         RouteOptions options = new RouteOptions();
+         return options.vpcPeeringConnectionId(vpcPeeringConnectionId);
+      }
+
+      /**
+       * @see RouteOptions#instanceId(String)
+       */
+      public static RouteOptions instanceId(String instanceId) {
+         RouteOptions options = new RouteOptions();
+         return options.instanceId(instanceId);
+      }
+
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/RouteTableOptions.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/RouteTableOptions.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/RouteTableOptions.java
new file mode 100644
index 0000000..4960d4e
--- /dev/null
+++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/options/RouteTableOptions.java
@@ -0,0 +1,75 @@
+/*
+ * 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.options;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.jclouds.ec2.options.internal.BaseEC2RequestOptions;
+
+/**
+ * Contains options supported in the Form API for the RouteTable operations. <h2>
+ * Usage</h2> The recommended way to instantiate such an object is to statically import
+ * RouteTableOptions.Builder.* and invoke a static creation method followed by an instance mutator
+ * (if needed):
+ * <p/>
+ * <code>
+ * import static org.jclouds.ec2.options.RouteTableOptions.Builder.*
+ * <p/>
+ * EC2Api connection = // get connection
+ * RouteTable table = connection.getRouteTableApi().get().createRouteTable(vpcId, dryRun());
+ * <code>
+ *
+ * @see <a
+ * href="http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CreateRouteTable.html"
+ * />
+ */
+public class RouteTableOptions extends BaseEC2RequestOptions {
+
+   /**
+    * Checks whether you have the required permissions for the action, without actually making the request,
+    * and provides an error response.
+    */
+   public RouteTableOptions dryRun() {
+      formParameters.put("DryRun", "true");
+      return this;
+   }
+
+   public boolean isDryRun() {
+      return getFirstFormOrNull("DryRun") != null;
+   }
+
+   /**
+    * The IPv4 CIDR address block used for the destination match.
+    * Routing decisions are based on the most specific match.
+    */
+   public RouteTableOptions destinationCidrBlock(String destinationCidrBlock) {
+      formParameters.put("DestinationCidrBlock", checkNotNull(destinationCidrBlock, "destinationCidrBlock"));
+      return this;
+   }
+
+
+   public static class Builder {
+      /**
+       * @see RouteTableOptions#dryRun()
+       */
+      public static RouteTableOptions dryRun() {
+         RouteTableOptions options = new RouteTableOptions();
+         return options.dryRun();
+      }
+
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/AssociateRouteTableResponseHandler.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/AssociateRouteTableResponseHandler.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/AssociateRouteTableResponseHandler.java
new file mode 100644
index 0000000..fa104fb
--- /dev/null
+++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/AssociateRouteTableResponseHandler.java
@@ -0,0 +1,40 @@
+/*
+ * 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.xml;
+
+import org.jclouds.http.functions.ParseSax;
+
+public class AssociateRouteTableResponseHandler extends ParseSax.HandlerWithResult<String> {
+
+   private StringBuilder currentText = new StringBuilder();
+   private String value;
+
+   public String getResult() {
+      return value;
+   }
+
+   public void endElement(String uri, String name, String qName) {
+      if (qName.equalsIgnoreCase("associationId")) {
+         this.value = currentText.toString().trim();
+      }
+      currentText.setLength(0);
+   }
+
+   public void characters(char[] ch, int start, int length) {
+      currentText.append(ch, start, length);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/CreateRouteTableResponseHandler.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/CreateRouteTableResponseHandler.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/CreateRouteTableResponseHandler.java
new file mode 100644
index 0000000..eacb4f2
--- /dev/null
+++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/CreateRouteTableResponseHandler.java
@@ -0,0 +1,55 @@
+/*
+ * 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.xml;
+
+import javax.inject.Inject;
+
+import org.jclouds.aws.ec2.domain.RouteTable;
+import org.jclouds.http.functions.ParseSax;
+import org.xml.sax.Attributes;
+
+/**
+ * @see <a href="http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RouteTable.html">RouteTable docs</a>
+ */
+public class CreateRouteTableResponseHandler extends ParseSax.HandlerForGeneratedRequestWithResult<RouteTable> {
+
+   private RouteTableHandler routeTableHandler;
+
+   @Inject
+   CreateRouteTableResponseHandler(RouteTableHandler routeTableHandler) {
+      this.routeTableHandler = routeTableHandler;
+   }
+
+   public RouteTable getResult() {
+      return routeTableHandler.getResult();
+   }
+
+   @Override
+   public void startElement(String uri, String name, String qName, Attributes attrs) {
+      routeTableHandler.startElement(uri, name, qName, attrs);
+   }
+
+   @Override
+   public void endElement(String uri, String name, String qName) {
+      routeTableHandler.endElement(uri, name, qName);
+   }
+
+   @Override
+   public void characters(char[] ch, int start, int length) {
+      routeTableHandler.characters(ch, start, length);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/DescribeRouteTablesResponseHandler.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/DescribeRouteTablesResponseHandler.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/DescribeRouteTablesResponseHandler.java
new file mode 100644
index 0000000..0146f67
--- /dev/null
+++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/DescribeRouteTablesResponseHandler.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.xml;
+
+import static org.jclouds.util.SaxUtils.equalsOrSuffix;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.jclouds.aws.ec2.domain.RouteTable;
+import org.jclouds.http.functions.ParseSax;
+import org.xml.sax.Attributes;
+
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Lists;
+
+/**
+ * @see <a href="http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RouteTable.html">RouteTable docs</a>
+ */
+public class DescribeRouteTablesResponseHandler
+   extends ParseSax.HandlerForGeneratedRequestWithResult<FluentIterable<RouteTable>> {
+
+   private RouteTableHandler routeTableHandler;
+   private List<RouteTable> tables = Lists.newArrayList();
+
+   private boolean inRouteSet;
+   private boolean inAssociationSet;
+   private boolean inPropagatingVgwSet;
+   private boolean inTagSet;
+
+   @Inject
+   DescribeRouteTablesResponseHandler(RouteTableHandler routeTableHandler) {
+      this.routeTableHandler = routeTableHandler;
+   }
+
+   public FluentIterable<RouteTable> getResult() {
+      try {
+         return FluentIterable.from(tables);
+      } finally {
+         tables = Lists.newArrayList();
+      }
+   }
+
+   @Override
+   public void startElement(String uri, String name, String qName, Attributes attrs) {
+      if (equalsOrSuffix(qName, "routeSet")) {
+         inRouteSet = true;
+      } else if (equalsOrSuffix(qName, "associationSet")) {
+         inAssociationSet = true;
+      } else if (equalsOrSuffix(qName, "tagSet")) {
+         inTagSet = true;
+      } else if (equalsOrSuffix(qName, "propagatingVgwSet")) {
+         inPropagatingVgwSet = true;
+      }
+      routeTableHandler.startElement(uri, name, qName, attrs);
+   }
+
+   private boolean inSubElement() {
+      return inRouteSet || inTagSet || inAssociationSet || inPropagatingVgwSet;
+   }
+
+   @Override
+   public void endElement(String uri, String name, String qName) {
+      if (equalsOrSuffix(qName, "routeSet")) {
+         inRouteSet = false;
+         routeTableHandler.endElement(uri, name, qName);
+      } else if (equalsOrSuffix(qName, "associationSet")) {
+         inAssociationSet = false;
+         routeTableHandler.endElement(uri, name, qName);
+      } else if (equalsOrSuffix(qName, "tagSet")) {
+         inTagSet = false;
+         routeTableHandler.endElement(uri, name, qName);
+      } else if (equalsOrSuffix(qName, "item") && !inSubElement()) {
+         final RouteTable table = routeTableHandler.getResult();
+         tables.add(table);
+      } else {
+         routeTableHandler.endElement(uri, name, qName);
+      }
+   }
+
+   @Override
+   public void characters(char[] ch, int start, int length) {
+      routeTableHandler.characters(ch, start, length);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/RouteHandler.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/RouteHandler.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/RouteHandler.java
new file mode 100644
index 0000000..2956cff
--- /dev/null
+++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/RouteHandler.java
@@ -0,0 +1,48 @@
+/*
+ * 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.xml;
+
+import org.jclouds.aws.ec2.domain.Route;
+import org.jclouds.http.functions.ParseSax;
+import org.xml.sax.Attributes;
+
+public class RouteHandler extends ParseSax.HandlerForGeneratedRequestWithResult<Route> {
+
+   private StringBuilder currentText = new StringBuilder();
+
+   Route.Builder builder = Route.builder();
+
+   @Override
+   public void startElement(String uri, String localName, String qName, Attributes attributes) {
+
+   }
+
+   @Override
+   public void endElement(String uri, String localName, String qName)  {
+
+   }
+
+   @Override
+   public void characters(char[] ch, int start, int length) {
+      currentText.append(ch, start, length);
+   }
+
+   @Override
+   public Route getResult() {
+      return builder.build();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/RouteSetHandler.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/RouteSetHandler.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/RouteSetHandler.java
new file mode 100644
index 0000000..d5326c4
--- /dev/null
+++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/RouteSetHandler.java
@@ -0,0 +1,77 @@
+/*
+ * 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.xml;
+
+import static org.jclouds.util.SaxUtils.equalsOrSuffix;
+
+import java.util.List;
+
+import org.jclouds.aws.ec2.domain.Route;
+import org.jclouds.aws.ec2.domain.Route.RouteState;
+import org.jclouds.http.functions.ParseSax;
+import org.xml.sax.Attributes;
+
+import com.google.common.collect.Lists;
+
+public class RouteSetHandler extends ParseSax.HandlerForGeneratedRequestWithResult<List<Route>> {
+
+   private StringBuilder currentText = new StringBuilder();
+   List<Route> results = Lists.newArrayList();
+
+   Route.Builder builder;
+
+   @Override
+   public List<Route> getResult() {
+      try {
+         return results;
+      } finally {
+         results = Lists.newArrayList();
+      }
+   }
+
+   @Override
+   public void startElement(String uri, String localName, String qName, Attributes attributes) {
+      currentText.setLength(0);
+      if (qName.equalsIgnoreCase("item")) {
+         builder = Route.builder();
+      }
+   }
+
+   @Override
+   public void endElement(String uri, String name, String qName) {
+      if (builder == null) {
+         return;
+      }
+      if (equalsOrSuffix(qName, "item")) {
+         results.add(builder.build());
+         builder = null;
+      } else if (equalsOrSuffix(qName, "destinationCidrBlock")) {
+         builder.destinationCidrBlock(currentText.toString());
+      } else if (equalsOrSuffix(qName, "gatewayId")) {
+         builder.gatewayId(currentText.toString());
+      } else if (equalsOrSuffix(qName, "state")) {
+         builder.state(RouteState.fromValue(currentText.toString()));
+      } else if (equalsOrSuffix(qName, "origin")) {
+         builder.origin(currentText.toString());
+      }
+   }
+
+   @Override
+   public void characters(char[] ch, int start, int length) {
+      currentText.append(ch, start, length);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/RouteTableAssociationSetHandler.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/RouteTableAssociationSetHandler.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/RouteTableAssociationSetHandler.java
new file mode 100644
index 0000000..4a355d7
--- /dev/null
+++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/RouteTableAssociationSetHandler.java
@@ -0,0 +1,79 @@
+/*
+ * 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.xml;
+
+import static org.jclouds.util.SaxUtils.equalsOrSuffix;
+
+import java.util.List;
+
+import org.jclouds.aws.ec2.domain.RouteTableAssociation;
+import org.jclouds.http.functions.ParseSax;
+import org.xml.sax.Attributes;
+
+import com.google.common.collect.Lists;
+
+public class RouteTableAssociationSetHandler extends
+   ParseSax.HandlerForGeneratedRequestWithResult<List<RouteTableAssociation>> {
+
+   private StringBuilder currentText = new StringBuilder();
+   RouteTableAssociation.Builder builder;
+
+   List<RouteTableAssociation> results = Lists.newArrayList();
+
+   @Override
+   public List<RouteTableAssociation> getResult() {
+      try {
+         return results;
+      } finally {
+         results = Lists.newArrayList();
+      }
+   }
+
+
+   @Override
+   public void startElement(String uri, String localName, String qName, Attributes attributes) {
+      currentText.setLength(0);
+      if (qName.equalsIgnoreCase("item")) {
+         builder = RouteTableAssociation.builder();
+      }
+   }
+
+   @Override
+   public void endElement(String uri, String name, String qName) {
+      if (builder == null) {
+         return;
+      }
+      if (equalsOrSuffix(qName, "item")) {
+         results.add(builder.build());
+         builder = null;
+      } else if (equalsOrSuffix(qName, "routeTableAssociationId")) {
+         builder.id(currentText.toString());
+      } else if (equalsOrSuffix(qName, "routeTableId")) {
+         builder.routeTableId(currentText.toString());
+      } else if (equalsOrSuffix(qName, "subnetId")) {
+         builder.subnetId(currentText.toString());
+      } else if (equalsOrSuffix(qName, "main")) {
+         builder.main(Boolean.valueOf(currentText.toString()));
+      }
+   }
+
+   @Override
+   public void characters (char[] ch, int start, int length) {
+      currentText.append(ch, start, length);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/RouteTableHandler.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/RouteTableHandler.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/RouteTableHandler.java
new file mode 100644
index 0000000..733815d
--- /dev/null
+++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/RouteTableHandler.java
@@ -0,0 +1,116 @@
+/*
+ * 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.xml;
+
+import static org.jclouds.util.SaxUtils.equalsOrSuffix;
+
+import javax.inject.Inject;
+
+import org.jclouds.aws.ec2.domain.RouteTable;
+import org.jclouds.ec2.xml.TagSetHandler;
+import org.jclouds.http.functions.ParseSax;
+import org.xml.sax.Attributes;
+
+public class RouteTableHandler extends ParseSax.HandlerWithResult<RouteTable> {
+
+   RouteTable.Builder builder = RouteTable.builder();
+   private StringBuilder currentText = new StringBuilder();
+   private RouteSetHandler routeSetHandler;
+   private RouteTableAssociationSetHandler routeTableAssociationSetHandler;
+   private TagSetHandler tagSetHandler;
+   boolean inRouteSet;
+   boolean inRouteTableAssociationSet;
+   boolean inTagSet;
+   // TODO propagatingVgwSetHandler
+
+
+   @Inject
+   RouteTableHandler(TagSetHandler tagSetHandler, RouteSetHandler routeSetHandler,
+                     RouteTableAssociationSetHandler routeTableAssociationSetHandler) {
+      this.tagSetHandler = tagSetHandler;
+      this.routeSetHandler = routeSetHandler;
+      this.routeTableAssociationSetHandler = routeTableAssociationSetHandler;
+   }
+
+   @Override
+   public RouteTable getResult() {
+      try {
+         return builder.build();
+      } finally {
+         builder = RouteTable.builder();
+      }
+   }
+
+
+   @Override
+   public void startElement(String uri, String name, String qName, Attributes attrs) {
+      currentText.setLength(0);
+      if (equalsOrSuffix(qName, "routeSet")) {
+         inRouteSet = true;
+      } else if (equalsOrSuffix(qName, "associationSet")) {
+         inRouteTableAssociationSet = true;
+      } else if (equalsOrSuffix(qName, "tagSet")) {
+         inTagSet = true;
+      }
+
+      if (inTagSet) {
+         tagSetHandler.startElement(uri, name, qName, attrs);
+      } else if (inRouteTableAssociationSet) {
+         routeTableAssociationSetHandler.startElement(uri, name, qName, attrs);
+      } else if (inRouteSet) {
+         routeSetHandler.startElement(uri, name, qName, attrs);
+      }
+   }
+
+   @Override
+   public void endElement(String uri, String name, String qName) {
+      if (equalsOrSuffix(qName, "tagSet")) {
+         inTagSet = false;
+         builder.tags(tagSetHandler.getResult());
+      } else if (equalsOrSuffix(qName, "routeSet")) {
+         inRouteSet = false;
+         builder.routeSet(routeSetHandler.getResult());
+      } else if (equalsOrSuffix(qName, "associationSet")) {
+         inRouteTableAssociationSet = false;
+         builder.associationSet(routeTableAssociationSetHandler.getResult());
+      } else if (inRouteSet) {
+         routeSetHandler.endElement(uri, name, qName);
+      } else if (inRouteTableAssociationSet) {
+         routeTableAssociationSetHandler.endElement(uri, name, qName);
+      } else if (inTagSet) {
+         tagSetHandler.endElement(uri, name, qName);
+      } else if (equalsOrSuffix(qName, "vpcId")) {
+         builder.vpcId(currentText.toString());
+      } else if (equalsOrSuffix(qName, "routeTableId")) {
+         builder.id(currentText.toString());
+      }
+      currentText.setLength(0);
+   }
+
+   @Override
+   public void characters(char[] ch, int start, int length) {
+      if (inRouteSet) {
+         routeSetHandler.characters(ch, start, length);
+      } else if (inRouteTableAssociationSet) {
+         routeTableAssociationSetHandler.characters(ch, start, length);
+      } else if (inTagSet) {
+         tagSetHandler.characters(ch, start, length);
+      } else {
+         currentText.append(ch, start, length);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/InternetGatewayApiLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/InternetGatewayApiLiveTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/InternetGatewayApiLiveTest.java
index c44acf4..e2b2d5f 100644
--- a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/InternetGatewayApiLiveTest.java
+++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/InternetGatewayApiLiveTest.java
@@ -36,6 +36,7 @@ import org.jclouds.aws.ec2.domain.VPC;
 import org.jclouds.aws.ec2.options.CreateVpcOptions;
 import org.jclouds.aws.ec2.options.InternetGatewayOptions;
 import org.jclouds.ec2.features.TagApi;
+import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
@@ -49,7 +50,9 @@ import com.google.common.collect.ImmutableMap;
 @Test(groups = "live")
 public class InternetGatewayApiLiveTest extends BaseApiLiveTest<AWSEC2Api> {
 
-   private static final String TEST_REGION = "eu-west-1";
+   // Define -Djclouds.test.region=whatever to test in your preferred region;
+   // defaults to null, jclouds will pick the provider's default region
+   private static final String TEST_REGION = System.getProperty("jclouds.test.region");
 
    public InternetGatewayApiLiveTest() {
       provider = "aws-ec2";
@@ -60,8 +63,8 @@ public class InternetGatewayApiLiveTest extends BaseApiLiveTest<AWSEC2Api> {
 
    private VPCApi vpcClient;
    private VPC vpc;
-
    private InternetGateway gateway;
+
    private String simpleName = InternetGatewayApiLiveTest.class.getSimpleName() + new Random().nextInt(10000);
 
    @BeforeClass(groups = {"integration", "live"})
@@ -149,6 +152,7 @@ public class InternetGatewayApiLiveTest extends BaseApiLiveTest<AWSEC2Api> {
 
       try {
          gwClient.createInternetGateway(TEST_REGION, dryRun());
+         Assert.fail("Operation completed when exception was expected");
       } catch (AWSResponseException e) {
          assertEquals(e.getError().getCode(), "DryRunOperation", "Expected DryRunOperation but got " + e.getError());
       }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/InternetGatewayApiMockTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/InternetGatewayApiMockTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/InternetGatewayApiMockTest.java
index 8c65702..ef39e93 100644
--- a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/InternetGatewayApiMockTest.java
+++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/InternetGatewayApiMockTest.java
@@ -16,6 +16,7 @@
  */
 package org.jclouds.aws.ec2.features;
 
+import static javax.ws.rs.core.Response.Status.PRECONDITION_FAILED;
 import static org.jclouds.aws.ec2.options.InternetGatewayOptions.Builder.dryRun;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
@@ -29,6 +30,7 @@ import org.jclouds.aws.ec2.domain.InternetGateway;
 import org.jclouds.aws.ec2.domain.InternetGatewayAttachment;
 import org.jclouds.aws.ec2.internal.BaseAWSEC2ApiMockTest;
 import org.jclouds.aws.ec2.options.InternetGatewayOptions;
+import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import com.google.common.collect.FluentIterable;
@@ -164,7 +166,6 @@ public class InternetGatewayApiMockTest extends BaseAWSEC2ApiMockTest {
 
       assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
       assertPosted(DEFAULT_REGION, "Action=DescribeInternetGateways");
-
    }
 
    public void deleteInternetGateway() throws Exception {
@@ -193,18 +194,19 @@ public class InternetGatewayApiMockTest extends BaseAWSEC2ApiMockTest {
    public void testWithOptions() throws Exception {
 
       enqueueRegions(DEFAULT_REGION);
-      enqueueXml(DEFAULT_REGION, "/create_internet_gateway_dry_run.xml");
+      enqueueXml(PRECONDITION_FAILED, DEFAULT_REGION, "/dry_run.xml");
 
       try {
          gatewayApi().createInternetGateway(DEFAULT_REGION, dryRun());
+         Assert.fail("Expected 'DryRunOperation' exception was not thrown");
       } catch (AWSResponseException e) {
          assertEquals(e.getError().getCode(), "DryRunOperation", "Expected DryRunOperation but got " + e.getError());
       }
 
       assertPosted(DEFAULT_REGION, "Action=DescribeRegions");
       assertPosted(DEFAULT_REGION, "Action=CreateInternetGateway&DryRun=true");
-
    }
+
    private InternetGatewayApi gatewayApi() {
       return api().getInternetGatewayApi().get();
    }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b3d21f96/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/RouteTableApiLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/RouteTableApiLiveTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/RouteTableApiLiveTest.java
new file mode 100644
index 0000000..3c57984
--- /dev/null
+++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/features/RouteTableApiLiveTest.java
@@ -0,0 +1,293 @@
+/*
+ * 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 java.util.logging.Logger.getAnonymousLogger;
+import static org.jclouds.aws.ec2.options.RouteOptions.Builder.destinationCidrBlock;
+import static org.jclouds.aws.ec2.options.RouteOptions.Builder.gatewayId;
+import static org.jclouds.aws.ec2.options.RouteTableOptions.Builder.dryRun;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.List;
+import java.util.Random;
+
+import org.jclouds.apis.BaseApiLiveTest;
+import org.jclouds.aws.AWSResponseException;
+import org.jclouds.aws.ec2.AWSEC2Api;
+import org.jclouds.aws.ec2.domain.InternetGateway;
+import org.jclouds.aws.ec2.domain.Route;
+import org.jclouds.aws.ec2.domain.RouteTable;
+import org.jclouds.aws.ec2.domain.VPC;
+import org.jclouds.aws.ec2.options.InternetGatewayOptions;
+import org.jclouds.ec2.domain.Subnet;
+import org.jclouds.ec2.features.TagApi;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
+/**
+ * Tests behavior of {@link RouteTableApi}
+ */
+@Test(groups = "live")
+public class RouteTableApiLiveTest extends BaseApiLiveTest<AWSEC2Api> {
+
+   // Define -Djclouds.test.region=whatever to test in your preferred region;
+   // defaults to null, jclouds will pick the provider's default region
+   public static final String TEST_REGION = System.getProperty("jclouds.test.region");
+   public static final String TEST_DESTINATION_CIDR = "172.18.19.0/24";
+   public static final String VPC_CIDR = "10.20.30.0/24";
+   public static final String VPC_SUBNET = "10.20.30.0/28";
+
+   public RouteTableApiLiveTest() {
+      provider = "aws-ec2";
+   }
+
+   private RouteTableApi routeTableApi;
+   private InternetGatewayApi gwApi;
+   private TagApi tagger;
+   private VPCApi vpcClient;
+   private AWSSubnetApi subnetApi;
+
+   private VPC vpc;
+   private InternetGateway gateway;
+
+   private RouteTable routeTable;
+   private String associationId;
+   private Subnet subnet;
+
+   private String simpleName = RouteTableApiLiveTest.class.getSimpleName() + new Random().nextInt(10000);
+
+   @BeforeClass(groups = {"integration", "live"})
+   public void setupContext() {
+      routeTableApi = api.getRouteTableApiForRegion(TEST_REGION).get();
+      vpcClient = api.getVPCApi().get();
+      tagger = api.getTagApiForRegion(TEST_REGION).get();
+      gwApi = api.getInternetGatewayApiForRegion(TEST_REGION).get();
+      subnetApi = api.getAWSSubnetApi().get();
+   }
+
+   @Test
+   public void testDescribe() {
+      vpc = vpcClient.createVpc(TEST_REGION, VPC_CIDR);
+      assertNotNull(vpc, "Failed to create VPC to test attachments");
+      tagger.applyToResources(ImmutableMap.of("Name", simpleName), ImmutableList.of(vpc.id()));
+
+      // When you create a VPC it automatically gets a route table whose single route has the CIDR of the VPC
+      // and whose "target" is "local".
+      final FluentIterable<RouteTable> routeTables = routeTableApi.describeRouteTables(TEST_REGION);
+      assertNotNull(routeTables, "Failed to return list of RouteTables");
+      Optional<RouteTable> vpcRT = Iterables.tryFind(routeTables, new Predicate<RouteTable>() {
+         @Override public boolean apply(RouteTable input) {
+            return vpc.id().equals(input.vpcId());
+         }
+      });
+      assertTrue(vpcRT.isPresent(), "Could not find VPC " + vpc.id() + " in described route tables");
+      RouteTable rt = vpcRT.get();
+      assertEquals(rt.associationSet().size(), 1,
+         "Route for test VPC has wrong number of associations, should be 1: " + rt.associationSet());
+      assertTrue(rt.associationSet().get(0).main(), "Association for route " + rt.id() + "should be 'main'");
+      assertEquals(rt.routeSet().size(), 1,
+         "Wrong number of routes in default route table for VPC " + vpc.id());
+      final String defaultCidr = rt.routeSet().get(0).destinationCidrBlock();
+      assertEquals(defaultCidr, vpc.cidrBlock(),
+         "Route in default route table does not match CIDR of VPC, " + defaultCidr + " should be " + vpc.cidrBlock());
+
+   }
+
+   @Test(dependsOnMethods = "testDescribe")
+   public void testCreate() {
+
+      // When you create a new route table for the VPC it automatically gets a route to match the VPC CIDR
+      routeTable = routeTableApi.createRouteTable(TEST_REGION, vpc.id());
+      assertNotNull(routeTable, "Gateway was not successfully created");
+
+      assertEquals(routeTable.vpcId(), vpc.id(),
+         "RouteTable VPC ID " + routeTable.vpcId() + " does not match VPC's ID " + vpc.id());
+      final List<Route> routes = routeTable.routeSet();
+      assertEquals(routes.size(), 1, "Unexpected number of routes in new table: " + routes.size());
+      assertEquals(routes.get(0).destinationCidrBlock(), vpc.cidrBlock(),
+         "CIDR for route table " + routes.get(0).destinationCidrBlock() +
+            " does not match VPC CIDR" + vpc.cidrBlock());
+      assertEquals(routes.get(0).state(), Route.RouteState.ACTIVE, "Route should be active");
+      assertEquals(routeTable.tags().size(), 0, "Freshly created routeTable has tags");
+
+      tagger.applyToResources(ImmutableMap.of("Name", simpleName), ImmutableList.of(routeTable.id()));
+      getAnonymousLogger().info("Created routeTable " +  simpleName + " with id " + routeTable.id());
+   }
+
+   @Test(dependsOnMethods = "testDescribe")
+   public void testCreateWithOptions() {
+
+     try {
+        routeTableApi.createRouteTable(TEST_REGION, vpc.id(), dryRun());
+        Assert.fail("Expected 'DryRunOperation' exception was not thrown");
+     } catch (AWSResponseException e) {
+        assertDryRun(e);
+     }
+   }
+
+   @Test(dependsOnMethods = "testCreate")
+   public void testAssociateWithOptions() {
+      subnet = subnetApi.createSubnetInRegion(TEST_REGION, vpc.id(), VPC_SUBNET);
+      assertNotNull(subnet, "Failed to create subnet in " + vpc.id());
+
+      try {
+         routeTableApi.associateRouteTable(TEST_REGION, routeTable.id(), subnet.getSubnetId(), dryRun());
+         Assert.fail("Expected 'DryRunOperation' exception was not thrown");
+      } catch (AWSResponseException e) {
+         assertDryRun(e);
+      }
+   }
+
+   @Test(dependsOnMethods = "testAssociateWithOptions")
+   public void testAssociate() {
+      associationId = routeTableApi.associateRouteTable(TEST_REGION, routeTable.id(), subnet.getSubnetId());
+      assertNotNull(associationId,
+         "Failed to obtain association id for " + routeTable.id() + " and " + subnet.getSubnetId());
+
+      routeTable = routeTableApi.describeRouteTables(TEST_REGION, routeTable.id()).toList().get(0);
+      assertEquals(routeTable.associationSet().size(), 1,
+         "Could not find expected association in routeTable " + routeTable.id());
+   }
+
+   @Test(dependsOnMethods = "testAssociate")
+   public void testDisassociateWithOptions() {
+      try {
+         routeTableApi.disassociateRouteTable(TEST_REGION, associationId, dryRun());
+         Assert.fail("Expected 'DryRunOperation' exception was not thrown");
+      } catch (AWSResponseException e) {
+         assertDryRun(e);
+      }
+   }
+
+   @Test(dependsOnMethods = "testDisassociateWithOptions")
+   public void testDisassociate() {
+      final boolean result = routeTableApi.disassociateRouteTable(TEST_REGION, associationId);
+      assertTrue(result, "Failed to disassociate " + associationId + " from " + routeTable.id());
+
+      routeTable = routeTableApi.describeRouteTables(TEST_REGION, routeTable.id()).toList().get(0);
+      assertEquals(routeTable.associationSet().size(), 0,
+         "Found associations where none should exist in  " + routeTable.id() + ": " + routeTable.associationSet());
+
+      subnetApi.deleteSubnetInRegion(TEST_REGION, subnet.getSubnetId());
+   }
+
+   @Test(dependsOnMethods = "testCreate")
+   public void testCreateRoute() {
+
+      // If you attach an Internet Gateway, Network Interface, or Virtual Private Gateway to the VPC
+      // you can then add a route through it to the route table. Issue a CreateRoute request specifying
+      // the gateway (or network interface id etc.) to route through, and supplying the CIDR range that should
+      // be routed through it. This can be any CIDR.
+
+      gateway = gwApi.createInternetGateway(TEST_REGION, InternetGatewayOptions.NONE);
+      assertNotNull(gateway, "Gateway was not successfully created");
+
+      final Boolean attached = gwApi.attachInternetGateway(TEST_REGION, gateway.id(), vpc.id());
+      assertTrue(attached, "Gateway " + gateway.id() + " failed to attach to VPC " + vpc.id());
+
+      final boolean created = routeTableApi.createRoute(TEST_REGION, routeTable.id(),
+         gatewayId(gateway.id())
+            .destinationCidrBlock(TEST_DESTINATION_CIDR));
+      assertTrue(created, "Failed to add route to table " + routeTable.id());
+
+      final ImmutableList<RouteTable> routeTables =
+         routeTableApi.describeRouteTables(TEST_REGION, routeTable.id()).toList();
+      assertEquals(routeTables.size(), 1, "Could not find existing route table " + routeTable.id());
+      Optional<Route> optRoute = Iterables.tryFind(routeTables.get(0).routeSet(), new Predicate<Route>() {
+         @Override
+         public boolean apply(Route route) {
+            return route.gatewayId().equals(gateway.id());
+         }
+      });
+      assertTrue(optRoute.isPresent(), "Could not find route added to gateway " + gateway.id());
+      Route route = optRoute.get();
+      assertEquals(route.destinationCidrBlock(), TEST_DESTINATION_CIDR,
+         "CIDR routed through " + gateway.id() + " does not match specification "  + TEST_DESTINATION_CIDR);
+   }
+
+   @Test(dependsOnMethods = "testCreateRoute")
+   public void testDeleteRoute() {
+      final boolean deleted =
+         routeTableApi.deleteRoute(TEST_REGION, routeTable.id(), destinationCidrBlock(TEST_DESTINATION_CIDR));
+      assertTrue(deleted, "Failed to delete " + TEST_DESTINATION_CIDR + " route from route table " + routeTable.id());
+
+      // clean up the test gateway
+      final Boolean cleaned = gwApi.detachInternetGateway(TEST_REGION, gateway.id(), vpc.id());
+      assertTrue(cleaned, "Failed to delete gateway " + gateway.id());
+
+      final boolean gatewayDeleted = gwApi.deleteInternetGateway(TEST_REGION, gateway.id());
+      assertTrue(gatewayDeleted, "Failed to delete test gateway " + gateway.id());
+   }
+
+   @Test(enabled = false  /* dependsOnMethods = "testCreateRoute" */)
+   public void testReplaceRoute() {
+      // TODO:
+      // At present there is support for creating internet gateways and attaching them to VPCs.
+      // However, you can't attach two internet gateways to the same VPC, so the replaceRoute test must replace
+      // the internet gateway target with one of an virtual private gateway, NAT instance,
+      // NAT gateway, VPC peering connection, network interface, or egress-only Internet gateway.
+      // Add this test when e.g. NATGatewayApi is added.
+   }
+
+   @Test(dependsOnMethods = "testDeleteRoute")
+   public void testDeleteRouteTableWithOptions() {
+      try {
+         routeTableApi.deleteRouteTable(TEST_REGION, routeTable.id(), dryRun());
+         Assert.fail("Expected 'DryRunOperation' exception was not thrown");
+      } catch (AWSResponseException e) {
+         assertDryRun(e);
+      }
+   }
+
+   @Test(dependsOnMethods = "testDeleteRouteTableWithOptions")
+   public void testDeleteRouteTable() {
+
+      final ImmutableList<RouteTable> before =
+         routeTableApi.describeRouteTables(TEST_REGION, routeTable.id()).toList();
+      assertEquals(before.size(), 1, "Unexpected response to describe of " + routeTable.id() + ": " + before);
+      assertEquals(before.get(0).id(), routeTable.id(), "Wrong table returned for " + routeTable.id() + ": " + before);
+
+      final boolean deleted = routeTableApi.deleteRouteTable(TEST_REGION, routeTable.id());
+      assertTrue(deleted, "Failed to delete route table " + routeTable.id());
+
+      final ImmutableList<RouteTable> after = routeTableApi.describeRouteTables(TEST_REGION, routeTable.id()).toList();
+      assertEquals(after.size(), 0, "Unexpected response to describe after deleting " + routeTable.id() + ": " + after);
+   }
+
+   @AfterClass(alwaysRun = true)
+   public void cleanup() {
+      if (vpc != null) {
+         assertTrue(vpcClient.deleteVpc(TEST_REGION, vpc.id()));
+      }
+   }
+
+   private void assertDryRun(AWSResponseException e) {
+      assertEquals(e.getError().getCode(), "DryRunOperation", "Expected DryRunOperation but got " + e.getError());
+   }
+
+}