From 0bc602a3d26b93c18dc840089a81f25c94dad782 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Wed, 25 Jul 2012 22:42:05 -0700 Subject: [PATCH] Issue 309: support CRUD on instances, w/incidental security group fns --- .../BindInstanceRequestToFormParams.java | 79 ++++ .../org/jclouds/rds/domain/Authorization.java | 151 +++++++ .../jclouds/rds/domain/EC2SecurityGroup.java | 36 +- .../java/org/jclouds/rds/domain/IPRange.java | 45 +-- .../java/org/jclouds/rds/domain/Instance.java | 283 ++++++------- .../jclouds/rds/domain/InstanceRequest.java | 374 ++++++++++++++++++ .../org/jclouds/rds/domain/SecurityGroup.java | 3 +- .../rds/domain/internal/BaseInstance.java | 205 ++++++++++ .../org/jclouds/rds/features/InstanceApi.java | 64 ++- .../rds/features/InstanceAsyncApi.java | 56 ++- .../rds/features/SecurityGroupApi.java | 119 ++++++ .../rds/features/SecurityGroupAsyncApi.java | 89 ++++- ...eturnNullOnStateDeletingNotFoundOr404.java | 51 +++ .../rds/xml/EC2SecurityGroupHandler.java | 7 +- .../org/jclouds/rds/xml/IPRangeHandler.java | 7 +- .../org/jclouds/rds/xml/InstanceHandler.java | 14 +- .../rds/features/InstanceApiExpectTest.java | 119 +++++- .../rds/features/InstanceApiLiveTest.java | 149 +++++++ .../features/SecurityGroupApiExpectTest.java | 220 +++++++++++ .../features/SecurityGroupApiLiveTest.java | 104 ++++- .../DescribeDBInstancesResponseTest.java | 3 +- .../DescribeDBSecurityGroupsResponseTest.java | 14 +- .../rds/parse/GetInstanceResponseTest.java | 3 +- .../parse/GetSecurityGroupResponseTest.java | 9 +- .../resources/authorize_securitygroup.xml | 20 + .../src/test/resources/create_instance.xml | 67 ++++ .../test/resources/create_securitygroup.xml | 15 + .../src/test/resources/delete_instance.xml | 42 ++ labs/rds/src/test/resources/invalid_state.xml | 8 + .../test/resources/revoke_securitygroup.xml | 20 + 30 files changed, 2117 insertions(+), 259 deletions(-) create mode 100644 labs/rds/src/main/java/org/jclouds/rds/binders/BindInstanceRequestToFormParams.java create mode 100644 labs/rds/src/main/java/org/jclouds/rds/domain/Authorization.java create mode 100644 labs/rds/src/main/java/org/jclouds/rds/domain/InstanceRequest.java create mode 100644 labs/rds/src/main/java/org/jclouds/rds/domain/internal/BaseInstance.java create mode 100644 labs/rds/src/main/java/org/jclouds/rds/functions/ReturnNullOnStateDeletingNotFoundOr404.java create mode 100644 labs/rds/src/test/resources/authorize_securitygroup.xml create mode 100644 labs/rds/src/test/resources/create_instance.xml create mode 100644 labs/rds/src/test/resources/create_securitygroup.xml create mode 100644 labs/rds/src/test/resources/delete_instance.xml create mode 100644 labs/rds/src/test/resources/invalid_state.xml create mode 100644 labs/rds/src/test/resources/revoke_securitygroup.xml diff --git a/labs/rds/src/main/java/org/jclouds/rds/binders/BindInstanceRequestToFormParams.java b/labs/rds/src/main/java/org/jclouds/rds/binders/BindInstanceRequestToFormParams.java new file mode 100644 index 0000000000..1f11b9e4d3 --- /dev/null +++ b/labs/rds/src/main/java/org/jclouds/rds/binders/BindInstanceRequestToFormParams.java @@ -0,0 +1,79 @@ +/** + * 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.rds.binders; + +import static com.google.common.base.Preconditions.checkNotNull; + +import org.jclouds.http.HttpRequest; +import org.jclouds.rds.domain.InstanceRequest; + +import com.google.common.collect.ImmutableMultimap; + +/** + * + * @see doc + * + * @author Adrian Cole + */ +public class BindInstanceRequestToFormParams implements org.jclouds.rest.Binder { + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public R bindToRequest(R request, Object input) { + InstanceRequest instanceRequest = InstanceRequest.class.cast(checkNotNull(input, "instanceRequest must be set!")); + + ImmutableMultimap.Builder formParameters = ImmutableMultimap.builder(); + + formParameters.put("AllocatedStorage", instanceRequest.getAllocatedStorageGB() + ""); + formParameters.put("AutoMinorVersionUpgrade", instanceRequest.isAutoMinorVersionUpgrade() + ""); + formParameters.put("BackupRetentionPeriod", instanceRequest.getBackupRetentionPeriod() + ""); + if (instanceRequest.getCharacterSet().isPresent()) + formParameters.put("CharacterSetName", instanceRequest.getCharacterSet().get()); + formParameters.put("DBInstanceClass", instanceRequest.getInstanceClass()); + if (instanceRequest.getName().isPresent()) + formParameters.put("DBName", instanceRequest.getName().get()); + if (instanceRequest.getParameterGroup().isPresent()) + formParameters.put("DBParameterGroupName", instanceRequest.getParameterGroup().get()); + int groupIndex = 1; + for (String securityGroup : instanceRequest.getSecurityGroups()) + formParameters.put("DBSecurityGroups.member." + groupIndex++, securityGroup); + if (instanceRequest.getSubnetGroup().isPresent()) + formParameters.put("DBSubnetGroupName", instanceRequest.getSubnetGroup().get()); + formParameters.put("Engine", instanceRequest.getEngine()); + if (instanceRequest.getEngineVersion().isPresent()) + formParameters.put("EngineVersion", instanceRequest.getEngineVersion().get()); + if (instanceRequest.getLicenseModel().isPresent()) + formParameters.put("LicenseModel", instanceRequest.getLicenseModel().get()); + formParameters.put("MasterUserPassword", instanceRequest.getMasterPassword()); + formParameters.put("MasterUsername", instanceRequest.getMasterUsername()); + if (instanceRequest.getOptionGroup().isPresent()) + formParameters.put("OptionGroupName", instanceRequest.getOptionGroup().get()); + if (instanceRequest.getPort().isPresent()) + formParameters.put("Port", instanceRequest.getPort().get().toString()); + + return (R) request.toBuilder().replaceFormParams(formParameters.build()).build(); + + } + +} diff --git a/labs/rds/src/main/java/org/jclouds/rds/domain/Authorization.java b/labs/rds/src/main/java/org/jclouds/rds/domain/Authorization.java new file mode 100644 index 0000000000..ea6971cb83 --- /dev/null +++ b/labs/rds/src/main/java/org/jclouds/rds/domain/Authorization.java @@ -0,0 +1,151 @@ +/** + * 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.rds.domain; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.CaseFormat; +import com.google.common.base.Objects; + +/** + * + * @author Adrian Cole + */ +public class Authorization { + /** + * Status of a source of traffic to the security group + */ + public static enum Status { + + AUTHORIZING, AUTHORIZED, REVOKING, UNRECOGNIZED; + + public String value() { + return name().toLowerCase(); + } + + @Override + public String toString() { + return value(); + } + + public static Status fromValue(String status) { + try { + return valueOf(CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_UNDERSCORE, checkNotNull(status, "status"))); + } catch (IllegalArgumentException e) { + return UNRECOGNIZED; + } + } + } + + public static Builder builder() { + return new ConcreteBuilder(); + } + + public Builder toBuilder() { + return new ConcreteBuilder().fromAuthorization(this); + } + + public static abstract class Builder> { + protected abstract T self(); + + protected String rawStatus; + protected Status status; + + /** + * @see Authorization#getRawStatus() + */ + public T rawStatus(String rawStatus) { + this.rawStatus = rawStatus; + return self(); + } + + /** + * @see Authorization#getStatus() + */ + public T status(Status status) { + this.status = status; + return self(); + } + + public T fromAuthorization(Authorization in) { + return this.rawStatus(in.getRawStatus()).status(in.getStatus()); + } + } + + private static class ConcreteBuilder extends Builder { + @Override + protected ConcreteBuilder self() { + return this; + } + } + + protected final String rawStatus; + protected final Status status; + + protected Authorization(String rawStatus, Status status) { + this.rawStatus = checkNotNull(rawStatus, "rawStatus"); + this.status = checkNotNull(status, "status"); + } + + /** + * Specifies the status of the authorization. + */ + public Status getStatus() { + return status; + } + + /** + * Specifies the status of the authorization. + */ + public String getRawStatus() { + return rawStatus; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(rawStatus); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Authorization other = (Authorization) obj; + return Objects.equal(this.rawStatus, other.rawStatus); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return Objects.toStringHelper(this).omitNullValues().add("status", rawStatus).toString(); + } + +} diff --git a/labs/rds/src/main/java/org/jclouds/rds/domain/EC2SecurityGroup.java b/labs/rds/src/main/java/org/jclouds/rds/domain/EC2SecurityGroup.java index 13efdf969b..8eabd920be 100644 --- a/labs/rds/src/main/java/org/jclouds/rds/domain/EC2SecurityGroup.java +++ b/labs/rds/src/main/java/org/jclouds/rds/domain/EC2SecurityGroup.java @@ -31,7 +31,7 @@ import com.google.common.base.Optional; * * @author Adrian Cole */ -public class EC2SecurityGroup { +public class EC2SecurityGroup extends Authorization { public static Builder builder() { return new Builder(); @@ -41,12 +41,11 @@ public class EC2SecurityGroup { return new Builder().fromEC2SecurityGroup(this); } - public static class Builder { + public static class Builder extends Authorization.Builder { protected Optional id = Optional.absent(); protected String name; protected String ownerId; - protected String status; /** * @see EC2SecurityGroup#getId() @@ -72,33 +71,29 @@ public class EC2SecurityGroup { return this; } - /** - * @see EC2SecurityGroup#getStatus() - */ - public Builder status(String status) { - this.status = status; - return this; - } - public EC2SecurityGroup build() { - return new EC2SecurityGroup(id, name, ownerId, status); + return new EC2SecurityGroup(id, name, ownerId, rawStatus, status); } public Builder fromEC2SecurityGroup(EC2SecurityGroup in) { - return this.id(in.getId().orNull()).name(in.getName()).ownerId(in.getOwnerId()).status(in.getStatus()); + return fromAuthorization(in).id(in.getId().orNull()).name(in.getName()).ownerId(in.getOwnerId()); + } + + @Override + protected Builder self() { + return this; } } protected final Optional id; protected final String name; protected final String ownerId; - protected final String status; - protected EC2SecurityGroup(Optional id, String name, String ownerId, String status) { + protected EC2SecurityGroup(Optional id, String name, String ownerId, String rawStatus, Status status) { + super(rawStatus, status); this.id = checkNotNull(id, "id"); this.name = checkNotNull(name, "name"); this.ownerId = checkNotNull(ownerId, "ownerId"); - this.status = checkNotNull(status, "status"); } /** @@ -122,13 +117,6 @@ public class EC2SecurityGroup { return ownerId; } - /** - * Provides the status of the EC2 security group. - */ - public String getStatus() { - return status; - } - /** * {@inheritDoc} */ @@ -159,7 +147,7 @@ public class EC2SecurityGroup { @Override public String toString() { return Objects.toStringHelper(this).omitNullValues().add("id", id.orNull()).add("name", name) - .add("ownerId", ownerId).add("status", status).toString(); + .add("ownerId", ownerId).add("status", rawStatus).toString(); } } diff --git a/labs/rds/src/main/java/org/jclouds/rds/domain/IPRange.java b/labs/rds/src/main/java/org/jclouds/rds/domain/IPRange.java index 76f70e7b0a..83146d749e 100644 --- a/labs/rds/src/main/java/org/jclouds/rds/domain/IPRange.java +++ b/labs/rds/src/main/java/org/jclouds/rds/domain/IPRange.java @@ -23,13 +23,13 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.base.Objects; /** - * + * * @see doc * * @author Adrian Cole */ -public class IPRange { +public class IPRange extends Authorization { public static Builder builder() { return new Builder(); @@ -39,42 +39,37 @@ public class IPRange { return new Builder().fromIPRange(this); } - public static class Builder { + public static class Builder extends Authorization.Builder { protected String cidrIp; - protected String status; /** - * @see IPRange#getAvailabilityZone() + * @see IPRange#getCidrIp() */ public Builder cidrIp(String cidrIp) { this.cidrIp = cidrIp; return this; } - /** - * @see IPRange#getStatus() - */ - public Builder status(String status) { - this.status = status; - return this; - } - public IPRange build() { - return new IPRange(cidrIp, status); + return new IPRange(cidrIp, rawStatus, status); } public Builder fromIPRange(IPRange in) { - return this.cidrIp(in.getCIDRIP()).status(in.getStatus()); + return fromAuthorization(in).cidrIp(in.getCIDRIP()); + } + + @Override + protected Builder self() { + return this; } } protected final String cidrIp; - protected final String status; - protected IPRange(String cidrIp, String status) { + protected IPRange(String cidrIp, String rawStatus, Status status) { + super(rawStatus, status); this.cidrIp = checkNotNull(cidrIp, "cidrIp"); - this.status = checkNotNull(status, "status"); } /** @@ -84,19 +79,12 @@ public class IPRange { return cidrIp; } - /** - * Specifies the status of the IP range. - */ - public String getStatus() { - return status; - } - /** * {@inheritDoc} */ @Override public int hashCode() { - return Objects.hashCode(cidrIp, status); + return Objects.hashCode(cidrIp, rawStatus); } /** @@ -111,7 +99,7 @@ public class IPRange { if (getClass() != obj.getClass()) return false; IPRange other = IPRange.class.cast(obj); - return Objects.equal(this.cidrIp, other.cidrIp) && Objects.equal(this.status, other.status); + return Objects.equal(this.cidrIp, other.cidrIp) && Objects.equal(this.rawStatus, other.rawStatus); } /** @@ -119,8 +107,7 @@ public class IPRange { */ @Override public String toString() { - return Objects.toStringHelper(this).omitNullValues().add("cidrIp", cidrIp) - .add("status", status).toString(); + return Objects.toStringHelper(this).omitNullValues().add("cidrIp", cidrIp).add("status", rawStatus).toString(); } } diff --git a/labs/rds/src/main/java/org/jclouds/rds/domain/Instance.java b/labs/rds/src/main/java/org/jclouds/rds/domain/Instance.java index 1fe7a28c86..43faf0f716 100644 --- a/labs/rds/src/main/java/org/jclouds/rds/domain/Instance.java +++ b/labs/rds/src/main/java/org/jclouds/rds/domain/Instance.java @@ -23,6 +23,9 @@ import static com.google.common.base.Preconditions.checkNotNull; import java.util.Date; import java.util.Map; +import org.jclouds.rds.domain.internal.BaseInstance; + +import com.google.common.base.CaseFormat; import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.collect.ImmutableMap; @@ -57,7 +60,41 @@ import com.google.common.net.HostAndPort; * * @author Adrian Cole */ -public class Instance { +public class Instance extends BaseInstance { + public static enum Status { + + /** + * the instance is in the process of being created + */ + CREATING, + + /** + * the instance is available + */ + AVAILABLE, STORAGE_FULL, INCOMPATIBLE_OPTION_GROUP, INCOMPATIBLE_PARAMETERS, INCOMPATIBLE_RESTORE, FAILED, + /** + * the instance is deleting + */ + DELETING, UNRECOGNIZED; + + public String value() { + return name().toLowerCase(); + } + + @Override + public String toString() { + return value(); + } + + public static Status fromValue(String status) { + try { + return valueOf(CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_UNDERSCORE, checkNotNull(status, "status"))); + } catch (IllegalArgumentException e) { + return UNRECOGNIZED; + } + } + } + public static Builder builder() { return new ConcreteBuilder(); } @@ -66,22 +103,17 @@ public class Instance { return new ConcreteBuilder().fromInstance(this); } - public static abstract class Builder> { - protected abstract T self(); + public static abstract class Builder> extends BaseInstance.Builder { protected String id; - protected Optional name = Optional.absent(); - protected String instanceClass; - protected HostAndPort endpoint; - protected String status; - protected String availabilityZone; - protected boolean multiAZ; - protected String engine; + protected Optional endpoint = Optional.absent(); protected String engineVersion; + protected String rawStatus; + protected Status status; + protected Optional createdTime = Optional.absent(); protected String licenseModel; - protected String masterUsername; - protected int allocatedStorageGB; - protected Date createdTime; + protected Optional availabilityZone = Optional.absent(); + protected boolean multiAZ; protected Optional subnetGroup = Optional.absent(); protected ImmutableMap.Builder securityGroupNameToStatus = ImmutableMap . builder(); @@ -94,62 +126,30 @@ public class Instance { return self(); } - /** - * @see Instance#getName() - */ - public T name(String name) { - this.name = Optional.fromNullable(name); - return self(); - } - - /** - * @see Instance#getInstanceClass() - */ - public T instanceClass(String instanceClass) { - this.instanceClass = instanceClass; - return self(); - } - /** * @see Instance#getEndpoint() */ public T endpoint(HostAndPort endpoint) { - this.endpoint = endpoint; + this.endpoint = Optional.fromNullable(endpoint); + return self(); + } + + /** + * @see Instance#getRawStatus() + */ + public T rawStatus(String rawStatus) { + this.rawStatus = rawStatus; return self(); } /** * @see Instance#getStatus() */ - public T status(String status) { + public T status(Status status) { this.status = status; return self(); } - /** - * @see Instance#getAvailabilityZone() - */ - public T availabilityZone(String availabilityZone) { - this.availabilityZone = availabilityZone; - return self(); - } - - /** - * @see Instance#isMultiAZ() - */ - public T multiAZ(boolean multiAZ) { - this.multiAZ = multiAZ; - return self(); - } - - /** - * @see Instance#getEngine() - */ - public T engine(String engine) { - this.engine = engine; - return self(); - } - /** * @see Instance#getEngineVersion() */ @@ -166,27 +166,27 @@ public class Instance { return self(); } - /** - * @see Instance#getMasterUsername() - */ - public T masterUsername(String masterUsername) { - this.masterUsername = masterUsername; - return self(); - } - - /** - * @see Instance#getAllocatedStorageGB() - */ - public T allocatedStorageGB(int allocatedStorageGB) { - this.allocatedStorageGB = allocatedStorageGB; - return self(); - } - /** * @see Instance#getCreatedTime() */ public T createdTime(Date createdTime) { - this.createdTime = createdTime; + this.createdTime = Optional.fromNullable(createdTime); + return self(); + } + + /** + * @see Instance#getAvailabilityZone() + */ + public T availabilityZone(String availabilityZone) { + this.availabilityZone = Optional.fromNullable(availabilityZone); + return self(); + } + + /** + * @see Instance#isMultiAZ() + */ + public T multiAZ(boolean multiAZ) { + this.multiAZ = multiAZ; return self(); } @@ -216,18 +216,16 @@ public class Instance { } public Instance build() { - return new Instance(id, name, instanceClass, endpoint, status, availabilityZone, multiAZ, engine, + return new Instance(id, name, instanceClass, endpoint, rawStatus, status, availabilityZone, multiAZ, engine, engineVersion, licenseModel, masterUsername, allocatedStorageGB, createdTime, subnetGroup, securityGroupNameToStatus.build()); } public T fromInstance(Instance in) { - return this.id(in.getId()).name(in.getName().orNull()).instanceClass(in.getInstanceClass()) - .endpoint(in.getEndpoint()).status(in.getStatus()).availabilityZone(in.getAvailabilityZone()) - .multiAZ(in.isMultiAZ()).engine(in.getEngine()).engineVersion(in.getEngineVersion()) - .licenseModel(in.getLicenseModel()).masterUsername(in.getMasterUsername()) - .allocatedStorageGB(in.getAllocatedStorageGB()).createdTime(in.getCreatedTime()) - .subnetGroup(in.getSubnetGroup().orNull()) + return fromBaseInstance(in).id(in.getId()).endpoint(in.getEndpoint().orNull()).status(in.getStatus()) + .createdTime(in.getCreatedTime().orNull()).engineVersion(in.getEngineVersion()) + .licenseModel(in.getLicenseModel()).availabilityZone(in.getAvailabilityZone().orNull()) + .multiAZ(in.isMultiAZ()).subnetGroup(in.getSubnetGroup().orNull()) .securityGroupNameToStatus(in.getSecurityGroupNameToStatus()); } } @@ -240,37 +238,30 @@ public class Instance { } protected final String id; - protected final Optional name; - protected final String instanceClass; - protected final HostAndPort endpoint; - protected final String status; - protected final String availabilityZone; - protected final boolean multiAZ; - protected final String engine; + protected final Optional endpoint; + protected final String rawStatus; + protected final Status status; + protected final Optional createdTime; protected final String engineVersion; protected final String licenseModel; - protected final String masterUsername; - protected int allocatedStorageGB; - protected final Date createdTime; + protected final Optional availabilityZone; + protected final boolean multiAZ; protected final Optional subnetGroup; protected final Map securityGroupNameToStatus; - protected Instance(String id, Optional name, String instanceClass, HostAndPort endpoint, String status, - String availabilityZone, boolean multiAZ, String engine, String engineVersion, String licenseModel, - String masterUsername, int allocatedStorageGB, Date createdTime, Optional subnetGroup, - Map securityGroupNameToStatus) { + protected Instance(String id, Optional name, String instanceClass, Optional endpoint, + String rawStatus, Status status, Optional availabilityZone, boolean multiAZ, String engine, + String engineVersion, String licenseModel, String masterUsername, int allocatedStorageGB, + Optional createdTime, Optional subnetGroup, Map securityGroupNameToStatus) { + super(name, instanceClass, engine, masterUsername, allocatedStorageGB); this.id = checkNotNull(id, "id"); - this.name = checkNotNull(name, "name of %s", id); - this.endpoint = checkNotNull(endpoint, "endpoint of %s", id); - this.status = checkNotNull(status, "status of %s", id); - this.instanceClass = checkNotNull(instanceClass, "instanceClass of %s", id); this.availabilityZone = checkNotNull(availabilityZone, "availabilityZone of %s", id); this.multiAZ = multiAZ; - this.engine = checkNotNull(engine, "engine of %s", id); + this.endpoint = checkNotNull(endpoint, "endpoint of %s", id); + this.rawStatus = checkNotNull(rawStatus, "rawStatus of %s", id); + this.status = checkNotNull(status, "status of %s", id); this.engineVersion = checkNotNull(engineVersion, "engineVersion of %s", id); this.licenseModel = checkNotNull(licenseModel, "licenseModel of %s", id); - this.masterUsername = checkNotNull(masterUsername, "masterUsername of %s", id); - this.allocatedStorageGB = allocatedStorageGB; this.createdTime = checkNotNull(createdTime, "createdTime of %s", id); this.subnetGroup = checkNotNull(subnetGroup, "subnetGroup of %s", id); this.securityGroupNameToStatus = ImmutableMap.copyOf(checkNotNull(securityGroupNameToStatus, @@ -285,63 +276,33 @@ public class Instance { return id; } - /** - * The meaning of this parameter differs according to the database engine you use. - * - *

MySQL

- * - * Contains the name of the initial database of this instance that was provided at create time, - * if one was specified when the DB Instance was created. This same name is returned for the life - * of the DB Instance. - * - *

Oracle

- * - * Contains the Oracle System ID (SID) of the created DB Instance. - */ - public Optional getName() { - return name; - } - /** * Specifies the current state of this database. */ - public String getStatus() { + public Status getStatus() { return status; } /** - * Contains the name of the compute and memory capacity class of the DB Instance. + * Specifies the current state of this database unparsed. */ - public String getInstanceClass() { - return instanceClass; + public String getRawStatus() { + return rawStatus; } /** - * Specifies the connection endpoint. + * Specifies the connection endpoint, or absent if the database is in {@link Status#CREATING} or {@link Status#DELETING} states */ - public HostAndPort getEndpoint() { + public Optional getEndpoint() { return endpoint; } /** - * Specifies the name of the Availability Zone the DB Instance is located in. + * Provides the date and time the DB Instance was created, or absent if the database is in + * {@code creating} state */ - public String getAvailabilityZone() { - return availabilityZone; - } - - /** - * Specifies the name of the Availability Zone the DB Instance is located in. - */ - public boolean isMultiAZ() { - return multiAZ; - } - - /** - * Provides the name of the database engine to be used for this DB Instance. - */ - public String getEngine() { - return engine; + public Optional getCreatedTime() { + return createdTime; } /** @@ -359,24 +320,18 @@ public class Instance { } /** - * Contains the master username for the DB Instance. + * Specifies the name of the Availability Zone the DB Instance is located in, or absent if the + * database is in {@code creating} state */ - public String getMasterUsername() { - return masterUsername; + public Optional getAvailabilityZone() { + return availabilityZone; } /** - * Specifies the allocated storage size specified in gigabytes. + * Specifies the name of the Availability Zone the DB Instance is located in. */ - public int getAllocatedStorageGB() { - return allocatedStorageGB; - } - - /** - * Provides the date and time the DB Instance was created. - */ - public Date getCreatedTime() { - return createdTime; + public boolean isMultiAZ() { + return multiAZ; } /** @@ -394,27 +349,19 @@ public class Instance { return securityGroupNameToStatus; } - /** - * {@inheritDoc} - */ @Override public int hashCode() { - return Objects.hashCode(id, createdTime); + return Objects.hashCode(id); } - /** - * {@inheritDoc} - */ @Override public boolean equals(Object obj) { if (this == obj) return true; - if (obj == null) + if (obj == null || getClass() != obj.getClass()) return false; - if (getClass() != obj.getClass()) - return false; - Instance other = (Instance) obj; - return Objects.equal(this.id, other.id) && Objects.equal(this.createdTime, other.createdTime); + Instance that = Instance.class.cast(obj); + return Objects.equal(this.id, that.id); } /** @@ -423,11 +370,11 @@ public class Instance { @Override public String toString() { return Objects.toStringHelper(this).omitNullValues().add("id", id).add("name", name.orNull()) - .add("instanceClass", instanceClass).add("endpoint", endpoint).add("status", status) - .add("availabilityZone", availabilityZone).add("multiAZ", multiAZ).add("engine", engine) + .add("instanceClass", instanceClass).add("endpoint", endpoint.orNull()).add("status", rawStatus) + .add("availabilityZone", availabilityZone.orNull()).add("multiAZ", multiAZ).add("engine", engine) .add("engineVersion", engineVersion).add("licenseModel", licenseModel) .add("masterUsername", masterUsername).add("allocatedStorageGB", allocatedStorageGB) - .add("createdTime", createdTime).add("subnetGroup", subnetGroup.orNull()) + .add("createdTime", createdTime.orNull()).add("subnetGroup", subnetGroup.orNull()) .add("securityGroupNameToStatus", securityGroupNameToStatus).toString(); } diff --git a/labs/rds/src/main/java/org/jclouds/rds/domain/InstanceRequest.java b/labs/rds/src/main/java/org/jclouds/rds/domain/InstanceRequest.java new file mode 100644 index 0000000000..3da6cbee4f --- /dev/null +++ b/labs/rds/src/main/java/org/jclouds/rds/domain/InstanceRequest.java @@ -0,0 +1,374 @@ +/** + * 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.rds.domain; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Set; + +import org.jclouds.rds.domain.internal.BaseInstance; + +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableSet; + +/** + * Parameters used to create a new {@link Instance} + * + * @see doc + * + * @author Adrian Cole + */ +public class InstanceRequest extends BaseInstance { + public static Builder builder() { + return new ConcreteBuilder(); + } + + public Builder toBuilder() { + return new ConcreteBuilder().fromInstance(this); + } + + public static abstract class Builder> extends BaseInstance.Builder { + + protected Optional engineVersion = Optional.absent(); + protected Optional licenseModel = Optional.absent(); + protected Optional port = Optional.absent(); + protected Optional characterSet = Optional.absent(); + protected int backupRetentionPeriod = 1; + protected Optional optionGroup = Optional.absent(); + protected Optional parameterGroup = Optional.absent(); + protected boolean autoMinorVersionUpgrade = true; + protected Optional subnetGroup = Optional.absent(); + protected ImmutableSet.Builder securityGroups = ImmutableSet. builder(); + protected String masterPassword; + + /** + * @see InstanceRequest#getEngineVersion() + */ + public T engineVersion(String engineVersion) { + this.engineVersion = Optional.fromNullable(engineVersion); + return self(); + } + + /** + * @see InstanceRequest#getLicenseModel() + */ + public T licenseModel(String licenseModel) { + this.licenseModel = Optional.fromNullable(licenseModel); + return self(); + } + + /** + * @see InstanceRequest#getPort() + */ + public T port(Integer port) { + this.port = Optional.fromNullable(port); + return self(); + } + + /** + * @see InstanceRequest#getCharacterSet() + */ + public T characterSet(String characterSet) { + this.characterSet = Optional.fromNullable(characterSet); + return self(); + } + + /** + * @see InstanceRequest#getBackupRetentionPeriod() + */ + public T backupRetentionPeriod(int backupRetentionPeriod) { + this.backupRetentionPeriod = backupRetentionPeriod; + return self(); + } + + /** + * @see InstanceRequest#getOptionGroup() + */ + public T optionGroup(String optionGroup) { + this.optionGroup = Optional.fromNullable(optionGroup); + return self(); + } + + /** + * @see InstanceRequest#getParameterGroup() + */ + public T parameterGroup(String parameterGroup) { + this.parameterGroup = Optional.fromNullable(parameterGroup); + return self(); + } + + /** + * @see InstanceRequest#isAutoMinorVersionUpgrade() + */ + public T autoMinorVersionUpgrade(boolean autoMinorVersionUpgrade) { + this.autoMinorVersionUpgrade = autoMinorVersionUpgrade; + return self(); + } + + /** + * @see InstanceRequest#getSubnetGroup() + */ + public T subnetGroup(String subnetGroup) { + this.subnetGroup = Optional.fromNullable(subnetGroup); + return self(); + } + + /** + * @see InstanceRequest#getSecurityGroups() + */ + public T securityGroups(Iterable securityGroups) { + this.securityGroups.addAll(checkNotNull(securityGroups, "securityGroups")); + return self(); + } + + /** + * @see InstanceRequest#getSecurityGroups() + */ + public T securityGroups(String securityGroupName) { + this.securityGroups.add(checkNotNull(securityGroupName, "securityGroupName")); + return self(); + } + + /** + * @see InstanceRequest#getMasterPassword() + */ + public T masterPassword(String masterPassword) { + this.masterPassword = checkNotNull(masterPassword, "masterPassword"); + return self(); + } + + public InstanceRequest build() { + return new InstanceRequest(name, instanceClass, port, characterSet, optionGroup, parameterGroup, + autoMinorVersionUpgrade, engine, engineVersion, licenseModel, masterUsername, allocatedStorageGB, + backupRetentionPeriod, subnetGroup, securityGroups.build(), masterPassword); + } + + public T fromInstance(InstanceRequest in) { + return fromBaseInstance(in).engineVersion(in.getEngineVersion().orNull()) + .licenseModel(in.getLicenseModel().orNull()).port(in.getPort().orNull()) + .characterSet(in.getCharacterSet().orNull()).backupRetentionPeriod(in.getBackupRetentionPeriod()) + .optionGroup(in.getOptionGroup().orNull()).parameterGroup(in.getParameterGroup().orNull()) + .autoMinorVersionUpgrade(in.isAutoMinorVersionUpgrade()).subnetGroup(in.getSubnetGroup().orNull()) + .securityGroups(in.getSecurityGroups()); + } + } + + private static class ConcreteBuilder extends Builder { + @Override + protected ConcreteBuilder self() { + return this; + } + } + + protected final Optional engineVersion; + protected final Optional licenseModel; + protected final Optional port; + protected final Optional characterSet; + protected final int backupRetentionPeriod; + protected final Optional optionGroup; + protected final Optional parameterGroup; + protected final boolean autoMinorVersionUpgrade; + protected final Optional subnetGroup; + protected final Set securityGroups; + protected final String masterPassword; + + protected InstanceRequest(Optional name, String instanceClass, Optional port, + Optional characterSet, Optional optionGroup, Optional parameterGroup, + boolean autoMinorVersionUpgrade, String engine, Optional engineVersion, + Optional licenseModel, String masterUsername, int allocatedStorageGB, int backupRetentionPeriod, + Optional subnetGroup, Iterable securityGroups, String masterPassword) { + super(name, instanceClass, engine, masterUsername, allocatedStorageGB); + this.engineVersion = checkNotNull(engineVersion, "engineVersion"); + this.licenseModel = checkNotNull(licenseModel, "licenseModel"); + this.optionGroup = checkNotNull(optionGroup, "optionGroup"); + this.parameterGroup = checkNotNull(parameterGroup, "parameterGroup"); + this.autoMinorVersionUpgrade = autoMinorVersionUpgrade; + this.port = checkNotNull(port, "port"); + this.characterSet = checkNotNull(characterSet, "characterSet"); + this.backupRetentionPeriod = checkNotNull(backupRetentionPeriod, "backupRetentionPeriod"); + this.subnetGroup = checkNotNull(subnetGroup, "subnetGroup"); + this.securityGroups = ImmutableSet.copyOf(checkNotNull(securityGroups, "securityGroups")); + this.masterPassword = checkNotNull(masterPassword, "masterPassword"); + } + + /** + * The version number of the database engine to use. + * + * MySQL + * + * Example: 5.1.42 + * + * Oracle + * + * Example: 11.2.0.2.v2 + * + * SQL Server + * + * Example: 10.50.2789.0.v1 + * + */ + public Optional getEngineVersion() { + return engineVersion; + } + + /** + * License model information for this DB Instance. + * + * Valid values: license-included | bring-your-own-license | general-public-license + */ + public Optional getLicenseModel() { + return licenseModel; + } + + /** + * For supported engines, indicates that the DB Instance should be associated with the specified + * CharacterSet. + */ + public Optional getCharacterSet() { + return characterSet; + } + + /** + * The port number on which the database accepts connections. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
EngineDefaultRange
MySQL33061150-65535
Oracle15211150-65535
SQL Server14331150-65535 except for 1434 and 3389
+ */ + public Optional getPort() { + return port; + } + + /** + * The number of days for which automated backups are retained. Setting this parameter to a + * positive number enables backups. Setting this parameter to 0 disables automated backups. + * + * + *

Constraints

+ * + * Must be a value from 0 to 8 Cannot be set to 0 if the DB Instance is a master instance with + * read replicas + * + * @return value which defaults to {@code 1} + */ + public int getBackupRetentionPeriod() { + return backupRetentionPeriod; + } + + /** + * Indicates that the DB Instance should be associated with the specified option group. + */ + public Optional getOptionGroup() { + return optionGroup; + } + + /** + * The name of the DB Parameter Group to associate with this DB instance. If this argument is + * omitted, the default DBParameterGroup for the specified engine will be used. + * + *

Constraints

+ * + * Must be 1 to 255 alphanumeric characters First character must be a letter Cannot end with a + * hyphen or contain two consecutive hyphens + */ + public Optional getParameterGroup() { + return parameterGroup; + } + + /** + * Indicates that minor engine upgrades will be applied automatically to the DB Instance during + * the maintenance window. + * + * @return value defaulting to {@code true} + */ + public boolean isAutoMinorVersionUpgrade() { + return autoMinorVersionUpgrade; + } + + /** + * A DB Subnet Group to associate with this DB Instance. + * + * If there is no DB Subnet Group, then it is a non-VPC DB instance. + */ + public Optional getSubnetGroup() { + return subnetGroup; + } + + /** + * A list of DB Security Groups to associate with this DB Instance. + * + * Default: The default DB Security Group for the database engine. + */ + public Set getSecurityGroups() { + return securityGroups; + } + + /** + * The password for the master database user. + * + * MySQL + * + * Constraints: Must contain from 8 to 41 alphanumeric characters. + * + * Oracle + * + * Constraints: Must contain from 8 to 30 alphanumeric characters. + * + * SQL Server + * + * Constraints: Must contain from 8 to 128 alphanumeric characters. + */ + public String getMasterPassword() { + return masterPassword; + } + + /** + * {@inheritDoc} + */ + @Override + protected ToStringHelper string() { + return super.string().add("engineVersion", engineVersion.orNull()).add("licenseModel", licenseModel.orNull()) + .add("port", port.orNull()).add("characterSet", characterSet.orNull()).add("optionGroup", optionGroup) + .add("parameterGroup", parameterGroup).add("autoMinorVersionUpgrade", autoMinorVersionUpgrade) + .add("backupRetentionPeriod", backupRetentionPeriod).add("subnetGroup", subnetGroup.orNull()) + .add("securityGroups", securityGroups); + } + +} diff --git a/labs/rds/src/main/java/org/jclouds/rds/domain/SecurityGroup.java b/labs/rds/src/main/java/org/jclouds/rds/domain/SecurityGroup.java index 6e03a4a170..75805c04d3 100644 --- a/labs/rds/src/main/java/org/jclouds/rds/domain/SecurityGroup.java +++ b/labs/rds/src/main/java/org/jclouds/rds/domain/SecurityGroup.java @@ -23,8 +23,8 @@ import static com.google.common.base.Preconditions.checkNotNull; import java.util.Set; import com.google.common.base.Objects; -import com.google.common.base.Optional; import com.google.common.base.Objects.ToStringHelper; +import com.google.common.base.Optional; import com.google.common.collect.ImmutableSet; /** @@ -52,7 +52,6 @@ import com.google.common.collect.ImmutableSet; * @author Adrian Cole */ public class SecurityGroup { - public static Builder builder() { return new ConcreteBuilder(); } diff --git a/labs/rds/src/main/java/org/jclouds/rds/domain/internal/BaseInstance.java b/labs/rds/src/main/java/org/jclouds/rds/domain/internal/BaseInstance.java new file mode 100644 index 0000000000..ef222f06ac --- /dev/null +++ b/labs/rds/src/main/java/org/jclouds/rds/domain/internal/BaseInstance.java @@ -0,0 +1,205 @@ +/** + * 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.rds.domain.internal; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.base.Optional; + +/** + * + * @author Adrian Cole + */ +public class BaseInstance { + public static Builder builder() { + return new ConcreteBuilder(); + } + + public Builder toBuilder() { + return new ConcreteBuilder().fromBaseInstance(this); + } + + public static abstract class Builder> { + protected abstract T self(); + + protected Optional name = Optional.absent(); + protected String instanceClass; + protected String engine; + protected String masterUsername; + protected int allocatedStorageGB; + + /** + * @see BaseInstance#getName() + */ + public T name(String name) { + this.name = Optional.fromNullable(name); + return self(); + } + + /** + * @see BaseInstance#getInstanceClass() + */ + public T instanceClass(String instanceClass) { + this.instanceClass = instanceClass; + return self(); + } + + /** + * @see BaseInstance#getEngine() + */ + public T engine(String engine) { + this.engine = engine; + return self(); + } + + /** + * @see BaseInstance#getMasterUsername() + */ + public T masterUsername(String masterUsername) { + this.masterUsername = masterUsername; + return self(); + } + + /** + * @see BaseInstance#getAllocatedStorageGB() + */ + public T allocatedStorageGB(int allocatedStorageGB) { + this.allocatedStorageGB = allocatedStorageGB; + return self(); + } + + public BaseInstance build() { + return new BaseInstance(name, instanceClass, engine, masterUsername, allocatedStorageGB); + } + + public T fromBaseInstance(BaseInstance in) { + return this.name(in.getName().orNull()).instanceClass(in.getInstanceClass()).engine(in.getEngine()) + .masterUsername(in.getMasterUsername()).allocatedStorageGB(in.getAllocatedStorageGB()); + } + } + + private static class ConcreteBuilder extends Builder { + @Override + protected ConcreteBuilder self() { + return this; + } + } + + protected final Optional name; + protected final String instanceClass; + protected final String engine; + protected final String masterUsername; + protected int allocatedStorageGB; + + protected BaseInstance(Optional name, String instanceClass, String engine, String masterUsername, + int allocatedStorageGB) { + this.name = checkNotNull(name, "name"); + this.instanceClass = checkNotNull(instanceClass, "instanceClass"); + this.engine = checkNotNull(engine, "engine"); + this.masterUsername = checkNotNull(masterUsername, "masterUsername"); + this.allocatedStorageGB = allocatedStorageGB; + } + + /** + * The meaning of this parameter differs according to the database engine you use. + * + *

MySQL

+ * + * Contains the name of the initial database of this instance that was provided at create time, + * if one was specified when the DB BaseInstance was created. This same name is returned for the + * life of the DB BaseInstance. + * + *

Oracle

+ * + * Contains the Oracle System ID (SID) of the created DB BaseInstance. + */ + public Optional getName() { + return name; + } + + /** + * Contains the name of the compute and memory capacity class of the DB BaseInstance. + */ + public String getInstanceClass() { + return instanceClass; + } + + /** + * Provides the name of the database engine to be used for this DB BaseInstance. + */ + public String getEngine() { + return engine; + } + + /** + * Contains the master username for the DB BaseInstance. + */ + public String getMasterUsername() { + return masterUsername; + } + + /** + * Specifies the allocated storage size specified in gigabytes. + */ + public int getAllocatedStorageGB() { + return allocatedStorageGB; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(name.orNull(), instanceClass, engine, masterUsername, allocatedStorageGB); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + BaseInstance other = (BaseInstance) obj; + return Objects.equal(this.name, other.name) && Objects.equal(this.instanceClass, other.instanceClass) + && Objects.equal(this.engine, other.engine) && Objects.equal(this.masterUsername, other.masterUsername) + && Objects.equal(this.allocatedStorageGB, other.allocatedStorageGB); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return string().toString(); + } + + protected ToStringHelper string() { + return Objects.toStringHelper(this).omitNullValues().add("name", name.orNull()).add("instanceClass", instanceClass) + .add("engine", engine).add("masterUsername", masterUsername) + .add("allocatedStorageGB", allocatedStorageGB); + } + +} diff --git a/labs/rds/src/main/java/org/jclouds/rds/features/InstanceApi.java b/labs/rds/src/main/java/org/jclouds/rds/features/InstanceApi.java index 1e2fcc2566..d54e8ca096 100644 --- a/labs/rds/src/main/java/org/jclouds/rds/features/InstanceApi.java +++ b/labs/rds/src/main/java/org/jclouds/rds/features/InstanceApi.java @@ -25,6 +25,7 @@ import org.jclouds.collect.PagedIterable; import org.jclouds.concurrent.Timeout; import org.jclouds.javax.annotation.Nullable; import org.jclouds.rds.domain.Instance; +import org.jclouds.rds.domain.InstanceRequest; import org.jclouds.rds.options.ListInstancesOptions; /** @@ -37,6 +38,41 @@ import org.jclouds.rds.options.ListInstancesOptions; */ @Timeout(duration = 30, timeUnit = TimeUnit.SECONDS) public interface InstanceApi { + /** + * Creates a new DB instance in a random, system-chosen Availability Zone in the endpoint's + * region. + * + * @param id + * unique id of the new instance + * @param instanceRequest + * parameters to create the instance with + * @return new instance being created + */ + Instance create(String id, InstanceRequest instanceRequest); + + /** + * Creates a new DB instance in the specified {@code availabilityZone} + * + * @param id + * unique id of the new instance + * @param instanceRequest + * parameters to create the instance with + * @param availabilityZone + * The EC2 Availability Zone that the database instance will be created in + * @return new instance being created + */ + Instance createInAvailabilityZone(String id, InstanceRequest instanceRequest, String availabilityZone); + + /** + * Creates a Multi-AZ deployment. This is not compatible with Microsoft SQL Server. + * + * @param id + * unique id of the new instance + * @param instanceRequest + * parameters to create the instance with + * @return new instance being created + */ + Instance createMultiAZ(String id, InstanceRequest instanceRequest); /** * Retrieves information about the specified instance. @@ -72,20 +108,38 @@ public interface InstanceApi { PagedIterable list(); /** - * Deletes the specified Instance. + * Deletes the specified Instance, skipping final snapshot. * *

* The DeleteDBInstance API deletes a previously provisioned RDS instance. A successful response - * from the web service indicates the request was received correctly. If a final DBSnapshot is - * requested the status of the RDS instance will be "deleting" until the DBSnapshot is created. - * DescribeDBInstance is used to monitor the status of this operation. This cannot be canceled or + * from the web service indicates the request was received correctly. This cannot be canceled or * reverted once submitted. * * * @param id * The DB Instance identifier for the DB Instance to be deleted. This parameter isn't * case sensitive. + * @return final state of instance or null if not found */ - void delete(String id); + Instance delete(String id); + /** + * Deletes the specified Instance. + * + *

+ * The DeleteDBInstance API deletes a previously provisioned RDS instance. A successful response + * from the web service indicates the request was received correctly. The status of the RDS + * instance will be "deleting" until the DBSnapshot is created. DescribeDBInstance is used to + * monitor the status of this operation. This cannot be canceled or reverted once submitted. + * + * + * @param id + * The DB Instance identifier for the DB Instance to be deleted. This parameter isn't + * case sensitive. + * @param snapshotId + * The DBSnapshotIdentifier of the new DBSnapshot created when SkipFinalSnapshot is set + * to false. + * @return final state of instance or null if not found + */ + Instance deleteAndSaveSnapshot(String id, String snapshotId); } diff --git a/labs/rds/src/main/java/org/jclouds/rds/features/InstanceAsyncApi.java b/labs/rds/src/main/java/org/jclouds/rds/features/InstanceAsyncApi.java index 79381ae2a4..65f7e3c502 100644 --- a/labs/rds/src/main/java/org/jclouds/rds/features/InstanceAsyncApi.java +++ b/labs/rds/src/main/java/org/jclouds/rds/features/InstanceAsyncApi.java @@ -27,11 +27,15 @@ import javax.ws.rs.Path; import org.jclouds.aws.filters.FormSigner; import org.jclouds.collect.IterableWithMarker; import org.jclouds.collect.PagedIterable; +import org.jclouds.rds.binders.BindInstanceRequestToFormParams; import org.jclouds.rds.domain.Instance; +import org.jclouds.rds.domain.InstanceRequest; import org.jclouds.rds.functions.InstancesToPagedIterable; +import org.jclouds.rds.functions.ReturnNullOnStateDeletingNotFoundOr404; import org.jclouds.rds.options.ListInstancesOptions; import org.jclouds.rds.xml.DescribeDBInstancesResultHandler; import org.jclouds.rds.xml.InstanceHandler; +import org.jclouds.rest.annotations.BinderParam; import org.jclouds.rest.annotations.ExceptionParser; import org.jclouds.rest.annotations.FormParams; import org.jclouds.rest.annotations.RequestFilters; @@ -39,7 +43,6 @@ import org.jclouds.rest.annotations.Transform; import org.jclouds.rest.annotations.VirtualHost; import org.jclouds.rest.annotations.XMLResponseParser; import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; -import org.jclouds.rest.functions.ReturnVoidOnNotFoundOr404; import com.google.common.util.concurrent.ListenableFuture; @@ -47,15 +50,44 @@ import com.google.common.util.concurrent.ListenableFuture; * Provides access to Amazon RDS via the Query API *

* - * @see doc + * @see doc * @see InstanceApi * @author Adrian Cole */ @RequestFilters(FormSigner.class) @VirtualHost public interface InstanceAsyncApi { - + /** + * @see InstanceApi#create + */ + @POST + @Path("/") + @XMLResponseParser(InstanceHandler.class) + @FormParams(keys = ACTION, values = "CreateDBInstance") + ListenableFuture create(@FormParam("DBInstanceIdentifier") String id, + @BinderParam(BindInstanceRequestToFormParams.class) InstanceRequest instanceRequest); + + /** + * @see InstanceApi#createInAvailabilityZone + */ + @POST + @Path("/") + @XMLResponseParser(InstanceHandler.class) + @FormParams(keys = ACTION, values = "CreateDBInstance") + ListenableFuture createInAvailabilityZone(@FormParam("DBInstanceIdentifier") String id, + @BinderParam(BindInstanceRequestToFormParams.class) InstanceRequest instanceRequest, + @FormParam("AvailabilityZone") String availabilityZone); + + /** + * @see InstanceApi#createMultiAZ + */ + @POST + @Path("/") + @XMLResponseParser(InstanceHandler.class) + @FormParams(keys = { ACTION, "MultiAZ" }, values = { "CreateDBInstance", "true" }) + ListenableFuture createMultiAZ(@FormParam("DBInstanceIdentifier") String id, + @BinderParam(BindInstanceRequestToFormParams.class) InstanceRequest instanceRequest); + /** * @see InstanceApi#get() */ @@ -90,7 +122,19 @@ public interface InstanceAsyncApi { */ @POST @Path("/") - @ExceptionParser(ReturnVoidOnNotFoundOr404.class) + @XMLResponseParser(InstanceHandler.class) + @ExceptionParser(ReturnNullOnStateDeletingNotFoundOr404.class) + @FormParams(keys = { ACTION, "SkipFinalSnapshot" }, values = { "DeleteDBInstance", "true" }) + ListenableFuture delete(@FormParam("DBInstanceIdentifier") String id); + + /** + * @see InstanceApi#deleteAndSaveSnapshot + */ + @POST + @Path("/") + @XMLResponseParser(InstanceHandler.class) + @ExceptionParser(ReturnNullOnStateDeletingNotFoundOr404.class) @FormParams(keys = ACTION, values = "DeleteDBInstance") - ListenableFuture delete(@FormParam("DBInstanceIdentifier") String id); + ListenableFuture deleteAndSaveSnapshot(@FormParam("DBInstanceIdentifier") String id, + @FormParam("FinalDBSnapshotIdentifier") String snapshotId); } diff --git a/labs/rds/src/main/java/org/jclouds/rds/features/SecurityGroupApi.java b/labs/rds/src/main/java/org/jclouds/rds/features/SecurityGroupApi.java index 363fb4ad02..78a8320a2d 100644 --- a/labs/rds/src/main/java/org/jclouds/rds/features/SecurityGroupApi.java +++ b/labs/rds/src/main/java/org/jclouds/rds/features/SecurityGroupApi.java @@ -37,6 +37,38 @@ import org.jclouds.rds.options.ListSecurityGroupsOptions; */ @Timeout(duration = 30, timeUnit = TimeUnit.SECONDS) public interface SecurityGroupApi { + /** + * Creates a new DB Security Group. DB Security Groups control access to a DB Instance. + * + * @param name + * The name for the DB Security Group. This value is stored as a lowercase string. + * + * Constraints: Must contain no more than 255 alphanumeric characters or hyphens. Must + * not be "Default". + * @param description + * The description for the DB Security Group. + * + * @return the new security group + */ + SecurityGroup createWithNameAndDescription(String name, String description); + + /** + * Creates a new DB Security Group. DB Security Groups control access to a DB Instance. + * + * @param vpcId + * The Id of VPC. Indicates which VPC this DB Security Group should belong to. Must be + * specified to create a DB Security Group for a VPC; may not be specified otherwise. + * + * @param name + * The name for the DB Security Group. This value is stored as a lowercase string. + * + * Constraints: Must contain no more than 255 alphanumeric characters or hyphens. Must + * not be "Default". + * @param description + * The description for the DB Security Group. + * @return the new security group + */ + SecurityGroup createInVPCWithNameAndDescription(String vpcId, String name, String description); /** * Retrieves information about the specified {@link SecurityGroup}. @@ -68,6 +100,93 @@ public interface SecurityGroupApi { */ PagedIterable list(); + /** + * Enables ingress to a DBSecurityGroup to an IP range, if the application accessing your + * database is running on the Internet. + * + * @param name + * The name of the DB Security Group to add authorization to. + * @param CIDR + * The IP range to authorize. + * @return updated security group, noting the authorization status may not be complete + */ + SecurityGroup authorizeIngressToIPRange(String name, String CIDR); + + /** + * Enables ingress to a DBSecurityGroup if the application using the database is running on EC2 + * instances. + * + *

Note

+ * + * You cannot authorize ingress from an EC2 security group in one Region to an Amazon RDS DB + * Instance in another. + * + * @param name + * The name of the DB Security Group to add authorization to. + * @param ec2SecurityGroupName + * Name of the EC2 Security Group to authorize. + * @param ec2SecurityGroupOwnerId + * AWS Account Number of the owner of the EC2 Security Group specified in the + * EC2SecurityGroupName parameter. The AWS Access Key ID is not an acceptable value. + * @return updated security group, noting the authorization status may not be complete + */ + SecurityGroup authorizeIngressToEC2SecurityGroupOfOwner(String name, String ec2SecurityGroupName, + String ec2SecurityGroupOwnerId); + + /** + * Enables ingress to a DBSecurityGroup if the application using the database is running on VPC + * instances. + * + *

Note

+ * + * You cannot authorize ingress from a VPC security group in one VPC to an Amazon RDS DB Instance + * in another. + * + * @param name + * The name of the DB Security Group to add authorization to. + * @param vpcSecurityGroupId + * Id of the EC2 Security Group to authorize. + * @return updated security group, noting the authorization status may not be complete + */ + SecurityGroup authorizeIngressToVPCSecurityGroup(String name, String vpcSecurityGroupId); + + /** + * Revokes ingress from a DBSecurityGroup for previously authorized IP range. + * + * @param name + * The name of the DB Security Group to revoke ingress from. + * @param CIDR + * The IP range to revoke. + * @return updated security group, noting the authorization status may not be complete + */ + SecurityGroup revokeIngressFromIPRange(String name, String CIDR); + + /** + * Revokes ingress from a DBSecurityGroup for previously authorized EC2 Security Group. + * + * @param name + * The name of the DB Security Group to revoke ingress from. + * @param ec2SecurityGroupName + * Name of the EC2 Security Group to revoke. + * @param ec2SecurityGroupOwnerId + * AWS Account Number of the owner of the EC2 Security Group specified in the + * EC2SecurityGroupName parameter. The AWS Access Key ID is not an acceptable value. + * @return updated security group, noting the authorization status may not be complete + */ + SecurityGroup revokeIngressFromEC2SecurityGroupOfOwner(String name, String ec2SecurityGroupName, + String ec2SecurityGroupOwnerId); + + /** + * Revokes ingress from a DBSecurityGroup for previously authorized VPC Security Group. + * + * @param name + * The name of the DB Security Group to revoke ingress from. + * @param vpcSecurityGroupId + * Id of the EC2 Security Group to revoke. + * @return updated security group, noting the authorization status may not be complete + */ + SecurityGroup revokeIngressFromVPCSecurityGroup(String name, String vpcSecurityGroupId); + /** * Deletes a DB security group. * diff --git a/labs/rds/src/main/java/org/jclouds/rds/features/SecurityGroupAsyncApi.java b/labs/rds/src/main/java/org/jclouds/rds/features/SecurityGroupAsyncApi.java index f3af0e5734..43ff44f4c2 100644 --- a/labs/rds/src/main/java/org/jclouds/rds/features/SecurityGroupAsyncApi.java +++ b/labs/rds/src/main/java/org/jclouds/rds/features/SecurityGroupAsyncApi.java @@ -47,15 +47,33 @@ import com.google.common.util.concurrent.ListenableFuture; * Provides access to Amazon RDS via the Query API *

* - * @see doc + * @see doc * @see SecurityGroupApi * @author Adrian Cole */ @RequestFilters(FormSigner.class) @VirtualHost public interface SecurityGroupAsyncApi { - + /** + * @see SecurityGroupApi#createWithNameAndDescription + */ + @POST + @Path("/") + @XMLResponseParser(SecurityGroupHandler.class) + @FormParams(keys = ACTION, values = "CreateDBSecurityGroup") + ListenableFuture createWithNameAndDescription(@FormParam("DBSecurityGroupName") String name, + @FormParam("DBSecurityGroupDescription") String description); + + /** + * @see SecurityGroupApi#createInVPCWithNameAndDescription + */ + @POST + @Path("/") + @XMLResponseParser(SecurityGroupHandler.class) + @FormParams(keys = ACTION, values = "CreateDBSecurityGroup") + ListenableFuture createInVPCWithNameAndDescription(@FormParam("EC2VpcId") String vpcId, + @FormParam("DBSecurityGroupName") String name, @FormParam("DBSecurityGroupDescription") String description); + /** * @see SecurityGroupApi#get() */ @@ -85,6 +103,71 @@ public interface SecurityGroupAsyncApi { @FormParams(keys = "Action", values = "DescribeDBSecurityGroups") ListenableFuture> list(ListSecurityGroupsOptions options); + /** + * @see SecurityGroupApi#authorizeIngressToIPRange + */ + @POST + @Path("/") + @XMLResponseParser(SecurityGroupHandler.class) + @FormParams(keys = ACTION, values = "AuthorizeDBSecurityGroupIngress") + ListenableFuture authorizeIngressToIPRange(@FormParam("DBSecurityGroupName") String name, + @FormParam("CIDRIP") String CIDR); + + /** + * @see SecurityGroupApi#authorizeIngressToEC2SecurityGroupOfOwner + */ + @POST + @Path("/") + @XMLResponseParser(SecurityGroupHandler.class) + @FormParams(keys = ACTION, values = "AuthorizeDBSecurityGroupIngress") + ListenableFuture authorizeIngressToEC2SecurityGroupOfOwner( + @FormParam("DBSecurityGroupName") String name, + @FormParam("EC2SecurityGroupName") String ec2SecurityGroupName, + @FormParam("EC2SecurityGroupOwnerId") String ec2SecurityGroupOwnerId); + + /** + * @see SecurityGroupApi#authorizeIngressToVPCSecurityGroup + */ + @POST + @Path("/") + @XMLResponseParser(SecurityGroupHandler.class) + @FormParams(keys = ACTION, values = "AuthorizeDBSecurityGroupIngress") + ListenableFuture authorizeIngressToVPCSecurityGroup(@FormParam("DBSecurityGroupName") String name, + @FormParam("EC2SecurityGroupId") String vpcSecurityGroupId); + + + /** + * @see SecurityGroupApi#revokeIngressFromIPRange + */ + @POST + @Path("/") + @XMLResponseParser(SecurityGroupHandler.class) + @FormParams(keys = ACTION, values = "RevokeDBSecurityGroupIngress") + ListenableFuture revokeIngressFromIPRange(@FormParam("DBSecurityGroupName") String name, + @FormParam("CIDRIP") String CIDR); + + /** + * @see SecurityGroupApi#revokeIngressFromEC2SecurityGroupOfOwner + */ + @POST + @Path("/") + @XMLResponseParser(SecurityGroupHandler.class) + @FormParams(keys = ACTION, values = "RevokeDBSecurityGroupIngress") + ListenableFuture revokeIngressFromEC2SecurityGroupOfOwner( + @FormParam("DBSecurityGroupName") String name, + @FormParam("EC2SecurityGroupName") String ec2SecurityGroupName, + @FormParam("EC2SecurityGroupOwnerId") String ec2SecurityGroupOwnerId); + + /** + * @see SecurityGroupApi#revokeIngressFromVPCSecurityGroup + */ + @POST + @Path("/") + @XMLResponseParser(SecurityGroupHandler.class) + @FormParams(keys = ACTION, values = "RevokeDBSecurityGroupIngress") + ListenableFuture revokeIngressFromVPCSecurityGroup(@FormParam("DBSecurityGroupName") String name, + @FormParam("EC2SecurityGroupId") String vpcSecurityGroupId); + /** * @see SecurityGroupApi#delete() */ diff --git a/labs/rds/src/main/java/org/jclouds/rds/functions/ReturnNullOnStateDeletingNotFoundOr404.java b/labs/rds/src/main/java/org/jclouds/rds/functions/ReturnNullOnStateDeletingNotFoundOr404.java new file mode 100644 index 0000000000..aa5038d19a --- /dev/null +++ b/labs/rds/src/main/java/org/jclouds/rds/functions/ReturnNullOnStateDeletingNotFoundOr404.java @@ -0,0 +1,51 @@ +/** + * 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.rds.functions; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.jclouds.aws.AWSResponseException; +import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; + +import com.google.common.base.Function; + +/** + * it is ok to have a db instance already in the process of deleting + */ +@Singleton +public class ReturnNullOnStateDeletingNotFoundOr404 implements Function { + private ReturnNullOnNotFoundOr404 rto404; + + @Inject + private ReturnNullOnStateDeletingNotFoundOr404(ReturnNullOnNotFoundOr404 rto404) { + this.rto404 = rto404; + } + + public Object apply(Exception from) { + if (from instanceof AWSResponseException) { + AWSResponseException e = AWSResponseException.class.cast(from); + if (e.getError().getCode().equals("InvalidDBInstanceState") + && e.getError().getMessage().contains("has state: deleting")) + return null; + } + return rto404.apply(from); + } + +} diff --git a/labs/rds/src/main/java/org/jclouds/rds/xml/EC2SecurityGroupHandler.java b/labs/rds/src/main/java/org/jclouds/rds/xml/EC2SecurityGroupHandler.java index 84ce687fa6..faca7fb131 100644 --- a/labs/rds/src/main/java/org/jclouds/rds/xml/EC2SecurityGroupHandler.java +++ b/labs/rds/src/main/java/org/jclouds/rds/xml/EC2SecurityGroupHandler.java @@ -21,8 +21,9 @@ package org.jclouds.rds.xml; import static org.jclouds.util.SaxUtils.currentOrNull; import static org.jclouds.util.SaxUtils.equalsOrSuffix; -import org.jclouds.rds.domain.EC2SecurityGroup; import org.jclouds.http.functions.ParseSax; +import org.jclouds.rds.domain.Authorization.Status; +import org.jclouds.rds.domain.EC2SecurityGroup; import org.xml.sax.SAXException; /** @@ -61,7 +62,9 @@ public class EC2SecurityGroupHandler extends ParseSax.HandlerForGeneratedRequest } else if (equalsOrSuffix(qName, "EC2SecurityGroupOwnerId")) { builder.ownerId(currentOrNull(currentText)); } else if (equalsOrSuffix(qName, "Status")) { - builder.status(currentOrNull(currentText)); + String rawStatus = currentOrNull(currentText); + builder.rawStatus(rawStatus); + builder.status(Status.fromValue(rawStatus)); } currentText = new StringBuilder(); } diff --git a/labs/rds/src/main/java/org/jclouds/rds/xml/IPRangeHandler.java b/labs/rds/src/main/java/org/jclouds/rds/xml/IPRangeHandler.java index bc52f65e9a..3888c735cb 100644 --- a/labs/rds/src/main/java/org/jclouds/rds/xml/IPRangeHandler.java +++ b/labs/rds/src/main/java/org/jclouds/rds/xml/IPRangeHandler.java @@ -21,8 +21,9 @@ package org.jclouds.rds.xml; import static org.jclouds.util.SaxUtils.currentOrNull; import static org.jclouds.util.SaxUtils.equalsOrSuffix; -import org.jclouds.rds.domain.IPRange; import org.jclouds.http.functions.ParseSax; +import org.jclouds.rds.domain.Authorization.Status; +import org.jclouds.rds.domain.IPRange; import org.xml.sax.SAXException; /** @@ -57,7 +58,9 @@ public class IPRangeHandler extends ParseSax.HandlerForGeneratedRequestWithResul if (equalsOrSuffix(qName, "CIDRIP")) { builder.cidrIp(currentOrNull(currentText)); } else if (equalsOrSuffix(qName, "Status")) { - builder.status(currentOrNull(currentText)); + String rawStatus = currentOrNull(currentText); + builder.rawStatus(rawStatus); + builder.status(Status.fromValue(rawStatus)); } currentText = new StringBuilder(); } diff --git a/labs/rds/src/main/java/org/jclouds/rds/xml/InstanceHandler.java b/labs/rds/src/main/java/org/jclouds/rds/xml/InstanceHandler.java index e6ee52c2ff..8acd84b53d 100644 --- a/labs/rds/src/main/java/org/jclouds/rds/xml/InstanceHandler.java +++ b/labs/rds/src/main/java/org/jclouds/rds/xml/InstanceHandler.java @@ -94,9 +94,11 @@ public class InstanceHandler extends ParseSax.HandlerForGeneratedRequestWithResu @Override public void endElement(String uri, String name, String qName) throws SAXException { - if (equalsOrSuffix(qName, "SubnetGroup")) { + if (equalsOrSuffix(qName, "DBSubnetGroup")) { builder.subnetGroup(subnetGroupHandler.getResult()); inSubnetGroup = false; + } else if (inSubnetGroup) { + subnetGroupHandler.endElement(uri, name, qName); } else if (equalsOrSuffix(qName, "DBInstanceIdentifier")) { builder.id(currentOrNull(currentText)); } else if (equalsOrSuffix(qName, "InstanceCreateTime")) { @@ -106,13 +108,17 @@ public class InstanceHandler extends ParseSax.HandlerForGeneratedRequestWithResu } else if (equalsOrSuffix(qName, "AllocatedStorage")) { builder.allocatedStorageGB(Integer.parseInt(currentOrNull(currentText))); } else if (equalsOrSuffix(qName, "DBInstanceStatus")) { - builder.status(currentOrNull(currentText)); + String rawStatus = currentOrNull(currentText); + builder.rawStatus(rawStatus); + builder.status(Instance.Status.fromValue(rawStatus)); } else if (equalsOrSuffix(qName, "Address")) { address = currentOrNull(currentText); } else if (equalsOrSuffix(qName, "Port")) { port = Integer.valueOf(currentOrNull(currentText)); } else if (equalsOrSuffix(qName, "Endpoint")) { - builder.endpoint(HostAndPort.fromParts(address, port)); + // sometimes in deleting state, address is null while port isn't + if (address != null && port != null) + builder.endpoint(HostAndPort.fromParts(address, port)); address = null; port = null; } else if (equalsOrSuffix(qName, "DBSecurityGroupName")) { @@ -139,8 +145,6 @@ public class InstanceHandler extends ParseSax.HandlerForGeneratedRequestWithResu builder.licenseModel(currentOrNull(currentText)); } else if (equalsOrSuffix(qName, "MasterUsername")) { builder.masterUsername(currentOrNull(currentText)); - } else if (inSubnetGroup) { - subnetGroupHandler.endElement(uri, name, qName); } currentText = new StringBuilder(); } diff --git a/labs/rds/src/test/java/org/jclouds/rds/features/InstanceApiExpectTest.java b/labs/rds/src/test/java/org/jclouds/rds/features/InstanceApiExpectTest.java index 18ef406a7f..cf552098af 100644 --- a/labs/rds/src/test/java/org/jclouds/rds/features/InstanceApiExpectTest.java +++ b/labs/rds/src/test/java/org/jclouds/rds/features/InstanceApiExpectTest.java @@ -27,6 +27,7 @@ import java.util.TimeZone; import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpResponse; import org.jclouds.rds.RDSApi; +import org.jclouds.rds.domain.InstanceRequest; import org.jclouds.rds.internal.BaseRDSApiExpectTest; import org.jclouds.rds.parse.DescribeDBInstancesResponseTest; import org.jclouds.rds.parse.GetInstanceResponseTest; @@ -193,9 +194,10 @@ public class InstanceApiExpectTest extends BaseRDSApiExpectTest { payloadFromStringWithContentType( "Action=DeleteDBInstance" + "&DBInstanceIdentifier=id" + - "&Signature=3UFxv5zaG%2B3dZRAhinJZ10De0LAxkJtr3Nb6kfspC7w%3D" + + "&Signature=Kag9creOPsl%2BslM1J0fNzWIzo1LrF4ycnOI21v%2Bl6VM%3D" + "&SignatureMethod=HmacSHA256" + "&SignatureVersion=2" + + "&SkipFinalSnapshot=true" + "&Timestamp=2009-11-08T15%3A54%3A08.897Z" + "&Version=2012-04-23" + "&AWSAccessKeyId=identity", @@ -204,7 +206,8 @@ public class InstanceApiExpectTest extends BaseRDSApiExpectTest { public void testDeleteWhenResponseIs2xx() throws Exception { - HttpResponse deleteResponse = HttpResponse.builder().statusCode(200).build(); + HttpResponse deleteResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResourceWithContentType("/delete_instance.xml", "text/xml")).build(); RDSApi apiWhenExist = requestSendsResponse(delete, deleteResponse); @@ -219,4 +222,116 @@ public class InstanceApiExpectTest extends BaseRDSApiExpectTest { apiWhenDontExist.getInstanceApi().delete("id"); } + + public void testDeleteWhenInvalidStateDeleting() throws Exception { + HttpResponse deleteResponse = HttpResponse.builder().statusCode(400).message("HTTP/1.1 400 Bad Request") + .payload(payloadFromResourceWithContentType("/invalid_state.xml", "text/xml")).build(); + + RDSApi apiWhenDeleting = requestSendsResponse(delete, deleteResponse); + + apiWhenDeleting.getInstanceApi().delete("id"); + } + + public void testDeleteAndSaveSnapshotWhenResponseIs2xx() throws Exception { + HttpRequest delete = HttpRequest.builder().method("POST") + .endpoint("https://rds.us-east-1.amazonaws.com/") + .addHeader("Host", "rds.us-east-1.amazonaws.com") + .payload( + payloadFromStringWithContentType( + "Action=DeleteDBInstance" + + "&DBInstanceIdentifier=id" + + "&FinalDBSnapshotIdentifier=snap" + + "&Signature=aKuG1%2FYbZAzUFdAZTjke1LYRfR5JU86UxDt%2BtwdPJwE%3D" + + "&SignatureMethod=HmacSHA256" + + "&SignatureVersion=2" + + "&Timestamp=2009-11-08T15%3A54%3A08.897Z" + + "&Version=2012-04-23" + + "&AWSAccessKeyId=identity", + "application/x-www-form-urlencoded")) + .build(); + HttpResponse deleteResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResourceWithContentType("/delete_instance.xml", "text/xml")).build(); + + RDSApi apiWhenExist = requestSendsResponse(delete, deleteResponse); + + apiWhenExist.getInstanceApi().deleteAndSaveSnapshot("id", "snap"); + } + + public void testCreateWithMinumumParamsWhenResponseIs2xx() throws Exception { + HttpRequest create = HttpRequest.builder() + .method("POST") + .endpoint("https://rds.us-east-1.amazonaws.com/") + .addHeader("Host", "rds.us-east-1.amazonaws.com") + .payload( + payloadFromStringWithContentType( + "Action=CreateDBInstance" + + "&AllocatedStorage=5" + + "&AutoMinorVersionUpgrade=true" + + "&BackupRetentionPeriod=1" + + "&DBInstanceClass=db.t1.micro" + + "&DBInstanceIdentifier=SimCoProd01" + + "&Engine=mysql" + + "&MasterUserPassword=Password01" + + "&MasterUsername=master" + + "&Signature=TecIUViW09soXGFT3kAXcW2dhsK6fY2cNykLpzLJtvk%3D" + + "&SignatureMethod=HmacSHA256" + + "&SignatureVersion=2" + + "&Timestamp=2009-11-08T15%3A54%3A08.897Z" + + "&Version=2012-04-23" + + "&AWSAccessKeyId=identity", + "application/x-www-form-urlencoded")) + .build(); + + HttpResponse createResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResourceWithContentType("/create_instance.xml", "text/xml")).build(); + + RDSApi apiWhenExist = requestSendsResponse(create, createResponse); + + apiWhenExist.getInstanceApi().create("SimCoProd01", InstanceRequest.builder() + .engine("mysql") + .masterUsername("master") + .masterPassword("Password01") + .allocatedStorageGB(5) + .instanceClass("db.t1.micro").build()); + } + + public void testCreateWithOptionalParamsWhenResponseIs2xx() throws Exception { + HttpRequest create = HttpRequest.builder() + .method("POST") + .endpoint("https://rds.us-east-1.amazonaws.com/") + .addHeader("Host", "rds.us-east-1.amazonaws.com") + .payload( + payloadFromStringWithContentType( + "Action=CreateDBInstance" + + "&AllocatedStorage=10" + + "&AutoMinorVersionUpgrade=true" + + "&BackupRetentionPeriod=1" + + "&DBInstanceClass=db.m1.large" + + "&DBInstanceIdentifier=SimCoProd01" + + "&DBSubnetGroupName=dbSubnetgroup01" + + "&Engine=mysql" + + "&MasterUserPassword=Password01" + + "&MasterUsername=master" + + "&Signature=kfDFp50sxBkSlZd%2Bv8G9u6%2BFdZ133BEVcIRGwwoa8%2Fs%3D" + + "&SignatureMethod=HmacSHA256" + + "&SignatureVersion=2" + + "&Timestamp=2009-11-08T15%3A54%3A08.897Z" + + "&Version=2012-04-23" + + "&AWSAccessKeyId=identity", + "application/x-www-form-urlencoded")) + .build(); + + HttpResponse createResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResourceWithContentType("/create_instance.xml", "text/xml")).build(); + + RDSApi apiWhenExist = requestSendsResponse(create, createResponse); + + apiWhenExist.getInstanceApi().create("SimCoProd01", InstanceRequest.builder() + .engine("mysql") + .masterPassword("Password01") + .allocatedStorageGB(10) + .masterUsername("master") + .instanceClass("db.m1.large") + .subnetGroup("dbSubnetgroup01").build()); + } } diff --git a/labs/rds/src/test/java/org/jclouds/rds/features/InstanceApiLiveTest.java b/labs/rds/src/test/java/org/jclouds/rds/features/InstanceApiLiveTest.java index a33da87bc9..0f1b702028 100644 --- a/labs/rds/src/test/java/org/jclouds/rds/features/InstanceApiLiveTest.java +++ b/labs/rds/src/test/java/org/jclouds/rds/features/InstanceApiLiveTest.java @@ -19,27 +19,172 @@ package org.jclouds.rds.features; import static com.google.common.base.Preconditions.checkNotNull; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotEquals; +import static org.testng.Assert.assertTrue; + +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +import javax.annotation.Nullable; import org.jclouds.collect.IterableWithMarker; +import org.jclouds.predicates.InetSocketAddressConnect; +import org.jclouds.predicates.RetryablePredicate; +import org.jclouds.rds.domain.Authorization; +import org.jclouds.rds.domain.Authorization.Status; import org.jclouds.rds.domain.Instance; +import org.jclouds.rds.domain.InstanceRequest; +import org.jclouds.rds.domain.SecurityGroup; import org.jclouds.rds.internal.BaseRDSApiLiveTest; import org.jclouds.rds.options.ListInstancesOptions; import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; +import com.google.common.base.Predicate; import com.google.common.collect.Iterables; +import com.google.common.net.HostAndPort; /** * @author Adrian Cole */ @Test(groups = "live", testName = "InstanceApiLiveTest") public class InstanceApiLiveTest extends BaseRDSApiLiveTest { + public static final String INSTANCE = (System.getProperty("user.name") + "-jclouds-instance").toLowerCase(); + + private RetryablePredicate socketTester; + private RetryablePredicate instanceAvailable; + private RetryablePredicate instanceGone; + + private SecurityGroup securityGroup; + + @BeforeClass(groups = "live") + @Override + public void setupContext() { + super.setupContext(); + securityGroup = createSecurityGroupAndAuthorizeIngressToAll(INSTANCE); + + socketTester = new RetryablePredicate(new InetSocketAddressConnect(), 180, 1, 1, TimeUnit.SECONDS); + instanceAvailable = new RetryablePredicate(new Predicate() { + + @Override + public boolean apply(Instance input) { + return api().get(input.getId()).getStatus() == Instance.Status.AVAILABLE; + } + + }, 600, 5, 5, TimeUnit.SECONDS); + + instanceGone = new RetryablePredicate(new Predicate() { + + @Override + public boolean apply(Instance input) { + return api().get(input.getId()) == null; + } + + }, 600, 5, 5, TimeUnit.SECONDS); + } + + private SecurityGroup createSecurityGroupAndAuthorizeIngressToAll(String name) { + RetryablePredicate ipRangesAuthorized = new RetryablePredicate( + new Predicate() { + + @Override + public boolean apply(SecurityGroup input) { + return Iterables.all(sgApi().get(input.getName()).getIPRanges(), new Predicate() { + + @Override + public boolean apply(@Nullable Authorization i2) { + return i2.getStatus() == Status.AUTHORIZED; + } + + }); + } + + }, 30000, 100, 500, TimeUnit.MILLISECONDS); + + try { + SecurityGroup securityGroup = sgApi().createWithNameAndDescription(name, "jclouds"); + + Logger.getAnonymousLogger().info("created securityGroup: " + securityGroup); + + // we could look up our IP address alternatively + securityGroup = sgApi().authorizeIngressToIPRange(name, "0.0.0.0/0"); + + assertTrue(ipRangesAuthorized.apply(securityGroup), securityGroup.toString()); + + securityGroup = sgApi().get(securityGroup.getName()); + Logger.getAnonymousLogger().info("ip range authorized: " + securityGroup); + return securityGroup; + } catch (RuntimeException e) { + sgApi().delete(name); + throw e; + } + } + + private Instance instance; + + public void testCreateInstance() { + + Instance newInstance = api().create( + INSTANCE, + InstanceRequest.builder() + .instanceClass("db.t1.micro") + .allocatedStorageGB(5) + .securityGroups(securityGroup.getName()) + .name("jclouds") + .engine("mysql") + .masterUsername("master").masterPassword("Password01") + .backupRetentionPeriod(0).build()); + + instance = newInstance; + Logger.getAnonymousLogger().info("created instance: " + instance); + + assertEquals(instance.getId(), INSTANCE); + assertEquals(instance.getName().get(), "jclouds"); + + checkInstance(newInstance); + + assertTrue(instanceAvailable.apply(newInstance), newInstance.toString()); + instance = api().get(newInstance.getId()); + Logger.getAnonymousLogger().info("instance available: " + instance); + + } + + @Test(dependsOnMethods = "testCreateInstance") + protected void testPortResponds() { + assertTrue(socketTester.apply(instance.getEndpoint().get()), instance.toString()); + Logger.getAnonymousLogger().info("instance reachable on: " + instance.getEndpoint().get()); + } + + @Test(dependsOnMethods = "testPortResponds") + public void testDeleteInstance() { + instance = api().delete(instance.getId()); + assertTrue(instanceGone.apply(instance), instance.toString()); + Logger.getAnonymousLogger().info("instance deleted: " + instance); + } + + @Override + @AfterClass(groups = "live") + protected void tearDownContext() { + try { + api().delete(INSTANCE); + } finally { + sgApi().delete(INSTANCE); + } + super.tearDownContext(); + } private void checkInstance(Instance instance) { checkNotNull(instance.getId(), "Id cannot be null for a Instance: %s", instance); + checkNotNull(instance.getStatus(), "Status cannot be null for a Instance: %s", instance); + assertNotEquals(instance.getStatus(), Instance.Status.UNRECOGNIZED, + "Status cannot be UNRECOGNIZED for a Instance: " + instance); checkNotNull(instance.getCreatedTime(), "CreatedTime cannot be null for a Instance: %s", instance); checkNotNull(instance.getName(), "While Name can be null for a Instance, its Optional wrapper cannot: %s", instance); + checkNotNull(instance.getSubnetGroup(), "While SubnetGroup can be null for a Instance, its Optional wrapper cannot: %s", instance); // TODO: other checks @@ -70,4 +215,8 @@ public class InstanceApiLiveTest extends BaseRDSApiLiveTest { protected InstanceApi api() { return context.getApi().getInstanceApi(); } + + protected SecurityGroupApi sgApi() { + return context.getApi().getSecurityGroupApi(); + } } diff --git a/labs/rds/src/test/java/org/jclouds/rds/features/SecurityGroupApiExpectTest.java b/labs/rds/src/test/java/org/jclouds/rds/features/SecurityGroupApiExpectTest.java index 8a04e2b528..995f64d8aa 100644 --- a/labs/rds/src/test/java/org/jclouds/rds/features/SecurityGroupApiExpectTest.java +++ b/labs/rds/src/test/java/org/jclouds/rds/features/SecurityGroupApiExpectTest.java @@ -46,6 +46,61 @@ public class SecurityGroupApiExpectTest extends BaseRDSApiExpectTest { TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles")); } + public void testCreateWithNameAndDescriptionWhenResponseIs2xx() throws Exception { + HttpRequest create = HttpRequest.builder() + .method("POST") + .endpoint("https://rds.us-east-1.amazonaws.com/") + .addHeader("Host", "rds.us-east-1.amazonaws.com") + .payload( + payloadFromStringWithContentType( + "Action=CreateDBSecurityGroup" + + "&DBSecurityGroupDescription=My%20new%20DBSecurityGroup" + + "&DBSecurityGroupName=mydbsecuritygroup" + + "&Signature=ZJ0F0Y5veTPir5NWc7KhmHp7cYIijAxKQFikPHJzzBI%3D" + + "&SignatureMethod=HmacSHA256" + + "&SignatureVersion=2" + + "&Timestamp=2009-11-08T15%3A54%3A08.897Z" + + "&Version=2012-04-23" + + "&AWSAccessKeyId=identity", + "application/x-www-form-urlencoded")) + .build(); + + HttpResponse createResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResourceWithContentType("/create_securitygroup.xml", "text/xml")).build(); + + RDSApi apiWhenExist = requestSendsResponse(create, createResponse); + + apiWhenExist.getSecurityGroupApi().createWithNameAndDescription("mydbsecuritygroup", "My new DBSecurityGroup"); + } + + public void testCreateInVPCWithNameAndDescriptionWhenResponseIs2xx() throws Exception { + HttpRequest create = HttpRequest.builder() + .method("POST") + .endpoint("https://rds.us-east-1.amazonaws.com/") + .addHeader("Host", "rds.us-east-1.amazonaws.com") + .payload( + payloadFromStringWithContentType( + "Action=CreateDBSecurityGroup" + + "&DBSecurityGroupDescription=My%20new%20DBSecurityGroup" + + "&DBSecurityGroupName=mydbsecuritygroup" + + "&EC2VpcId=vpc-1a2b3c4d" + + "&Signature=8MXHQRkGSKb0TzCKRIlDN9ymruqzY%2FQKgLMXoxYcqFI%3D" + + "&SignatureMethod=HmacSHA256" + + "&SignatureVersion=2" + + "&Timestamp=2009-11-08T15%3A54%3A08.897Z" + + "&Version=2012-04-23" + + "&AWSAccessKeyId=identity", + "application/x-www-form-urlencoded")) + .build(); + + HttpResponse createResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResourceWithContentType("/create_securitygroup.xml", "text/xml")).build(); + + RDSApi apiWhenExist = requestSendsResponse(create, createResponse); + + apiWhenExist.getSecurityGroupApi().createInVPCWithNameAndDescription("vpc-1a2b3c4d", "mydbsecuritygroup", "My new DBSecurityGroup"); + } + HttpRequest get = HttpRequest.builder() .method("POST") .endpoint("https://rds.us-east-1.amazonaws.com/") @@ -185,6 +240,171 @@ public class SecurityGroupApiExpectTest extends BaseRDSApiExpectTest { new DescribeDBSecurityGroupsResponseTest().expected().toString()); } + public void testAuthorizeIngressToIPRangeWhenResponseIs2xx() throws Exception { + HttpRequest authorize = HttpRequest.builder() + .method("POST") + .endpoint("https://rds.us-east-1.amazonaws.com/") + .addHeader("Host", "rds.us-east-1.amazonaws.com") + .payload( + payloadFromStringWithContentType( + "Action=AuthorizeDBSecurityGroupIngress" + + "&CIDRIP=0.0.0.0%2F0" + + "&DBSecurityGroupName=mydbsecuritygroup" + + "&Signature=Wk06HjnbFH5j%2FyfguUK6p3ZJU9kpYPgOlN9IGctLVSk%3D" + + "&SignatureMethod=HmacSHA256" + + "&SignatureVersion=2" + + "&Timestamp=2009-11-08T15%3A54%3A08.897Z" + + "&Version=2012-04-23" + + "&AWSAccessKeyId=identity", + "application/x-www-form-urlencoded")) + .build(); + + HttpResponse authorizeResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResourceWithContentType("/authorize_securitygroup.xml", "text/xml")).build(); + + RDSApi apiWhenExist = requestSendsResponse(authorize, authorizeResponse); + + apiWhenExist.getSecurityGroupApi().authorizeIngressToIPRange("mydbsecuritygroup", "0.0.0.0/0"); + } + + public void testAuthorizeIngressToEC2SecurityGroupOfOwnerWhenResponseIs2xx() throws Exception { + HttpRequest authorize = HttpRequest.builder() + .method("POST") + .endpoint("https://rds.us-east-1.amazonaws.com/") + .addHeader("Host", "rds.us-east-1.amazonaws.com") + .payload( + payloadFromStringWithContentType( + "Action=AuthorizeDBSecurityGroupIngress" + + "&DBSecurityGroupName=mydbsecuritygroup" + + "&EC2SecurityGroupName=myec2securitygroup" + + "&EC2SecurityGroupOwnerId=054794666394" + + "&Signature=MM%2B8ccK7Mh%2BWLS4qA1NUyOqtkjC1ICXug8wcEyD4a6c%3D" + + "&SignatureMethod=HmacSHA256" + + "&SignatureVersion=2" + + "&Timestamp=2009-11-08T15%3A54%3A08.897Z" + + "&Version=2012-04-23" + + "&AWSAccessKeyId=identity", + "application/x-www-form-urlencoded")) + .build(); + + HttpResponse authorizeResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResourceWithContentType("/authorize_securitygroup.xml", "text/xml")).build(); + + RDSApi apiWhenExist = requestSendsResponse(authorize, authorizeResponse); + + apiWhenExist.getSecurityGroupApi().authorizeIngressToEC2SecurityGroupOfOwner("mydbsecuritygroup", "myec2securitygroup", "054794666394"); + } + + public void testAuthorizeIngressToVPCSecurityGroupWhenResponseIs2xx() throws Exception { + HttpRequest authorize = HttpRequest.builder() + .method("POST") + .endpoint("https://rds.us-east-1.amazonaws.com/") + .addHeader("Host", "rds.us-east-1.amazonaws.com") + .payload( + payloadFromStringWithContentType( + "Action=AuthorizeDBSecurityGroupIngress" + + "&DBSecurityGroupName=mydbsecuritygroup" + + "&EC2SecurityGroupId=sg-1312321312" + + "&Signature=o31Wey%2FwliTbHJoxdF7KGqIJwSM6pfqzkjIYio3XNGs%3D" + + "&SignatureMethod=HmacSHA256" + + "&SignatureVersion=2" + + "&Timestamp=2009-11-08T15%3A54%3A08.897Z" + + "&Version=2012-04-23" + + "&AWSAccessKeyId=identity", + "application/x-www-form-urlencoded")) + .build(); + + HttpResponse authorizeResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResourceWithContentType("/authorize_securitygroup.xml", "text/xml")).build(); + + RDSApi apiWhenExist = requestSendsResponse(authorize, authorizeResponse); + + apiWhenExist.getSecurityGroupApi().authorizeIngressToVPCSecurityGroup("mydbsecuritygroup", "sg-1312321312"); + } + + public void testRevokeIngressFromIPRangeWhenResponseIs2xx() throws Exception { + HttpRequest revoke = HttpRequest.builder() + .method("POST") + .endpoint("https://rds.us-east-1.amazonaws.com/") + .addHeader("Host", "rds.us-east-1.amazonaws.com") + .payload( + payloadFromStringWithContentType( + "Action=RevokeDBSecurityGroupIngress" + + "&CIDRIP=0.0.0.0%2F0" + + "&DBSecurityGroupName=mydbsecuritygroup" + + "&Signature=YD1%2BzKmoWyYCmqWq1X9f%2FVj6UC7UnkwkPf%2BA5urnz%2BE%3D" + + "&SignatureMethod=HmacSHA256" + + "&SignatureVersion=2" + + "&Timestamp=2009-11-08T15%3A54%3A08.897Z" + + "&Version=2012-04-23" + + "&AWSAccessKeyId=identity", + "application/x-www-form-urlencoded")) + .build(); + + HttpResponse revokeResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResourceWithContentType("/revoke_securitygroup.xml", "text/xml")).build(); + + RDSApi apiWhenExist = requestSendsResponse(revoke, revokeResponse); + + apiWhenExist.getSecurityGroupApi().revokeIngressFromIPRange("mydbsecuritygroup", "0.0.0.0/0"); + } + + public void testRevokeIngressFromEC2SecurityGroupOfOwnerWhenResponseIs2xx() throws Exception { + HttpRequest revoke = HttpRequest.builder() + .method("POST") + .endpoint("https://rds.us-east-1.amazonaws.com/") + .addHeader("Host", "rds.us-east-1.amazonaws.com") + .payload( + payloadFromStringWithContentType( + "Action=RevokeDBSecurityGroupIngress" + + "&DBSecurityGroupName=mydbsecuritygroup" + + "&EC2SecurityGroupName=myec2securitygroup" + + "&EC2SecurityGroupOwnerId=054794666394" + + "&Signature=OknWXceQDAgmZBNzDdhxjaOJI48hYrnFJDOySBc4Qy4%3D" + + "&SignatureMethod=HmacSHA256" + + "&SignatureVersion=2" + + "&Timestamp=2009-11-08T15%3A54%3A08.897Z" + + "&Version=2012-04-23" + + "&AWSAccessKeyId=identity", + "application/x-www-form-urlencoded")) + .build(); + + HttpResponse revokeResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResourceWithContentType("/revoke_securitygroup.xml", "text/xml")).build(); + + RDSApi apiWhenExist = requestSendsResponse(revoke, revokeResponse); + + apiWhenExist.getSecurityGroupApi().revokeIngressFromEC2SecurityGroupOfOwner("mydbsecuritygroup", "myec2securitygroup", "054794666394"); + } + + public void testRevokeIngressFromVPCSecurityGroupWhenResponseIs2xx() throws Exception { + HttpRequest revoke = HttpRequest.builder() + .method("POST") + .endpoint("https://rds.us-east-1.amazonaws.com/") + .addHeader("Host", "rds.us-east-1.amazonaws.com") + .payload( + payloadFromStringWithContentType( + "Action=RevokeDBSecurityGroupIngress" + + "&DBSecurityGroupName=mydbsecuritygroup" + + "&EC2SecurityGroupId=sg-1312321312" + + "&Signature=YI2oGYI%2BCx4DGYx43WH%2FehW6CWe6X6wEipsp5zPySzw%3D" + + "&SignatureMethod=HmacSHA256" + + "&SignatureVersion=2" + + "&Timestamp=2009-11-08T15%3A54%3A08.897Z" + + "&Version=2012-04-23" + + "&AWSAccessKeyId=identity", + "application/x-www-form-urlencoded")) + .build(); + + HttpResponse revokeResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResourceWithContentType("/revoke_securitygroup.xml", "text/xml")).build(); + + RDSApi apiWhenExist = requestSendsResponse(revoke, revokeResponse); + + apiWhenExist.getSecurityGroupApi().revokeIngressFromVPCSecurityGroup("mydbsecuritygroup", "sg-1312321312"); + } + + HttpRequest delete = HttpRequest.builder() .method("POST") .endpoint("https://rds.us-east-1.amazonaws.com/") diff --git a/labs/rds/src/test/java/org/jclouds/rds/features/SecurityGroupApiLiveTest.java b/labs/rds/src/test/java/org/jclouds/rds/features/SecurityGroupApiLiveTest.java index e6cfb389cd..0fc069701e 100644 --- a/labs/rds/src/test/java/org/jclouds/rds/features/SecurityGroupApiLiveTest.java +++ b/labs/rds/src/test/java/org/jclouds/rds/features/SecurityGroupApiLiveTest.java @@ -19,15 +19,28 @@ package org.jclouds.rds.features; import static com.google.common.base.Preconditions.checkNotNull; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +import javax.annotation.Nullable; import org.jclouds.collect.IterableWithMarker; +import org.jclouds.predicates.RetryablePredicate; +import org.jclouds.rds.domain.Authorization; +import org.jclouds.rds.domain.Authorization.Status; import org.jclouds.rds.domain.EC2SecurityGroup; import org.jclouds.rds.domain.SecurityGroup; import org.jclouds.rds.internal.BaseRDSApiLiveTest; import org.jclouds.rds.options.ListSecurityGroupsOptions; import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; +import com.google.common.base.Predicate; import com.google.common.collect.Iterables; /** @@ -35,19 +48,102 @@ import com.google.common.collect.Iterables; */ @Test(groups = "live", testName = "SecurityGroupApiLiveTest") public class SecurityGroupApiLiveTest extends BaseRDSApiLiveTest { + public static final String SECURITYGROUP = (System.getProperty("user.name") + "-jclouds-securityGroup") + .toLowerCase(); + + private RetryablePredicate ipRangesAuthorized; + private RetryablePredicate ipRangesRevoked; + + @BeforeClass(groups = "live") + @Override + public void setupContext() { + super.setupContext(); + ipRangesAuthorized = new RetryablePredicate(new Predicate() { + + @Override + public boolean apply(SecurityGroup input) { + return Iterables.all(api().get(input.getName()).getIPRanges(), new Predicate() { + + @Override + public boolean apply(@Nullable Authorization i2) { + return i2.getStatus() == Status.AUTHORIZED; + } + + }); + } + + }, 30000, 100, 500, TimeUnit.MILLISECONDS); + ipRangesRevoked = new RetryablePredicate(new Predicate() { + + @Override + public boolean apply(SecurityGroup input) { + return api().get(input.getName()).getIPRanges().size() == 0; + } + + }, 30000, 100, 500, TimeUnit.MILLISECONDS); + } + + private SecurityGroup securityGroup; + + public void testCreateSecurityGroup() { + + SecurityGroup newSecurityGroup = api().createWithNameAndDescription(SECURITYGROUP, "jclouds"); + + securityGroup = newSecurityGroup; + Logger.getAnonymousLogger().info("created securityGroup: " + securityGroup); + + assertEquals(securityGroup.getName(), SECURITYGROUP); + assertEquals(securityGroup.getDescription(), "jclouds"); + + checkSecurityGroup(newSecurityGroup); + + } + + @Test(dependsOnMethods = "testCreateSecurityGroup") + protected void testAuthorizeIPRange() { + securityGroup = api().authorizeIngressToIPRange(SECURITYGROUP, "0.0.0.0/0"); + + assertTrue(ipRangesAuthorized.apply(securityGroup), securityGroup.toString()); + securityGroup = api().get(securityGroup.getName()); + Logger.getAnonymousLogger().info("ip range authorized: " + securityGroup); + } + + @Test(dependsOnMethods = "testAuthorizeIPRange") + protected void testRevokeIPRange() { + securityGroup = api().revokeIngressFromIPRange(SECURITYGROUP, "0.0.0.0/0"); + + assertTrue(ipRangesRevoked.apply(securityGroup), securityGroup.toString()); + securityGroup = api().get(securityGroup.getName()); + Logger.getAnonymousLogger().info("ip range revoked: " + securityGroup); + } + + @Test(dependsOnMethods = "testRevokeIPRange") + public void testDeleteSecurityGroup() { + api().delete(securityGroup.getName()); + // TODO block and determine the state of a deleted securityGroup + Logger.getAnonymousLogger().info("securityGroup deleted: " + securityGroup); + } + + @Override + @AfterClass(groups = "live") + protected void tearDownContext() { + api().delete(SECURITYGROUP); + super.tearDownContext(); + } static void checkSecurityGroup(SecurityGroup securityGroup) { checkNotNull(securityGroup.getName(), "Name cannot be null for a SecurityGroup: %s", securityGroup); - checkNotNull(securityGroup.getDescription(), "Description cannot be null for a SecurityGroup: %s", securityGroup); - checkNotNull(securityGroup.getOwnerId(), "OwnerId cannot be null for a SecurityGroup: %s", securityGroup); - checkNotNull(securityGroup.getVpcId(), "VpcId cannot be null for a SecurityGroup: %s", securityGroup); + checkNotNull(securityGroup.getDescription(), "Description cannot be null for a SecurityGroup: %s", securityGroup); + checkNotNull(securityGroup.getOwnerId(), "OwnerId cannot be null for a SecurityGroup: %s", securityGroup); + checkNotNull(securityGroup.getVpcId(), "VpcId cannot be null for a SecurityGroup: %s", securityGroup); for (EC2SecurityGroup security : securityGroup.getEC2SecurityGroups()) { checkEC2SecurityGroup(security); } } static void checkEC2SecurityGroup(EC2SecurityGroup security) { - checkNotNull(security.getId(), "Id can be null for a SecurityGroup, but its Optional Wrapper cannot: %s", security); + checkNotNull(security.getId(), "Id can be null for a SecurityGroup, but its Optional Wrapper cannot: %s", + security); checkNotNull(security.getStatus(), "Status cannot be null for a SecurityGroup: %s", security); checkNotNull(security.getName(), "Name cannot be null for a SecurityGroup: %s", security); checkNotNull(security.getOwnerId(), "Name cannot be null for a SecurityGroup: %s", security); diff --git a/labs/rds/src/test/java/org/jclouds/rds/parse/DescribeDBInstancesResponseTest.java b/labs/rds/src/test/java/org/jclouds/rds/parse/DescribeDBInstancesResponseTest.java index d02ce20b86..8be97983ce 100644 --- a/labs/rds/src/test/java/org/jclouds/rds/parse/DescribeDBInstancesResponseTest.java +++ b/labs/rds/src/test/java/org/jclouds/rds/parse/DescribeDBInstancesResponseTest.java @@ -60,7 +60,8 @@ public class DescribeDBInstancesResponseTest extends BaseHandlerTest { .engine("mysql") .multiAZ(false) .licenseModel("general-public-license") - .status("available") + .rawStatus("available") + .status(Instance.Status.AVAILABLE) .engineVersion("5.1.50") .endpoint(HostAndPort.fromParts("simcoprod01.cu7u2t4uz396.us-east-1.rds.amazonaws.com", 3306)) .id("simcoprod01") diff --git a/labs/rds/src/test/java/org/jclouds/rds/parse/DescribeDBSecurityGroupsResponseTest.java b/labs/rds/src/test/java/org/jclouds/rds/parse/DescribeDBSecurityGroupsResponseTest.java index 74831b6541..8a48714037 100644 --- a/labs/rds/src/test/java/org/jclouds/rds/parse/DescribeDBSecurityGroupsResponseTest.java +++ b/labs/rds/src/test/java/org/jclouds/rds/parse/DescribeDBSecurityGroupsResponseTest.java @@ -25,6 +25,7 @@ import java.io.InputStream; import org.jclouds.collect.IterableWithMarker; import org.jclouds.collect.IterableWithMarkers; import org.jclouds.http.functions.BaseHandlerTest; +import org.jclouds.rds.domain.Authorization.Status; import org.jclouds.rds.domain.EC2SecurityGroup; import org.jclouds.rds.domain.IPRange; import org.jclouds.rds.domain.SecurityGroup; @@ -56,17 +57,24 @@ public class DescribeDBSecurityGroupsResponseTest extends BaseHandlerTest { return IterableWithMarkers.from(ImmutableSet.builder() .add(SecurityGroup.builder() .ec2SecurityGroup(EC2SecurityGroup.builder() - .status("authorized") + .rawStatus("authorized") + .status(Status.AUTHORIZED) .name("myec2securitygroup") .ownerId("054794666394").build()) .description("default") - .ipRange(IPRange.builder().cidrIp("127.0.0.1/30").status("authorized").build()) + .ipRange(IPRange.builder() + .cidrIp("127.0.0.1/30") + .rawStatus("authorized") + .status(Status.AUTHORIZED).build()) .ownerId("621567473609") .name("default") .vpcId("vpc-1ab2c3d4").build()) .add(SecurityGroup.builder() .description("My new DBSecurityGroup") - .ipRange(IPRange.builder().cidrIp("192.168.1.1/24").status("authorized").build()) + .ipRange(IPRange.builder() + .cidrIp("192.168.1.1/24") + .rawStatus("authorized") + .status(Status.AUTHORIZED).build()) .ownerId("621567473609") .name("mydbsecuritygroup") .vpcId("vpc-1ab2c3d5").build()) diff --git a/labs/rds/src/test/java/org/jclouds/rds/parse/GetInstanceResponseTest.java b/labs/rds/src/test/java/org/jclouds/rds/parse/GetInstanceResponseTest.java index 603c43ca96..2c47ea9371 100644 --- a/labs/rds/src/test/java/org/jclouds/rds/parse/GetInstanceResponseTest.java +++ b/labs/rds/src/test/java/org/jclouds/rds/parse/GetInstanceResponseTest.java @@ -55,7 +55,8 @@ public class GetInstanceResponseTest extends BaseHandlerTest { .engine("mysql") .multiAZ(false) .licenseModel("general-public-license") - .status("available") + .rawStatus("available") + .status(Instance.Status.AVAILABLE) .engineVersion("5.1.50") .endpoint(HostAndPort.fromParts("simcoprod01.cu7u2t4uz396.us-east-1.rds.amazonaws.com", 3306)) .id("simcoprod01") diff --git a/labs/rds/src/test/java/org/jclouds/rds/parse/GetSecurityGroupResponseTest.java b/labs/rds/src/test/java/org/jclouds/rds/parse/GetSecurityGroupResponseTest.java index 16c53e5f4c..d6bdb77c96 100644 --- a/labs/rds/src/test/java/org/jclouds/rds/parse/GetSecurityGroupResponseTest.java +++ b/labs/rds/src/test/java/org/jclouds/rds/parse/GetSecurityGroupResponseTest.java @@ -23,6 +23,7 @@ import static org.testng.Assert.assertEquals; import java.io.InputStream; import org.jclouds.http.functions.BaseHandlerTest; +import org.jclouds.rds.domain.Authorization.Status; import org.jclouds.rds.domain.EC2SecurityGroup; import org.jclouds.rds.domain.IPRange; import org.jclouds.rds.domain.SecurityGroup; @@ -50,11 +51,15 @@ public class GetSecurityGroupResponseTest extends BaseHandlerTest { public SecurityGroup expected() { return SecurityGroup.builder() .ec2SecurityGroup(EC2SecurityGroup.builder() - .status("authorized") + .rawStatus("authorized") + .status(Status.AUTHORIZED) .name("myec2securitygroup") .ownerId("054794666394").build()) .description("default") - .ipRange(IPRange.builder().cidrIp("127.0.0.1/30").status("authorized").build()) + .ipRange(IPRange.builder() + .cidrIp("127.0.0.1/30") + .rawStatus("authorized") + .status(Status.AUTHORIZED).build()) .ownerId("621567473609") .name("default") .vpcId("vpc-1ab2c3d4").build(); diff --git a/labs/rds/src/test/resources/authorize_securitygroup.xml b/labs/rds/src/test/resources/authorize_securitygroup.xml new file mode 100644 index 0000000000..4e8d560135 --- /dev/null +++ b/labs/rds/src/test/resources/authorize_securitygroup.xml @@ -0,0 +1,20 @@ + + + + + My new DBSecurityGroup + + + 192.168.1.1/24 + authorizing + + + 621567473609 + mydbsecuritygroup + vpc-1ab2c3d4 + + + + d9799197-bf2d-11de-b88d-993294bf1c81 + + \ No newline at end of file diff --git a/labs/rds/src/test/resources/create_instance.xml b/labs/rds/src/test/resources/create_instance.xml new file mode 100644 index 0000000000..58d28898dd --- /dev/null +++ b/labs/rds/src/test/resources/create_instance.xml @@ -0,0 +1,67 @@ + + + + + mysql + + **** + + 1 + false + general-public-license + + 990524496922 + Complete + description + subnet_grp1 + + + Active + subnet-7c5b4115 + + us-east-1c + + + + Active + subnet-7b5b4112 + + us-east-1b + + + + Active + subnet-3ea6bd57 + + us-east-1d + + + + + creating + 5.1.50 + simcoprod01 + + + in-sync + default.mysql5.1 + + + + + active + default + + + 00:00-00:30 + true + sat:07:30-sat:08:00 + 10 + db.m1.large + master + + + + 2e5d4270-8501-11e0-bd9b-a7b1ece36d51 + + \ No newline at end of file diff --git a/labs/rds/src/test/resources/create_securitygroup.xml b/labs/rds/src/test/resources/create_securitygroup.xml new file mode 100644 index 0000000000..5488bef106 --- /dev/null +++ b/labs/rds/src/test/resources/create_securitygroup.xml @@ -0,0 +1,15 @@ + + + + + My new DBSecurityGroup + + 565419523791 + mydbsecuritygroup + vpc-1a2b3c4d + + + + ed662948-a57b-11df-9e38-7ffab86c801f + + \ No newline at end of file diff --git a/labs/rds/src/test/resources/delete_instance.xml b/labs/rds/src/test/resources/delete_instance.xml new file mode 100644 index 0000000000..910fc0e271 --- /dev/null +++ b/labs/rds/src/test/resources/delete_instance.xml @@ -0,0 +1,42 @@ + + + + + 2011-05-23T07:15:00Z + mysql + + 1 + false + general-public-license + deleting + 5.1.50 + + 3306 + + myrestoreddbinstance + + + in-sync + default.mysql5.1 + + + + + active + default + + + 00:00-00:30 + true + sat:07:30-sat:08:00 + us-east-1d + 2011-05-23T06:52:48.255Z + 10 + db.m1.large + master + + + + 03ea4ae8-850d-11e0-90aa-eb648410240d + + \ No newline at end of file diff --git a/labs/rds/src/test/resources/invalid_state.xml b/labs/rds/src/test/resources/invalid_state.xml new file mode 100644 index 0000000000..de10a23b0f --- /dev/null +++ b/labs/rds/src/test/resources/invalid_state.xml @@ -0,0 +1,8 @@ + + + Sender + InvalidDBInstanceState + Can only delete db instances with state in: available, failed, storage-full, incompatible-option-group, incompatible-parameters, incompatible-restore, incompatible-network. Instance adriancole-jclouds has state: deleting. + + 77db43a3-d6d2-11e1-aaa2-59d4ca8d1ef6 + \ No newline at end of file diff --git a/labs/rds/src/test/resources/revoke_securitygroup.xml b/labs/rds/src/test/resources/revoke_securitygroup.xml new file mode 100644 index 0000000000..ef2458bd6c --- /dev/null +++ b/labs/rds/src/test/resources/revoke_securitygroup.xml @@ -0,0 +1,20 @@ + + + + + My new DBSecurityGroup + + + 192.168.1.1/24 + revoking + + + 621567473609 + mydbsecuritygroup + vpc-1ab2c3d4 + + + + beecb8ac-bf5a-11de-9f9f-53d6aee22de9 + + \ No newline at end of file