Skip to content
This repository was archived by the owner on Oct 23, 2025. It is now read-only.

Commit ddfbdc2

Browse files
author
Keynan Pratt
committed
[Refactoring] Decomposing DBInstance BaseHandlerStd in advance of expanding the DBInstance type.
1 parent 7877fff commit ddfbdc2

File tree

6 files changed

+379
-378
lines changed

6 files changed

+379
-378
lines changed

aws-rds-dbinstance/src/main/java/software/amazon/rds/dbinstance/BaseHandlerStd.java

Lines changed: 32 additions & 296 deletions
Large diffs are not rendered by default.

aws-rds-dbinstance/src/main/java/software/amazon/rds/dbinstance/CreateHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ protected void validateRequest(final ResourceHandlerRequest<ResourceModel> reque
6262
}
6363

6464
private void validateDeletionPolicyForClusterInstance(final ResourceHandlerRequest<ResourceModel> request) throws RequestValidationException {
65-
if (isDBClusterMember(request.getDesiredResourceState()) && BooleanUtils.isTrue(request.getSnapshotRequested())) {
65+
if (DBInstancePredicates.isDBClusterMember(request.getDesiredResourceState()) && BooleanUtils.isTrue(request.getSnapshotRequested())) {
6666
throw new RequestValidationException(ILLEGAL_DELETION_POLICY_ERROR);
6767
}
6868
}
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
package software.amazon.rds.dbinstance;
2+
3+
import com.amazonaws.arn.Arn;
4+
import com.amazonaws.util.CollectionUtils;
5+
import com.google.common.collect.ImmutableList;
6+
import com.google.common.collect.ImmutableMap;
7+
import lombok.AccessLevel;
8+
import lombok.NoArgsConstructor;
9+
import org.apache.commons.lang3.BooleanUtils;
10+
import software.amazon.awssdk.services.rds.model.DBCluster;
11+
import software.amazon.awssdk.services.rds.model.DBInstance;
12+
import software.amazon.awssdk.services.rds.model.DomainMembership;
13+
import software.amazon.awssdk.services.rds.model.OptionGroupMembership;
14+
import software.amazon.awssdk.services.rds.model.PendingModifiedValues;
15+
import software.amazon.awssdk.utils.StringUtils;
16+
import software.amazon.cloudformation.exceptions.CfnNotStabilizedException;
17+
import software.amazon.rds.common.logging.RequestLogger;
18+
import software.amazon.rds.dbinstance.status.DBInstanceStatus;
19+
import software.amazon.rds.dbinstance.status.DBParameterGroupStatus;
20+
import software.amazon.rds.dbinstance.status.DomainMembershipStatus;
21+
import software.amazon.rds.dbinstance.status.OptionGroupStatus;
22+
import software.amazon.rds.dbinstance.status.ReadReplicaStatus;
23+
import software.amazon.rds.dbinstance.status.VPCSecurityGroupStatus;
24+
25+
import java.util.Collections;
26+
import java.util.List;
27+
import java.util.Optional;
28+
import java.util.stream.Collectors;
29+
30+
@NoArgsConstructor(access = AccessLevel.PRIVATE)
31+
public class DBInstancePredicates {
32+
33+
private static final String SECRET_STATUS_ACTIVE = "active";
34+
private static final List<String> RDS_CUSTOM_ORACLE_ENGINES = ImmutableList.of(
35+
"custom-oracle-ee",
36+
"custom-oracle-ee-cdb"
37+
);
38+
private static final String READ_REPLICA_STATUS_TYPE = "read replication";
39+
40+
public static void assertNoDBInstanceTerminalStatus(final DBInstance dbInstance) throws CfnNotStabilizedException {
41+
final DBInstanceStatus status = DBInstanceStatus.fromString(dbInstance.dbInstanceStatus());
42+
if (status != null && status.isTerminal()) {
43+
throw new CfnNotStabilizedException(new Exception("DB Instance is in state: " + status.toString()));
44+
}
45+
}
46+
47+
public static void assertNoOptionGroupTerminalStatus(final DBInstance dbInstance) throws CfnNotStabilizedException {
48+
final List<OptionGroupMembership> termOptionGroups = Optional.ofNullable(dbInstance.optionGroupMemberships()).orElse(Collections.emptyList())
49+
.stream()
50+
.filter(optionGroup -> {
51+
final OptionGroupStatus status = OptionGroupStatus.fromString(optionGroup.status());
52+
return status != null && status.isTerminal();
53+
})
54+
.collect(Collectors.toList());
55+
56+
if (!termOptionGroups.isEmpty()) {
57+
throw new CfnNotStabilizedException(new Exception(
58+
String.format("OptionGroup %s is in a terminal state",
59+
termOptionGroups.get(0).optionGroupName())));
60+
}
61+
}
62+
63+
public static void assertNoDomainMembershipTerminalStatus(final DBInstance dbInstance) throws CfnNotStabilizedException {
64+
final List<DomainMembership> terminalDomainMemberships = Optional.ofNullable(dbInstance.domainMemberships()).orElse(Collections.emptyList())
65+
.stream()
66+
.filter(domainMembership -> {
67+
final DomainMembershipStatus status = DomainMembershipStatus.fromString(domainMembership.status());
68+
return status != null && status.isTerminal();
69+
})
70+
.collect(Collectors.toList());
71+
72+
if (!terminalDomainMemberships.isEmpty()) {
73+
throw new CfnNotStabilizedException(new Exception(String.format("Domain %s is in a terminal state",
74+
terminalDomainMemberships.get(0).domain())));
75+
}
76+
}
77+
78+
public static void assertNoTerminalStatus(final DBInstance dbInstance) throws CfnNotStabilizedException {
79+
assertNoDBInstanceTerminalStatus(dbInstance);
80+
assertNoOptionGroupTerminalStatus(dbInstance);
81+
assertNoDomainMembershipTerminalStatus(dbInstance);
82+
}
83+
84+
public static boolean isInstanceStabilizedAfterReplicationStop(
85+
final DBInstance dbInstance,
86+
final ResourceModel model
87+
) {
88+
assertNoTerminalStatus(dbInstance);
89+
return isDBInstanceAvailable(dbInstance)
90+
&& !dbInstance.hasDbInstanceAutomatedBackupsReplications();
91+
}
92+
93+
public static boolean isDBInstanceAvailable(final DBInstance dbInstance) {
94+
return DBInstanceStatus.Available.equalsString(dbInstance.dbInstanceStatus());
95+
}
96+
97+
public static boolean isDomainMembershipsJoined(final DBInstance dbInstance) {
98+
return Optional.ofNullable(dbInstance.domainMemberships()).orElse(Collections.emptyList())
99+
.stream()
100+
.allMatch(membership -> DomainMembershipStatus.Joined.equalsString(membership.status()) ||
101+
DomainMembershipStatus.KerberosEnabled.equalsString(membership.status()));
102+
}
103+
104+
public static boolean isVpcSecurityGroupsActive(final DBInstance dbInstance) {
105+
return Optional.ofNullable(dbInstance.vpcSecurityGroups()).orElse(Collections.emptyList())
106+
.stream()
107+
.allMatch(group -> VPCSecurityGroupStatus.Active.equalsString(group.status()));
108+
}
109+
110+
public static boolean isNoPendingChanges(final DBInstance dbInstance) {
111+
final PendingModifiedValues pending = dbInstance.pendingModifiedValues();
112+
return (pending == null) || (pending.dbInstanceClass() == null &&
113+
pending.allocatedStorage() == null &&
114+
pending.automationMode() == null &&
115+
pending.backupRetentionPeriod() == null &&
116+
pending.dbInstanceIdentifier() == null &&
117+
pending.dbSubnetGroupName() == null &&
118+
pending.engine() == null &&
119+
pending.engineVersion() == null &&
120+
pending.iamDatabaseAuthenticationEnabled() == null &&
121+
pending.iops() == null &&
122+
pending.licenseModel() == null &&
123+
pending.masterUserPassword() == null &&
124+
pending.multiAZ() == null &&
125+
pending.pendingCloudwatchLogsExports() == null &&
126+
pending.port() == null &&
127+
CollectionUtils.isNullOrEmpty(pending.processorFeatures()) &&
128+
pending.resumeFullAutomationModeTime() == null &&
129+
pending.storageThroughput() == null &&
130+
pending.storageType() == null
131+
);
132+
}
133+
134+
public static boolean isCaCertificateChangesApplied(final DBInstance dbInstance, final ResourceModel model) {
135+
final PendingModifiedValues pending = dbInstance.pendingModifiedValues();
136+
return pending == null ||
137+
pending.caCertificateIdentifier() == null ||
138+
BooleanUtils.isNotTrue(model.getCertificateRotationRestart());
139+
}
140+
141+
public static boolean isDBParameterGroupNotApplying(final DBInstance dbInstance) {
142+
return Optional.ofNullable(dbInstance.dbParameterGroups()).orElse(Collections.emptyList())
143+
.stream()
144+
.noneMatch(group -> DBParameterGroupStatus.Applying.equalsString(group.parameterApplyStatus()));
145+
}
146+
147+
public static boolean isReplicationComplete(final DBInstance dbInstance) {
148+
return Optional.ofNullable(dbInstance.statusInfos()).orElse(Collections.emptyList())
149+
.stream()
150+
.filter(statusInfo -> READ_REPLICA_STATUS_TYPE.equals(statusInfo.statusType()))
151+
.allMatch(statusInfo -> ReadReplicaStatus.Replicating.equalsString(statusInfo.status()));
152+
}
153+
154+
public static boolean isDBClusterParameterGroupInSync(final ResourceModel model, final DBCluster dbCluster) {
155+
return Optional.ofNullable(dbCluster.dbClusterMembers()).orElse(Collections.emptyList())
156+
.stream()
157+
.filter(member -> model.getDBInstanceIdentifier().equalsIgnoreCase(member.dbInstanceIdentifier()))
158+
.anyMatch(member -> DBParameterGroupStatus.InSync.equalsString(member.dbClusterParameterGroupStatus()));
159+
}
160+
161+
public static boolean isDBClusterMember(final ResourceModel model) {
162+
return StringUtils.isNotBlank(model.getDBClusterIdentifier());
163+
}
164+
165+
public static boolean isRdsCustomOracleInstance(final ResourceModel model) {
166+
return RDS_CUSTOM_ORACLE_ENGINES.contains(model.getEngine());
167+
}
168+
169+
public static boolean isOptionGroupInSync(final DBInstance dbInstance) {
170+
return Optional.ofNullable(dbInstance.optionGroupMemberships()).orElse(Collections.emptyList())
171+
.stream()
172+
.allMatch(optionGroup -> OptionGroupStatus.InSync.equalsString(optionGroup.status()));
173+
}
174+
175+
public static boolean isDBParameterGroupInSync(final DBInstance dbInstance) {
176+
return Optional.ofNullable(dbInstance.dbParameterGroups()).orElse(Collections.emptyList())
177+
.stream()
178+
.allMatch(parameterGroup -> DBParameterGroupStatus.InSync.equalsString(parameterGroup.parameterApplyStatus()));
179+
}
180+
181+
public static boolean isMasterUserSecretStabilized(final DBInstance instance) {
182+
if (instance.masterUserSecret() == null) {
183+
return true;
184+
}
185+
return SECRET_STATUS_ACTIVE.equalsIgnoreCase(instance.masterUserSecret().secretStatus());
186+
}
187+
188+
public static boolean isDBInstanceStabilizedAfterMutate(
189+
final DBInstance dbInstance,
190+
final ResourceModel model,
191+
final CallbackContext context,
192+
final RequestLogger requestLogger
193+
) {
194+
assertNoTerminalStatus(dbInstance);
195+
196+
final boolean isDBInstanceStabilizedAfterMutateResult = isDBInstanceAvailable(dbInstance) &&
197+
isReplicationComplete(dbInstance) &&
198+
isDBParameterGroupNotApplying(dbInstance) &&
199+
isNoPendingChanges(dbInstance) &&
200+
isCaCertificateChangesApplied(dbInstance, model) &&
201+
isVpcSecurityGroupsActive(dbInstance) &&
202+
isDomainMembershipsJoined(dbInstance) &&
203+
isMasterUserSecretStabilized(dbInstance);
204+
205+
requestLogger.log(String.format("isDBInstanceStabilizedAfterMutate: %b", isDBInstanceStabilizedAfterMutateResult),
206+
ImmutableMap.of("isDBInstanceAvailable", isDBInstanceAvailable(dbInstance),
207+
"isReplicationComplete", isReplicationComplete(dbInstance),
208+
"isDBParameterGroupNotApplying", isDBParameterGroupNotApplying(dbInstance),
209+
"isNoPendingChanges", isNoPendingChanges(dbInstance),
210+
"isCaCertificateChangesApplied", isCaCertificateChangesApplied(dbInstance, model),
211+
"isVpcSecurityGroupsActive", isVpcSecurityGroupsActive(dbInstance),
212+
"isDomainMembershipsJoined", isDomainMembershipsJoined(dbInstance),
213+
"isMasterUserSecretStabilized", isMasterUserSecretStabilized(dbInstance)),
214+
ImmutableMap.of("Description", "isDBInstanceStabilizedAfterMutate method will be repeatedly" +
215+
" called with a backoff mechanism after the modify call until it returns true. This" +
216+
" process will continue until all included flags are true."));
217+
218+
return isDBInstanceStabilizedAfterMutateResult;
219+
}
220+
221+
public static boolean isDBInstanceStabilizedAfterReboot(
222+
final DBInstance dbInstance,
223+
final RequestLogger requestLogger
224+
) {
225+
assertNoTerminalStatus(dbInstance);
226+
227+
final boolean isDBClusterParameterGroupStabilized = true;
228+
return isDBInstanceStabilizedAfterReboot(dbInstance, isDBClusterParameterGroupStabilized, requestLogger);
229+
}
230+
231+
public static boolean isDBInstanceStabilizedAfterReboot(
232+
final DBInstance dbInstance,
233+
final DBCluster dbCluster,
234+
final ResourceModel model,
235+
final RequestLogger requestLogger
236+
) {
237+
assertNoTerminalStatus(dbInstance);
238+
239+
final boolean isDBClusterParameterGroupStabilized = isDBClusterParameterGroupInSync(model, dbCluster);
240+
return isDBInstanceStabilizedAfterReboot(dbInstance, isDBClusterParameterGroupStabilized, requestLogger);
241+
}
242+
243+
private static boolean isDBInstanceStabilizedAfterReboot(
244+
final DBInstance dbInstance,
245+
final boolean isDBClusterParameterGroupStabilized,
246+
final RequestLogger requestLogger
247+
) {
248+
final boolean isDBInstanceStabilizedAfterReboot = isDBInstanceAvailable(dbInstance) &&
249+
isDBParameterGroupInSync(dbInstance) &&
250+
isOptionGroupInSync(dbInstance) &&
251+
isDBClusterParameterGroupStabilized;
252+
253+
requestLogger.log(String.format("isDBInstanceStabilizedAfterReboot: %b", isDBInstanceStabilizedAfterReboot),
254+
ImmutableMap.of("isDBInstanceAvailable", isDBInstanceAvailable(dbInstance),
255+
"isDBParameterGroupInSync", isDBParameterGroupInSync(dbInstance),
256+
"isOptionGroupInSync", isOptionGroupInSync(dbInstance),
257+
"isDBClusterParameterGroupStabilized", isDBClusterParameterGroupStabilized),
258+
ImmutableMap.of("Description", "isDBInstanceStabilizedAfterReboot method will be repeatedly" +
259+
" called with a backoff mechanism after the reboot call until it returns true. This" +
260+
" process will continue until all included flags are true."));
261+
262+
return isDBInstanceStabilizedAfterReboot;
263+
}
264+
265+
public static boolean isInstanceStabilizedAfterReplicationStart(
266+
final DBInstance dbInstance,
267+
final ResourceModel model
268+
) {
269+
assertNoTerminalStatus(dbInstance);
270+
return isDBInstanceAvailable(dbInstance)
271+
&& dbInstance.hasDbInstanceAutomatedBackupsReplications() &&
272+
!dbInstance.dbInstanceAutomatedBackupsReplications().isEmpty() &&
273+
model.getAutomaticBackupReplicationRegion()
274+
.equalsIgnoreCase(
275+
Arn.fromString(dbInstance.dbInstanceAutomatedBackupsReplications().get(0).dbInstanceAutomatedBackupsArn()).getRegion());
276+
}
277+
}

aws-rds-dbinstance/src/main/java/software/amazon/rds/dbinstance/DeleteHandler.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
package software.amazon.rds.dbinstance;
22

3-
import java.util.Collections;
4-
import java.util.function.Function;
5-
import java.util.Optional;
6-
73
import com.google.common.annotations.VisibleForTesting;
84
import org.apache.commons.lang3.BooleanUtils;
95
import org.apache.commons.lang3.StringUtils;
10-
116
import software.amazon.awssdk.services.ec2.Ec2Client;
127
import software.amazon.awssdk.services.rds.RdsClient;
138
import software.amazon.awssdk.services.rds.model.DBInstance;
@@ -20,6 +15,8 @@
2015
import software.amazon.rds.common.util.IdentifierFactory;
2116
import software.amazon.rds.dbinstance.client.VersionedProxyClient;
2217

18+
import java.util.function.Function;
19+
2320
public class DeleteHandler extends BaseHandlerStd {
2421

2522
private static final String SNAPSHOT_PREFIX = "Snapshot-";

0 commit comments

Comments
 (0)