diff --git a/Makefile b/Makefile index 39c617a1..646dcc44 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -# # /bin/sh does not support source command needed in make test-all +# # /bin/sh does not support source command needed in make all-test #SHELL := /bin/bash ROOT_DIR=$(shell git rev-parse --show-toplevel) diff --git a/api/v1beta1/aerospikebackup_types.go b/api/v1beta1/aerospikebackup_types.go index ff5f2629..e2678646 100644 --- a/api/v1beta1/aerospikebackup_types.go +++ b/api/v1beta1/aerospikebackup_types.go @@ -21,15 +21,15 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) -// AerospikeBackupSpec defines the desired state of AerospikeBackup +// AerospikeBackupSpec defines the desired state of AerospikeBackup for a given AerospikeCluster // +k8s:openapi-gen=true type AerospikeBackupSpec struct { // BackupService is the backup service reference i.e. name and namespace. // It is used to communicate to the backup service to trigger backups. This field is immutable // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Backup Service" - BackupService *BackupService `json:"backupService"` + BackupService BackupService `json:"backupService"` - // Config is the configuration for the backup in YAML format. + // Config is the free form configuration for the backup in YAML format. // This config is used to trigger backups. It includes: aerospike-cluster, backup-routines. // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Backup Config" Config runtime.RawExtension `json:"config"` @@ -63,7 +63,7 @@ type OnDemandSpec struct { // AerospikeBackupStatus defines the observed state of AerospikeBackup type AerospikeBackupStatus struct { // BackupService is the backup service reference i.e. name and namespace. - BackupService *BackupService `json:"backupService"` + BackupService BackupService `json:"backupService"` // Config is the configuration for the backup in YAML format. // This config is used to trigger backups. It includes: aerospike-cluster, backup-routines. diff --git a/api/v1beta1/aerospikebackup_webhook.go b/api/v1beta1/aerospikebackup_webhook.go index 60969d52..1673b9cf 100644 --- a/api/v1beta1/aerospikebackup_webhook.go +++ b/api/v1beta1/aerospikebackup_webhook.go @@ -38,7 +38,7 @@ import ( ) // log is for logging in this package. -var aerospikebackuplog = logf.Log.WithName("aerospikebackup-resource") +var aerospikeBackupLog = logf.Log.WithName("aerospikebackup-resource") func (r *AerospikeBackup) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). @@ -50,7 +50,7 @@ var _ webhook.Defaulter = &AerospikeBackup{} // Default implements webhook.Defaulter so a webhook will be registered for the type func (r *AerospikeBackup) Default() { - aerospikebackuplog.Info("default", "name", r.Name) + aerospikeBackupLog.Info("default", "name", r.Name) } //nolint:lll // for readability @@ -60,7 +60,7 @@ var _ webhook.Validator = &AerospikeBackup{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type func (r *AerospikeBackup) ValidateCreate() (admission.Warnings, error) { - aerospikebackuplog.Info("validate create", "name", r.Name) + aerospikeBackupLog.Info("validate create", "name", r.Name) if len(r.Spec.OnDemand) != 0 && r.Spec.Config.Raw != nil { return nil, fmt.Errorf("onDemand and backup config cannot be specified together while creating backup") @@ -75,7 +75,7 @@ func (r *AerospikeBackup) ValidateCreate() (admission.Warnings, error) { // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type func (r *AerospikeBackup) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - aerospikebackuplog.Info("validate update", "name", r.Name) + aerospikeBackupLog.Info("validate update", "name", r.Name) oldObj := old.(*AerospikeBackup) @@ -107,7 +107,7 @@ func (r *AerospikeBackup) ValidateUpdate(old runtime.Object) (admission.Warnings // ValidateDelete implements webhook.Validator so a webhook will be registered for the type func (r *AerospikeBackup) ValidateDelete() (admission.Warnings, error) { - aerospikebackuplog.Info("validate delete", "name", r.Name) + aerospikeBackupLog.Info("validate delete", "name", r.Name) // TODO(user): fill in your validation logic upon object deletion. return nil, nil @@ -127,106 +127,57 @@ func (r *AerospikeBackup) validateBackupConfig() error { return err } - var config model.Config + var backupSvcConfig model.Config - if err := yaml.Unmarshal(backupSvc.Spec.Config.Raw, &config); err != nil { + if err := yaml.Unmarshal(backupSvc.Spec.Config.Raw, &backupSvcConfig); err != nil { return err } - configMap := make(map[string]interface{}) + backupSvcConfigMap := make(map[string]interface{}) - if err := yaml.Unmarshal(r.Spec.Config.Raw, &configMap); err != nil { + if err := yaml.Unmarshal(r.Spec.Config.Raw, &backupSvcConfigMap); err != nil { return err } - if _, ok := configMap[common.AerospikeClusterKey]; !ok { - return fmt.Errorf("aerospike-cluster field is required in config") - } - - cluster, ok := configMap[common.AerospikeClusterKey].(map[string]interface{}) - if !ok { - return fmt.Errorf("aerospike-cluster field is not in the right format") - } - - clusterBytes, cErr := yaml.Marshal(cluster) - if cErr != nil { - return cErr - } - - aeroClusters := make(map[string]*model.AerospikeCluster) - - if err := yaml.Unmarshal(clusterBytes, &aeroClusters); err != nil { + aeroClusters, err := validateAerospikeCluster(&backupSvcConfig, backupSvcConfigMap) + if err != nil { return err } - if len(aeroClusters) != 1 { - return fmt.Errorf("only one aerospike cluster is allowed in backup config") - } - - if len(config.AerospikeClusters) == 0 { - config.AerospikeClusters = make(map[string]*model.AerospikeCluster) - } - - for name, aeroCluster := range aeroClusters { - config.AerospikeClusters[name] = aeroCluster - } - - if _, ok = configMap[common.BackupRoutinesKey]; !ok { - return fmt.Errorf("backup-routines field is required in config") - } - - routines, ok := configMap[common.BackupRoutinesKey].(map[string]interface{}) - if !ok { - return fmt.Errorf("backup-routines field is not in the right format") - } - - routineBytes, rErr := yaml.Marshal(routines) - if rErr != nil { - return rErr - } - - backupRoutines := make(map[string]*model.BackupRoutine) - - if err := yaml.Unmarshal(routineBytes, &backupRoutines); err != nil { + backupRoutines, err := validateBackupRoutines(&backupSvcConfig, backupSvcConfigMap) + if err != nil { return err } - if len(config.BackupRoutines) == 0 { - config.BackupRoutines = make(map[string]*model.BackupRoutine) - } - - for name, routine := range backupRoutines { - config.BackupRoutines[name] = routine - } - // validate if only correct aerospike cluster (the one referred in Backup CR) is used in backup routines - for _, routine := range config.BackupRoutines { - if _, ok = aeroClusters[routine.SourceCluster]; !ok { - return fmt.Errorf("cluster %s not found in backup config", routine.SourceCluster) + for _, routine := range backupRoutines { + if _, ok := aeroClusters[routine.SourceCluster]; !ok { + return fmt.Errorf("cluster %s not found in backup backupSvcConfig", routine.SourceCluster) } } - // Add empty placeholders for missing config sections. This is required for validation to work. - if config.ServiceConfig == nil { - config.ServiceConfig = &model.BackupServiceConfig{} + // Add empty placeholders for missing backupSvcConfig sections. This is required for validation to work. + if backupSvcConfig.ServiceConfig == nil { + backupSvcConfig.ServiceConfig = &model.BackupServiceConfig{} } - if config.ServiceConfig.HTTPServer == nil { - config.ServiceConfig.HTTPServer = &model.HTTPServerConfig{} + if backupSvcConfig.ServiceConfig.HTTPServer == nil { + backupSvcConfig.ServiceConfig.HTTPServer = &model.HTTPServerConfig{} } - if config.ServiceConfig.Logger == nil { - config.ServiceConfig.Logger = &model.LoggerConfig{} + if backupSvcConfig.ServiceConfig.Logger == nil { + backupSvcConfig.ServiceConfig.Logger = &model.LoggerConfig{} } - if err := config.Validate(); err != nil { + if err := backupSvcConfig.Validate(); err != nil { return err } // Validate on-demand backup if len(r.Spec.OnDemand) > 0 { - if _, ok = config.BackupRoutines[r.Spec.OnDemand[0].RoutineName]; !ok { - return fmt.Errorf("backup routine %s not found", r.Spec.OnDemand[0].RoutineName) + if _, ok := backupSvcConfig.BackupRoutines[r.Spec.OnDemand[0].RoutineName]; !ok { + return fmt.Errorf("invalid onDemand config, backup routine %s not found", + r.Spec.OnDemand[0].RoutineName) } } @@ -251,3 +202,73 @@ func getK8sClient() (client.Client, error) { return cl, nil } + +func 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") + } + + cluster, ok := backupSvcConfigMap[common.AerospikeClusterKey].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("aerospike-cluster field is not in the right format") + } + + clusterBytes, cErr := yaml.Marshal(cluster) + if cErr != nil { + return nil, cErr + } + + aeroClusters := make(map[string]*model.AerospikeCluster) + + if err := yaml.Unmarshal(clusterBytes, &aeroClusters); err != nil { + return nil, err + } + + if len(aeroClusters) != 1 { + return aeroClusters, fmt.Errorf("only one aerospike cluster is allowed in backup backupSvcConfig") + } + + if len(backupSvcConfig.AerospikeClusters) == 0 { + backupSvcConfig.AerospikeClusters = make(map[string]*model.AerospikeCluster) + } + + for name, aeroCluster := range aeroClusters { + backupSvcConfig.AerospikeClusters[name] = aeroCluster + } + + return aeroClusters, nil +} + +func validateBackupRoutines(backupSvcConfig *model.Config, backupSvcConfigMap map[string]interface{}, +) (map[string]*model.BackupRoutine, error) { + if _, ok := backupSvcConfigMap[common.BackupRoutinesKey]; !ok { + return nil, fmt.Errorf("backup-routines field is required in backupSvcConfig") + } + + routines, ok := backupSvcConfigMap[common.BackupRoutinesKey].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("backup-routines field is not in the right format") + } + + routineBytes, rErr := yaml.Marshal(routines) + if rErr != nil { + return nil, rErr + } + + backupRoutines := make(map[string]*model.BackupRoutine) + + if err := yaml.Unmarshal(routineBytes, &backupRoutines); err != nil { + return nil, err + } + + if len(backupSvcConfig.BackupRoutines) == 0 { + backupSvcConfig.BackupRoutines = make(map[string]*model.BackupRoutine) + } + + for name, routine := range backupRoutines { + backupSvcConfig.BackupRoutines[name] = routine + } + + return backupRoutines, nil +} diff --git a/api/v1beta1/aerospikebackupservice_types.go b/api/v1beta1/aerospikebackupservice_types.go index 70673039..8a7b6344 100644 --- a/api/v1beta1/aerospikebackupservice_types.go +++ b/api/v1beta1/aerospikebackupservice_types.go @@ -48,7 +48,7 @@ type AerospikeBackupServiceSpec struct { // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Backup Service Image" Image string `json:"image"` - // Config is the configuration for the backup service in YAML format. + // Config is the free form configuration for the backup service in YAML format. // This config is used to start the backup service. The config is passed as a file to the backup service. // It includes: service, backup-policies, storage, secret-agent. // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Backup Service Config" @@ -57,7 +57,7 @@ type AerospikeBackupServiceSpec struct { // Resources defines the requests and limits for the backup service container. // Resources.Limits should be more than Resources.Requests. // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Resources" - Resources corev1.ResourceRequirements `json:"resources,omitempty"` + Resources *corev1.ResourceRequirements `json:"resources,omitempty"` // SecretMounts is the list of secret to be mounted in the backup service. // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Backup Service Volume" @@ -70,18 +70,36 @@ type AerospikeBackupServiceSpec struct { } // AerospikeBackupServiceStatus defines the observed state of AerospikeBackupService +// +//nolint:govet // for readbility type AerospikeBackupServiceStatus struct { - // ContextPath is the backup service API context path - ContextPath string `json:"contextPath"` + // Image is the image for the backup service. + Image string `json:"image,omitempty"` + + // Config is the free form configuration for the backup service in YAML format. + // This config is used to start the backup service. The config is passed as a file to the backup service. + // It includes: service, backup-policies, storage, secret-agent. + Config runtime.RawExtension `json:"config,omitempty"` + + // Resources defines the requests and limits for the backup service container. + // Resources.Limits should be more than Resources.Requests. + Resources *corev1.ResourceRequirements `json:"resources,omitempty"` - // ConfigHash is the hash string of backup service config - ConfigHash string `json:"configHash"` + // SecretMounts is the list of secret to be mounted in the backup service. + SecretMounts []SecretMount `json:"secrets,omitempty"` + + // Service defines the Kubernetes service configuration for the backup service. + // It is used to expose the backup service deployment. By default, the service type is ClusterIP. + Service *Service `json:"service,omitempty"` + + // ContextPath is the backup service API context path + ContextPath string `json:"contextPath,omitempty"` // Phase denotes Backup service phase - Phase AerospikeBackupServicePhase `json:"phase,omitempty"` + Phase AerospikeBackupServicePhase `json:"phase"` // Port is the listening port of backup service - Port int32 `json:"port"` + Port int32 `json:"port,omitempty"` } // +kubebuilder:object:root=true diff --git a/api/v1beta1/aerospikebackupservice_webhook.go b/api/v1beta1/aerospikebackupservice_webhook.go index 08806f4a..311c6b26 100644 --- a/api/v1beta1/aerospikebackupservice_webhook.go +++ b/api/v1beta1/aerospikebackupservice_webhook.go @@ -28,7 +28,7 @@ import ( ) // log is for logging in this package. -var aerospikebackupservicelog = logf.Log.WithName("aerospikebackupservice-resource") +var aerospikeBackupServiceLog = logf.Log.WithName("aerospikebackupservice-resource") func (r *AerospikeBackupService) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). @@ -40,7 +40,7 @@ var _ webhook.Defaulter = &AerospikeBackupService{} // Default implements webhook.Defaulter so a webhook will be registered for the type func (r *AerospikeBackupService) Default() { - aerospikebackupservicelog.Info("default", "name", r.Name) + aerospikeBackupServiceLog.Info("default", "name", r.Name) } //nolint:lll // for readability @@ -50,7 +50,7 @@ var _ webhook.Validator = &AerospikeBackupService{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type func (r *AerospikeBackupService) ValidateCreate() (admission.Warnings, error) { - aerospikebackupservicelog.Info("validate create", "name", r.Name) + aerospikeBackupServiceLog.Info("validate create", "name", r.Name) if err := r.validateBackupServiceConfig(); err != nil { return nil, err @@ -61,7 +61,7 @@ func (r *AerospikeBackupService) ValidateCreate() (admission.Warnings, error) { // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type func (r *AerospikeBackupService) ValidateUpdate(_ runtime.Object) (admission.Warnings, error) { - aerospikebackupservicelog.Info("validate update", "name", r.Name) + aerospikeBackupServiceLog.Info("validate update", "name", r.Name) if err := r.validateBackupServiceConfig(); err != nil { return nil, err @@ -72,7 +72,7 @@ func (r *AerospikeBackupService) ValidateUpdate(_ runtime.Object) (admission.War // ValidateDelete implements webhook.Validator so a webhook will be registered for the type func (r *AerospikeBackupService) ValidateDelete() (admission.Warnings, error) { - aerospikebackupservicelog.Info("validate delete", "name", r.Name) + aerospikeBackupServiceLog.Info("validate delete", "name", r.Name) // TODO(user): fill in your validation logic upon object deletion. return nil, nil diff --git a/api/v1beta1/aerospikerestore_types.go b/api/v1beta1/aerospikerestore_types.go index b8bc516d..a9df1ae4 100644 --- a/api/v1beta1/aerospikerestore_types.go +++ b/api/v1beta1/aerospikerestore_types.go @@ -50,7 +50,7 @@ type AerospikeRestoreSpec struct { // BackupService is the backup service reference i.e. name and namespace. // It is used to communicate to the backup service to trigger restores. This field is immutable // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Backup Service" - BackupService *BackupService `json:"backupService"` + BackupService BackupService `json:"backupService"` // Type is the type of restore. It can of type Full, Incremental, and Timestamp. // Based on the restore type, relevant restore config is given. @@ -58,7 +58,7 @@ type AerospikeRestoreSpec struct { // +kubebuilder:validation:Enum=Full;Incremental;TimeStamp Type RestoreType `json:"type"` - // Config is the configuration for the restore in YAML format. + // Config is the free form configuration for the restore in YAML format. // This config is used to trigger restores. It includes: destination, policy, source, secret-agent, time and routine. // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Restore Config" Config runtime.RawExtension `json:"config"` @@ -79,14 +79,14 @@ type AerospikeRestoreStatus struct { RestoreResult runtime.RawExtension `json:"restoreResult,omitempty"` // Phase denotes the current phase of Aerospike restore operation. - Phase AerospikeRestorePhase `json:"phase,omitempty"` + Phase AerospikeRestorePhase `json:"phase"` } // +kubebuilder:object:root=true // +kubebuilder:subresource:status // +kubebuilder:printcolumn:name="Backup Service Name",type=string,JSONPath=`.spec.backupService.name` // +kubebuilder:printcolumn:name="Backup Service Namespace",type=string,JSONPath=`.spec.backupService.namespace` -//+kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.restoreResult.status` +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.phase` // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // AerospikeRestore is the Schema for the aerospikerestores API diff --git a/api/v1beta1/aerospikerestore_webhook.go b/api/v1beta1/aerospikerestore_webhook.go index e0e39e6a..74a771a2 100644 --- a/api/v1beta1/aerospikerestore_webhook.go +++ b/api/v1beta1/aerospikerestore_webhook.go @@ -32,7 +32,7 @@ import ( ) // log is for logging in this package. -var aerospikerestorelog = logf.Log.WithName("aerospikerestore-resource") +var aerospikeRestoreLog = logf.Log.WithName("aerospikerestore-resource") const defaultPollingPeriod time.Duration = 60 * time.Second @@ -49,7 +49,7 @@ var _ webhook.Defaulter = &AerospikeRestore{} // Default implements webhook.Defaulter so a webhook will be registered for the type func (r *AerospikeRestore) Default() { - aerospikerestorelog.Info("default", "name", r.Name) + aerospikeRestoreLog.Info("default", "name", r.Name) if r.Spec.PollingPeriod.Duration.Seconds() == 0 { r.Spec.PollingPeriod.Duration = defaultPollingPeriod @@ -63,7 +63,7 @@ var _ webhook.Validator = &AerospikeRestore{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type func (r *AerospikeRestore) ValidateCreate() (admission.Warnings, error) { - aerospikerestorelog.Info("validate create", "name", r.Name) + aerospikeRestoreLog.Info("validate create", "name", r.Name) if err := r.validateRestoreConfig(); err != nil { return nil, err @@ -74,7 +74,7 @@ func (r *AerospikeRestore) ValidateCreate() (admission.Warnings, error) { // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type func (r *AerospikeRestore) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - aerospikerestorelog.Info("validate update", "name", r.Name) + aerospikeRestoreLog.Info("validate update", "name", r.Name) oldRestore := old.(*AerospikeRestore) @@ -87,7 +87,7 @@ func (r *AerospikeRestore) ValidateUpdate(old runtime.Object) (admission.Warning // ValidateDelete implements webhook.Validator so a webhook will be registered for the type func (r *AerospikeRestore) ValidateDelete() (admission.Warnings, error) { - aerospikerestorelog.Info("validate delete", "name", r.Name) + aerospikeRestoreLog.Info("validate delete", "name", r.Name) // TODO(user): fill in your validation logic upon object deletion. return nil, nil diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 6c0b45a6..d7fce012 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -127,7 +127,7 @@ func (in *AerospikeBackupService) DeepCopyInto(out *AerospikeBackupService) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AerospikeBackupService. @@ -184,7 +184,11 @@ func (in *AerospikeBackupServiceList) DeepCopyObject() runtime.Object { func (in *AerospikeBackupServiceSpec) DeepCopyInto(out *AerospikeBackupServiceSpec) { *out = *in in.Config.DeepCopyInto(&out.Config) - in.Resources.DeepCopyInto(&out.Resources) + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = new(v1.ResourceRequirements) + (*in).DeepCopyInto(*out) + } if in.SecretMounts != nil { in, out := &in.SecretMounts, &out.SecretMounts *out = make([]SecretMount, len(*in)) @@ -212,6 +216,24 @@ func (in *AerospikeBackupServiceSpec) DeepCopy() *AerospikeBackupServiceSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AerospikeBackupServiceStatus) DeepCopyInto(out *AerospikeBackupServiceStatus) { *out = *in + in.Config.DeepCopyInto(&out.Config) + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = new(v1.ResourceRequirements) + (*in).DeepCopyInto(*out) + } + if in.SecretMounts != nil { + in, out := &in.SecretMounts, &out.SecretMounts + *out = make([]SecretMount, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Service != nil { + in, out := &in.Service, &out.Service + *out = new(Service) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AerospikeBackupServiceStatus. @@ -227,11 +249,7 @@ func (in *AerospikeBackupServiceStatus) DeepCopy() *AerospikeBackupServiceStatus // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AerospikeBackupSpec) DeepCopyInto(out *AerospikeBackupSpec) { *out = *in - if in.BackupService != nil { - in, out := &in.BackupService, &out.BackupService - *out = new(BackupService) - **out = **in - } + out.BackupService = in.BackupService in.Config.DeepCopyInto(&out.Config) if in.OnDemand != nil { in, out := &in.OnDemand, &out.OnDemand @@ -253,11 +271,7 @@ func (in *AerospikeBackupSpec) DeepCopy() *AerospikeBackupSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AerospikeBackupStatus) DeepCopyInto(out *AerospikeBackupStatus) { *out = *in - if in.BackupService != nil { - in, out := &in.BackupService, &out.BackupService - *out = new(BackupService) - **out = **in - } + out.BackupService = in.BackupService in.Config.DeepCopyInto(&out.Config) if in.OnDemand != nil { in, out := &in.OnDemand, &out.OnDemand @@ -867,11 +881,7 @@ func (in *AerospikeRestoreList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AerospikeRestoreSpec) DeepCopyInto(out *AerospikeRestoreSpec) { *out = *in - if in.BackupService != nil { - in, out := &in.BackupService, &out.BackupService - *out = new(BackupService) - **out = **in - } + out.BackupService = in.BackupService in.Config.DeepCopyInto(&out.Config) out.PollingPeriod = in.PollingPeriod } diff --git a/config/crd/bases/asdb.aerospike.com_aerospikebackups.yaml b/config/crd/bases/asdb.aerospike.com_aerospikebackups.yaml index 708309f0..e08c3110 100644 --- a/config/crd/bases/asdb.aerospike.com_aerospikebackups.yaml +++ b/config/crd/bases/asdb.aerospike.com_aerospikebackups.yaml @@ -43,6 +43,7 @@ spec: type: object spec: description: AerospikeBackupSpec defines the desired state of AerospikeBackup + for a given AerospikeCluster properties: backupService: description: BackupService is the backup service reference i.e. name @@ -60,9 +61,9 @@ spec: - namespace type: object config: - description: 'Config is the configuration for the backup in YAML format. - This config is used to trigger backups. It includes: aerospike-cluster, - backup-routines.' + description: 'Config is the free form configuration for the backup + in YAML format. This config is used to trigger backups. It includes: + aerospike-cluster, backup-routines.' type: object x-kubernetes-preserve-unknown-fields: true onDemand: diff --git a/config/crd/bases/asdb.aerospike.com_aerospikebackupservices.yaml b/config/crd/bases/asdb.aerospike.com_aerospikebackupservices.yaml index f14e7e76..d823be87 100644 --- a/config/crd/bases/asdb.aerospike.com_aerospikebackupservices.yaml +++ b/config/crd/bases/asdb.aerospike.com_aerospikebackupservices.yaml @@ -49,10 +49,10 @@ spec: description: AerospikeBackupServiceSpec defines the desired state of AerospikeBackupService properties: config: - description: 'Config is the configuration for the backup service in - YAML format. This config is used to start the backup service. The - config is passed as a file to the backup service. It includes: service, - backup-policies, storage, secret-agent.' + description: 'Config is the free form configuration for the backup + service in YAML format. This config is used to start the backup + service. The config is passed as a file to the backup service. It + includes: service, backup-policies, storage, secret-agent.' type: object x-kubernetes-preserve-unknown-fields: true image: @@ -178,12 +178,19 @@ spec: description: AerospikeBackupServiceStatus defines the observed state of AerospikeBackupService properties: - configHash: - description: ConfigHash is the hash string of backup service config - type: string + config: + description: 'Config is the free form configuration for the backup + service in YAML format. This config is used to start the backup + service. The config is passed as a file to the backup service. It + includes: service, backup-policies, storage, secret-agent.' + type: object + x-kubernetes-preserve-unknown-fields: true contextPath: description: ContextPath is the backup service API context path type: string + image: + description: Image is the image for the backup service. + type: string phase: description: Phase denotes Backup service phase enum: @@ -195,10 +202,120 @@ spec: description: Port is the listening port of backup service format: int32 type: integer + resources: + description: Resources defines the requests and limits for the backup + service container. Resources.Limits should be more than Resources.Requests. + properties: + claims: + description: "Claims lists the names of resources, defined in + spec.resourceClaims, that are used by this container. \n This + is an alpha field and requires enabling the DynamicResourceAllocation + feature gate. \n This field is immutable. It can only be set + for containers." + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name of one entry in pod.spec.resourceClaims + of the Pod where this field is used. It makes that resource + available inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute resources + allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + secrets: + description: SecretMounts is the list of secret to be mounted in the + backup service. + items: + description: SecretMount specifies the secret and its corresponding + volume mount options. + properties: + secretName: + description: SecretName is the name of the secret to be mounted. + type: string + volumeMount: + description: VolumeMount is the volume mount options for the + secret. + properties: + mountPath: + description: Path within the container at which the volume + should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts are + propagated from the host to container and the other way + around. When not set, MountPropagationNone is used. This + field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write otherwise + (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the container's + volume should be mounted. Defaults to "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from which + the container's volume should be mounted. Behaves similarly + to SubPath but environment variable references $(VAR_NAME) + are expanded using the container's environment. Defaults + to "" (volume's root). SubPathExpr and SubPath are mutually + exclusive. + type: string + required: + - mountPath + - name + type: object + required: + - secretName + - volumeMount + type: object + type: array + service: + description: Service defines the Kubernetes service configuration + for the backup service. It is used to expose the backup service + deployment. By default, the service type is ClusterIP. + properties: + type: + description: Type is the Kubernetes service type. + type: string + required: + - type + type: object required: - - configHash - - contextPath - - port + - phase type: object type: object served: true diff --git a/config/crd/bases/asdb.aerospike.com_aerospikerestores.yaml b/config/crd/bases/asdb.aerospike.com_aerospikerestores.yaml index 1fedd4e4..accd2891 100644 --- a/config/crd/bases/asdb.aerospike.com_aerospikerestores.yaml +++ b/config/crd/bases/asdb.aerospike.com_aerospikerestores.yaml @@ -21,7 +21,7 @@ spec: - jsonPath: .spec.backupService.namespace name: Backup Service Namespace type: string - - jsonPath: .status.restoreResult.status + - jsonPath: .status.phase name: Status type: string - jsonPath: .metadata.creationTimestamp @@ -63,9 +63,9 @@ spec: - namespace type: object config: - description: 'Config is the configuration for the restore in YAML - format. This config is used to trigger restores. It includes: destination, - policy, source, secret-agent, time and routine.' + description: 'Config is the free form configuration for the restore + in YAML format. This config is used to trigger restores. It includes: + destination, policy, source, secret-agent, time and routine.' type: object x-kubernetes-preserve-unknown-fields: true pollingPeriod: @@ -106,6 +106,8 @@ spec: description: RestoreResult is the result of the restore operation. type: object x-kubernetes-preserve-unknown-fields: true + required: + - phase type: object type: object served: true diff --git a/config/manifests/bases/aerospike-kubernetes-operator.clusterserviceversion.yaml b/config/manifests/bases/aerospike-kubernetes-operator.clusterserviceversion.yaml index 8a4d371b..eebb337e 100644 --- a/config/manifests/bases/aerospike-kubernetes-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/aerospike-kubernetes-operator.clusterserviceversion.yaml @@ -30,8 +30,9 @@ spec: field is immutable displayName: Backup Service path: backupService - - description: 'Config is the configuration for the backup in YAML format. This - config is used to trigger backups. It includes: aerospike-cluster, backup-routines.' + - description: 'Config is the free form configuration for the backup in YAML + format. This config is used to trigger backups. It includes: aerospike-cluster, + backup-routines.' displayName: Backup Config path: config - description: OnDemand is the configuration on demand backups. @@ -44,10 +45,10 @@ spec: kind: AerospikeBackupService name: aerospikebackupservices.asdb.aerospike.com specDescriptors: - - description: 'Config is the configuration for the backup service in YAML format. - This config is used to start the backup service. The config is passed as - a file to the backup service. It includes: service, backup-policies, storage, - secret-agent.' + - description: 'Config is the free form configuration for the backup service + in YAML format. This config is used to start the backup service. The config + is passed as a file to the backup service. It includes: service, backup-policies, + storage, secret-agent.' displayName: Backup Service Config path: config - description: Image is the image for the backup service. @@ -220,9 +221,9 @@ spec: field is immutable displayName: Backup Service path: backupService - - description: 'Config is the configuration for the restore in YAML format. - This config is used to trigger restores. It includes: destination, policy, - source, secret-agent, time and routine.' + - description: 'Config is the free form configuration for the restore in YAML + format. This config is used to trigger restores. It includes: destination, + policy, source, secret-agent, time and routine.' displayName: Restore Config path: config - description: PollingPeriod is the polling period for restore operation status. diff --git a/config/samples/asdb_v1beta1_aerospikebackup.yaml b/config/samples/aerospikebackup.yaml similarity index 82% rename from config/samples/asdb_v1beta1_aerospikebackup.yaml rename to config/samples/aerospikebackup.yaml index a5267e42..4ea3d64c 100644 --- a/config/samples/asdb_v1beta1_aerospikebackup.yaml +++ b/config/samples/aerospikebackup.yaml @@ -30,5 +30,12 @@ spec: interval-cron: "@daily" incr-interval-cron: "@hourly" namespaces: ["test"] + source-cluster: test-cluster1 + storage: local + test-routine1: + backup-policy: test-policy + interval-cron: "@daily" + incr-interval-cron: "@hourly" + namespaces: [ "test" ] source-cluster: test-cluster storage: local diff --git a/config/samples/asdb_v1beta1_aerospikebackupservice.yaml b/config/samples/aerospikebackupservice.yaml similarity index 100% rename from config/samples/asdb_v1beta1_aerospikebackupservice.yaml rename to config/samples/aerospikebackupservice.yaml diff --git a/config/samples/asdb_v1beta1_aerospikerestore.yaml b/config/samples/aerospikerestore.yaml similarity index 100% rename from config/samples/asdb_v1beta1_aerospikerestore.yaml rename to config/samples/aerospikerestore.yaml diff --git a/controllers/backup-service/reconciler.go b/controllers/backup-service/reconciler.go index 304030f1..eb3f3cd0 100644 --- a/controllers/backup-service/reconciler.go +++ b/controllers/backup-service/reconciler.go @@ -121,7 +121,7 @@ func (r *SingleBackupServiceReconciler) reconcileConfigMap() error { Data: r.getConfigMapData(), } - // Set AerospikeCluster instance as the owner and controller + // Set AerospikeBackupService instance as the owner and controller err = controllerutil.SetControllerReference( r.aeroBackupService, cm, r.Scheme, ) @@ -240,12 +240,12 @@ func (r *SingleBackupServiceReconciler) reconcileDeployment() error { oldResourceVersion := deploy.ResourceVersion - deployObj, err := r.getDeploymentObject() + desiredDeployObj, err := r.getDeploymentObject() if err != nil { return err } - deploy.Spec = deployObj.Spec + deploy.Spec = desiredDeployObj.Spec if err = r.Client.Update(context.TODO(), &deploy, common.UpdateOption); err != nil { return fmt.Errorf("failed to update Backup service deployment: %v", err) @@ -256,14 +256,24 @@ func (r *SingleBackupServiceReconciler) reconcileDeployment() error { return r.waitForDeploymentToBeReady() } - hash, err := utils.GetHash(string(r.aeroBackupService.Spec.Config.Raw)) + // If status is empty then no need for config Hash comparison + if len(r.aeroBackupService.Status.Config.Raw) == 0 { + return r.waitForDeploymentToBeReady() + } + + desiredHash, err := utils.GetHash(string(r.aeroBackupService.Spec.Config.Raw)) + if err != nil { + return err + } + + currentHash, err := utils.GetHash(string(r.aeroBackupService.Status.Config.Raw)) if err != nil { return err } // If there is a change in config hash, then restart the deployment pod - if r.aeroBackupService.Status.ConfigHash != "" && hash != r.aeroBackupService.Status.ConfigHash { - r.Log.Info("Config hash is updated, will result in rolling restart") + if desiredHash != currentHash { + r.Log.Info("BackupService config is updated, will result in rolling restart") podList, err := r.getBackupServicePodList() if err != nil { @@ -306,6 +316,12 @@ func (r *SingleBackupServiceReconciler) getDeploymentObject() (*app.Deployment, svcLabels := utils.LabelsForAerospikeBackupService(r.aeroBackupService.Name) volumeMounts, volumes := r.getVolumeAndMounts() + resources := corev1.ResourceRequirements{} + + if r.aeroBackupService.Spec.Resources != nil { + resources = *r.aeroBackupService.Spec.Resources + } + svcConf, err := r.getBackupServiceConfig() if err != nil { return nil, err @@ -344,7 +360,7 @@ func (r *SingleBackupServiceReconciler) getDeploymentObject() (*app.Deployment, Image: r.aeroBackupService.Spec.Image, ImagePullPolicy: corev1.PullIfNotPresent, VolumeMounts: volumeMounts, - Resources: r.aeroBackupService.Spec.Resources, + Resources: resources, Ports: containerPorts, }, }, @@ -646,17 +662,25 @@ func (r *SingleBackupServiceReconciler) updateStatus() error { return err } - hash, err := utils.GetHash(string(r.aeroBackupService.Spec.Config.Raw)) - if err != nil { - return err - } + status := r.CopySpecToStatus() + status.ContextPath = svcConfig.contextPath + status.Port = svcConfig.portInfo[common.HTTPKey] + status.Phase = asdbv1beta1.AerospikeBackupServiceCompleted - r.aeroBackupService.Status.ContextPath = svcConfig.contextPath - r.aeroBackupService.Status.Port = svcConfig.portInfo[common.HTTPKey] - r.aeroBackupService.Status.ConfigHash = hash - r.aeroBackupService.Status.Phase = asdbv1beta1.AerospikeBackupServiceCompleted + r.aeroBackupService.Status = *status r.Log.Info(fmt.Sprintf("Updating status: %+v", r.aeroBackupService.Status)) return r.Client.Status().Update(context.Background(), r.aeroBackupService) } + +func (r *SingleBackupServiceReconciler) CopySpecToStatus() *asdbv1beta1.AerospikeBackupServiceStatus { + status := asdbv1beta1.AerospikeBackupServiceStatus{} + status.Image = r.aeroBackupService.Spec.Image + status.Config = r.aeroBackupService.Spec.Config + status.Resources = r.aeroBackupService.Spec.Resources + status.SecretMounts = r.aeroBackupService.Spec.SecretMounts + status.Service = r.aeroBackupService.Spec.Service + + return &status +} diff --git a/controllers/backup/reconciler.go b/controllers/backup/reconciler.go index 409f8729..6a66a881 100644 --- a/controllers/backup/reconciler.go +++ b/controllers/backup/reconciler.go @@ -99,8 +99,7 @@ func (r *SingleBackupReconciler) removeFinalizer(finalizerName string) error { return err } - r.Log.Info("Removing finalizer", - "name", r.aeroBackup.Name, "namespace", r.aeroBackup.Namespace) + r.Log.Info("Removing finalizer") // Remove finalizer from the list r.aeroBackup.ObjectMeta.Finalizers = utils.RemoveString( r.aeroBackup.ObjectMeta.Finalizers, finalizerName, @@ -286,7 +285,7 @@ func (r *SingleBackupReconciler) scheduleOnDemandBackup() error { r.Log.Info("Schedule on-demand backup", "name", r.aeroBackup.Name, "namespace", r.aeroBackup.Namespace) - backupServiceClient, err := backup_service.GetBackupServiceClient(r.Client, r.aeroBackup.Spec.BackupService) + backupServiceClient, err := backup_service.GetBackupServiceClient(r.Client, &r.aeroBackup.Spec.BackupService) if err != nil { return err } @@ -297,7 +296,8 @@ func (r *SingleBackupReconciler) scheduleOnDemandBackup() error { return err } - r.Log.Info("Scheduled on-demand backup", "name", r.aeroBackup.Name, "namespace", r.aeroBackup.Namespace) + r.Log.Info("Scheduled on-demand backup", "routine-name", + r.aeroBackup.Spec.OnDemand[0].RoutineName) return nil } @@ -329,7 +329,7 @@ func (r *SingleBackupReconciler) reconcileScheduledBackup() error { r.Log.Info("Registering backup", "name", r.aeroBackup.Name, "namespace", r.aeroBackup.Namespace) - serviceClient, err := backup_service.GetBackupServiceClient(r.Client, r.aeroBackup.Spec.BackupService) + serviceClient, err := backup_service.GetBackupServiceClient(r.Client, &r.aeroBackup.Spec.BackupService) if err != nil { return err } @@ -363,7 +363,7 @@ func (r *SingleBackupReconciler) reconcileScheduledBackup() error { return err } } else { - err = serviceClient.UpdateCluster(name, clusterConfig) + err = serviceClient.AddCluster(name, clusterConfig) if err != nil { return err } @@ -386,7 +386,7 @@ func (r *SingleBackupReconciler) reconcileScheduledBackup() error { return err } } else { - err = serviceClient.UpdateBackupRoutine(name, routine) + err = serviceClient.AddBackupRoutine(name, routine) if err != nil { return err } @@ -400,6 +400,8 @@ func (r *SingleBackupReconciler) reconcileScheduledBackup() error { return err } + r.Log.Info("Registered backup", "name", r.aeroBackup.Name, "namespace", r.aeroBackup.Namespace) + return nil } @@ -416,7 +418,7 @@ func (r *SingleBackupReconciler) reconcileOnDemandBackup() error { } func (r *SingleBackupReconciler) unregisterBackup() error { - serviceClient, err := backup_service.GetBackupServiceClient(r.Client, r.aeroBackup.Spec.BackupService) + serviceClient, err := backup_service.GetBackupServiceClient(r.Client, &r.aeroBackup.Spec.BackupService) if err != nil { return err } diff --git a/controllers/restore/reconciler.go b/controllers/restore/reconciler.go index bd123edf..455f3335 100644 --- a/controllers/restore/reconciler.go +++ b/controllers/restore/reconciler.go @@ -64,7 +64,7 @@ func (r *SingleRestoreReconciler) reconcileRestore() common.ReconcileResult { return common.ReconcileSuccess() } - serviceClient, err := backup_service.GetBackupServiceClient(r.Client, r.aeroRestore.Spec.BackupService) + serviceClient, err := backup_service.GetBackupServiceClient(r.Client, &r.aeroRestore.Spec.BackupService) if err != nil { return common.ReconcileError(err) } @@ -120,7 +120,7 @@ func (r *SingleRestoreReconciler) reconcileRestore() common.ReconcileResult { } func (r *SingleRestoreReconciler) checkRestoreStatus() error { - serviceClient, err := backup_service.GetBackupServiceClient(r.Client, r.aeroRestore.Spec.BackupService) + serviceClient, err := backup_service.GetBackupServiceClient(r.Client, &r.aeroRestore.Spec.BackupService) if err != nil { return err } diff --git a/helm-charts/aerospike-kubernetes-operator/crds/customresourcedefinition_aerospikebackups.asdb.aerospike.com.yaml b/helm-charts/aerospike-kubernetes-operator/crds/customresourcedefinition_aerospikebackups.asdb.aerospike.com.yaml index 708309f0..e08c3110 100644 --- a/helm-charts/aerospike-kubernetes-operator/crds/customresourcedefinition_aerospikebackups.asdb.aerospike.com.yaml +++ b/helm-charts/aerospike-kubernetes-operator/crds/customresourcedefinition_aerospikebackups.asdb.aerospike.com.yaml @@ -43,6 +43,7 @@ spec: type: object spec: description: AerospikeBackupSpec defines the desired state of AerospikeBackup + for a given AerospikeCluster properties: backupService: description: BackupService is the backup service reference i.e. name @@ -60,9 +61,9 @@ spec: - namespace type: object config: - description: 'Config is the configuration for the backup in YAML format. - This config is used to trigger backups. It includes: aerospike-cluster, - backup-routines.' + description: 'Config is the free form configuration for the backup + in YAML format. This config is used to trigger backups. It includes: + aerospike-cluster, backup-routines.' type: object x-kubernetes-preserve-unknown-fields: true onDemand: diff --git a/helm-charts/aerospike-kubernetes-operator/crds/customresourcedefinition_aerospikebackupservices.asdb.aerospike.com.yaml b/helm-charts/aerospike-kubernetes-operator/crds/customresourcedefinition_aerospikebackupservices.asdb.aerospike.com.yaml index f14e7e76..d823be87 100644 --- a/helm-charts/aerospike-kubernetes-operator/crds/customresourcedefinition_aerospikebackupservices.asdb.aerospike.com.yaml +++ b/helm-charts/aerospike-kubernetes-operator/crds/customresourcedefinition_aerospikebackupservices.asdb.aerospike.com.yaml @@ -49,10 +49,10 @@ spec: description: AerospikeBackupServiceSpec defines the desired state of AerospikeBackupService properties: config: - description: 'Config is the configuration for the backup service in - YAML format. This config is used to start the backup service. The - config is passed as a file to the backup service. It includes: service, - backup-policies, storage, secret-agent.' + description: 'Config is the free form configuration for the backup + service in YAML format. This config is used to start the backup + service. The config is passed as a file to the backup service. It + includes: service, backup-policies, storage, secret-agent.' type: object x-kubernetes-preserve-unknown-fields: true image: @@ -178,12 +178,19 @@ spec: description: AerospikeBackupServiceStatus defines the observed state of AerospikeBackupService properties: - configHash: - description: ConfigHash is the hash string of backup service config - type: string + config: + description: 'Config is the free form configuration for the backup + service in YAML format. This config is used to start the backup + service. The config is passed as a file to the backup service. It + includes: service, backup-policies, storage, secret-agent.' + type: object + x-kubernetes-preserve-unknown-fields: true contextPath: description: ContextPath is the backup service API context path type: string + image: + description: Image is the image for the backup service. + type: string phase: description: Phase denotes Backup service phase enum: @@ -195,10 +202,120 @@ spec: description: Port is the listening port of backup service format: int32 type: integer + resources: + description: Resources defines the requests and limits for the backup + service container. Resources.Limits should be more than Resources.Requests. + properties: + claims: + description: "Claims lists the names of resources, defined in + spec.resourceClaims, that are used by this container. \n This + is an alpha field and requires enabling the DynamicResourceAllocation + feature gate. \n This field is immutable. It can only be set + for containers." + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name of one entry in pod.spec.resourceClaims + of the Pod where this field is used. It makes that resource + available inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute resources + allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + secrets: + description: SecretMounts is the list of secret to be mounted in the + backup service. + items: + description: SecretMount specifies the secret and its corresponding + volume mount options. + properties: + secretName: + description: SecretName is the name of the secret to be mounted. + type: string + volumeMount: + description: VolumeMount is the volume mount options for the + secret. + properties: + mountPath: + description: Path within the container at which the volume + should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts are + propagated from the host to container and the other way + around. When not set, MountPropagationNone is used. This + field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write otherwise + (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the container's + volume should be mounted. Defaults to "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from which + the container's volume should be mounted. Behaves similarly + to SubPath but environment variable references $(VAR_NAME) + are expanded using the container's environment. Defaults + to "" (volume's root). SubPathExpr and SubPath are mutually + exclusive. + type: string + required: + - mountPath + - name + type: object + required: + - secretName + - volumeMount + type: object + type: array + service: + description: Service defines the Kubernetes service configuration + for the backup service. It is used to expose the backup service + deployment. By default, the service type is ClusterIP. + properties: + type: + description: Type is the Kubernetes service type. + type: string + required: + - type + type: object required: - - configHash - - contextPath - - port + - phase type: object type: object served: true diff --git a/helm-charts/aerospike-kubernetes-operator/crds/customresourcedefinition_aerospikerestores.asdb.aerospike.com.yaml b/helm-charts/aerospike-kubernetes-operator/crds/customresourcedefinition_aerospikerestores.asdb.aerospike.com.yaml index 1fedd4e4..accd2891 100644 --- a/helm-charts/aerospike-kubernetes-operator/crds/customresourcedefinition_aerospikerestores.asdb.aerospike.com.yaml +++ b/helm-charts/aerospike-kubernetes-operator/crds/customresourcedefinition_aerospikerestores.asdb.aerospike.com.yaml @@ -21,7 +21,7 @@ spec: - jsonPath: .spec.backupService.namespace name: Backup Service Namespace type: string - - jsonPath: .status.restoreResult.status + - jsonPath: .status.phase name: Status type: string - jsonPath: .metadata.creationTimestamp @@ -63,9 +63,9 @@ spec: - namespace type: object config: - description: 'Config is the configuration for the restore in YAML - format. This config is used to trigger restores. It includes: destination, - policy, source, secret-agent, time and routine.' + description: 'Config is the free form configuration for the restore + in YAML format. This config is used to trigger restores. It includes: + destination, policy, source, secret-agent, time and routine.' type: object x-kubernetes-preserve-unknown-fields: true pollingPeriod: @@ -106,6 +106,8 @@ spec: description: RestoreResult is the result of the restore operation. type: object x-kubernetes-preserve-unknown-fields: true + required: + - phase type: object type: object served: true diff --git a/pkg/backup-service/client.go b/pkg/backup-service/client.go index f2a6a53a..f7509f80 100644 --- a/pkg/backup-service/client.go +++ b/pkg/backup-service/client.go @@ -53,15 +53,15 @@ func GetBackupServiceClient(k8sClient client.Client, svc *v1beta1.BackupService) }, nil } -func (c *Client) GetAddress() string { +func (c *Client) getAddress() string { return c.Address } -func (c *Client) GetPort() int32 { +func (c *Client) getPort() int32 { return c.Port } -func (c *Client) GetContextPath() string { +func (c *Client) getContextPath() string { if c.ContextPath != "" { return c.ContextPath } @@ -94,14 +94,14 @@ func (c *Client) GetBackupServiceConfig() (map[string]interface{}, error) { return nil, err } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("failed to get backup service config") } conf := make(map[string]interface{}) - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) if err != nil { return nil, err @@ -122,9 +122,9 @@ func (c *Client) ApplyConfig() error { return err } - if resp.StatusCode != http.StatusOK { - defer resp.Body.Close() + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { body, err := io.ReadAll(resp.Body) if err != nil { return err @@ -144,14 +144,14 @@ func (c *Client) GetClusters() (map[string]interface{}, error) { return nil, err } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("failed to get aerospike clusters") } aerospikeClusters := make(map[string]interface{}) - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) if err != nil { return nil, err @@ -186,9 +186,9 @@ func (c *Client) PutCluster(name, cluster interface{}) error { return err } - if resp.StatusCode != http.StatusOK { - defer resp.Body.Close() + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { body, err := io.ReadAll(resp.Body) if err != nil { return err @@ -215,9 +215,9 @@ func (c *Client) DeleteCluster(name string) error { return err } - if resp.StatusCode != http.StatusNoContent { - defer resp.Body.Close() + defer resp.Body.Close() + if resp.StatusCode != http.StatusNoContent { body, err := io.ReadAll(resp.Body) if err != nil { return err @@ -229,7 +229,7 @@ func (c *Client) DeleteCluster(name string) error { return nil } -func (c *Client) UpdateCluster(name, cluster interface{}) error { +func (c *Client) AddCluster(name, cluster interface{}) error { url := c.API(fmt.Sprintf("/config/clusters/%s", name)) jsonBody, err := json.Marshal(cluster) @@ -244,9 +244,9 @@ func (c *Client) UpdateCluster(name, cluster interface{}) error { return err } - if resp.StatusCode != http.StatusCreated { - defer resp.Body.Close() + defer resp.Body.Close() + if resp.StatusCode != http.StatusCreated { body, err := io.ReadAll(resp.Body) if err != nil { return err @@ -266,14 +266,14 @@ func (c *Client) GetBackupPolicies() (map[string]interface{}, error) { return nil, err } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("failed to get backup policies") } policies := make(map[string]interface{}) - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) if err != nil { return nil, err @@ -308,9 +308,9 @@ func (c *Client) PutBackupPolicy(name string, policy interface{}) error { return err } - if resp.StatusCode != http.StatusOK { - defer resp.Body.Close() + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { body, err := io.ReadAll(resp.Body) if err != nil { return err @@ -322,7 +322,7 @@ func (c *Client) PutBackupPolicy(name string, policy interface{}) error { return nil } -func (c *Client) UpdateBackupPolicy(name string, policy interface{}) error { +func (c *Client) AddBackupPolicy(name string, policy interface{}) error { url := c.API(fmt.Sprintf("/config/policies/%s", name)) jsonBody, err := json.Marshal(policy) @@ -337,9 +337,9 @@ func (c *Client) UpdateBackupPolicy(name string, policy interface{}) error { return err } - if resp.StatusCode != http.StatusCreated { - defer resp.Body.Close() + defer resp.Body.Close() + if resp.StatusCode != http.StatusCreated { body, err := io.ReadAll(resp.Body) if err != nil { return err @@ -375,9 +375,9 @@ func (c *Client) PutBackupRoutine(name string, routine interface{}) error { return err } - if resp.StatusCode != http.StatusOK { - defer resp.Body.Close() + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { body, err := io.ReadAll(resp.Body) if err != nil { return err @@ -389,7 +389,7 @@ func (c *Client) PutBackupRoutine(name string, routine interface{}) error { return nil } -func (c *Client) UpdateBackupRoutine(name string, routine interface{}) error { +func (c *Client) AddBackupRoutine(name string, routine interface{}) error { url := c.API(fmt.Sprintf("/config/routines/%s", name)) jsonBody, err := json.Marshal(routine) @@ -404,9 +404,9 @@ func (c *Client) UpdateBackupRoutine(name string, routine interface{}) error { return err } - if resp.StatusCode != http.StatusCreated { - defer resp.Body.Close() + defer resp.Body.Close() + if resp.StatusCode != http.StatusCreated { body, err := io.ReadAll(resp.Body) if err != nil { return err @@ -433,9 +433,9 @@ func (c *Client) DeleteBackupRoutine(name string) error { return err } - if resp.StatusCode != http.StatusNoContent { - defer resp.Body.Close() + defer resp.Body.Close() + if resp.StatusCode != http.StatusNoContent { body, err := io.ReadAll(resp.Body) if err != nil { return err @@ -455,14 +455,14 @@ func (c *Client) GetStorage() (map[string]interface{}, error) { return nil, err } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("failed to get backup storage") } storage := make(map[string]interface{}) - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) if err != nil { return nil, err @@ -497,9 +497,9 @@ func (c *Client) PutStorage(name string, storage interface{}) error { return err } - if resp.StatusCode != http.StatusOK { - defer resp.Body.Close() + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { body, err := io.ReadAll(resp.Body) if err != nil { return err @@ -511,7 +511,7 @@ func (c *Client) PutStorage(name string, storage interface{}) error { return nil } -func (c *Client) UpdateStorage(name string, storage interface{}) error { +func (c *Client) AddStorage(name string, storage interface{}) error { url := c.API(fmt.Sprintf("/config/storage/%s", name)) jsonBody, err := json.Marshal(storage) @@ -526,9 +526,9 @@ func (c *Client) UpdateStorage(name string, storage interface{}) error { return err } - if resp.StatusCode != http.StatusCreated { - defer resp.Body.Close() + defer resp.Body.Close() + if resp.StatusCode != http.StatusCreated { body, err := io.ReadAll(resp.Body) if err != nil { return err @@ -548,14 +548,14 @@ func (c *Client) GetFullBackups() (map[string][]interface{}, error) { return nil, err } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("failed to get backups") } backups := make(map[string][]interface{}) - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) if err != nil { return nil, err @@ -576,14 +576,14 @@ func (c *Client) GetFullBackupForRoutine(routineName string) ([]interface{}, err return nil, err } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("failed to get backups") } var backups []interface{} - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) if err != nil { return nil, err @@ -654,11 +654,11 @@ func (c *Client) TriggerRestoreWithType(log logr.Logger, restoreType string, return nil, nil, err } + defer resp.Body.Close() + if resp.StatusCode != http.StatusAccepted { log.Info("Response", "status-code", resp.StatusCode) - defer resp.Body.Close() - body, rErr := io.ReadAll(resp.Body) if rErr != nil { return nil, &resp.StatusCode, rErr @@ -670,8 +670,6 @@ func (c *Client) TriggerRestoreWithType(log logr.Logger, restoreType string, jobID = new(int64) - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) if err != nil { return nil, &resp.StatusCode, err @@ -694,14 +692,14 @@ func (c *Client) CheckRestoreStatus(jobID *int64) (map[string]interface{}, error return nil, err } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("failed to check restore restoreStatus") } restoreStatus := make(map[string]interface{}) - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) if err != nil { return nil, err @@ -715,13 +713,13 @@ func (c *Client) CheckRestoreStatus(jobID *int64) (map[string]interface{}, error } func (c *Client) API(pattern string) string { - contextPath := c.GetContextPath() + contextPath := c.getContextPath() if !strings.HasSuffix(contextPath, "/") { contextPath += "/" } - address := fmt.Sprintf("%s:%d", c.GetAddress(), c.Port) + address := fmt.Sprintf("%s:%d", c.getAddress(), c.getPort()) return fmt.Sprintf("http://%s%s%s%s", address, contextPath, restAPIVersion, pattern) } diff --git a/test/backup/test_utils.go b/test/backup/test_utils.go index 4debc145..7f6cd348 100644 --- a/test/backup/test_utils.go +++ b/test/backup/test_utils.go @@ -9,10 +9,7 @@ import ( "reflect" "time" - "github.com/abhishekdwivedi3060/aerospike-backup-service/pkg/model" - "github.com/aerospike/aerospike-kubernetes-operator/controllers/common" corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -22,7 +19,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" + "github.com/abhishekdwivedi3060/aerospike-backup-service/pkg/model" asdbv1beta1 "github.com/aerospike/aerospike-kubernetes-operator/api/v1beta1" + "github.com/aerospike/aerospike-kubernetes-operator/controllers/common" ) const ( @@ -55,7 +54,7 @@ func newBackup() (*asdbv1beta1.AerospikeBackup, error) { Namespace: namespace, }, Spec: asdbv1beta1.AerospikeBackupSpec{ - BackupService: &asdbv1beta1.BackupService{ + BackupService: asdbv1beta1.BackupService{ Name: backupServiceName, Namespace: backupServiceNamespace, }, @@ -73,7 +72,7 @@ func newBackupWithConfig(conf []byte) *asdbv1beta1.AerospikeBackup { Namespace: namespace, }, Spec: asdbv1beta1.AerospikeBackupSpec{ - BackupService: &asdbv1beta1.BackupService{ + BackupService: asdbv1beta1.BackupService{ Name: backupServiceName, Namespace: backupServiceNamespace, }, @@ -159,8 +158,10 @@ func getBackupConfigInMap() map[string]interface{} { }, "seed-nodes": []map[string]interface{}{ { - "host-name": "aerocluster.aerospike.svc.cluster.local", - "port": 3000, + "host-name": fmt.Sprintf("%s.%s.svc.cluster.local", + aerospikeNsNm.Name, aerospikeNsNm.Namespace, + ), + "port": 3000, }, }, }, diff --git a/test/backup_service/backup_service_test.go b/test/backup_service/backup_service_test.go index ed53cdd3..377d0673 100644 --- a/test/backup_service/backup_service_test.go +++ b/test/backup_service/backup_service_test.go @@ -104,7 +104,7 @@ var _ = Describe( Expect(err).ToNot(HaveOccurred()) // Change Pod spec - backupService.Spec.Resources = corev1.ResourceRequirements{ + backupService.Spec.Resources = &corev1.ResourceRequirements{ Limits: corev1.ResourceList{ corev1.ResourceCPU: resource.MustParse("0.5"), }, diff --git a/test/cluster/sample_files_test.go b/test/cluster/sample_files_test.go index 157b416f..6e718340 100644 --- a/test/cluster/sample_files_test.go +++ b/test/cluster/sample_files_test.go @@ -12,6 +12,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/yaml" asdbv1 "github.com/aerospike/aerospike-kubernetes-operator/api/v1" @@ -128,6 +129,20 @@ func getSamplesFiles() ([]string, error) { absolutePath := filepath.Join(projectRoot, fileDir) + // Files/Dirs ignored are: + // 1. PMEM sample file as hardware is not available + // 2. XDR related files as they are separately tested + // 3. All files which are not CR samples + // 4. BackupService, Backup and Restore related files + ignoreFiles := sets.New[string]( + "pmem_cluster_cr.yaml", + "xdr_dst_cluster_cr.yaml", + "xdr_src_cluster_cr.yaml", + "aerospikebackup.yaml", + "aerospikebackupservice.yaml", + "aerospikerestore.yaml", + ) + if err := filepath.Walk(absolutePath, func(path string, info fs.FileInfo, err error) error { if err != nil { return err @@ -138,12 +153,10 @@ func getSamplesFiles() ([]string, error) { return nil } - // Files/Dirs ignored are: - // 1. PMEM sample file as hardware is not available - // 2. XDR related files as they are separately tested - // 3. All files which are not CR samples - if strings.Contains(path, "pmem_cluster_cr.yaml") || strings.Contains(path, "xdr_") || - !strings.HasSuffix(path, "_cr.yaml") { + parts := strings.Split(path, "/") + file := parts[len(parts)-1] + + if ignoreFiles.Has(file) || !strings.HasSuffix(path, "_cr.yaml") { return nil } diff --git a/test/test.sh b/test/test.sh index 8ef8900f..9f437577 100755 --- a/test/test.sh +++ b/test/test.sh @@ -62,4 +62,4 @@ echo "---------------------" export CUSTOM_INIT_REGISTRY="$REGISTRY" export IMAGE_PULL_SECRET_NAME="$IMAGE_PULL_SECRET" -make test-all FOCUS="$FOCUS" ARGS="$ARGS" +make all-test FOCUS="$FOCUS" ARGS="$ARGS"