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:29 UTC

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

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());
+   }
+
+}