diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/EC2Api.java b/apis/ec2/src/main/java/org/jclouds/ec2/EC2Api.java index cf42fe4b0c..cb89cc3816 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/EC2Api.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/EC2Api.java @@ -19,6 +19,7 @@ package org.jclouds.ec2; import java.util.Set; +import org.jclouds.ec2.features.SubnetApi; import org.jclouds.ec2.features.TagApi; import org.jclouds.ec2.features.WindowsApi; import org.jclouds.javax.annotation.Nullable; @@ -77,4 +78,14 @@ public interface EC2Api { @Delegate Optional getTagApiForRegion( @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region); + + /** + * Provides synchronous access to Subnet features. + */ + @Delegate + Optional getSubnetApi(); + + @Delegate + Optional getSubnetApiForRegion( + @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region); } diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/EC2AsyncApi.java b/apis/ec2/src/main/java/org/jclouds/ec2/EC2AsyncApi.java index 655b1eb9d0..e9f77c210f 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/EC2AsyncApi.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/EC2AsyncApi.java @@ -20,6 +20,7 @@ package org.jclouds.ec2; import java.util.Set; +import org.jclouds.ec2.features.SubnetAsyncApi; import org.jclouds.ec2.features.TagAsyncApi; import org.jclouds.ec2.features.WindowsAsyncApi; import org.jclouds.javax.annotation.Nullable; @@ -65,4 +66,14 @@ public interface EC2AsyncApi { @Delegate Optional getTagApiForRegion( @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region); + + /** + * Provides asynchronous access to Subnet features. + */ + @Delegate + Optional getSubnetApi(); + + @Delegate + Optional getSubnetApiForRegion( + @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region); } diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/compute/EC2ComputeService.java b/apis/ec2/src/main/java/org/jclouds/ec2/compute/EC2ComputeService.java index 54f617e66a..0334ab0273 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/compute/EC2ComputeService.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/compute/EC2ComputeService.java @@ -81,6 +81,7 @@ import org.jclouds.ec2.compute.options.EC2TemplateOptions; import org.jclouds.ec2.domain.KeyPair; import org.jclouds.ec2.domain.RunningInstance; import org.jclouds.ec2.domain.Tag; +import org.jclouds.ec2.util.SubnetFilterBuilder; import org.jclouds.ec2.util.TagFilterBuilder; import org.jclouds.scriptbuilder.functions.InitAdminAccess; diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/config/EC2RestClientModule.java b/apis/ec2/src/main/java/org/jclouds/ec2/config/EC2RestClientModule.java index 8aa2cc8b00..ba58ed3193 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/config/EC2RestClientModule.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/config/EC2RestClientModule.java @@ -29,6 +29,8 @@ import org.jclouds.ec2.EC2Api; import org.jclouds.ec2.EC2AsyncApi; import org.jclouds.ec2.EC2AsyncClient; import org.jclouds.ec2.EC2Client; +import org.jclouds.ec2.features.SubnetApi; +import org.jclouds.ec2.features.SubnetAsyncApi; import org.jclouds.ec2.features.TagApi; import org.jclouds.ec2.features.TagAsyncApi; import org.jclouds.ec2.features.WindowsApi; @@ -87,6 +89,7 @@ public class EC2RestClientModule extend .put(ElasticBlockStoreClient.class, ElasticBlockStoreAsyncClient.class)// .put(WindowsApi.class, WindowsAsyncApi.class)// .put(TagApi.class, TagAsyncApi.class)// + .put(SubnetApi.class, SubnetAsyncApi.class)// .build(); @SuppressWarnings("unchecked") diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/domain/Subnet.java b/apis/ec2/src/main/java/org/jclouds/ec2/domain/Subnet.java new file mode 100644 index 0000000000..13f2137b89 --- /dev/null +++ b/apis/ec2/src/main/java/org/jclouds/ec2/domain/Subnet.java @@ -0,0 +1,272 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.ec2.domain; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Map; + +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.base.Optional; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; + +/** + * Amazon EC2 VPCs contain one or more subnets. + * + * @see doc + * + * @author Adrian Cole + * @author Andrew Bayer + */ +public class Subnet { + + public static enum State { + /** + * The subnet is available for use. + */ + AVAILABLE, + /** + * The subnet is not yet available for use. + */ + PENDING, UNRECOGNIZED; + public String value() { + return name().toLowerCase(); + } + + public static State fromValue(String v) { + try { + return valueOf(v.toUpperCase()); + } catch (IllegalArgumentException e) { + return UNRECOGNIZED; + } + } + } + + private final String subnetId; + private final State subnetState; + private final String vpcId; + private final String cidrBlock; + private final int availableIpAddressCount; + private final String availabilityZone; + private final Map tags; + + public Subnet(String subnetId, State subnetState, String vpcId, String cidrBlock, + int availableIpAddressCount, String availabilityZone, Map tags) { + this.subnetId = checkNotNull(subnetId, "subnetId"); + this.subnetState = checkNotNull(subnetState, "subnetState for %s", subnetId); + this.vpcId = checkNotNull(vpcId, "vpcId for %s", subnetId); + this.cidrBlock = checkNotNull(cidrBlock, "cidrBlock for %s", subnetId); + this.availableIpAddressCount = availableIpAddressCount; + this.availabilityZone = checkNotNull(availabilityZone, "availabilityZone for %s", subnetId); + this.tags = ImmutableMap. copyOf(checkNotNull(tags, "tags for %s", subnetId)); + + } + + /** + * The subnet ID, ex. subnet-c5473ba8 + */ + public String getSubnetId() { + return subnetId; + } + + /** + * The subnet state - either available or pending. + */ + public State getSubnetState() { + return subnetState; + } + + /** + * The vpc ID this subnet belongs to. + */ + public String getVpcId() { + return vpcId; + } + + /** + * The CIDR block for this subnet. + */ + public String getCidrBlock() { + return cidrBlock; + } + + /** + * The number of available IPs in this subnet. + */ + public int getAvailableIpAddressCount() { + return availableIpAddressCount; + } + + /** + * The availability zone this subnet is in. + */ + public String getAvailabilityZone() { + return availabilityZone; + } + + /** + * Tags that are attached to this subnet. + */ + public Map getTags() { + return tags; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(subnetId, vpcId, availabilityZone); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Subnet other = (Subnet) obj; + return Objects.equal(this.subnetId, other.subnetId) + && Objects.equal(this.vpcId, other.vpcId) + && Objects.equal(this.availabilityZone, other.availabilityZone); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return string().toString(); + } + + private final ToStringHelper string() { + return Objects.toStringHelper(this).omitNullValues().add("subnetId", subnetId) + .add("subnetState", subnetState).add("vpcId", vpcId) + .add("cidrBlock", cidrBlock).add("availableIpAddressCount", availableIpAddressCount) + .add("availabilityZone", availabilityZone).add("tags", tags); + } + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return builder().fromSubnet(this); + } + + public static class Builder { + private String subnetId; + private State subnetState; + private String vpcId; + private String cidrBlock; + private int availableIpAddressCount; + private String availabilityZone; + private Map tags = Maps.newLinkedHashMap(); + + /** + * @see Subnet#getSubnetId() + */ + public Builder subnetId(String subnetId) { + this.subnetId = subnetId; + return this; + } + + /** + * @see Subnet#getState() + */ + public Builder subnetState(State subnetState) { + this.subnetState = subnetState; + return this; + } + + /** + * @see Subnet#getVpcId() + */ + public Builder vpcId(String vpcId) { + this.vpcId = vpcId; + return this; + } + + /** + * @see Subnet#getCidrBlock() + */ + public Builder cidrBlock(String cidrBlock) { + this.cidrBlock = cidrBlock; + return this; + } + + /** + * @see Subnet#getAvailableIpAddressCount() + */ + public Builder availableIpAddressCount(int availableIpAddressCount) { + this.availableIpAddressCount = availableIpAddressCount; + return this; + } + + /** + * @see Subnet#getAvailabilityZone() + */ + public Builder availabilityZone(String availabilityZone) { + this.availabilityZone = availabilityZone; + return this; + } + + /** + * @see Subnet#getTags() + */ + public Builder tags(Map tags) { + this.tags = ImmutableMap.copyOf(checkNotNull(tags, "tags")); + return this; + } + + /** + * @see Subnet#getTags() + */ + public Builder tag(String key, String value) { + if (key != null) + this.tags.put(key, Strings.nullToEmpty(value)); + return this; + } + + public Subnet build() { + return new Subnet(subnetId, subnetState, vpcId, cidrBlock, availableIpAddressCount, + availabilityZone, tags); + } + + public Builder fromSubnet(Subnet in) { + return this.subnetId(in.getSubnetId()) + .subnetState(in.getSubnetState()) + .vpcId(in.getVpcId()) + .cidrBlock(in.getCidrBlock()) + .availableIpAddressCount(in.getAvailableIpAddressCount()) + .availabilityZone(in.getAvailabilityZone()) + .tags(in.getTags()); + } + } +} diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/features/SubnetApi.java b/apis/ec2/src/main/java/org/jclouds/ec2/features/SubnetApi.java new file mode 100644 index 0000000000..c0e2a5c281 --- /dev/null +++ b/apis/ec2/src/main/java/org/jclouds/ec2/features/SubnetApi.java @@ -0,0 +1,72 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.ec2.features; + +import java.util.Map; +import org.jclouds.ec2.domain.Subnet; +import org.jclouds.ec2.util.SubnetFilterBuilder; +import org.jclouds.rest.annotations.SinceApiVersion; + +import com.google.common.collect.FluentIterable; +import com.google.common.collect.Multimap; + +/** + * To help you manage your Amazon EC2 instances, images, and other Amazon EC2 + * resources, you can assign your own metadata to each resource in the form of + * tags. + * + * @see doc + * @see SubnetAsyncApi + * @author Adrian Cole + * @author Andrew Bayer + */ +@SinceApiVersion("2011-01-01") +public interface SubnetApi { + + /** + * Describes all of your subnets for your EC2 resources. + * + * @return subnets or empty if there are none + * @see docs + */ + FluentIterable list(); + + /** + * Describes subnets for your EC2 resources qualified by a filter + * + *

example

+ * + *
+    * subnets = subnetApi.filter(new SubnetFilterBuilder().vpcId("vpc-1a2b3c4d").build());
+    * 
+ * + * @param filter + * which is typically built by {@link SubnetFilterBuilder} + * @return tags or empty if there are none that match the filter + * @see
docs + */ + FluentIterable filter(Multimap filter); + +} diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/features/SubnetAsyncApi.java b/apis/ec2/src/main/java/org/jclouds/ec2/features/SubnetAsyncApi.java new file mode 100644 index 0000000000..e252dbcb92 --- /dev/null +++ b/apis/ec2/src/main/java/org/jclouds/ec2/features/SubnetAsyncApi.java @@ -0,0 +1,88 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.ec2.features; + +import static org.jclouds.aws.reference.FormParameters.ACTION; + +import java.util.Map; + +import javax.inject.Named; +import javax.ws.rs.POST; +import javax.ws.rs.Path; + +import org.jclouds.Fallbacks.EmptyFluentIterableOnNotFoundOr404; +import org.jclouds.aws.filters.FormSigner; +import org.jclouds.ec2.binders.BindFiltersToIndexedFormParams; +import org.jclouds.ec2.domain.Subnet; +import org.jclouds.ec2.xml.DescribeSubnetsResponseHandler; +import org.jclouds.rest.annotations.BinderParam; +import org.jclouds.rest.annotations.Fallback; +import org.jclouds.rest.annotations.FormParams; +import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.annotations.SinceApiVersion; +import org.jclouds.rest.annotations.VirtualHost; +import org.jclouds.rest.annotations.XMLResponseParser; + +import com.google.common.collect.FluentIterable; +import com.google.common.collect.Multimap; +import com.google.common.util.concurrent.ListenableFuture; + +/** + * Provides access to Amazon EC2 via the Query API + *

+ * + * @see doc + * @see SubnetApi + * @author Adrian Cole + * @author Andrew Bayer + */ +@SinceApiVersion("2011-01-01") +@RequestFilters(FormSigner.class) +@VirtualHost +public interface SubnetAsyncApi { + /** + * @see SubnetApi#list() + * @see docs + */ + @Named("DescribeSubnets") + @POST + @Path("/") + @FormParams(keys = ACTION, values = "DescribeSubnets") + @XMLResponseParser(DescribeSubnetsResponseHandler.class) + @Fallback(EmptyFluentIterableOnNotFoundOr404.class) + ListenableFuture> list(); + + /** + * @see SubnetApi#filter + * @see docs + */ + @Named("DescribeSubnets") + @POST + @Path("/") + @FormParams(keys = ACTION, values = "DescribeSubnets") + @XMLResponseParser(DescribeSubnetsResponseHandler.class) + @Fallback(EmptyFluentIterableOnNotFoundOr404.class) + ListenableFuture> filter( + @BinderParam(BindFiltersToIndexedFormParams.class) Multimap filter); + +} diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/util/SubnetFilterBuilder.java b/apis/ec2/src/main/java/org/jclouds/ec2/util/SubnetFilterBuilder.java new file mode 100644 index 0000000000..db0609e7de --- /dev/null +++ b/apis/ec2/src/main/java/org/jclouds/ec2/util/SubnetFilterBuilder.java @@ -0,0 +1,254 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.ec2.util; + +import java.util.Comparator; +import java.util.Map.Entry; + +import org.jclouds.ec2.domain.Subnet; +import org.jclouds.ec2.features.SubnetApi; + +import com.google.common.annotations.Beta; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; + +/** + * You can specify filters so that the response includes information for only + * certain subnets. For example, you can use a filter to specify that you're + * interested in the subnets in the available state. You can specify multiple + * values for a filter. The response includes information for a subnet only if + * it matches at least one of the filter values that you specified. + * + * You can specify multiple filters; for example, specify subnets that are in a + * specific VPC and are in the available state. The response includes + * information for a subnet only if it matches all the filters that you + * specified. If there's no match, no special message is returned, the response + * is simply empty. + * + *

Wildcards

You can use wildcards with the filter values: {@code *} + * matches zero or more characters, and ? matches exactly one character. You can + * escape special characters using a backslash before the character. For + * example, a value of {@code \*amazon\?\\} searches for the literal string + * {@code *amazon?\}. + * + * @author Adrian Cole + * @author Andrew Bayer + * @see SubnetApi + */ +public class SubnetFilterBuilder extends ImmutableMultimap.Builder { + + private static final String AVAILABILITY_ZONE = "availability-zone"; + private static final String AVAILABLE_IP_ADDRESS_COUNT = "available-ip-address-count"; + private static final String CIDR = "cidr"; + private static final String STATE = "state"; + private static final String SUBNET_ID = "subnet-id"; + private static final String TAG_KEY = "tag-key"; + private static final String TAG_VALUE = "tag-value"; + private static final String TAG_ARBITRARY_BASE = "tag:"; + private static final String VPC_ID = "vpc-id"; + + public SubnetFilterBuilder availabilityZone(String availabilityZone) { + return put(AVAILABILITY_ZONE, availabilityZone); + } + + public SubnetFilterBuilder availabilityZones(String... availabilityZones) { + return putAll(AVAILABILITY_ZONE, availabilityZones); + } + + public SubnetFilterBuilder availabilityZones(Iterable availabilityZones) { + return putAll(AVAILABILITY_ZONE, availabilityZones); + } + + public SubnetFilterBuilder anyAvailabilityZone() { + return putAll(AVAILABILITY_ZONE, ImmutableSet. of()); + } + + public SubnetFilterBuilder availableIpAddressCount(String availableIpAddressCount) { + return put(AVAILABLE_IP_ADDRESS_COUNT, availableIpAddressCount); + } + + public SubnetFilterBuilder availableIpAddressCounts(String... availableIpAddressCounts) { + return putAll(AVAILABLE_IP_ADDRESS_COUNT, availableIpAddressCounts); + } + + public SubnetFilterBuilder availableIpAddressCounts(Iterable availableIpAddressCounts) { + return putAll(AVAILABLE_IP_ADDRESS_COUNT, availableIpAddressCounts); + } + + public SubnetFilterBuilder anyAvailableIpAddressCount() { + return putAll(AVAILABLE_IP_ADDRESS_COUNT, ImmutableSet. of()); + } + + public SubnetFilterBuilder cidr(String cidr) { + return put(CIDR, cidr); + } + + public SubnetFilterBuilder cidrs(String... cidrs) { + return putAll(CIDR, cidrs); + } + + public SubnetFilterBuilder cidrs(Iterable cidrs) { + return putAll(CIDR, cidrs); + } + + public SubnetFilterBuilder anyCidr() { + return putAll(CIDR, ImmutableSet. of()); + } + + public SubnetFilterBuilder state(String state) { + return put(STATE, state); + } + + public SubnetFilterBuilder states(String... states) { + return putAll(STATE, states); + } + + public SubnetFilterBuilder states(Iterable states) { + return putAll(STATE, states); + } + + public SubnetFilterBuilder anyState() { + return putAll(STATE, ImmutableSet. of()); + } + + public SubnetFilterBuilder available() { + return put(STATE, Subnet.State.AVAILABLE.value()); + } + + public SubnetFilterBuilder pending() { + return put(STATE, Subnet.State.PENDING.value()); + } + + public SubnetFilterBuilder subnetId(String subnetId) { + return put(SUBNET_ID, subnetId); + } + + public SubnetFilterBuilder subnetIds(String... subnetIds) { + return putAll(SUBNET_ID, subnetIds); + } + + public SubnetFilterBuilder subnetIds(Iterable subnetIds) { + return putAll(SUBNET_ID, subnetIds); + } + + public SubnetFilterBuilder anySubnetId() { + return putAll(SUBNET_ID, ImmutableSet. of()); + } + + public SubnetFilterBuilder tagKey(String tagKey) { + return put(TAG_KEY, tagKey); + } + + public SubnetFilterBuilder tagKeys(String... tagKeys) { + return putAll(TAG_KEY, tagKeys); + } + + public SubnetFilterBuilder tagKeys(Iterable tagKeys) { + return putAll(TAG_KEY, tagKeys); + } + + public SubnetFilterBuilder anyTagKey() { + return putAll(TAG_KEY, ImmutableSet. of()); + } + + public SubnetFilterBuilder tagValue(String tagValue) { + return put(TAG_VALUE, tagValue); + } + + public SubnetFilterBuilder tagValues(String... tagValues) { + return putAll(TAG_VALUE, tagValues); + } + + public SubnetFilterBuilder tagValues(Iterable tagValues) { + return putAll(TAG_VALUE, tagValues); + } + + public SubnetFilterBuilder anyTagValue() { + return putAll(TAG_VALUE, ImmutableSet. of()); + } + + public SubnetFilterBuilder vpcId(String vpcId) { + return put(VPC_ID, vpcId); + } + + public SubnetFilterBuilder vpcIds(String... vpcIds) { + return putAll(VPC_ID, vpcIds); + } + + public SubnetFilterBuilder vpcIds(Iterable vpcIds) { + return putAll(VPC_ID, vpcIds); + } + + public SubnetFilterBuilder anyVpcId() { + return putAll(VPC_ID, ImmutableSet. of()); + } + + public SubnetFilterBuilder arbitraryTag(String arbitraryTagKey, String arbitraryTagValue) { + return put(TAG_ARBITRARY_BASE + arbitraryTagKey, arbitraryTagValue); + } + + public SubnetFilterBuilder arbitraryTag(String arbitraryTagKey, String... arbitraryTagValues) { + return putAll(TAG_ARBITRARY_BASE + arbitraryTagKey, arbitraryTagValues); + } + + public SubnetFilterBuilder arbitraryTag(String arbitraryTagKey, Iterable arbitraryTagValues) { + return putAll(TAG_ARBITRARY_BASE + arbitraryTagKey, arbitraryTagValues); + } + + + // to set correct return val in chain + + @Override + public SubnetFilterBuilder put(String key, String value) { + return SubnetFilterBuilder.class.cast(super.put(key, value)); + } + + @Override + public SubnetFilterBuilder put(Entry entry) { + return SubnetFilterBuilder.class.cast(super.put(entry)); + } + + @Override + public SubnetFilterBuilder putAll(String key, Iterable values) { + return SubnetFilterBuilder.class.cast(super.putAll(key, values)); + } + + @Override + public SubnetFilterBuilder putAll(String key, String... values) { + return SubnetFilterBuilder.class.cast(super.putAll(key, values)); + } + + @Override + public SubnetFilterBuilder putAll(Multimap multimap) { + return SubnetFilterBuilder.class.cast(super.putAll(multimap)); + } + + @Override + @Beta + public SubnetFilterBuilder orderKeysBy(Comparator keyComparator) { + return SubnetFilterBuilder.class.cast(super.orderKeysBy(keyComparator)); + } + + @Override + @Beta + public SubnetFilterBuilder orderValuesBy(Comparator valueComparator) { + return SubnetFilterBuilder.class.cast(super.orderValuesBy(valueComparator)); + } +} diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/xml/DescribeSubnetsResponseHandler.java b/apis/ec2/src/main/java/org/jclouds/ec2/xml/DescribeSubnetsResponseHandler.java new file mode 100644 index 0000000000..fca2b95729 --- /dev/null +++ b/apis/ec2/src/main/java/org/jclouds/ec2/xml/DescribeSubnetsResponseHandler.java @@ -0,0 +1,112 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.ec2.xml; + +import static org.jclouds.util.SaxUtils.currentOrNull; +import static org.jclouds.util.SaxUtils.equalsOrSuffix; + +import java.util.Set; + +import org.jclouds.ec2.domain.Subnet; +import org.jclouds.http.functions.ParseSax; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; +import com.google.common.collect.Sets; +import com.google.inject.Inject; + +/** + * @see xml + * + * @author Adrian Cole + * @author Andrew Bayer + */ +public class DescribeSubnetsResponseHandler extends ParseSax.HandlerForGeneratedRequestWithResult> { + private final SubnetHandler subnetHandler; + + private StringBuilder currentText = new StringBuilder(); + private boolean inSubnetSet; + private boolean inTagSet; + private Builder subnets = ImmutableSet. builder(); + + @Inject + public DescribeSubnetsResponseHandler(SubnetHandler subnetHandler) { + this.subnetHandler = subnetHandler; + } + + /** + * {@inheritDoc} + */ + @Override + public FluentIterable getResult() { + return FluentIterable.from(subnets.build()); + } + + /** + * {@inheritDoc} + */ + @Override + public void startElement(String url, String name, String qName, Attributes attributes) throws SAXException { + if (equalsOrSuffix(qName, "subnetSet")) { + inSubnetSet = true; + } else if (inSubnetSet) { + if (equalsOrSuffix(qName, "tagSet")) { + inTagSet = true; + } + subnetHandler.startElement(url, name, qName, attributes); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void endElement(String uri, String name, String qName) throws SAXException { + if (equalsOrSuffix(qName, "subnetSet")) { + inSubnetSet = false; + } else if (equalsOrSuffix(qName, "tagSet")) { + inTagSet = false; + subnetHandler.endElement(uri, name, qName); + } else if (equalsOrSuffix(qName, "item") && !inTagSet) { + subnets.add(subnetHandler.getResult()); + } else if (inSubnetSet) { + subnetHandler.endElement(uri, name, qName); + } + + currentText = new StringBuilder(); + } + + /** + * {@inheritDoc} + */ + @Override + public void characters(char ch[], int start, int length) { + if (inSubnetSet) { + subnetHandler.characters(ch, start, length); + } else { + currentText.append(ch, start, length); + } + } + +} diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/xml/SubnetHandler.java b/apis/ec2/src/main/java/org/jclouds/ec2/xml/SubnetHandler.java new file mode 100644 index 0000000000..13f6235e0c --- /dev/null +++ b/apis/ec2/src/main/java/org/jclouds/ec2/xml/SubnetHandler.java @@ -0,0 +1,122 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.ec2.xml; + +import static org.jclouds.util.SaxUtils.currentOrNull; +import static org.jclouds.util.SaxUtils.equalsOrSuffix; + +import javax.inject.Inject; + +import org.jclouds.ec2.domain.Subnet; +import org.jclouds.ec2.domain.Subnet.State; +import org.jclouds.http.functions.ParseSax; + +import org.xml.sax.Attributes; + +import com.google.common.base.Supplier; + +/** + * @see xml + * + * @author Adrian Cole + * @author Andrew Bayer + */ +public class SubnetHandler extends ParseSax.HandlerForGeneratedRequestWithResult { + private StringBuilder currentText = new StringBuilder(); + private Subnet.Builder builder = newBuilder(); + private final TagSetHandler tagSetHandler; + private boolean inTagSet; + + @Inject + public SubnetHandler(TagSetHandler tagSetHandler) { + this.tagSetHandler = tagSetHandler; + } + + /** + * {@inheritDoc} + */ + @Override + public Subnet getResult() { + try { + return builder.build(); + } finally { + builder = Subnet.builder(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void startElement(String uri, String name, String qName, Attributes attrs) { + if (equalsOrSuffix(qName, "tagSet")) { + inTagSet = true; + } + if (inTagSet) { + tagSetHandler.startElement(uri, name, qName, attrs); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void endElement(String uri, String name, String qName) { + if (equalsOrSuffix(qName, "tagSet")) { + inTagSet = false; + builder.tags(tagSetHandler.getResult()); + } else if (inTagSet) { + tagSetHandler.endElement(uri, name, qName); + } else if (equalsOrSuffix(qName, "subnetId")) { + builder.subnetId(currentOrNull(currentText)); + } else if (equalsOrSuffix(qName, "state")) { + builder.subnetState(Subnet.State.fromValue(currentOrNull(currentText))); + } else if (equalsOrSuffix(qName, "vpcId")) { + builder.vpcId(currentOrNull(currentText)); + } else if (equalsOrSuffix(qName, "cidrBlock")) { + builder.cidrBlock(currentOrNull(currentText)); + } else if (equalsOrSuffix(qName, "availableIpAddressCount")) { + builder.availableIpAddressCount(Integer.parseInt(currentOrNull(currentText))); + } else if (equalsOrSuffix(qName, "availabilityZone")) { + builder.availabilityZone(currentOrNull(currentText)); + } + currentText = new StringBuilder(); + } + + + + /** + * {@inheritDoc} + */ + @Override + public void characters(char ch[], int start, int length) { + if (inTagSet) { + tagSetHandler.characters(ch, start, length); + } else { + currentText.append(ch, start, length); + } + } + + + private Subnet.Builder newBuilder() { + return Subnet.builder(); + } +} diff --git a/apis/ec2/src/test/java/org/jclouds/ec2/features/SubnetApiExpectTest.java b/apis/ec2/src/test/java/org/jclouds/ec2/features/SubnetApiExpectTest.java new file mode 100644 index 0000000000..6516b1bac3 --- /dev/null +++ b/apis/ec2/src/test/java/org/jclouds/ec2/features/SubnetApiExpectTest.java @@ -0,0 +1,133 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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 + * + * Unles 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 expres or implied. See the License for the + * specific language governing permisions and limitations + * under the License. + */ +package org.jclouds.ec2.features; +import static org.testng.Assert.assertEquals; + +import java.util.Properties; + +import org.jclouds.Constants; +import org.jclouds.ec2.EC2Api; +import org.jclouds.ec2.internal.BaseEC2ApiExpectTest; +import org.jclouds.ec2.parse.DescribeSubnetsResponseTest; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.jclouds.rest.ResourceNotFoundException; +import org.jclouds.rest.annotations.SinceApiVersion; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSet; + +/** + * @author Adrian Cole + */ +@Test(groups = "unit") +public class SubnetApiExpectTest extends BaseEC2ApiExpectTest { + + /** + * @see SubnetApi + * @see SinceApiVersion + */ + protected Properties setupProperties() { + Properties props = super.setupProperties(); + props.put(Constants.PROPERTY_API_VERSION, "2011-01-01"); + return props; + } + + HttpRequest list = HttpRequest.builder() + .method("POST") + .endpoint("https://ec2.us-east-1.amazonaws.com/") + .addHeader("Host", "ec2.us-east-1.amazonaws.com") + .payload( + payloadFromStringWithContentType( + "Action=DescribeSubnets" + + "&Signature=Uuafp9lnYQmMUcf/JE1epPTQVCSMPqfns%2BwlZssUsi4%3D" + + "&SignatureMethod=HmacSHA256" + + "&SignatureVersion=2" + + "&Timestamp=2012-04-16T15%3A54%3A08.897Z" + + "&Version=2011-01-01" + + "&AWSAccessKeyId=identity", + "application/x-www-form-urlencoded")) + .build(); + + public void testListWhenResponseIs2xx() throws Exception { + + HttpResponse listResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResourceWithContentType("/describe_subnets.xml", "text/xml")).build(); + + EC2Api apiWhenExist = requestSendsResponse( + list, listResponse); + + assertEquals(apiWhenExist.getSubnetApi().get().list().toString(), new DescribeSubnetsResponseTest().expected().toString()); + } + + public void testListWhenResponseIs404() throws Exception { + + HttpResponse listResponse = HttpResponse.builder().statusCode(404).build(); + + EC2Api apiWhenDontExist = requestSendsResponse( + list, listResponse); + + assertEquals(apiWhenDontExist.getSubnetApi().get().list().toSet(), ImmutableSet.of()); + } + + HttpRequest filter = + HttpRequest.builder() + .method("POST") + .endpoint("https://ec2.us-east-1.amazonaws.com/") + .addHeader("Host", "ec2.us-east-1.amazonaws.com") + .payload(payloadFromStringWithContentType( + "Action=DescribeSubnets" + + "&Filter.1.Name=subnet-id" + + "&Filter.1.Value.1=subnet-9d4a7b6c" + + "&Signature=%2Bp34YACfLk9km1H3eALnDmrkst9FhJttojVSf7VztLk%3D" + + "&SignatureMethod=HmacSHA256" + + "&SignatureVersion=2" + + "&Timestamp=2012-04-16T15%3A54%3A08.897Z" + + "&Version=2011-01-01" + + "&AWSAccessKeyId=identity", + "application/x-www-form-urlencoded")) + .build(); + + public void testFilterWhenResponseIs2xx() throws Exception { + + HttpResponse filterResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResourceWithContentType("/describe_subnets.xml", "text/xml")).build(); + + EC2Api apiWhenExist = requestSendsResponse(filter, filterResponse); + + assertEquals(apiWhenExist.getSubnetApi().get().filter(ImmutableMultimap. builder() + .put("subnet-id", "subnet-9d4a7b6c") + .build()).toString(), + new DescribeSubnetsResponseTest().expected().toString()); + } + + public void testFilterWhenResponseIs404() throws Exception { + + HttpResponse filterResponse = HttpResponse.builder().statusCode(404).build(); + + EC2Api apiWhenDontExist = requestSendsResponse(filter, filterResponse); + + assertEquals(apiWhenDontExist.getSubnetApi().get().filter(ImmutableMultimap. builder() + .put("subnet-id", "subnet-9d4a7b6c") + .build()).toSet(), ImmutableSet.of()); + } + +} diff --git a/apis/ec2/src/test/java/org/jclouds/ec2/features/SubnetApiLiveTest.java b/apis/ec2/src/test/java/org/jclouds/ec2/features/SubnetApiLiveTest.java new file mode 100644 index 0000000000..d4800a61f0 --- /dev/null +++ b/apis/ec2/src/test/java/org/jclouds/ec2/features/SubnetApiLiveTest.java @@ -0,0 +1,96 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.ec2.features.internal; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.lang.String.format; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.logging.Logger.getAnonymousLogger; +import static org.jclouds.util.Predicates2.retry; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.util.logging.Logger; + +import org.jclouds.ec2.domain.Subnet; +import org.jclouds.ec2.features.SubnetApi; +import org.jclouds.ec2.internal.BaseEC2ApiLiveTest; +import org.jclouds.ec2.util.SubnetFilterBuilder; +import org.testng.SkipException; +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.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +/** + * tests ability to list/filter subnets + * + * @author Adrian Cole + * @author Andrew Bayer + */ +@Test(groups = "live") +public class SubnetApiLiveTest extends BaseEC2ApiLiveTest { + private Subnet subnet; + + + private void checkSubnet(Subnet subnet) { + getAnonymousLogger().info(format("subnet %s vpc: %s", subnet.getSubnetId(), subnet.getVpcId())); + + checkNotNull(subnet.getSubnetId(), "Id: Subnet %s", subnet); + checkNotNull(subnet.getVpcId(), "VPC: Subnet %s", subnet); + checkNotNull(subnet.getSubnetState(), "SubnetState: Subnet %s", subnet); + checkNotNull(subnet.getCidrBlock(), "CIDR Block: %s", subnet); + checkNotNull(subnet.getAvailabilityZone(), "Availability Zone: %s", subnet); + } + + @Test + public void testListSubnets() { + ImmutableSet subnets = api().list().toSet(); + getAnonymousLogger().info("subnets: " + subnets.size()); + + for (Subnet subnet : subnets) { + checkSubnet(subnet); + assertEquals(api().filter(new SubnetFilterBuilder().subnetId(subnet.getSubnetId()).build()).get(0), subnet); + } + } + + @Test + public void testFilterWhenNotFound() { + assertTrue(retry(new Predicate>() { + public boolean apply(Iterable input) { + return api().filter(new SubnetFilterBuilder().subnetId("subnet-pants").build()) + .toSet().equals(input); + } + }, 600, 200, 200, MILLISECONDS).apply(ImmutableSet. of())); + } + + private SubnetApi api() { + Optional subnetOption = context.getApi().getSubnetApi(); + if (!subnetOption.isPresent()) + throw new SkipException("subnet api not present"); + return subnetOption.get(); + } + +} diff --git a/apis/ec2/src/test/java/org/jclouds/ec2/parse/DescribeSubnetsResponseTest.java b/apis/ec2/src/test/java/org/jclouds/ec2/parse/DescribeSubnetsResponseTest.java new file mode 100644 index 0000000000..2d6357aa1d --- /dev/null +++ b/apis/ec2/src/test/java/org/jclouds/ec2/parse/DescribeSubnetsResponseTest.java @@ -0,0 +1,73 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.ec2.parse; + +import static org.testng.Assert.assertEquals; + +import java.io.InputStream; + +import org.jclouds.ec2.domain.Subnet; +import org.jclouds.ec2.xml.DescribeSubnetsResponseHandler; +import org.jclouds.http.functions.BaseHandlerTest; +import org.testng.annotations.Test; + +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableSet; + +/** + * @author Adrian Cole + * @author Andrew Bayer + */ +// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire +@Test(groups = "unit", testName = "DescribeSubnetsResponseTest") +public class DescribeSubnetsResponseTest extends BaseHandlerTest { + + public void test() { + InputStream is = getClass().getResourceAsStream("/describe_subnets.xml"); + + FluentIterable expected = expected(); + + DescribeSubnetsResponseHandler handler = injector.getInstance(DescribeSubnetsResponseHandler.class); + FluentIterable result = factory.create(handler).parse(is); + + assertEquals(result.toString(), expected.toString()); + + } + public FluentIterable expected() { + return FluentIterable.from(ImmutableSet.builder() + .add(Subnet.builder() + .subnetId("subnet-9d4a7b6c") + .subnetState(Subnet.State.AVAILABLE) + .vpcId("vpc-1a2b3c4d") + .cidrBlock("10.0.1.0/24") + .availableIpAddressCount(250) + .availabilityZone("us-east-1a") + .tag("Name", "ec2-o") + .tag("Empty", "") + .build()) + .add(Subnet.builder() + .subnetId("subnet-6e7f829e") + .subnetState(Subnet.State.AVAILABLE) + .vpcId("vpc-1a2b3c4d") + .cidrBlock("10.0.0.0/24") + .availableIpAddressCount(250) + .availabilityZone("us-east-1a") + .build()).build()); + } +} diff --git a/apis/ec2/src/test/java/org/jclouds/ec2/parse/DescribeTagsResponseTest.java b/apis/ec2/src/test/java/org/jclouds/ec2/parse/DescribeTagsResponseTest.java index 95ef3027e0..ee5849f9ea 100644 --- a/apis/ec2/src/test/java/org/jclouds/ec2/parse/DescribeTagsResponseTest.java +++ b/apis/ec2/src/test/java/org/jclouds/ec2/parse/DescribeTagsResponseTest.java @@ -33,7 +33,8 @@ import com.google.common.collect.ImmutableSet; /** * @author Adrian Cole */ -@Test(groups = "unit") +// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire +@Test(groups = "unit", testName = "DescribeTagsResponseTest") public class DescribeTagsResponseTest extends BaseHandlerTest { public void test() { diff --git a/apis/ec2/src/test/resources/describe_subnets.xml b/apis/ec2/src/test/resources/describe_subnets.xml new file mode 100644 index 0000000000..7fe8697f81 --- /dev/null +++ b/apis/ec2/src/test/resources/describe_subnets.xml @@ -0,0 +1,33 @@ + + 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE + + + subnet-9d4a7b6c + available + vpc-1a2b3c4d + 10.0.1.0/24 + 250 + us-east-1a + + + Name + ec2-o + + + Empty + + + + + + subnet-6e7f829e + available + vpc-1a2b3c4d + 10.0.0.0/24 + 250 + us-east-1a + + + + + \ No newline at end of file