Skip to content

Commit 76cb430

Browse files
Fix OADP-6459: Adding a backupLocation to DPA with an existing backupLocation name is not rejected (#1867)
* Fix OADP-6459: Adding a backupLocation to DPA with an existing backupLcation name is not rejected * add func for bslName
1 parent 08b9b61 commit 76cb430

File tree

2 files changed

+172
-5
lines changed

2 files changed

+172
-5
lines changed

internal/controller/bsl.go

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,40 @@ import (
1919
"github.com/openshift/oadp-operator/pkg/storage/aws"
2020
)
2121

22+
// getBSLName generates the BackupStorageLocation name for a given backup location spec and index.
23+
// It returns the user-provided name if specified, otherwise generates a name using the DPA name and index.
24+
func (r *DataProtectionApplicationReconciler) getBSLName(bslSpec *oadpv1alpha1.BackupLocation, index int) string {
25+
if bslSpec.Name != "" {
26+
return bslSpec.Name
27+
}
28+
return fmt.Sprintf("%s-%d", r.NamespacedName.Name, index+1)
29+
}
30+
2231
func (r *DataProtectionApplicationReconciler) ValidateBackupStorageLocations() (bool, error) {
2332
// Ensure BSL is a valid configuration
2433
// First, check for provider and then call functions based on the cloud provider for each backupstoragelocation configured
2534
dpa := r.dpa
2635
numDefaultLocations := 0
36+
namesSeen := make(map[string]bool)
37+
38+
// Check for duplicate backup location names and validate name format
39+
for i, bslSpec := range dpa.Spec.BackupLocations {
40+
// Validate that user-provided names are not empty strings
41+
if bslSpec.Name == "" {
42+
// Empty names are allowed and will be auto-generated, skip validation
43+
} else if strings.TrimSpace(bslSpec.Name) == "" {
44+
return false, fmt.Errorf("backup location name cannot be empty or whitespace only")
45+
}
46+
47+
// Determine the BSL name using the helper function
48+
bslName := r.getBSLName(&bslSpec, i)
49+
50+
if namesSeen[bslName] {
51+
return false, fmt.Errorf("backup location name '%s' is duplicated. Backup location names must be unique", bslName)
52+
}
53+
namesSeen[bslName] = true
54+
}
55+
2756
for _, bslSpec := range dpa.Spec.BackupLocations {
2857
if err := r.ensureBackupLocationHasVeleroOrCloudStorage(&bslSpec); err != nil {
2958
return false, err
@@ -96,11 +125,8 @@ func (r *DataProtectionApplicationReconciler) ReconcileBackupStorageLocations(lo
96125
// Create BSL as is, we can safely assume they are valid from
97126
// ValidateBackupStorageLocations
98127

99-
// check if BSL name is specified in DPA spec
100-
bslName := fmt.Sprintf("%s-%d", r.NamespacedName.Name, i+1)
101-
if bslSpec.Name != "" {
102-
bslName = bslSpec.Name
103-
}
128+
// Get BSL name using the helper function
129+
bslName := r.getBSLName(&bslSpec, i)
104130
dpaBSLNames = append(dpaBSLNames, bslName)
105131

106132
bsl := velerov1.BackupStorageLocation{

internal/controller/bsl_test.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1494,6 +1494,147 @@ func TestDPAReconciler_ValidateBackupStorageLocations(t *testing.T) {
14941494
want: false,
14951495
wantErr: true,
14961496
},
1497+
{
1498+
name: "test duplicate backup location names should fail validation",
1499+
dpa: &oadpv1alpha1.DataProtectionApplication{
1500+
ObjectMeta: metav1.ObjectMeta{
1501+
Name: "foo",
1502+
Namespace: "test-ns",
1503+
},
1504+
Spec: oadpv1alpha1.DataProtectionApplicationSpec{
1505+
Configuration: &oadpv1alpha1.ApplicationConfig{
1506+
Velero: &oadpv1alpha1.VeleroConfig{
1507+
DefaultPlugins: []oadpv1alpha1.DefaultPlugin{
1508+
oadpv1alpha1.DefaultPluginAWS,
1509+
oadpv1alpha1.DefaultPluginMicrosoftAzure,
1510+
},
1511+
},
1512+
},
1513+
BackupLocations: []oadpv1alpha1.BackupLocation{
1514+
{
1515+
Name: "duplicate-name",
1516+
Velero: &velerov1.BackupStorageLocationSpec{
1517+
Provider: "aws",
1518+
Default: true,
1519+
StorageType: velerov1.StorageType{
1520+
ObjectStorage: &velerov1.ObjectStorageLocation{
1521+
Bucket: "test-aws-bucket",
1522+
Prefix: "velero/backups",
1523+
},
1524+
},
1525+
Config: map[string]string{
1526+
"region": "us-east-1",
1527+
},
1528+
Credential: &corev1.SecretKeySelector{
1529+
LocalObjectReference: corev1.LocalObjectReference{
1530+
Name: "cloud-credentials",
1531+
},
1532+
Key: "cloud",
1533+
},
1534+
},
1535+
},
1536+
{
1537+
Name: "duplicate-name", // Same name as above
1538+
Velero: &velerov1.BackupStorageLocationSpec{
1539+
Provider: "azure",
1540+
Default: false,
1541+
StorageType: velerov1.StorageType{
1542+
ObjectStorage: &velerov1.ObjectStorageLocation{
1543+
Bucket: "test-azure-bucket",
1544+
Prefix: "velero/backups",
1545+
},
1546+
},
1547+
Config: map[string]string{
1548+
"resourceGroup": "test-rg",
1549+
"storageAccount": "test-sa",
1550+
},
1551+
Credential: &corev1.SecretKeySelector{
1552+
LocalObjectReference: corev1.LocalObjectReference{
1553+
Name: "azure-credentials",
1554+
},
1555+
Key: "cloud",
1556+
},
1557+
},
1558+
},
1559+
},
1560+
},
1561+
},
1562+
secret: &corev1.Secret{
1563+
ObjectMeta: metav1.ObjectMeta{
1564+
Name: "cloud-credentials",
1565+
Namespace: "test-ns",
1566+
},
1567+
Data: map[string][]byte{
1568+
"cloud": []byte("[default]\naws_access_key_id=test\naws_secret_access_key=test"),
1569+
},
1570+
},
1571+
objects: []client.Object{
1572+
&corev1.Secret{
1573+
ObjectMeta: metav1.ObjectMeta{
1574+
Name: "azure-credentials",
1575+
Namespace: "test-ns",
1576+
},
1577+
Data: map[string][]byte{
1578+
"cloud": []byte("AZURE_SUBSCRIPTION_ID=test\nAZURE_TENANT_ID=test\nAZURE_CLIENT_ID=test\nAZURE_CLIENT_SECRET=test\nAZURE_RESOURCE_GROUP=test-rg\nAZURE_CLOUD_NAME=AzurePublicCloud"),
1579+
},
1580+
},
1581+
},
1582+
want: false,
1583+
wantErr: true,
1584+
},
1585+
{
1586+
name: "test backup location with whitespace-only name should fail validation",
1587+
dpa: &oadpv1alpha1.DataProtectionApplication{
1588+
ObjectMeta: metav1.ObjectMeta{
1589+
Name: "foo",
1590+
Namespace: "test-ns",
1591+
},
1592+
Spec: oadpv1alpha1.DataProtectionApplicationSpec{
1593+
Configuration: &oadpv1alpha1.ApplicationConfig{
1594+
Velero: &oadpv1alpha1.VeleroConfig{
1595+
DefaultPlugins: []oadpv1alpha1.DefaultPlugin{
1596+
oadpv1alpha1.DefaultPluginAWS,
1597+
},
1598+
},
1599+
},
1600+
BackupLocations: []oadpv1alpha1.BackupLocation{
1601+
{
1602+
Name: " ", // Whitespace-only name should fail
1603+
Velero: &velerov1.BackupStorageLocationSpec{
1604+
Provider: "aws",
1605+
Default: true,
1606+
StorageType: velerov1.StorageType{
1607+
ObjectStorage: &velerov1.ObjectStorageLocation{
1608+
Bucket: "test-aws-bucket",
1609+
Prefix: "velero/backups",
1610+
},
1611+
},
1612+
Config: map[string]string{
1613+
"region": "us-east-1",
1614+
},
1615+
Credential: &corev1.SecretKeySelector{
1616+
LocalObjectReference: corev1.LocalObjectReference{
1617+
Name: "cloud-credentials",
1618+
},
1619+
Key: "cloud",
1620+
},
1621+
},
1622+
},
1623+
},
1624+
},
1625+
},
1626+
secret: &corev1.Secret{
1627+
ObjectMeta: metav1.ObjectMeta{
1628+
Name: "cloud-credentials",
1629+
Namespace: "test-ns",
1630+
},
1631+
Data: map[string][]byte{
1632+
"cloud": []byte("[default]\naws_access_key_id=test\naws_secret_access_key=test"),
1633+
},
1634+
},
1635+
want: false,
1636+
wantErr: true,
1637+
},
14971638
}
14981639
for _, tt := range tests {
14991640
t.Run(tt.name, func(t *testing.T) {

0 commit comments

Comments
 (0)