Skip to content

Commit

Permalink
Merge pull request #2049 from wotolom/feat-rdsinstance-storagethrough…
Browse files Browse the repository at this point in the history
…put-fix-dbinstance-iops-storagethroughput

feat(rdsinstance): add storageThroughput
  • Loading branch information
MisterMX committed Apr 24, 2024
2 parents a55852f + 017cdbb commit 4331baa
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 6 deletions.
21 changes: 20 additions & 1 deletion apis/database/v1beta1/rdsinstance_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -491,13 +491,18 @@ type RDSInstanceParameters struct {

// IOPS is the amount of Provisioned IOPS (input/output operations per second) to be
// initially allocated for the DB instance. For information about valid IOPS
// values, see see Amazon RDS Provisioned IOPS Storage to Improve Performance
// values, see Amazon RDS Provisioned IOPS Storage to Improve Performance
// (http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html#USER_PIOPS)
// in the Amazon RDS User Guide.
// Constraints: Must be a multiple between 1 and 50 of the storage amount for
// the DB instance. Must also be an integer multiple of 1000. For example, if
// the size of your DB instance is 500 GiB, then your IOPS value can be 2000,
// 3000, 4000, or 5000.
//
// For valid IOPS values on DB instances with storage type "gp3",
// see https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html#gp3-storage.
//
// Note: controller considers 0 and null as equivalent
// +optional
IOPS *int `json:"iops,omitempty"`

Expand Down Expand Up @@ -713,6 +718,16 @@ type RDSInstanceParameters struct {
// +optional
StorageEncrypted *bool `json:"storageEncrypted,omitempty"`

// The storage throughput value for the DB instance.
//
// This setting applies only to the gp3 storage type.
//
// This setting doesn't apply to Amazon Aurora or RDS Custom DB instances.
//
// Note: controller considers 0 and null as equivalent
// +optional
StorageThroughput *int `json:"storageThroughput,omitempty"`

// StorageType specifies the storage type to be associated with the DB instance.
// Valid values: standard | gp2 | io1
// If you specify io1, you must also include a value for the IOPS parameter.
Expand Down Expand Up @@ -1089,6 +1104,10 @@ type PendingModifiedValues struct {
// class of the DB instance.
ProcessorFeatures []ProcessorFeature `json:"processorFeatures,omitempty"`

// StorageThroughput indicates the new storage throughput value for the DB instance
// that will be applied or is currently being applied.
StorageThroughput int `json:"storageThroughput,omitempty"`

// StorageType specifies the storage type to be associated with the DB instance.
StorageType string `json:"storageType,omitempty"`
}
Expand Down
5 changes: 5 additions & 0 deletions apis/database/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 16 additions & 3 deletions package/crds/database.aws.crossplane.io_rdsinstances.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -492,15 +492,17 @@ spec:
deleting a Read Replica.'
type: string
iops:
description: 'IOPS is the amount of Provisioned IOPS (input/output
description: "IOPS is the amount of Provisioned IOPS (input/output
operations per second) to be initially allocated for the DB
instance. For information about valid IOPS values, see see Amazon
instance. For information about valid IOPS values, see Amazon
RDS Provisioned IOPS Storage to Improve Performance (http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html#USER_PIOPS)
in the Amazon RDS User Guide. Constraints: Must be a multiple
between 1 and 50 of the storage amount for the DB instance.
Must also be an integer multiple of 1000. For example, if the
size of your DB instance is 500 GiB, then your IOPS value can
be 2000, 3000, 4000, or 5000.'
be 2000, 3000, 4000, or 5000. \n For valid IOPS values on DB
instances with storage type \"gp3\", see https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html#gp3-storage.
\n Note: controller considers 0 and null as equivalent"
type: integer
kmsKeyId:
description: KMSKeyID for an encrypted DB instance. The KMS key
Expand Down Expand Up @@ -1062,6 +1064,12 @@ spec:
DB instances is managed by the DB cluster. For more information,
see CreateDBCluster. Default: false'
type: boolean
storageThroughput:
description: "The storage throughput value for the DB instance.
\n This setting applies only to the gp3 storage type. \n This
setting doesn't apply to Amazon Aurora or RDS Custom DB instances.
\n Note: controller considers 0 and null as equivalent"
type: integer
storageType:
description: 'StorageType specifies the storage type to be associated
with the DB instance. Valid values: standard | gp2 | io1 If
Expand Down Expand Up @@ -1664,6 +1672,11 @@ spec:
- value
type: object
type: array
storageThroughput:
description: StorageThroughput indicates the new storage throughput
value for the DB instance that will be applied or is currently
being applied.
type: integer
storageType:
description: StorageType specifies the storage type to be
associated with the DB instance.
Expand Down
41 changes: 39 additions & 2 deletions pkg/clients/database/rds.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ func GenerateCreateRDSInstanceInput(name, password string, p *v1beta1.RDSInstanc
EnablePerformanceInsights: p.EnablePerformanceInsights,
Engine: aws.String(p.Engine),
EngineVersion: p.EngineVersion,
Iops: pointer.ToIntAsInt32Ptr(p.IOPS),
KmsKeyId: p.KMSKeyID,
LicenseModel: p.LicenseModel,
MasterUserPassword: pointer.ToOrNilIfZeroValue(password),
Expand Down Expand Up @@ -140,6 +139,13 @@ func GenerateCreateRDSInstanceInput(name, password string, p *v1beta1.RDSInstanc
}
}
}
// for storageType gp3 below engine specific allocatedStorage threshold, do not send iops and storageThroughput
// to avoid errors like "You can't specify IOPS or storage throughput for engine postgres and a storage size less than 400."
// This allows users to set iops/storageThroughput to the default values themselves.
if !IsStorageTypeGP3BelowAllocatedStorageThreshold(p) {
c.Iops = pointer.ToIntAsInt32Ptr(p.IOPS)
c.StorageThroughput = pointer.ToIntAsInt32Ptr(p.StorageThroughput)
}
return c
}

Expand Down Expand Up @@ -185,6 +191,7 @@ func GenerateRestoreRDSInstanceFromS3Input(name, password string, p *v1beta1.RDS
SourceEngine: p.RestoreFrom.S3.SourceEngine,
SourceEngineVersion: p.RestoreFrom.S3.SourceEngineVersion,
StorageEncrypted: p.StorageEncrypted,
StorageThroughput: pointer.ToIntAsInt32Ptr(p.StorageThroughput),
StorageType: p.StorageType,
VpcSecurityGroupIds: p.VPCSecurityGroupIDs,
}
Expand Down Expand Up @@ -234,6 +241,7 @@ func GenerateRestoreRDSInstanceFromSnapshotInput(name string, p *v1beta1.RDSInst
OptionGroupName: p.OptionGroupName,
Port: pointer.ToIntAsInt32Ptr(p.Port),
PubliclyAccessible: p.PubliclyAccessible,
StorageThroughput: pointer.ToIntAsInt32Ptr(p.StorageThroughput),
StorageType: p.StorageType,
VpcSecurityGroupIds: p.VPCSecurityGroupIDs,
}
Expand Down Expand Up @@ -287,6 +295,7 @@ func GenerateRestoreRDSInstanceToPointInTimeInput(name string, p *v1beta1.RDSIns
OptionGroupName: p.OptionGroupName,
Port: pointer.ToIntAsInt32Ptr(p.Port),
PubliclyAccessible: p.PubliclyAccessible,
StorageThroughput: pointer.ToIntAsInt32Ptr(p.StorageThroughput),
StorageType: p.StorageType,
VpcSecurityGroupIds: p.VPCSecurityGroupIDs,

Expand Down Expand Up @@ -321,7 +330,7 @@ func GenerateRestoreRDSInstanceToPointInTimeInput(name string, p *v1beta1.RDSIns
// CreatePatch creates a *v1beta1.RDSInstanceParameters that has only the changed
// values between the target *v1beta1.RDSInstanceParameters and the current
// *rds.DBInstance
func CreatePatch(in *rdstypes.DBInstance, spec *v1beta1.RDSInstanceParameters) (*v1beta1.RDSInstanceParameters, error) {
func CreatePatch(in *rdstypes.DBInstance, spec *v1beta1.RDSInstanceParameters) (*v1beta1.RDSInstanceParameters, error) { //nolint:gocyclo
target := spec.DeepCopy()
currentParams := &v1beta1.RDSInstanceParameters{}
LateInitialize(currentParams, in)
Expand Down Expand Up @@ -361,6 +370,14 @@ func CreatePatch(in *rdstypes.DBInstance, spec *v1beta1.RDSInstanceParameters) (
}
}

// Depending on whether the instance was created as gp2 or modified from another type (e.g. gp3) to gp2,
// AWS provides different responses for IOPS/StorageThroughput (either 0 or nil).
// Therefore, we consider both 0 and nil to be equivalent.
if aws.ToInt(target.IOPS) == aws.ToInt(currentParams.IOPS) {
currentParams.IOPS = target.IOPS
currentParams.StorageThroughput = target.StorageThroughput
}

jsonPatch, err := jsonpatch.CreateJSONPatch(currentParams, target)
if err != nil {
return nil, err
Expand Down Expand Up @@ -412,6 +429,7 @@ func GenerateModifyDBInstanceInput(name string, p *v1beta1.RDSInstanceParameters
PreferredMaintenanceWindow: p.PreferredMaintenanceWindow,
PromotionTier: pointer.ToIntAsInt32Ptr(p.PromotionTier),
PubliclyAccessible: p.PubliclyAccessible,
StorageThroughput: pointer.ToIntAsInt32Ptr(p.StorageThroughput),
StorageType: p.StorageType,
UseDefaultProcessorFeatures: p.UseDefaultProcessorFeatures,
VpcSecurityGroupIds: p.VPCSecurityGroupIDs,
Expand Down Expand Up @@ -539,6 +557,7 @@ func GenerateObservation(db rdstypes.DBInstance) v1beta1.RDSInstanceObservation
LicenseModel: aws.ToString(db.PendingModifiedValues.LicenseModel),
MultiAZ: aws.ToBool(db.PendingModifiedValues.MultiAZ),
Port: int(aws.ToInt32(db.PendingModifiedValues.Port)),
StorageThroughput: int(aws.ToInt32(db.PendingModifiedValues.StorageThroughput)),
StorageType: aws.ToString(db.PendingModifiedValues.StorageType),
}
if db.PendingModifiedValues.PendingCloudwatchLogsExports != nil {
Expand Down Expand Up @@ -616,6 +635,7 @@ func LateInitialize(in *v1beta1.RDSInstanceParameters, db *rdstypes.DBInstance)
in.PromotionTier = pointer.LateInitializeIntFrom32Ptr(in.PromotionTier, db.PromotionTier)
in.PubliclyAccessible = pointer.LateInitialize(in.PubliclyAccessible, ptr.To(db.PubliclyAccessible))
in.StorageEncrypted = pointer.LateInitialize(in.StorageEncrypted, ptr.To(db.StorageEncrypted))
in.StorageThroughput = pointer.LateInitializeIntFrom32Ptr(in.StorageThroughput, db.StorageThroughput)
in.StorageType = pointer.LateInitialize(in.StorageType, db.StorageType)
in.Timezone = pointer.LateInitialize(in.Timezone, db.Timezone)

Expand Down Expand Up @@ -928,3 +948,20 @@ func DiffTags(spec []v1beta1.Tag, current []rdstypes.Tag) (addTags []rdstypes.Ta

return addTags, removeTags
}

// IsStorageTypeGP3BelowAllocatedStorageThreshold returns true if storageType is gp3 and allocatedStorage is below engine specific threshold
// See also https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html#gp3-storage.
func IsStorageTypeGP3BelowAllocatedStorageThreshold(p *v1beta1.RDSInstanceParameters) bool {
if pointer.StringValue(p.StorageType) != "gp3" {
return false
}

switch allocatedStorage, engine := aws.ToInt(p.AllocatedStorage), p.Engine; engine {
case "mariadb", "mysql", "postgres":
return allocatedStorage < 400
case "oracle-ee", "oracle-ee-cdb", "oracle-se2", "oracle-se2-cdb":
return allocatedStorage < 200
}

return false
}
8 changes: 8 additions & 0 deletions pkg/clients/database/rds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,7 @@ func TestGenerateObservation(t *testing.T) {
LicenseModel: &name,
MultiAZ: &multiAZ,
Port: &port32,
StorageThroughput: &storage32,
StorageType: &storageType,
}
pendingCloudwatch := rdstypes.PendingCloudwatchLogsExports{
Expand Down Expand Up @@ -891,6 +892,7 @@ func TestGenerateObservation(t *testing.T) {
LicenseModel: name,
MultiAZ: multiAZ,
Port: port,
StorageThroughput: storage,
StorageType: storageType,
PendingCloudwatchLogsExports: v1beta1.PendingCloudwatchLogsExports{
LogTypesToDisable: nil,
Expand Down Expand Up @@ -1077,6 +1079,7 @@ func TestLateInitialize(t *testing.T) {
PromotionTier: &tier32,
PubliclyAccessible: trueFlag,
StorageEncrypted: trueFlag,
StorageThroughput: &storage32,
StorageType: &storageType,
Timezone: &zone,
DBSecurityGroups: []rdstypes.DBSecurityGroupMembership{{DBSecurityGroupName: &name, Status: &status}},
Expand Down Expand Up @@ -1123,6 +1126,7 @@ func TestLateInitialize(t *testing.T) {
PromotionTier: &tier,
PubliclyAccessible: &trueFlag,
StorageEncrypted: &trueFlag,
StorageThroughput: &storage,
StorageType: &storageType,
Timezone: &zone,
DBSecurityGroups: []string{name},
Expand Down Expand Up @@ -1344,6 +1348,7 @@ func TestGenerateModifyDBInstanceInput(t *testing.T) {
PromotionTier: &tier,
PubliclyAccessible: &trueFlag,
StorageEncrypted: &trueFlag,
StorageThroughput: &storage,
StorageType: &storageType,
Timezone: &zone,
DBSecurityGroups: dbSecurityGroups,
Expand Down Expand Up @@ -1395,6 +1400,7 @@ func TestGenerateModifyDBInstanceInput(t *testing.T) {
PreferredMaintenanceWindow: &window,
PromotionTier: &tier32,
PubliclyAccessible: &trueFlag,
StorageThroughput: &storage32,
StorageType: &storageType,
UseDefaultProcessorFeatures: &trueFlag,
VpcSecurityGroupIds: vpcIds,
Expand Down Expand Up @@ -1473,6 +1479,7 @@ func TestGenerateCreateRDSInstanceInput(t *testing.T) {
PromotionTier: &tier,
PubliclyAccessible: &trueFlag,
StorageEncrypted: &trueFlag,
StorageThroughput: &storage,
StorageType: &storageType,
Timezone: &zone,
DBSecurityGroups: dbSecurityGroups,
Expand Down Expand Up @@ -1524,6 +1531,7 @@ func TestGenerateCreateRDSInstanceInput(t *testing.T) {
PromotionTier: &tier32,
PubliclyAccessible: &trueFlag,
StorageEncrypted: &trueFlag,
StorageThroughput: &storage32,
StorageType: &storageType,
Timezone: &zone,
VpcSecurityGroupIds: vpcIds,
Expand Down
7 changes: 7 additions & 0 deletions pkg/controller/database/rdsinstance/rdsinstance.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,13 @@ func (e *external) Update(ctx context.Context, mg resource.Managed) (managed.Ext
if cr.Spec.ForProvider.MasterUsername != nil {
conn[xpv1.ResourceCredentialsSecretUserKey] = []byte(aws.ToString(cr.Spec.ForProvider.MasterUsername))
}
// for storageType gp3 below engine specific allocatedStorage threshold, do not send iops and storageThroughput
// to avoid errors like "You can't specify IOPS or storage throughput for engine postgres and a storage size less than 400."
// This allows users to set iops/storageThroughput to the default values themselves.
if rds.IsStorageTypeGP3BelowAllocatedStorageThreshold(&cr.Spec.ForProvider) {
modify.Iops = nil
modify.StorageThroughput = nil
}

if _, err = e.client.ModifyDBInstance(ctx, modify); err != nil {
return managed.ExternalUpdate{}, errorutils.Wrap(err, errModifyFailed)
Expand Down
33 changes: 33 additions & 0 deletions pkg/controller/rds/dbinstance/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,14 @@ func (e *custom) preCreate(ctx context.Context, cr *svcapitypes.DBInstance, obj
return errors.Wrap(err, dbinstance.ErrCachePassword)
}

// for storageType gp3 below engine specific allocatedStorage threshold, do not send iops and storageThroughput
// to avoid errors like "You can't specify IOPS or storage throughput for engine postgres and a storage size less than 400."
// This allows users to set iops/storageThroughput to the default values themselves.
if isStorageTypeGP3BelowAllocatedStorageThreshold(cr) {
obj.Iops = nil
obj.StorageThroughput = nil
}

return nil
}

Expand Down Expand Up @@ -253,6 +261,14 @@ func (e *custom) preUpdate(ctx context.Context, cr *svcapitypes.DBInstance, obj
obj.VpcSecurityGroupIds = nil
}

// for storageType gp3 below engine specific allocatedStorage threshold, do not send iops and storageThroughput
// to avoid errors like "You can't specify IOPS or storage throughput for engine postgres and a storage size less than 400."
// This allows users to set iops/storageThroughput to the default values themselves.
if isStorageTypeGP3BelowAllocatedStorageThreshold(cr) {
obj.Iops = nil
obj.StorageThroughput = nil
}

return nil
}

Expand Down Expand Up @@ -715,3 +731,20 @@ func handleKmsKey(inKey *string, dbKey *string) *string {
}
return dbKey
}

// isStorageTypeGP3BelowAllocatedStorageThreshold returns true if storageType is gp3 and allocatedStorage is below engine specific threshold
// See also https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html#gp3-storage.
func isStorageTypeGP3BelowAllocatedStorageThreshold(cr *svcapitypes.DBInstance) bool {
if pointer.StringValue(cr.Spec.ForProvider.StorageType) != "gp3" {
return false
}

switch allocatedStorage, engine := pointer.Int64Value(cr.Spec.ForProvider.AllocatedStorage), pointer.StringValue(cr.Spec.ForProvider.Engine); engine {
case "mariadb", "mysql", "postgres":
return allocatedStorage < 400
case "oracle-ee", "oracle-ee-cdb", "oracle-se2", "oracle-se2-cdb":
return allocatedStorage < 200
}

return false
}

0 comments on commit 4331baa

Please sign in to comment.