shardingsphere-operator/pkg/reconcile/storagenode/aws/rdsinstance.go (157 lines of code) (raw):
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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 aws
import (
"context"
"crypto/rand"
"errors"
"fmt"
"math/big"
"regexp"
"strconv"
"github.com/apache/shardingsphere-on-cloud/shardingsphere-operator/api/v1alpha1"
"github.com/database-mesh/golang-sdk/aws/client/rds"
"github.com/database-mesh/golang-sdk/pkg/random"
)
// nolint:gocognit
func validCreateInstanceParams(node *v1alpha1.StorageNode, paramsptr *map[string]string) error {
requiredParams := map[string]string{
"engine": "engine is empty",
"engineVersion": "engine version is empty",
"instanceClass": "instance class is empty",
"masterUsername": "master username is empty",
"masterUserPassword": "master user password is empty",
"allocatedStorage": "allocated storage is empty",
}
params := *paramsptr
for k, v := range requiredParams {
if val, ok := params[k]; !ok || val == "" {
if k == "masterUserPassword" || k == "masterUsername" {
generator := generate(k)
params[k] = generator
} else {
return errors.New(v)
}
}
}
if username, ok := params["masterUsername"]; ok {
validatedUsername, err := validateusername(username)
if err != nil {
return err
}
params["masterUsername"] = validatedUsername
}
// validate instance identifier.
if val, ok := node.Annotations[v1alpha1.AnnotationsInstanceIdentifier]; !ok || val == "" {
return errors.New("instance identifier is empty")
}
// TODO set options to generate password and write back to storage node annos.
// TODO set options to set master username by user.
// validate master user password length. must be greater than 8. from aws doc.
lp := len(params["masterUserPassword"])
if lp < 8 || lp > 41 {
return errors.New("master user password length should be greater than 8")
} else {
node.Annotations[v1alpha1.AnnotationsMasterUserPassword] = params["masterUserPassword"]
}
return nil
}
func validateusername(str string) (string, error) {
if l := len(str); l > 16 {
return "", errors.New("username length should be less than 16")
}
pattern := "^[a-zA-Z0-9_-]+$"
matched, err := regexp.MatchString(pattern, str)
if err != nil {
return "", fmt.Errorf("validateusername error: %s", err.Error())
}
if !matched {
return "", errors.New("username contains invalid characters")
}
str = regexp.MustCompile(`_{2,}`).ReplaceAllString(str, "_")
str = regexp.MustCompile(`-{2,}`).ReplaceAllString(str, "-")
return str, nil
}
func generate(gentype string) string {
switch gentype {
case "masterUsername":
nBig, err := rand.Int(rand.Reader, big.NewInt(int64(16)))
if err != nil {
return ""
}
length := int(nBig.Int64()) + 1
return random.StringN(length)
case "masterUserPassword":
nBig, err := rand.Int(rand.Reader, big.NewInt(int64(34)))
if err != nil {
return ""
}
length := int(nBig.Int64()) + 8
chars := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%^&*()_+-={}[]:;?"
return random.StringCustom(length, []byte(chars))
default:
return ""
}
}
func (c *RdsClient) CreateInstance(ctx context.Context, node *v1alpha1.StorageNode, params map[string]string) error {
// validate params
if err := validCreateInstanceParams(node, ¶ms); err != nil {
return err
}
storage, err := strconv.ParseInt(params["allocatedStorage"], 10, 64)
if err != nil {
return fmt.Errorf("allocated storage is not a number: %s", err.Error())
}
instance := c.Instance()
instance.SetEngine(params["engine"]).
SetEngineVersion(params["engineVersion"]).
SetDBInstanceClass(params["instanceClass"]).
SetDBInstanceIdentifier(node.Annotations[v1alpha1.AnnotationsInstanceIdentifier]).
SetMasterUsername(params["masterUsername"]).
SetMasterUserPassword(params["masterUserPassword"]).
SetAllocatedStorage(int32(storage))
// set database name if needed.
if v, ok := params[node.Annotations[v1alpha1.AnnotationsInstanceDBName]]; ok {
instance.SetDBName(v)
}
return instance.Create(ctx)
}
func (c *RdsClient) GetInstance(ctx context.Context, node *v1alpha1.StorageNode) (*rds.DescInstance, error) {
identifier, ok := node.Annotations[v1alpha1.AnnotationsInstanceIdentifier]
if !ok {
return nil, errors.New("instance identifier is empty")
}
instance := c.Instance()
instance.SetDBInstanceIdentifier(identifier)
return instance.Describe(ctx)
}
func (c *RdsClient) GetInstanceByIdentifier(ctx context.Context, identifier string) (*rds.DescInstance, error) {
instance := c.Instance()
instance.SetDBInstanceIdentifier(identifier)
return instance.Describe(ctx)
}
func (c *RdsClient) GetInstancesByFilters(ctx context.Context, filters map[string][]string) ([]*rds.DescInstance, error) {
instance := c.Instance()
for k, v := range filters {
instance.SetFilter(k, v)
}
return instance.DescribeAll(ctx)
}
// DeleteInstance delete rds instance.
// aws rds instance status doc: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/accessing-monitoring.html
func (c *RdsClient) DeleteInstance(ctx context.Context, node *v1alpha1.StorageNode, storageProvider *v1alpha1.StorageProvider) error {
identifier, ok := node.Annotations[v1alpha1.AnnotationsInstanceIdentifier]
if !ok {
return errors.New("instance identifier is empty")
}
instance := c.Instance()
instance.SetDBInstanceIdentifier(identifier)
// check instance status first
ins, err := c.GetInstance(ctx, node)
if err != nil {
return err
}
if ins == nil || ins.DBInstanceStatus == rds.DBInstanceStatusDeleting {
return nil
}
var isDeleteBackup, isSkipFinalSnapshot bool
switch storageProvider.Spec.ReclaimPolicy {
case v1alpha1.StorageReclaimPolicyDeleteWithFinalSnapshot:
isDeleteBackup, isSkipFinalSnapshot = true, false
case v1alpha1.StorageReclaimPolicyDelete:
isDeleteBackup, isSkipFinalSnapshot = true, true
case v1alpha1.StorageReclaimPolicyRetain:
isDeleteBackup, isSkipFinalSnapshot = false, true
}
instance.SetDeleteAutomateBackups(isDeleteBackup)
instance.SetSkipFinalSnapshot(isSkipFinalSnapshot)
return instance.Delete(ctx)
}