Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions api/v1alpha1/dataprotectionapplication_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,8 +433,33 @@ type NodeAgentConfig struct {
// Embedding NodeAgentConfigMapSettings
// +optional
NodeAgentConfigMapSettings `json:",inline"`
// Embedding KopiaRepoOptions
// +optional
KopiaRepoOptions `json:",inline"`
}

type KopiaRepoOptions struct {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the idea to have a separate struct is because this would be datamover options and not FSB options? (I think I heard some discussion around this)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mateusoliveira43 This is for BackupRepository configuration, so used for both Datamover and FSB. I grouped them together because they go into the same ConfigMap entry -- as @mpryc mentioned, so a single Marshal statement will generate the json chunk we need.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alright

// CacheLimitMB specifies the size limit(in MB) for the local data cache
// +kubebuilder:validation:Minimum=0
// +optional
CacheLimitMB *int64 `json:"cacheLimitMB,omitempty"`
// fullMaintenanceInterval determines the time between kopia full maintenance operations.
// normalGC: 24 hours
// fastGC: 12 hours
// eagerGC: 6 hours
// +kubebuilder:validation:Enum=normalGC;fastGC;eagerGC
Comment thread
mateusoliveira43 marked this conversation as resolved.
// +optional
FullMaintenanceInterval FullMaintenanceInterval `json:"fullMaintenanceInterval,omitempty"`
}

type FullMaintenanceInterval string

const (
FullMaintenanceIntervalNormalGC FullMaintenanceInterval = "normalGC"
FullMaintenanceIntervalFastGC FullMaintenanceInterval = "fastGC"
FullMaintenanceIntervalEagerGC FullMaintenanceInterval = "eagerGC"
)

// ResticConfig is the configuration for restic server
type ResticConfig struct {
// Embedding NodeAgentCommonFields
Expand Down
21 changes: 21 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

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

16 changes: 16 additions & 0 deletions bundle/manifests/oadp.openshift.io_dataprotectionapplications.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,11 @@ spec:
type: object
description: BackupPVCConfig is the config for backupPVC (intermediate PVC) of snapshot data movement
type: object
cacheLimitMB:
description: CacheLimitMB specifies the size limit(in MB) for the local data cache
format: int64
minimum: 0
type: integer
dataMoverPrepareTimeout:
description: How long to wait for preparing a DataUpload/DataDownload. Default is 30 minutes.
type: string
Expand All @@ -229,6 +234,17 @@ spec:
enable defines a boolean pointer whether we want the daemonset to
exist or not
type: boolean
fullMaintenanceInterval:
description: |-
fullMaintenanceInterval determines the time between kopia full maintenance operations.
normalGC: 24 hours
fastGC: 12 hours
eagerGC: 6 hours
enum:
- normalGC
- fastGC
- eagerGC
type: string
loadConcurrency:
description: LoadConcurrency is the config for data path load concurrency per node.
properties:
Expand Down
16 changes: 16 additions & 0 deletions config/crd/bases/oadp.openshift.io_dataprotectionapplications.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,11 @@ spec:
type: object
description: BackupPVCConfig is the config for backupPVC (intermediate PVC) of snapshot data movement
type: object
cacheLimitMB:
description: CacheLimitMB specifies the size limit(in MB) for the local data cache
format: int64
minimum: 0
type: integer
dataMoverPrepareTimeout:
description: How long to wait for preparing a DataUpload/DataDownload. Default is 30 minutes.
type: string
Expand All @@ -229,6 +234,17 @@ spec:
enable defines a boolean pointer whether we want the daemonset to
exist or not
type: boolean
fullMaintenanceInterval:
description: |-
fullMaintenanceInterval determines the time between kopia full maintenance operations.
normalGC: 24 hours
fastGC: 12 hours
eagerGC: 6 hours
enum:
- normalGC
- fastGC
- eagerGC
type: string
loadConcurrency:
description: LoadConcurrency is the config for data path load concurrency per node.
properties:
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0
github.com/deckarep/golang-set/v2 v2.3.0
github.com/google/go-cmp v0.6.0
github.com/stretchr/testify v1.10.0
github.com/vmware-tanzu/velero v1.14.0
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
k8s.io/klog/v2 v2.130.1
Expand Down Expand Up @@ -97,6 +98,7 @@ require (
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/oklog/run v1.0.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.20.5 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
Expand Down
106 changes: 106 additions & 0 deletions internal/controller/backup_repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package controller

import (
"context"
"encoding/json"
"fmt"

"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

oadpv1alpha1 "github.com/openshift/oadp-operator/api/v1alpha1"
"github.com/openshift/oadp-operator/pkg/common"
)

func isBackupRepositoryCmRequired(config *oadpv1alpha1.NodeAgentConfig) bool {
return config != nil && (config.KopiaRepoOptions.CacheLimitMB != nil || len(config.KopiaRepoOptions.FullMaintenanceInterval) > 0)
}

// updateBackupRepositoryCM handles the creation or update of the BackupRepository ConfigMap with all required data.
func (r *DataProtectionApplicationReconciler) updateBackupRepositoryCM(cm *corev1.ConfigMap) error {
// Set the owner reference to ensure the ConfigMap is managed by the DPA
if err := controllerutil.SetControllerReference(r.dpa, cm, r.Scheme); err != nil {
return fmt.Errorf("failed to set controller reference: %w", err)
}

// Convert KopiaRepoOptions to a generic map
configBackupRepositoryJSON, err := json.Marshal(r.dpa.Spec.Configuration.NodeAgent.KopiaRepoOptions)
if err != nil {
return fmt.Errorf("failed to serialize backup repository config: %w", err)
}

cm.Name = common.BackupRepoConfigMapPrefix + r.dpa.Name
cm.Namespace = r.NamespacedName.Namespace
cm.Labels = map[string]string{
"app.kubernetes.io/instance": r.dpa.Name,
"app.kubernetes.io/managed-by": common.OADPOperator,
"app.kubernetes.io/component": "backup-repository-config",
oadpv1alpha1.OadpOperatorLabel: "True",
}

if cm.Data == nil {
cm.Data = make(map[string]string)
}
cm.Data["kopia"] = string(configBackupRepositoryJSON)

return nil
}

// GetBackupRepositoryConfigMapName returns the NamespacedName of the BackupRepository ConfigMap
func (r *DataProtectionApplicationReconciler) GetBackupRepositoryConfigMapName() types.NamespacedName {
return types.NamespacedName{
Name: common.BackupRepoConfigMapPrefix + r.dpa.Name,
Namespace: r.NamespacedName.Namespace,
}
}

// ReconcileBackupRepositoryConfigMap handles creation, update, and deletion of the BackupRepository ConfigMap.
func (r *DataProtectionApplicationReconciler) ReconcileBackupRepositoryConfigMap(log logr.Logger) (bool, error) {
dpa := r.dpa
cmName := r.GetBackupRepositoryConfigMapName()
configMap := corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: cmName.Name,
Namespace: cmName.Namespace,
},
}

// Delete CM if it is not required
if !isBackupRepositoryCmRequired(dpa.Spec.Configuration.NodeAgent) {
err := r.Get(r.Context, cmName, &configMap)
if err != nil && !errors.IsNotFound(err) {
return false, err
}
if errors.IsNotFound(err) {
return true, nil
}
deleteContext := context.Background()
if err := r.Delete(deleteContext, &configMap); err != nil {
if errors.IsNotFound(err) {
return true, nil
}
return false, err
}
r.EventRecorder.Event(&configMap, corev1.EventTypeNormal, "DeletedBackupRepositoryConfigMap", "BackupRepository config map deleted")
return true, nil
}

op, err := controllerutil.CreateOrPatch(r.Context, r.Client, &configMap, func() error {
return r.updateBackupRepositoryCM(&configMap)
})
if err != nil {
return false, fmt.Errorf("failed to create or patch config map: %w", err)
}

if op == controllerutil.OperationResultCreated {
r.EventRecorder.Event(&configMap, corev1.EventTypeNormal, "CreatedBackupRepositoryConfigMap", "BackupRepository config map created")
} else if op == controllerutil.OperationResultUpdated {
r.EventRecorder.Event(&configMap, corev1.EventTypeNormal, "UpdatedBackupRepositoryConfigMap", "BackupRepository config map updated")
}

return true, nil
}
109 changes: 109 additions & 0 deletions internal/controller/backup_repository_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package controller

import (
"context"
"encoding/json"
"testing"

"github.com/go-logr/logr"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record"
"k8s.io/utils/ptr"

oadpv1alpha1 "github.com/openshift/oadp-operator/api/v1alpha1"
)

func TestDataProtectionApplicationReconciler_updateBackupRepositoryCM(t *testing.T) {
tests := []struct {
name string
cm *corev1.ConfigMap
dpa *oadpv1alpha1.DataProtectionApplication
wantCM *corev1.ConfigMap
}{
{
name: "backup repository cm is updated successfully with full config",
cm: &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "backup-repository-test-dpa",
Namespace: "test-ns",
},
},
dpa: &oadpv1alpha1.DataProtectionApplication{
ObjectMeta: metav1.ObjectMeta{
Name: "test-dpa",
Namespace: "test-ns",
},
Spec: oadpv1alpha1.DataProtectionApplicationSpec{
BackupImages: ptr.To(false),
Configuration: &oadpv1alpha1.ApplicationConfig{
Velero: &oadpv1alpha1.VeleroConfig{
NoDefaultBackupLocation: true,
},
NodeAgent: &oadpv1alpha1.NodeAgentConfig{
KopiaRepoOptions: oadpv1alpha1.KopiaRepoOptions{
CacheLimitMB: ptr.To(int64(4096)),
FullMaintenanceInterval: "fastGC",
},
},
},
},
},
wantCM: &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "backup-repository-test-dpa",
Namespace: "test-ns",
Labels: map[string]string{
"app.kubernetes.io/instance": "test-dpa",
"app.kubernetes.io/managed-by": "oadp-operator",
"app.kubernetes.io/component": "backup-repository-config",
"openshift.io/oadp": "True",
},
},
Data: map[string]string{
"kopia": `{"cacheLimitMB":4096,"fullMaintenanceInterval":"fastGC"}`,
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fakeClient, err := getFakeClientFromObjects(tt.cm, tt.dpa)
if err != nil {
t.Errorf("error in creating fake client, likely programmer error")
}
r := &DataProtectionApplicationReconciler{
Client: fakeClient,
Scheme: fakeClient.Scheme(),
Log: logr.Discard(),
Context: context.Background(),
NamespacedName: types.NamespacedName{
Namespace: tt.cm.Namespace,
Name: tt.cm.Name,
},
EventRecorder: record.NewFakeRecorder(10),
dpa: tt.dpa,
}

err = r.updateBackupRepositoryCM(tt.cm)
require.NoError(t, err)
require.Equal(t, tt.wantCM.ObjectMeta.Name, tt.cm.ObjectMeta.Name, "ConfigMap Name does not match")
require.Equal(t, tt.wantCM.ObjectMeta.Namespace, tt.cm.ObjectMeta.Namespace, "ConfigMap Namespace does not match")
require.Equal(t, tt.wantCM.ObjectMeta.Labels, tt.cm.ObjectMeta.Labels, "ConfigMap Labels do not match")

// Compare Data fields, we need to unmarshal the JSON to ignore key order
expectedData := tt.wantCM.Data["kopia"]
actualData := tt.cm.Data["kopia"]

var expectedMap map[string]interface{}
var actualMap map[string]interface{}

require.NoError(t, json.Unmarshal([]byte(expectedData), &expectedMap), "Failed to unmarshal expected Data")
require.NoError(t, json.Unmarshal([]byte(actualData), &actualMap), "Failed to unmarshal actual Data")
require.Equal(t, expectedMap, actualMap, "ConfigMap Data does not match")
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ func (r *DataProtectionApplicationReconciler) Reconcile(ctx context.Context, req
r.ReconcileVolumeSnapshotLocations,
r.ReconcileVeleroDeployment,
r.ReconcileNodeAgentConfigMap,
r.ReconcileBackupRepositoryConfigMap,
r.ReconcileNodeAgentDaemonset,
r.ReconcileVeleroMetricsSVC,
r.ReconcileNonAdminController,
Expand Down
10 changes: 10 additions & 0 deletions internal/controller/velero.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,16 @@ func (r *DataProtectionApplicationReconciler) customizeVeleroDeployment(veleroDe
veleroContainer.Args = append(veleroContainer.Args, fmt.Sprintf("--item-block-worker-count=%v", dpa.Spec.Configuration.Velero.ItemBlockWorkerCount))
}

// check for backup-repository-configmap parameter
if isBackupRepositoryCmRequired(dpa.Spec.Configuration.NodeAgent) {
// Add the --backup-repository-configmap parameter with the name
// of the backup-repository ConfigMap
cmName := r.GetBackupRepositoryConfigMapName()
if cmName.Name != "" {
veleroContainer.Args = append(veleroContainer.Args, fmt.Sprintf("--backup-repository-configmap=%s", cmName.Name))
}
}

// Set defaults to avoid update events
if veleroDeployment.Spec.Strategy.Type == "" {
veleroDeployment.Spec.Strategy.Type = appsv1.RollingUpdateDeploymentStrategyType
Expand Down
Loading