feat: add aurora serverless v2 in storage#4075
Conversation
Codecov Report
@@ Coverage Diff @@
## mainline #4075 +/- ##
============================================
- Coverage 69.09% 69.00% -0.09%
============================================
Files 248 250 +2
Lines 35453 35768 +315
Branches 264 264
============================================
+ Hits 24495 24683 +188
- Misses 9766 9893 +127
Partials 1192 1192
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. |
|
|
||
| const ( | ||
| // Aurora Serverless versions. | ||
| ServerlessVersionV1 = "V1" |
There was a problem hiding this comment.
Maybe we should lowercase the 'v' (here, flag description, prompt, examples, etc.), based on https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2.html and Go/Semantic Versioning conventions. The flag should accept both upper and lower case input, though.
There was a problem hiding this comment.
I lowercased the serverless-version flag to v1 or v2.
The flag should accept both upper and lower case input, though.
I think the flag should accept only lower case for now. The database engine flag currently accepts only "MySQL" or "PostgreSQL", not "mysql" nor "postgresql" and serverless-version flag should do the same.
Lou1415926
left a comment
There was a problem hiding this comment.
Thank you @hkford so much for adding this ❤️ !!
I wonder if we should just always generate the template for v2. That is, when people run
copilot storage init --storage-type Aurora
we will generate a template that creates the aurora serverless v2 cluster.
|
@Lou1415926 Create an RDS Aurora Serverless v2 cluster using PostgreSQL as the database engine.
`$ copilot storage init -n my-cluster -t Aurora -w frontend --engine PostgreSQL --initial-db testdb`
Create an RDS Aurora Serverless v1 cluster using MySQL as the database engine.
`$ copilot storage init -n my-cluster -t Aurora --serverless-version v1 -w frontend --engine MySQL --initial-db testdb` |
efekarakus
left a comment
There was a problem hiding this comment.
Awesome, thank you so much @hkford ! Apologies on the late review
| ServerlessVersionV1 = "v1" | ||
| ServerlessVersionV2 = "v2" |
There was a problem hiding this comment.
nit: do these need to be public?
| ServerlessVersionV1 = "v1" | |
| ServerlessVersionV2 = "v2" | |
| auroraServerlessVersionV1 = "v1" | |
| auraraServerlessVersionV2 = "v2" |
There was a problem hiding this comment.
addon.AuroraServerlessVersion is used in newRDSTemplate() of cli/storage_init.go. This function calls addon.NewServerlessVxTemplate using switch statement, same as addon.RDSEngineTypeMySQL or addon.RDSEngineTypePostgreSQL.
| Envs []string // The copilot environments found inside the current app. | ||
| WorkloadType string // The type of the workload associated with the RDS addon. | ||
| ClusterName string // The name of the cluster. | ||
| ServerlessVersion string // The version of Aurora Serverless |
There was a problem hiding this comment.
I wonder if this is a hint that we need two types of constructors? and can replace the existing RDSTemplate constructor:
type RDSProps struct {
WorkloadType
// Other existing fields...
Envs []string
serverlessVersion string
}
func NewServerlessV1Template(rds RDSProps) *RDSTemplate {
rds.serverlessVersion = auroraServerlessVersionV1
// existing logic...
}
func NewServerlessV2Template(rds RDSProps) *RDSTemplate {
rds.serverlessVersion = auroraServerlessVersionV2
}There was a problem hiding this comment.
I agree with you. I made two types of constructors.
| const ( | ||
| serverlessVersionV1 = "v1" | ||
| serverlessVersionV2 = "v2" | ||
| defaultServerlessVersionString = "v2" |
There was a problem hiding this comment.
nit:
| defaultServerlessVersionString = "v2" | |
| defaultServerlessVersion = serverlessVersionV2 |
| defaultServerlessVersion := defaultServerlessVersionString | ||
| o.rdsServerlessVersion = defaultServerlessVersion |
There was a problem hiding this comment.
nit:
| defaultServerlessVersion := defaultServerlessVersionString | |
| o.rdsServerlessVersion = defaultServerlessVersion | |
| o.rdsServerlessVersion = defaultServerlessVersion |
| Properties: | ||
| Description: !Ref 'AWS::StackName' | ||
| {{- if eq .Engine "MySQL"}} | ||
| Family: 'aurora-mysql8.0' |
There was a problem hiding this comment.
Just to make sure was the addon tested in an an account with copilot deploy for both MySQL and Postgres?
| # Replace "All" below with "!Ref Env" to set different autoscaling limits per environment. | ||
| MinCapacity: !FindInMap [{{logicalIDSafe .ClusterName}}EnvScalingConfigurationMap, All, DBMinCapacity] | ||
| MaxCapacity: !FindInMap [{{logicalIDSafe .ClusterName}}EnvScalingConfigurationMap, All, DBMaxCapacity] | ||
| {{logicalIDSafe .ClusterName}}DBWriterInstance: |
There was a problem hiding this comment.
Oh very interesting, can you explain to be why an explicit writer instance is needed?
We didn't set one for aurora serverless v1 so I'm curious why v2 is different?
There was a problem hiding this comment.
Same question! When creating AWS::RDS::DBCluster, does RDS automatically create a writer instance for that cluster?
There was a problem hiding this comment.
We had an offline discussion and I'm sharing it here for visibility:
Does RDS automatically create a writer instance for that cluster?
For v2, it doesn't. If we have only the DBCluster resource in the template, only a cluster is created - no writer instance.
We didn't set one for aurora serverless v1 so I'm curious why v2 is different?
v1 contains one and only one read/write instance. When we create a DBCluster, that one and only one read/write instance also gets automatically created. This is different from v2 and is also the reason why we didn't need to explicitly create an instance before.
| ServerlessVersionV1 = "v1" | ||
| ServerlessVersionV2 = "v2" |
There was a problem hiding this comment.
nit:
| ServerlessVersionV1 = "v1" | |
| ServerlessVersionV2 = "v2" | |
| AuroraServerlessVersionV1 = "v1" | |
| AuroraServerlessVersionV2 = "v2" |
| }, | ||
| wantedBinary: []byte("mysql"), | ||
| }, | ||
| "renders postgresql v2 content": { |
| func validateServerlessVersion(val interface{}) error { | ||
| version, ok := val.(string) | ||
| if !ok { | ||
| return errValueNotAString | ||
| } | ||
| for _, valid := range serverlessVersions { | ||
| if version == valid { | ||
| return nil | ||
| } | ||
| } | ||
| return fmt.Errorf(fmtErrInvalidServerlessVersion, version, prettify(serverlessVersions)) | ||
| } | ||
|
|
There was a problem hiding this comment.
I think in this case we can move this validation function to storage_init.go, similar to
copilot-cli/internal/pkg/cli/storage_init.go
Line 333 in 08983f7
| func validateServerlessVersion(val interface{}) error { | |
| version, ok := val.(string) | |
| if !ok { | |
| return errValueNotAString | |
| } | |
| for _, valid := range serverlessVersions { | |
| if version == valid { | |
| return nil | |
| } | |
| } | |
| return fmt.Errorf(fmtErrInvalidServerlessVersion, version, prettify(serverlessVersions)) | |
| } | |
| func (o *initStorageOpts) validateServerlessVersion() error { | |
| for _, valid := range serverlessVersions { | |
| if o.serverlessVersion == valid { | |
| return nil | |
| } | |
| } | |
| return fmt.Errorf(fmtErrInvalidServerlessVersion, version, prettify(serverlessVersions)) | |
| } | |
The validation functions in this validate.go file accept val interface{} because these validation functions are passed to the prompt handlers which require the function signature to be (interface{}) error.
In our case, since we no longer prompt for versions, we won't need to adhere to this signature!
There was a problem hiding this comment.
Thank you for your suggestion. I moved validateServerlessVersion to storage_init.go.
| cmd.Flags().BoolVar(&vars.noLSI, storageNoLSIFlag, false, storageNoLSIFlagDescription) | ||
| cmd.Flags().BoolVar(&vars.noSort, storageNoSortFlag, false, storageNoSortFlagDescription) | ||
|
|
||
| cmd.Flags().StringVar(&vars.rdsServerlessVersion, storageRDSServerlessVersionFlag, "", storageRDSServerlessVersionFlagDescription) |
There was a problem hiding this comment.
instead of using askAuroraServerlessVersion to set the default, what do you think of setting it here:
| cmd.Flags().StringVar(&vars.rdsServerlessVersion, storageRDSServerlessVersionFlag, "", storageRDSServerlessVersionFlagDescription) | |
| cmd.Flags().StringVar(&vars.rdsServerlessVersion, storageRDSServerlessVersionFlag, defaultServerlessVersion, storageRDSServerlessVersionFlagDescription) |
| storageRDSServerlessVersionFlagDescription = `Optional. Aurora Serverless version. Must be either "v1" or "v2". | ||
| (default v2)` |
There was a problem hiding this comment.
If we do decide to set the default in cmd.Flags().StringVar(&vars.rdsServerlessVersion, storageRDSServerlessVersionFlag, "", storageRDSServerlessVersionFlagDescription) (in storage_init.go), then the cobra will automatically append (default "v2") to the end of the flag description I think!
| storageRDSServerlessVersionFlagDescription = `Optional. Aurora Serverless version. Must be either "v1" or "v2". | |
| (default v2)` | |
| storageRDSServerlessVersionFlagDescription = `Optional. Aurora Serverless version. Must be either "v1" or "v2".` |
There was a problem hiding this comment.
I set the default value in cmd.Flags().StringVar(&vars.rdsServerlessVersion, storageRDSServerlessVersionFlag, defaultServerlessVersion, storageRDSServerlessVersionFlagDescription) and cobra appended (default "v2") as expected.
| {{- if eq $.Engine "MySQL"}} | ||
| "DBMinCapacity": 0.5 # AllowedValues: from 0.5 through 128 | ||
| "DBMaxCapacity": 8 # AllowedValues: from 0.5 through 128 | ||
| {{- else}} | ||
| "DBMinCapacity": 0.5 # AllowedValues: from 0.5 through 128 | ||
| "DBMaxCapacity": 8 # AllowedValues: from 0.5 through 128 | ||
| {{end -}} |
There was a problem hiding this comment.
nit: seems like we don't need to differentiate based on engine anymore (previously, the min-max was different for mysql vs. postgresql)
| {{- if eq $.Engine "MySQL"}} | |
| "DBMinCapacity": 0.5 # AllowedValues: from 0.5 through 128 | |
| "DBMaxCapacity": 8 # AllowedValues: from 0.5 through 128 | |
| {{- else}} | |
| "DBMinCapacity": 0.5 # AllowedValues: from 0.5 through 128 | |
| "DBMaxCapacity": 8 # AllowedValues: from 0.5 through 128 | |
| {{end -}} | |
| "DBMinCapacity": 0.5 # AllowedValues: from 0.5 through 128 | |
| "DBMaxCapacity": 8 # AllowedValues: from 0.5 through 128 |
| # Replace "All" below with "!Ref Env" to set different autoscaling limits per environment. | ||
| MinCapacity: !FindInMap [{{logicalIDSafe .ClusterName}}EnvScalingConfigurationMap, All, DBMinCapacity] | ||
| MaxCapacity: !FindInMap [{{logicalIDSafe .ClusterName}}EnvScalingConfigurationMap, All, DBMaxCapacity] | ||
| {{logicalIDSafe .ClusterName}}DBWriterInstance: |
There was a problem hiding this comment.
Same question! When creating AWS::RDS::DBCluster, does RDS automatically create a writer instance for that cluster?
|
@efekarakus @Lou1415926 |
efekarakus
left a comment
There was a problem hiding this comment.
Looks great, just some tiny comments and then we can
! thank you @hkford
| AuroraServerlessVersionV1 = "v1" | ||
| AuroraServerlessVersionV2 = "v2" |
There was a problem hiding this comment.
nit: can these be private?
| Envs []string // The copilot environments found inside the current app. | ||
| WorkloadType string // The type of the workload associated with the RDS addon. | ||
| ClusterName string // The name of the cluster. | ||
| AuroraServerlessVersion string // The version of Aurora Serverless |
There was a problem hiding this comment.
nit: can be private, and can we add a period at the end of the comment plz 🥺
| AuroraServerlessVersion string // The version of Aurora Serverless | |
| auroraServerlessVersion string // The version of Aurora Serverless. |
There was a problem hiding this comment.
changed into private field and added a period.
| var version string | ||
| switch o.auroraServerlessVersion { | ||
| case auroraServerlessVersionV1: | ||
| version = addon.AuroraServerlessVersionV1 | ||
| return addon.NewServerlessV1Template(addon.RDSProps{ | ||
| ClusterName: o.storageName, | ||
| AuroraServerlessVersion: version, | ||
| Engine: engine, | ||
| InitialDBName: o.rdsInitialDBName, | ||
| ParameterGroup: o.rdsParameterGroup, | ||
| Envs: envs, | ||
| WorkloadType: o.workloadType, | ||
| }), nil | ||
| case auroraServerlessVersionV2: | ||
| version = addon.AuroraServerlessVersionV2 | ||
| return addon.NewServerlessV2Template(addon.RDSProps{ | ||
| ClusterName: o.storageName, | ||
| AuroraServerlessVersion: version, | ||
| Engine: engine, | ||
| InitialDBName: o.rdsInitialDBName, | ||
| ParameterGroup: o.rdsParameterGroup, | ||
| Envs: envs, | ||
| WorkloadType: o.workloadType, | ||
| }), nil | ||
| default: | ||
| return nil, errors.New("unknown serverless version") | ||
| } |
There was a problem hiding this comment.
To reduce duplication:
props := addon.RDSProps{
ClusterName: o.storageName,
Engine: engine,
InitialDBName: o.rdsInitialDBName,
ParameterGroup: o.rdsParameterGroup,
Envs: envs,
WorkloadType: o.workloadType,
}
switch v:= o.auroraServerlessVersion; v {
case auroraServerlessVersionV1:
return addon.NewServerlessV1Template(props), nil
case auroraServerlessVersionV2:
return addon.NewServerlessV2Template(props), nil
default:
return nil, fmt.Errorf("unknown Aurora serverless version %q", v)
}We shouldn't need to set the AuroraServerlessVersion as that's done by the constructor in the addon pkg
There was a problem hiding this comment.
Yes I noticed. I reduced duplication.
Lou1415926
left a comment
There was a problem hiding this comment.
@hkford Thank you very much! This looks great. I don't have much to add in addition to what Efe said. I will approve and add the do-not-merge label for you to address Efe's feedback!
| inAppName: "bowie", | ||
| inStorageType: rdsStorageType, | ||
| inServerlessVersion: auroraServerlessVersionV1, | ||
| wantedErr: nil, |
There was a problem hiding this comment.
nit: since nil is the zero value for wantedErr, it would by default be nil -
| wantedErr: nil, |
| inAppName: "bowie", | ||
| inStorageType: rdsStorageType, | ||
| inServerlessVersion: auroraServerlessVersionV2, | ||
| wantedErr: nil, |
There was a problem hiding this comment.
samesies!
| wantedErr: nil, |
There was a problem hiding this comment.
I removed wantedErr: nil in two test cases and left other test cases alone. Should I remove other wantedErr: nil or do so in another PR?
| mockStore: func(m *mocks.Mockstore) {}, | ||
| inAppName: "bowie", | ||
| inStorageType: rdsStorageType, | ||
| inServerlessVersion: "weird-serverless-version", |
I updated `copilot storage init` command's documentation to include Aurora Serverless Flags and examples of creating both Aurora Serverless v1 and v2. The aurora Serverless v2 feature is merged at [this PR](#4075). By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the Apache 2.0 License.

Added
--serverless-versionflag instorage initcommand.When you pass
--serverless-version V1thestorage initcommand generates an addon template of Aurora Serverless V1 cluster the same as before. When you pass--serverless-version V2thestorage initcommand generates an addon template of Aurora Serverless V2 cluster, which is recently released.Fixes #3493
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the Apache 2.0 License.