Skip to content

Commit

Permalink
Added cluster name update validation
Browse files Browse the repository at this point in the history
  • Loading branch information
abhishekdwivedi3060 committed Jul 29, 2024
1 parent 2c2b437 commit 439cfd3
Show file tree
Hide file tree
Showing 7 changed files with 306 additions and 156 deletions.
107 changes: 86 additions & 21 deletions api/v1beta1/aerospikebackup_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"fmt"
"reflect"
"strings"

"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
Expand Down Expand Up @@ -87,19 +88,12 @@ func (r *AerospikeBackup) ValidateUpdate(old runtime.Object) (admission.Warnings
return nil, err
}

if len(r.Spec.OnDemandBackups) > 0 && len(oldObj.Spec.OnDemandBackups) > 0 {
// Check if onDemand backup spec is updated
if r.Spec.OnDemandBackups[0].ID == oldObj.Spec.OnDemandBackups[0].ID &&
!reflect.DeepEqual(r.Spec.OnDemandBackups[0], oldObj.Spec.OnDemandBackups[0]) {
return nil, fmt.Errorf("onDemand backup cannot be updated")
}
if err := r.validateAerospikeClusterUpdate(oldObj); err != nil {
return nil, err
}

// Check if previous onDemand backup is completed before allowing new onDemand backup
if r.Spec.OnDemandBackups[0].ID != oldObj.Spec.OnDemandBackups[0].ID && (len(r.Status.OnDemandBackups) == 0 ||
r.Status.OnDemandBackups[0].ID != oldObj.Spec.OnDemandBackups[0].ID) {
return nil,
fmt.Errorf("can not add new onDemand backup when previous onDemand backup is not completed")
}
if err := r.validateOnDemandBackupsUpdate(oldObj); err != nil {
return nil, err
}

return nil, nil
Expand Down Expand Up @@ -155,12 +149,12 @@ func (r *AerospikeBackup) validateBackupConfig() error {
return fmt.Errorf("secret-agent field cannot be specified in backup config")
}

aeroClusters, err := validateAerospikeCluster(&backupSvcConfig, backupConfigMap)
aeroClusters, err := r.validateAerospikeCluster(&backupSvcConfig, backupConfigMap)
if err != nil {
return err
}

err = validateBackupRoutines(&backupSvcConfig, backupConfigMap, aeroClusters)
err = r.validateBackupRoutines(&backupSvcConfig, backupConfigMap, aeroClusters)
if err != nil {
return err
}
Expand Down Expand Up @@ -212,7 +206,8 @@ func getK8sClient() (client.Client, error) {
return cl, nil
}

func validateAerospikeCluster(backupSvcConfig *model.Config, backupSvcConfigMap map[string]interface{},
func (r *AerospikeBackup) validateAerospikeCluster(backupSvcConfig *model.Config,
backupSvcConfigMap map[string]interface{},
) (map[string]*model.AerospikeCluster, error) {
if _, ok := backupSvcConfigMap[common.AerospikeClusterKey]; !ok {
return nil, fmt.Errorf("aerospike-cluster field is required in backupSvcConfig")
Expand Down Expand Up @@ -242,15 +237,63 @@ func validateAerospikeCluster(backupSvcConfig *model.Config, backupSvcConfigMap
backupSvcConfig.AerospikeClusters = make(map[string]*model.AerospikeCluster)
}

for name, aeroCluster := range aeroClusters {
backupSvcConfig.AerospikeClusters[name] = aeroCluster
for clusterName, aeroCluster := range aeroClusters {
if err := validateName(r.namePrefix(), clusterName); err != nil {
return nil, fmt.Errorf("invalid cluster name %s, %s", clusterName, err.Error())
}

backupSvcConfig.AerospikeClusters[clusterName] = aeroCluster
}

return aeroClusters, nil
}

func validateBackupRoutines(backupSvcConfig *model.Config, backupSvcConfigMap map[string]interface{},
aeroClusters map[string]*model.AerospikeCluster) error {
func (r *AerospikeBackup) validateOnDemandBackupsUpdate(oldObj *AerospikeBackup) error {
if len(r.Spec.OnDemandBackups) > 0 && len(oldObj.Spec.OnDemandBackups) > 0 {
// Check if onDemand backup spec is updated
if r.Spec.OnDemandBackups[0].ID == oldObj.Spec.OnDemandBackups[0].ID &&
!reflect.DeepEqual(r.Spec.OnDemandBackups[0], oldObj.Spec.OnDemandBackups[0]) {
return fmt.Errorf("onDemand backup cannot be updated")
}

// Check if previous onDemand backup is completed before allowing new onDemand backup
if r.Spec.OnDemandBackups[0].ID != oldObj.Spec.OnDemandBackups[0].ID && (len(r.Status.OnDemandBackups) == 0 ||
r.Status.OnDemandBackups[0].ID != oldObj.Spec.OnDemandBackups[0].ID) {
return fmt.Errorf("can not add new onDemand backup when previous onDemand backup is not completed")
}
}

return nil
}

func (r *AerospikeBackup) validateAerospikeClusterUpdate(oldObj *AerospikeBackup) error {
oldObjConfig := make(map[string]interface{})
currentConfig := make(map[string]interface{})

if err := yaml.Unmarshal(oldObj.Spec.Config.Raw, &oldObjConfig); err != nil {
return err
}

if err := yaml.Unmarshal(r.Spec.Config.Raw, &currentConfig); err != nil {
return err
}

oldCluster := oldObjConfig[common.AerospikeClusterKey].(map[string]interface{})
newCluster := currentConfig[common.AerospikeClusterKey].(map[string]interface{})

for clusterName := range newCluster {
if _, ok := oldCluster[clusterName]; !ok {
return fmt.Errorf("aerospike-cluster name update is not allowed")
}
}

return nil
}

func (r *AerospikeBackup) validateBackupRoutines(backupSvcConfig *model.Config,
backupSvcConfigMap map[string]interface{},
aeroClusters map[string]*model.AerospikeCluster,
) error {
if _, ok := backupSvcConfigMap[common.BackupRoutinesKey]; !ok {
return fmt.Errorf("backup-routines field is required in backup onfig")
}
Expand All @@ -275,8 +318,14 @@ func validateBackupRoutines(backupSvcConfig *model.Config, backupSvcConfigMap ma
return fmt.Errorf("backup-routines field is empty")
}

// validate if only correct aerospike cluster (the one referred in Backup CR) is used in backup routines
for _, routine := range backupRoutines {
// validate:
// 1. if the correct format name is given
// 2. if only correct aerospike cluster (the one referred in Backup CR) is used in backup routines
for routineName, routine := range backupRoutines {
if err := validateName(r.namePrefix(), routineName); err != nil {
return fmt.Errorf("invalid backup routine name %s, %s", routineName, err.Error())
}

if _, ok := aeroClusters[routine.SourceCluster]; !ok {
return fmt.Errorf("cluster %s not found in backup aerospike-cluster config", routine.SourceCluster)
}
Expand All @@ -292,3 +341,19 @@ func validateBackupRoutines(backupSvcConfig *model.Config, backupSvcConfigMap ma

return nil
}

func (r *AerospikeBackup) namePrefix() string {
return r.Namespace + "-" + r.Name
}

func validateName(reqPrefix, name string) error {
if name == "" {
return fmt.Errorf("name cannot be empty")
}

if !strings.HasPrefix(name, reqPrefix) {
return fmt.Errorf("name should start with %s", reqPrefix)
}

return nil
}
13 changes: 7 additions & 6 deletions config/samples/aerospikebackup.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ metadata:
app.kubernetes.io/part-of: aerospike-kubernetes-operator
app.kubernetes.io/managed-by: kustomize
app.kubernetes.io/created-by: aerospike-kubernetes-operator
name: aerospikebackup-sample
name: aerospikebackup
namespace: aerospike
spec:
backupService:
name: aerospikebackupservice-sample
Expand All @@ -17,25 +18,25 @@ spec:
# routineName: test-routine
config:
aerospike-cluster:
test-cluster:
aerospike-aerospikebackup-test-cluster:
credentials:
password: admin123
user: admin
seed-nodes:
- host-name: aerocluster.aerospike.svc.cluster.local
port: 3000
backup-routines:
test-routine:
aerospike-aerospikebackup-test-routine:
backup-policy: test-policy
interval-cron: "@daily"
incr-interval-cron: "@hourly"
namespaces: ["test"]
source-cluster: test-cluster
source-cluster: aerospike-aerospikebackup-test-cluster
storage: local
test-routine1:
aerospike-aerospikebackup-test-routine1:
backup-policy: test-policy1
interval-cron: "@daily"
incr-interval-cron: "@hourly"
namespaces: [ "test" ]
source-cluster: test-cluster
source-cluster: aerospike-aerospikebackup-test-cluster
storage: s3Storage
1 change: 1 addition & 0 deletions config/samples/aerospikerestore.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ metadata:
app.kubernetes.io/managed-by: kustomize
app.kubernetes.io/created-by: aerospike-kubernetes-operator
name: aerospikerestore-sample
namespace: aerospike
spec:
backupService:
name: aerospikebackupservice-sample
Expand Down
45 changes: 30 additions & 15 deletions controllers/backup/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,54 +130,69 @@ func (r *SingleBackupReconciler) reconcileConfigMap() error {
"namespace", r.aeroBackup.Spec.BackupService.Namespace,
)

backupDataMap := make(map[string]interface{})
cmDataMap := make(map[string]interface{})
specBackupConfigMap := make(map[string]interface{})
backupSvcConfigMap := make(map[string]interface{})

if err := yaml.Unmarshal(r.aeroBackup.Spec.Config.Raw, &backupDataMap); err != nil {
if err := yaml.Unmarshal(r.aeroBackup.Spec.Config.Raw, &specBackupConfigMap); err != nil {
return err
}

data := cm.Data[common.BackupServiceConfigYAML]

if err := yaml.Unmarshal([]byte(data), &cmDataMap); err != nil {
if err := yaml.Unmarshal([]byte(data), &backupSvcConfigMap); err != nil {
return err
}

if _, ok := cmDataMap[common.AerospikeClustersKey]; !ok {
cmDataMap[common.AerospikeClustersKey] = make(map[string]interface{})
if _, ok := backupSvcConfigMap[common.AerospikeClustersKey]; !ok {
backupSvcConfigMap[common.AerospikeClustersKey] = make(map[string]interface{})
}

clusterMap, ok := cmDataMap[common.AerospikeClustersKey].(map[string]interface{})
clusterMap, ok := backupSvcConfigMap[common.AerospikeClustersKey].(map[string]interface{})
if !ok {
return fmt.Errorf("aerospike-clusters is not a map")
}

newCluster := backupDataMap[common.AerospikeClusterKey].(map[string]interface{})
newCluster := specBackupConfigMap[common.AerospikeClusterKey].(map[string]interface{})

for name, cluster := range newCluster {
clusterMap[name] = cluster
}

cmDataMap[common.AerospikeClustersKey] = clusterMap
backupSvcConfigMap[common.AerospikeClustersKey] = clusterMap

if _, ok = cmDataMap[common.BackupRoutinesKey]; !ok {
cmDataMap[common.BackupRoutinesKey] = make(map[string]interface{})
if _, ok = backupSvcConfigMap[common.BackupRoutinesKey]; !ok {
backupSvcConfigMap[common.BackupRoutinesKey] = make(map[string]interface{})
}

routineMap, ok := cmDataMap[common.BackupRoutinesKey].(map[string]interface{})
routineMap, ok := backupSvcConfigMap[common.BackupRoutinesKey].(map[string]interface{})
if !ok {
return fmt.Errorf("backup-routines is not a map")
}

newRoutines := backupDataMap[common.BackupRoutinesKey].(map[string]interface{})
newRoutines := specBackupConfigMap[common.BackupRoutinesKey].(map[string]interface{})

for name, routine := range newRoutines {
routineMap[name] = routine
}

cmDataMap[common.BackupRoutinesKey] = routineMap
// Remove the routines which are not in spec
if len(r.aeroBackup.Status.Config.Raw) != 0 {
statusBackupConfigMap := make(map[string]interface{})

updatedConfig, err := yaml.Marshal(cmDataMap)
if err := yaml.Unmarshal(r.aeroBackup.Status.Config.Raw, &statusBackupConfigMap); err != nil {
return err
}

for name := range statusBackupConfigMap[common.BackupRoutinesKey].(map[string]interface{}) {
if _, ok := specBackupConfigMap[common.BackupRoutinesKey].(map[string]interface{})[name]; !ok {
delete(routineMap, name)
}
}
}

backupSvcConfigMap[common.BackupRoutinesKey] = routineMap

updatedConfig, err := yaml.Marshal(backupSvcConfigMap)
if err != nil {
return err
}
Expand Down
Loading

0 comments on commit 439cfd3

Please sign in to comment.