diff --git a/common/aws/src/main/java/org/jclouds/aws/util/AWSUtils.java b/common/aws/src/main/java/org/jclouds/aws/util/AWSUtils.java index 2d8e880f19..bb72785ad3 100644 --- a/common/aws/src/main/java/org/jclouds/aws/util/AWSUtils.java +++ b/common/aws/src/main/java/org/jclouds/aws/util/AWSUtils.java @@ -22,6 +22,8 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG; +import java.util.Map; + import javax.annotation.Resource; import javax.inject.Inject; import javax.inject.Named; @@ -45,6 +47,8 @@ import org.jclouds.rest.internal.GeneratedHttpRequest; import com.google.common.base.Function; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableMultimap.Builder; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimap; /** * Needed to sign and verify requests and responses. @@ -66,6 +70,7 @@ public class AWSUtils { private final Provider errorHandlerProvider; private final String requestId; private final String requestToken; + @Resource protected Logger logger = Logger.NULL; @@ -146,6 +151,62 @@ public class AWSUtils { return forms.size() == 0 ? request : ModifyRequest.putFormParams(request, forms); } + public static R indexMapToFormValuesWithPrefix(R request, String prefix, String keySuffix, String valueSuffix, Object input) { + checkArgument(checkNotNull(input, "input") instanceof Map, "this binder is only valid for Map: " + input.getClass()); + Map map = (Map) input; + Builder builder = ImmutableMultimap. builder(); + int i = 1; + for (Map.Entry e : map.entrySet()) { + builder.put(prefix + "." + i + "." + keySuffix, checkNotNull(e.getKey().toString(), keySuffix.toLowerCase() + "s[" + i + "]")); + if (e.getValue() != null) { + builder.put(prefix + "." + i + "." + valueSuffix, e.getValue().toString()); + } + i++; + } + ImmutableMultimap forms = builder.build(); + return forms.size() == 0 ? request : ModifyRequest.putFormParams(request, forms); + } + + @SuppressWarnings("unchecked") + public static R indexMultimapToFormValuesWithPrefix(R request, String prefix, String keySuffix, String valueSuffix, Object input) { + checkArgument(checkNotNull(input, "input") instanceof Multimap, "this binder is only valid for Multimap: " + input.getClass()); + Multimap map = (Multimap) input; + Builder builder = ImmutableMultimap. builder(); + int i = 1; + for (Object k : map.keySet()) { + builder.put(prefix + "." + i + "." + keySuffix, checkNotNull(k.toString(), keySuffix.toLowerCase() + "s[" + i + "]")); + int j = 1; + for (Object v : map.get(k)) { + builder.put(prefix + "." + i + "." + valueSuffix + "." + j, v.toString()); + j++; + } + i++; + } + ImmutableMultimap forms = builder.build(); + return forms.size() == 0 ? request : ModifyRequest.putFormParams(request, forms); + } + + @SuppressWarnings("unchecked") + public static R indexMapOfIterableToFormValuesWithPrefix(R request, String prefix, String keySuffix, String valueSuffix, Object input) { + checkArgument(checkNotNull(input, "input") instanceof Map, "this binder is only valid for Map>: " + input.getClass()); + Map> map = (Map>) input; + Builder builder = ImmutableMultimap. builder(); + int i = 1; + for (Object k : map.keySet()) { + builder.put(prefix + "." + i + "." + keySuffix, checkNotNull(k.toString(), keySuffix.toLowerCase() + "s[" + i + "]")); + if (!Iterables.isEmpty(map.get(k))) { + int j = 1; + for (Object v : map.get(k)) { + builder.put(prefix + "." + i + "." + valueSuffix + "." + j, v.toString()); + j++; + } + } + i++; + } + ImmutableMultimap forms = builder.build(); + return forms.size() == 0 ? request : ModifyRequest.putFormParams(request, forms); + } + public static String getRegionFromLocationOrNull(Location location) { return location.getScope() == LocationScope.ZONE ? location.getParent().getId() : location.getId(); } diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/AWSEC2AsyncClient.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/AWSEC2AsyncClient.java index bf2681362b..1a1c7d5a13 100644 --- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/AWSEC2AsyncClient.java +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/AWSEC2AsyncClient.java @@ -25,6 +25,7 @@ import org.jclouds.aws.ec2.services.AWSSecurityGroupAsyncClient; import org.jclouds.aws.ec2.services.MonitoringAsyncClient; import org.jclouds.aws.ec2.services.PlacementGroupAsyncClient; import org.jclouds.aws.ec2.services.SpotInstanceAsyncClient; +import org.jclouds.aws.ec2.services.TagAsyncClient; import org.jclouds.ec2.EC2AsyncClient; import org.jclouds.rest.annotations.Delegate; @@ -81,4 +82,10 @@ public interface AWSEC2AsyncClient extends EC2AsyncClient { */ @Delegate SpotInstanceAsyncClient getSpotInstanceServices(); + + /** + * Provides asynchronous access to SpotInstance services. + */ + @Delegate + TagAsyncClient getTagServices(); } diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/AWSEC2Client.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/AWSEC2Client.java index 0f586a6480..d4e1c984a1 100644 --- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/AWSEC2Client.java +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/AWSEC2Client.java @@ -27,6 +27,7 @@ import org.jclouds.aws.ec2.services.AWSSecurityGroupClient; import org.jclouds.aws.ec2.services.MonitoringClient; import org.jclouds.aws.ec2.services.PlacementGroupClient; import org.jclouds.aws.ec2.services.SpotInstanceClient; +import org.jclouds.aws.ec2.services.TagClient; import org.jclouds.concurrent.Timeout; import org.jclouds.ec2.EC2Client; import org.jclouds.rest.annotations.Delegate; @@ -84,4 +85,10 @@ public interface AWSEC2Client extends EC2Client { */ @Delegate SpotInstanceClient getSpotInstanceServices(); + + /** + * Provides synchronous access to Tag services. + */ + @Delegate + TagClient getTagServices(); } diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/BindResourceIdsToIndexedFormParams.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/BindResourceIdsToIndexedFormParams.java new file mode 100644 index 0000000000..0f427a5a58 --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/BindResourceIdsToIndexedFormParams.java @@ -0,0 +1,42 @@ +/** + * 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.aws.ec2.binders; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import javax.inject.Singleton; + +import org.jclouds.aws.util.AWSUtils; +import org.jclouds.http.HttpRequest; +import org.jclouds.rest.Binder; + +/** + * Binds Ids to query parameters named with ResourceId.index + * + * @author grkvlt@apache.org + */ +@Singleton +public class BindResourceIdsToIndexedFormParams implements Binder { + @Override + public R bindToRequest(R request, Object input) { + checkArgument(checkNotNull(input, "input") instanceof Iterable, "this binder is only valid for Iterable"); + return AWSUtils.indexIterableToFormValuesWithPrefix(request, "ResourceId", input); + } +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/BindTagFiltersToIndexedFormParams.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/BindTagFiltersToIndexedFormParams.java new file mode 100644 index 0000000000..55d0d85e64 --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/BindTagFiltersToIndexedFormParams.java @@ -0,0 +1,38 @@ +/** + * 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.aws.ec2.binders; + +import static com.google.common.base.Preconditions.*; + +import java.util.Map; + +import org.jclouds.aws.util.AWSUtils; +import org.jclouds.http.HttpRequest; +import org.jclouds.rest.Binder; + +/** + * @author grkvlt@apache.org + */ +public class BindTagFiltersToIndexedFormParams implements Binder { + @Override + public R bindToRequest(R request, Object input) { + checkArgument(checkNotNull(input, "input") instanceof Map, "this binder is only valid for Map>"); + return AWSUtils.indexMapOfIterableToFormValuesWithPrefix(request, "Filter", "Name", "Value", input); + } +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/BindTagsToIndexedFormParams.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/BindTagsToIndexedFormParams.java new file mode 100644 index 0000000000..7dd8069b2b --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/binders/BindTagsToIndexedFormParams.java @@ -0,0 +1,38 @@ +/** + * 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.aws.ec2.binders; + +import static com.google.common.base.Preconditions.*; + +import java.util.Map; + +import org.jclouds.aws.util.AWSUtils; +import org.jclouds.http.HttpRequest; +import org.jclouds.rest.Binder; + +/** + * @author grkvlt@apache.org + */ +public class BindTagsToIndexedFormParams implements Binder { + @Override + public R bindToRequest(R request, Object input) { + checkArgument(checkNotNull(input, "input") instanceof Map, "This binder is only valid for Map"); + return AWSUtils.indexMapToFormValuesWithPrefix(request, "Tag", "Key", "Value", input); + } +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/config/AWSEC2RestClientModule.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/config/AWSEC2RestClientModule.java index 7c4d8ecd3a..a5b4c7105f 100644 --- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/config/AWSEC2RestClientModule.java +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/config/AWSEC2RestClientModule.java @@ -40,6 +40,8 @@ import org.jclouds.aws.ec2.services.PlacementGroupAsyncClient; import org.jclouds.aws.ec2.services.PlacementGroupClient; import org.jclouds.aws.ec2.services.SpotInstanceAsyncClient; import org.jclouds.aws.ec2.services.SpotInstanceClient; +import org.jclouds.aws.ec2.services.TagAsyncClient; +import org.jclouds.aws.ec2.services.TagClient; import org.jclouds.ec2.EC2AsyncClient; import org.jclouds.ec2.EC2Client; import org.jclouds.ec2.config.EC2RestClientModule; @@ -86,6 +88,7 @@ public class AWSEC2RestClientModule extends EC2RestClientModule + * @author grkvlt@apache.org + */ +public class Tag implements Comparable { + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String resourceId; + private ResourceType resourceType; + private String key; + private String value; + + public void clear() { + this.resourceId = null; + this.resourceType = null; + this.key = null; + this.value = null; + } + + public Builder resourceId(String resourceId) { + this.resourceId = resourceId; + return this; + } + + public Builder resourceType(ResourceType resourceType) { + this.resourceType = resourceType; + return this; + } + + public Builder key(String key) { + this.key = key; + return this; + } + + public Builder value(String value) { + this.value = value; + return this; + } + + public Tag build() { + return new Tag(resourceId, resourceType, key, value); + } + } + + private final String resourceId; + private final ResourceType resourceType; + private final String key; + private final String value; + + public Tag(String resourceId, ResourceType resourceType, String key, String value) { + this.resourceId = checkNotNull(resourceId, "resourceId"); + this.resourceType = checkNotNull(resourceType, "resourceType"); + this.key = checkNotNull(key, "key"); + this.value = checkNotNull(value, "value"); + } + + public String getResourceId() { + return resourceId; + } + + public ResourceType getResourceType() { + return resourceType; + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + + @Override + public int compareTo(Tag t) { + return ComparisonChain.start() + .compare(resourceId, t.resourceId) + .compare(resourceType, t.resourceType) + .compare(key, t.key) + .compare(value, t.value) + .result(); + } + + @Override + public int hashCode() { + return Objects.hashCode(resourceId, resourceType, key, value); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Tag other = (Tag) obj; + if (resourceId == null) { + if (other.resourceId != null) + return false; + } else if (!resourceId.equals(other.resourceId)) + return false; + if (resourceType == null) { + if (other.resourceType != null) + return false; + } else if (!resourceType.equals(other.resourceType)) + return false; + if (value == null) { + if (other.value != null) + return false; + } else if (!value.equals(other.value)) + return false; + if (key == null) { + if (other.key != null) + return false; + } else if (!key.equals(other.key)) + return false; + return true; + } + + @Override + public String toString() { + return "[resourceId=" + resourceId + ", resourceType=" + resourceType + ", key=" + key + ", value=" + value + "]"; + } +} \ No newline at end of file diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/services/TagAsyncClient.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/services/TagAsyncClient.java new file mode 100644 index 0000000000..bba53f1ff7 --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/services/TagAsyncClient.java @@ -0,0 +1,91 @@ +/** + * 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.aws.ec2.services; + +import static org.jclouds.aws.reference.FormParameters.*; + +import java.util.Map; +import java.util.Set; + +import javax.ws.rs.POST; +import javax.ws.rs.Path; + +import org.jclouds.aws.ec2.AWSEC2AsyncClient; +import org.jclouds.aws.ec2.binders.BindResourceIdsToIndexedFormParams; +import org.jclouds.aws.ec2.binders.BindTagFiltersToIndexedFormParams; +import org.jclouds.aws.ec2.binders.BindTagsToIndexedFormParams; +import org.jclouds.aws.ec2.domain.Tag; +import org.jclouds.aws.ec2.util.TagFilters.FilterName; +import org.jclouds.aws.ec2.xml.DescribeTagsResponseHandler; +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.ExceptionParser; +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 org.jclouds.rest.functions.ReturnEmptySetOnNotFoundOr404; +import org.jclouds.rest.functions.ReturnVoidOnNotFoundOr404; + +import com.google.common.util.concurrent.ListenableFuture; + +/** + * Provides access to EC2 Tags via their REST API. + * + * @author grkvlt@apache.org + */ +@RequestFilters(FormSigner.class) +@FormParams(keys = VERSION, values = AWSEC2AsyncClient.VERSION) +@VirtualHost +public interface TagAsyncClient { + /** + * @see TagClient#createTagsInRegion(String, Iterable, Map) + */ + @POST + @Path("/") + @FormParams(keys = ACTION, values = "CreateTags") + ListenableFuture createTagsInRegion(@EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region, + @BinderParam(BindResourceIdsToIndexedFormParams.class) Iterable resourceIds, + @BinderParam(BindTagsToIndexedFormParams.class) Map tags); + + /** + * @see TagClient#deleteTagsInRegion(String, Iterable, Map) + */ + @POST + @Path("/") + @FormParams(keys = ACTION, values = "DeleteTags") + @ExceptionParser(ReturnVoidOnNotFoundOr404.class) + ListenableFuture deleteTagsInRegion(@EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region, + @BinderParam(BindResourceIdsToIndexedFormParams.class) Iterable resourceIds, + @BinderParam(BindTagsToIndexedFormParams.class) Map tags); + + /** + * @see TagClient#describeTagsInRegion(String, Map) + */ + @POST + @Path("/") + @FormParams(keys = ACTION, values = "DescribeTags") + @XMLResponseParser(DescribeTagsResponseHandler.class) + @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) + ListenableFuture> describeTagsInRegion(@EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region, + @BinderParam(BindTagFiltersToIndexedFormParams.class) Map> filters); +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/services/TagClient.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/services/TagClient.java new file mode 100644 index 0000000000..b77ff78897 --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/services/TagClient.java @@ -0,0 +1,84 @@ +/** + * 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.aws.ec2.services; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.jclouds.aws.ec2.domain.Tag; +import org.jclouds.aws.ec2.util.TagFilters.FilterName; +import org.jclouds.concurrent.Timeout; +import org.jclouds.javax.annotation.Nullable; + +/** + * Provides Tag services for EC2. For more information, refer to the Amazon EC2 + * Developer Guide. + * + * @author grkvlt@apache.org + */ +@Timeout(duration = 45, timeUnit = TimeUnit.SECONDS) +public interface TagClient { + /** + * Creates tags. + * + * @param region + * Region to create the tag in. + * @param resourceIds + * IDs of the resources to tag. + * @param tags + * The tags to create. + * @see #describeTagsInRegion(String, Map) + * @see #deleteTagsInRegion(String, Iterable, Map) + * + * @see + */ + void createTagsInRegion(@Nullable String region, Iterable resourceIds, Map tags); + + /** + * Deletes tags. + * + * @param region + * Region to delete the tags from + * @param resourceIds + * IDs of the tagged resources. + * @param tags + * The tags to delete. + * + * @see #describeTagsInRegion(String, Map) + * @see #createTagsInRegion(String, Iterable, Map) + * + * @see + */ + void deleteTagsInRegion(@Nullable String region, Iterable resourceIds, Map tags); + + /** + * Returns filtered information about tags. + * + * @param region + * The bundleTask ID is tied to the Region. + * @param filters + * A collection of filters to apply before selecting the tags. + * + * @see #deleteTagsInRegion(String, Iterable, Map) + * @see #createTagsInRegion(String, Iterable, Map) + * @see + */ + Set describeTagsInRegion(@Nullable String region, Map> filters); +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/util/TagFilters.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/util/TagFilters.java new file mode 100644 index 0000000000..8e877a0752 --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/util/TagFilters.java @@ -0,0 +1,315 @@ +/** + * 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.aws.ec2.util; + +import static com.google.common.base.Preconditions.*; + +import java.util.Map; + +import com.google.common.base.CaseFormat; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +/** + * @author grkvlt@apache.org + */ +public class TagFilters { + public static enum FilterName { + KEY, + RESOURCE_ID, + RESOURCE_TYPE, + VALUE; + + public String value() { + return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, name()); + } + + @Override + public String toString() { + return value(); + } + + public static FilterName fromValue(String name) { + try { + return valueOf(CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_UNDERSCORE, checkNotNull(name, "name"))); + } catch (IllegalArgumentException e) { + return null; + } + } + } + + public static enum ResourceType { + CUSTOMER_GATEWAY, + DHCP_OPTIONS, + IMAGE, + INSTANCE, + INTERNET_GATEWAY, + NETWORK_ACL, + RESERVED_INSTANCES, + ROUTE_TABLE, + SECURITY_GROUP, + SNAPSHOT, + SPOT_INSTANCES_REQUEST, + SUBNET, + VOLUME, + VPC, + VPN_CONNECTION, + VPN_GATEWAY; + + public String value() { + return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, name()); + } + + @Override + public String toString() { + return value(); + } + + public static ResourceType fromValue(String name) { + try { + return valueOf(CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_UNDERSCORE, checkNotNull(name, "name"))); + } catch (IllegalArgumentException e) { + return null; + } + } + } + + protected final Map> map; + + protected TagFilters() { + map = Maps.>newLinkedHashMap(); + } + + public static TagFilters filters() { + return new TagFilters(); + } + + public Map> build() { + return ImmutableMap.copyOf(map); + } + + public TagFilters resourceId(String resourceId) { + put(FilterName.RESOURCE_ID, resourceId); + return this; + } + + public TagFilters key(String key) { + put(FilterName.KEY, key); + return this; + } + + public TagFilters keys(String...keys) { + put(FilterName.KEY, ImmutableSet.copyOf(keys)); + return this; + } + + public TagFilters keys(Iterable keys) { + putAll(FilterName.KEY, ImmutableSet.copyOf(keys)); + return this; + } + + public TagFilters value(String value) { + put(FilterName.VALUE, value); + return this; + } + + public TagFilters values(String...values) { + putAll(FilterName.VALUE, ImmutableSet.copyOf(values)); + return this; + } + + public TagFilters values(Iterable values) { + putAll(FilterName.VALUE, ImmutableSet.copyOf(values)); + return this; + } + + public TagFilters keyContains(String key) { + return key(String.format("*%s*", key)); + } + + public TagFilters valueContains(String value) { + return value(String.format("*%s*", value)); + } + + public TagFilters resourceIdContains(String value) { + return resourceId(String.format("*%s*", value)); + } + + public TagFilters keyStartsWith(String key) { + return key(String.format("%s*", key)); + } + + public TagFilters valueStartsWith(String value) { + return value(String.format("%s*", value)); + } + + public TagFilters resourceIdStartsWith(String value) { + return resourceId(String.format("%s*", value)); + } + + public TagFilters keyEndsWith(String key) { + return key(String.format("*%s", key)); + } + + public TagFilters valueEndsWith(String value) { + return value(String.format("*%s", value)); + } + + public TagFilters resourceIdEndsWith(String value) { + return resourceId(String.format("*%s", value)); + } + + public TagFilters keyValuePair(String key, String value) { + return key(key).value(value); + } + + public TagFilters keyValueSet(String key, Iterable values) { + return key(key).values(values); + } + + public TagFilters keyValueSet(String key, String...values) { + return key(key).values(values); + } + + public TagFilters anyKey() { + putAll(FilterName.KEY, ImmutableSet.of()); + return this; + } + + public TagFilters anyValue() { + putAll(FilterName.VALUE, ImmutableSet.of()); + return this; + } + + public TagFilters anyResourceId() { + putAll(FilterName.RESOURCE_TYPE, ImmutableSet.of()); + return this; + } + + public TagFilters anyResourceType() { + putAll(FilterName.RESOURCE_TYPE, ImmutableSet.of()); + return this; + } + + public TagFilters resourceType(ResourceType resourceType) { + put(FilterName.RESOURCE_TYPE, resourceType); + return this; + } + + public TagFilters customerGateway() { + put(FilterName.RESOURCE_TYPE, ResourceType.CUSTOMER_GATEWAY); + return this; + } + + public TagFilters dhcpOptions() { + put(FilterName.RESOURCE_TYPE, ResourceType.DHCP_OPTIONS); + return this; + } + + public TagFilters image() { + put(FilterName.RESOURCE_TYPE, ResourceType.IMAGE); + return this; + } + + public TagFilters instance() { + put(FilterName.RESOURCE_TYPE, ResourceType.INSTANCE); + return this; + } + + public TagFilters internetGateway() { + put(FilterName.RESOURCE_TYPE, ResourceType.INTERNET_GATEWAY); + return this; + } + + public TagFilters networkAcl() { + put(FilterName.RESOURCE_TYPE, ResourceType.NETWORK_ACL); + return this; + } + + public TagFilters reservedInstance() { + put(FilterName.RESOURCE_TYPE, ResourceType.RESERVED_INSTANCES); + return this; + } + + public TagFilters routeTable() { + put(FilterName.RESOURCE_TYPE, ResourceType.ROUTE_TABLE); + return this; + } + + public TagFilters securityGroup() { + put(FilterName.RESOURCE_TYPE, ResourceType.SECURITY_GROUP); + return this; + } + + public TagFilters snapshot() { + put(FilterName.RESOURCE_TYPE, ResourceType.SNAPSHOT); + return this; + } + + public TagFilters instancesRequest() { + put(FilterName.RESOURCE_TYPE, ResourceType.SPOT_INSTANCES_REQUEST); + return this; + } + + public TagFilters subnet() { + put(FilterName.RESOURCE_TYPE, ResourceType.SUBNET); + return this; + } + + public TagFilters volume() { + put(FilterName.RESOURCE_TYPE, ResourceType.VOLUME); + return this; + } + + public TagFilters vpc() { + put(FilterName.RESOURCE_TYPE, ResourceType.VPC); + return this; + } + + public TagFilters vpnConnection() { + put(FilterName.RESOURCE_TYPE, ResourceType.VPN_CONNECTION); + return this; + } + + public TagFilters vpnGateway() { + put(FilterName.RESOURCE_TYPE, ResourceType.VPN_GATEWAY); + return this; + } + + private void put(FilterName key, Object value) { + putAll(key, Sets.newHashSet(value)); + } + + private void putAll(FilterName key, Iterable values) { + if (values == null || Iterables.isEmpty(values)) { + // If we add an empty or null set of values, replace the value in the map entirely + map.put(key, ImmutableSet.of()); + } else { + // Add the values, to a new set if required + if (!map.containsKey(key)) { + map.put(key, Sets.newHashSet()); + } + Iterable entries = map.get(key); + map.put(key, Iterables.concat(entries, values)); + } + } +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/DescribeTagsResponseHandler.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/DescribeTagsResponseHandler.java new file mode 100644 index 0000000000..1d6cbd3948 --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/DescribeTagsResponseHandler.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.aws.ec2.xml; + +import java.util.Set; + +import javax.inject.Inject; + +import org.jclouds.aws.ec2.domain.Tag; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.functions.ParseSax; +import org.jclouds.http.functions.ParseSax.HandlerWithResult; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +import com.google.common.collect.Sets; + +/** + * @author grkvlt@apache.org + */ +public class DescribeTagsResponseHandler extends ParseSax.HandlerWithResult> { + private Set bundleTasks = Sets.newLinkedHashSet(); + private final TagsHandler bundleTaskHandler; + + @Inject + public DescribeTagsResponseHandler(TagsHandler bundleTaskHandler) { + this.bundleTaskHandler = bundleTaskHandler; + } + + public Set getResult() { + return bundleTasks; + } + + @Override + public HandlerWithResult> setContext(HttpRequest request) { + bundleTaskHandler.setContext(request); + return super.setContext(request); + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + if (!qName.equals("item")) + bundleTaskHandler.startElement(uri, localName, qName, attributes); + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + if (qName.equals("item")) { + bundleTasks.add(bundleTaskHandler.getResult()); + } + bundleTaskHandler.endElement(uri, localName, qName); + } + + public void characters(char ch[], int start, int length) { + bundleTaskHandler.characters(ch, start, length); + } +} diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/TagsHandler.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/TagsHandler.java new file mode 100644 index 0000000000..6d2a7a64ef --- /dev/null +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/xml/TagsHandler.java @@ -0,0 +1,57 @@ +/** + * 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.aws.ec2.xml; + +import org.jclouds.aws.ec2.domain.Tag; +import org.jclouds.aws.ec2.util.TagFilters.ResourceType; +import org.jclouds.http.functions.ParseSax; + +/** + * @author grkvlt@apache.org + */ +public class TagsHandler extends ParseSax.HandlerForGeneratedRequestWithResult { + private StringBuilder currentText = new StringBuilder(); + + private String resourceId; + private ResourceType resourceType; + private String key; + private String value; + + public Tag getResult() { + Tag returnVal = new Tag(resourceId, resourceType, key, value); + return returnVal; + } + + public void endElement(String uri, String name, String qName) { + if (qName.equals("resourceId")) { + this.resourceId = currentText.toString().trim(); + } else if (qName.equals("resourceType")) { + resourceType = ResourceType.fromValue(currentText.toString().trim()); + } else if (qName.equals("key")) { + key = currentText.toString().trim(); + } else if (qName.equals("value")) { + value = currentText.toString().trim(); + } + currentText = new StringBuilder(); + } + + public void characters(char ch[], int start, int length) { + currentText.append(ch, start, length); + } +} diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/AWSEC2AsyncClientTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/AWSEC2AsyncClientTest.java index b522bcef82..ac240cffa1 100644 --- a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/AWSEC2AsyncClientTest.java +++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/AWSEC2AsyncClientTest.java @@ -52,6 +52,7 @@ public class AWSEC2AsyncClientTest extends BaseAWSEC2AsyncClientTest>builder().put(FilterName.RESOURCE_TYPE, ImmutableSet.of(ResourceType.VPN_GATEWAY, ResourceType.INTERNET_GATEWAY)).build()); + assertEquals(request.getPayload().getRawContent(), "Filter.1.Name=resource-type&Filter.1.Value.1=vpn-gateway&Filter.1.Value.2=internet-gateway"); + } + + public void testMultipleKeys() { + HttpRequest request = HttpRequest.builder().method("POST").endpoint(URI.create("http://localhost")).build(); + request = binder.bindToRequest(request, ImmutableMap.>builder().put(FilterName.KEY, ImmutableSet.of("one", "two")).build()); + assertEquals(request.getPayload().getRawContent(), "Filter.1.Name=key&Filter.1.Value.1=one&Filter.1.Value.2=two"); + } + + public void testkeyWithValue() { + HttpRequest request = HttpRequest.builder().method("POST").endpoint(URI.create("http://localhost")).build(); + request = binder.bindToRequest(request, ImmutableMap.>builder().put(FilterName.KEY, ImmutableSet.of("one")).put(FilterName.VALUE, ImmutableSet.of("alpha")).build()); + assertEquals(request.getPayload().getRawContent(), "Filter.1.Name=key&Filter.1.Value.1=one&Filter.2.Name=value&Filter.2.Value.1=alpha"); + } + + public void testAnyKey() { + HttpRequest request = HttpRequest.builder().method("POST").endpoint(URI.create("http://localhost")).build(); + request = binder.bindToRequest(request, ImmutableMap.>builder().put(FilterName.KEY, ImmutableSet.of()).build()); + assertEquals(request.getPayload().getRawContent(), "Filter.1.Name=key"); + } + + public void testMultipleResourceTypesBuilder() { + HttpRequest request = HttpRequest.builder().method("POST").endpoint(URI.create("http://localhost")).build(); + request = binder.bindToRequest(request, TagFilters.filters().vpnGateway().internetGateway().build()); + assertEquals(request.getPayload().getRawContent(), "Filter.1.Name=resource-type&Filter.1.Value.1=vpn-gateway&Filter.1.Value.2=internet-gateway"); + } + + public void testMultipleKeysBuilder() { + HttpRequest request = HttpRequest.builder().method("POST").endpoint(URI.create("http://localhost")).build(); + request = binder.bindToRequest(request, TagFilters.filters().key("one").key("two").build()); + assertEquals(request.getPayload().getRawContent(), "Filter.1.Name=key&Filter.1.Value.1=one&Filter.1.Value.2=two"); + } + + public void testKeyWithValueBuilder() { + HttpRequest request = HttpRequest.builder().method("POST").endpoint(URI.create("http://localhost")).build(); + request = binder.bindToRequest(request, TagFilters.filters().keyValuePair("one", "alpha").build()); + assertEquals(request.getPayload().getRawContent(), "Filter.1.Name=key&Filter.1.Value.1=one&Filter.2.Name=value&Filter.2.Value.1=alpha"); + } + + public void testAnyKeyBuilder() { + HttpRequest request = HttpRequest.builder().method("POST").endpoint(URI.create("http://localhost")).build(); + request = binder.bindToRequest(request, TagFilters.filters().anyKey().build()); + assertEquals(request.getPayload().getRawContent(), "Filter.1.Name=key"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testMustBeArray() { + HttpRequest request = new HttpRequest(HttpMethod.POST, URI.create("http://localhost")); + binder.bindToRequest(request, new File("foo")); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testNullIsBad() { + HttpRequest request = HttpRequest.builder().method("GET").endpoint(URI.create("http://momma")).build(); + binder.bindToRequest(request, null); + } +} \ No newline at end of file diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/binders/BindTagsToIndexedFormParamsTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/binders/BindTagsToIndexedFormParamsTest.java new file mode 100644 index 0000000000..3f32a31283 --- /dev/null +++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/binders/BindTagsToIndexedFormParamsTest.java @@ -0,0 +1,62 @@ +/** + * 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.aws.ec2.binders; + +import static org.testng.Assert.*; + +import java.io.File; +import java.net.URI; + +import javax.ws.rs.HttpMethod; + +import org.jclouds.http.HttpRequest; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMap; +import com.google.inject.Guice; +import com.google.inject.Injector; + +/** + * Tests behavior of {@code BindTagsToIndexedFormParams} + * + * @author grkvlt@apache.org + */ +@Test(groups = "unit") +public class BindTagsToIndexedFormParamsTest { + Injector injector = Guice.createInjector(); + BindTagsToIndexedFormParams binder = injector.getInstance(BindTagsToIndexedFormParams.class); + + public void test() { + HttpRequest request = HttpRequest.builder().method("POST").endpoint(URI.create("http://localhost")).build(); + request = binder.bindToRequest(request, ImmutableMap.builder().put("one", "alpha").put("two", "beta").build()); + assertEquals(request.getPayload().getRawContent(), "Tag.1.Key=one&Tag.1.Value=alpha&Tag.2.Key=two&Tag.2.Value=beta"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testMustBeArray() { + HttpRequest request = new HttpRequest(HttpMethod.POST, URI.create("http://localhost")); + binder.bindToRequest(request, new File("foo")); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testNullIsBad() { + HttpRequest request = HttpRequest.builder().method("GET").endpoint(URI.create("http://momma")).build(); + binder.bindToRequest(request, null); + } +} \ No newline at end of file diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/services/TagAsyncClientTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/services/TagAsyncClientTest.java new file mode 100644 index 0000000000..4b0292f020 --- /dev/null +++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/services/TagAsyncClientTest.java @@ -0,0 +1,116 @@ +/** + * 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.aws.ec2.services; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.Map; + +import org.jclouds.aws.ec2.util.TagFilters; +import org.jclouds.aws.ec2.xml.DescribeTagsResponseHandler; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.functions.ParseSax; +import org.jclouds.http.functions.ReleasePayloadAndReturn; +import org.jclouds.rest.functions.ReturnEmptySetOnNotFoundOr404; +import org.jclouds.rest.functions.ReturnVoidOnNotFoundOr404; +import org.jclouds.rest.internal.RestAnnotationProcessor; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.inject.TypeLiteral; + +/** + * Tests behavior of {@code TagsAsyncClient} + * + * @author grkvlt@apache.org + */ +// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire +@Test(groups = "unit", testName = "TagsAsyncClientTest") +public class TagAsyncClientTest extends BaseAWSEC2AsyncClientTest { + public void testDeleteTags() throws SecurityException, NoSuchMethodException, IOException { + Method method = TagAsyncClient.class.getMethod("deleteTagsInRegion", String.class, Iterable.class, Map.class); + HttpRequest request = processor.createRequest(method, null, ImmutableList.builder().add("xxx").build(), ImmutableMap.builder().put("yyy", "zzz").build()); + + assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1"); + assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n"); + assertPayloadEquals(request, "Version=2011-05-15&Action=DeleteTags&Tag.1.Key=yyy&Tag.1.Value=zzz&ResourceId.1=xxx", + "application/x-www-form-urlencoded", false); + + assertResponseParserClassEquals(method, request, ReleasePayloadAndReturn.class); + assertSaxResponseParserClassEquals(method, null); + assertExceptionParserClassEquals(method, ReturnVoidOnNotFoundOr404.class); + + checkFilters(request); + } + + public void testCreateTags() throws SecurityException, NoSuchMethodException, IOException { + Method method = TagAsyncClient.class.getMethod("createTagsInRegion", String.class, Iterable.class, Map.class); + HttpRequest request = processor.createRequest(method, null, ImmutableList.builder().add("xxx").build(), ImmutableMap.builder().put("yyy", "zzz").build()); + + assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1"); + assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n"); + assertPayloadEquals(request, "Version=2011-05-15&Action=CreateTags&Tag.1.Key=yyy&Tag.1.Value=zzz&ResourceId.1=xxx", + "application/x-www-form-urlencoded", false); + + assertResponseParserClassEquals(method, request, ReleasePayloadAndReturn.class); + assertSaxResponseParserClassEquals(method, null); + assertExceptionParserClassEquals(method, null); + + checkFilters(request); + } + + public void testDescribeTags() throws SecurityException, NoSuchMethodException, IOException { + Method method = TagAsyncClient.class.getMethod("describeTagsInRegion", String.class, Map.class); + HttpRequest request = processor.createRequest(method, null, TagFilters.filters().build()); + + assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1"); + assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n"); + assertPayloadEquals(request, "Version=2011-05-15&Action=DescribeTags", + "application/x-www-form-urlencoded", false); + + assertResponseParserClassEquals(method, request, ParseSax.class); + assertSaxResponseParserClassEquals(method, DescribeTagsResponseHandler.class); + assertExceptionParserClassEquals(method, ReturnEmptySetOnNotFoundOr404.class); + + checkFilters(request); + } + + public void testDescribeTagsArgs() throws SecurityException, NoSuchMethodException, IOException { + Method method = TagAsyncClient.class.getMethod("describeTagsInRegion", String.class, Map.class); + HttpRequest request = processor.createRequest(method, null, TagFilters.filters().key("one").key("two").build()); + + assertRequestLineEquals(request, "POST https://ec2.us-east-1.amazonaws.com/ HTTP/1.1"); + assertNonPayloadHeadersEqual(request, "Host: ec2.us-east-1.amazonaws.com\n"); + assertPayloadEquals(request, "Version=2011-05-15&Action=DescribeTags&Filter.1.Name=key&Filter.1.Value.1=one&Filter.1.Value.2=two", + "application/x-www-form-urlencoded", false); + + assertResponseParserClassEquals(method, request, ParseSax.class); + assertSaxResponseParserClassEquals(method, DescribeTagsResponseHandler.class); + assertExceptionParserClassEquals(method, ReturnEmptySetOnNotFoundOr404.class); + + checkFilters(request); + } + + @Override + protected TypeLiteral> createTypeLiteral() { + return new TypeLiteral>() { + }; + } +} diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/services/TagClientLiveTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/services/TagClientLiveTest.java new file mode 100644 index 0000000000..08db5d22cf --- /dev/null +++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/services/TagClientLiveTest.java @@ -0,0 +1,146 @@ +/** + * 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.aws.ec2.services; + +import static com.google.common.base.Preconditions.*; +import static com.google.common.collect.Iterables.*; +import static org.testng.Assert.*; + +import java.util.Properties; +import java.util.Set; + +import org.jclouds.Constants; +import org.jclouds.aws.ec2.AWSEC2AsyncClient; +import org.jclouds.aws.ec2.AWSEC2Client; +import org.jclouds.aws.ec2.domain.AWSRunningInstance; +import org.jclouds.aws.ec2.domain.Tag; +import org.jclouds.aws.ec2.util.TagFilters; +import org.jclouds.aws.ec2.util.TagFilters.ResourceType; +import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.compute.ComputeServiceContextFactory; +import org.jclouds.logging.log4j.config.Log4JLoggingModule; +import org.jclouds.rest.RestContext; +import org.jclouds.sshj.config.SshjSshClientModule; +import org.testng.annotations.AfterGroups; +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeGroups; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.inject.Module; + +/** + * Tests live behavior of {@code TagClient} + * + * @author grkvlt@apache.org + */ +@Test(groups = "live", sequential = true) +public class TagClientLiveTest { + + private TagClient client; + private RestContext context; + + protected String provider = "aws-ec2"; + protected String identity; + protected String credential; + protected String endpoint; + protected String apiversion; + protected String testGroup; + private ComputeServiceContext computeContext; + + protected void setupCredentials() { + identity = checkNotNull(System.getProperty("test." + provider + ".identity"), "test." + provider + ".identity"); + credential = checkNotNull(System.getProperty("test." + provider + ".credential"), "test." + provider + ".credential"); + endpoint = System.getProperty("test." + provider + ".endpoint", null); + apiversion = System.getProperty("test." + provider + ".apiversion", null); + } + + protected Properties setupProperties() { + Properties overrides = new Properties(); + overrides.setProperty(Constants.PROPERTY_TRUST_ALL_CERTS, "true"); + overrides.setProperty(Constants.PROPERTY_RELAX_HOSTNAME, "true"); + overrides.setProperty(provider + ".identity", identity); + overrides.setProperty(provider + ".credential", credential); + if (endpoint != null) + overrides.setProperty(provider + ".endpoint", endpoint); + if (apiversion != null) + overrides.setProperty(provider + ".apiversion", apiversion); + return overrides; + } + + @BeforeGroups(groups = { "live" }) + public void setupClientAndSecurityGroup() { + setupCredentials(); + Properties overrides = setupProperties(); + computeContext = new ComputeServiceContextFactory().createContext(provider, ImmutableSet. of( + new Log4JLoggingModule(), new SshjSshClientModule()), overrides); + context = computeContext.getProviderSpecificContext(); + client = context.getApi().getTagServices(); + testGroup = context.getApi().getSecurityGroupServices().createSecurityGroupInRegionAndReturnId(null, "test-group", "test-group"); + } + + @AfterGroups(groups = { "live" }) + public void deleteSecurityGroup() { + context.getApi().getSecurityGroupServices().deleteSecurityGroupInRegionById(null, testGroup); + } + + public static final String PREFIX = System.getProperty("user.name") + "-ec2"; + + @Test + void test() { + cleanupTag(testGroup, "test-key"); + try { + client.createTagsInRegion(null, ImmutableList.builder().add(testGroup).build(), ImmutableMap.builder().put("test-key", "test-value").build()); + checkTag(testGroup, ResourceType.SECURITY_GROUP, "test-key", "test-value"); + } finally { + cleanupTag(testGroup, "test-key"); + } + } + + protected void cleanupTag(String resourceId, String key) { + try { + client.deleteTagsInRegion(null, ImmutableList.builder().add(resourceId).build(), ImmutableMap.builder().put(key, null).build()); + } catch (Exception e) { + // Ignore + } + } + + protected void checkTag(String resourceId, ResourceType resourceType, String key, String value) { + Set results = client.describeTagsInRegion(null, TagFilters.filters().resourceId(resourceId).resourceType(resourceType).keyValuePair(key, value).build()); + assertNotNull(results); + assertEquals(results.size(), 1); + Tag tag = Iterables.getOnlyElement(results); + assertEquals(tag.getResourceId(), resourceId); + assertEquals(tag.getResourceType(), resourceType); + assertEquals(tag.getKey(), key); + assertEquals(tag.getValue(), value); + } + + protected AWSRunningInstance getInstance(AWSInstanceClient instanceClient, String id) { + return getOnlyElement(getOnlyElement(instanceClient.describeInstancesInRegion(null, id))); + } + + @AfterTest + public void shutdown() { + context.close(); + } +}