Files
velero/internal/resourcepolicies/volume_resources_validator_test.go
Daniel Jiang 249d8f581a Add include/exclude policy to resources policy
fixes #8610

This commit extends the resources policy, such that user can define
resource include exclude filters in the policy and reuse it in different backups.

Signed-off-by: Daniel Jiang <daniel.jiang@broadcom.com>
2025-08-05 15:16:59 +08:00

571 lines
14 KiB
Go

/*
Copyright The Velero Contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package resourcepolicies
import (
"testing"
"k8s.io/apimachinery/pkg/api/resource"
)
func TestCapacityConditionValidate(t *testing.T) {
testCases := []struct {
name string
capacity *capacity
wantErr bool
}{
{
name: "lower and upper are both zero",
capacity: &capacity{lower: *resource.NewQuantity(0, resource.DecimalSI), upper: *resource.NewQuantity(0, resource.DecimalSI)},
wantErr: false,
},
{
name: "lower is zero and upper is greater than zero",
capacity: &capacity{lower: *resource.NewQuantity(0, resource.DecimalSI), upper: *resource.NewQuantity(100, resource.DecimalSI)},
wantErr: false,
},
{
name: "lower is greater than upper",
capacity: &capacity{lower: *resource.NewQuantity(100, resource.DecimalSI), upper: *resource.NewQuantity(50, resource.DecimalSI)},
wantErr: true,
},
{
name: "lower and upper are equal",
capacity: &capacity{lower: *resource.NewQuantity(100, resource.DecimalSI), upper: *resource.NewQuantity(100, resource.DecimalSI)},
wantErr: false,
},
{
name: "lower is greater than zero and upper is zero",
capacity: &capacity{lower: *resource.NewQuantity(100, resource.DecimalSI), upper: *resource.NewQuantity(0, resource.DecimalSI)},
wantErr: false,
},
{
name: "lower and upper are both not zero and lower is less than upper",
capacity: &capacity{lower: *resource.NewQuantity(100, resource.DecimalSI), upper: *resource.NewQuantity(200, resource.DecimalSI)},
wantErr: false,
},
{
name: "lower and upper are both not zero and lower is equal to upper",
capacity: &capacity{lower: *resource.NewQuantity(100, resource.DecimalSI), upper: *resource.NewQuantity(100, resource.DecimalSI)},
wantErr: false,
},
{
name: "lower and upper are both not zero and lower is greater than upper",
capacity: &capacity{lower: *resource.NewQuantity(200, resource.DecimalSI), upper: *resource.NewQuantity(100, resource.DecimalSI)},
wantErr: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
c := &capacityCondition{capacity: *tc.capacity}
err := c.validate()
if (err != nil) != tc.wantErr {
t.Fatalf("Expected error %v, but got error %v", tc.wantErr, err)
}
})
}
}
func TestValidate(t *testing.T) {
testCases := []struct {
name string
res *ResourcePolicies
wantErr bool
}{
{
name: "unknown key in yaml",
res: &ResourcePolicies{
Version: "v1",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: "skip"},
Conditions: map[string]any{
"capacity": "0,10Gi",
"unknown": "",
"storageClass": []string{"gp2", "ebs-sc"},
"csi": any(
map[string]any{
"driver": "aws.efs.csi.driver",
}),
},
},
},
},
wantErr: true,
},
{
name: "error format of capacity",
res: &ResourcePolicies{
Version: "v1",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: "skip"},
Conditions: map[string]any{
"capacity": "10Gi",
"storageClass": []string{"gp2", "ebs-sc"},
"csi": any(
map[string]any{
"driver": "aws.efs.csi.driver",
}),
},
},
},
},
wantErr: true,
},
{
name: "error format of storageClass",
res: &ResourcePolicies{
Version: "v1",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: "skip"},
Conditions: map[string]any{
"capacity": "0,10Gi",
"storageClass": "ebs-sc",
"csi": any(
map[string]any{
"driver": "aws.efs.csi.driver",
}),
},
},
},
},
wantErr: true,
},
{
name: "error format of csi",
res: &ResourcePolicies{
Version: "v1",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: "skip"},
Conditions: map[string]any{
"capacity": "0,10Gi",
"storageClass": []string{"gp2", "ebs-sc"},
"csi": "aws.efs.csi.driver",
},
},
},
},
wantErr: true,
},
{
name: "error format of csi driver",
res: &ResourcePolicies{
Version: "v1",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: "skip"},
Conditions: map[string]any{
"capacity": "0,10Gi",
"storageClass": []string{"gp2", "ebs-sc"},
"csi": any(
map[string]any{
"driver": []string{"aws.efs.csi.driver"},
}),
},
},
},
},
wantErr: true,
},
{
name: "error format of csi driver volumeAttributes",
res: &ResourcePolicies{
Version: "v1",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: "skip"},
Conditions: map[string]any{
"capacity": "0,10Gi",
"storageClass": []string{"gp2", "ebs-sc"},
"csi": any(
map[string]any{
"driver": "aws.efs.csi.driver",
"volumeAttributes": "test",
}),
},
},
},
},
wantErr: true,
},
{
name: "unsupported version",
res: &ResourcePolicies{
Version: "v2",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: "skip"},
Conditions: map[string]any{
"capacity": "0,10Gi",
"csi": any(
map[string]any{
"driver": "aws.efs.csi.driver",
}),
},
},
},
},
wantErr: true,
},
{
name: "unsupported action",
res: &ResourcePolicies{
Version: "v1",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: "unsupported"},
Conditions: map[string]any{
"capacity": "0,10Gi",
"csi": any(
map[string]any{
"driver": "aws.efs.csi.driver",
}),
},
},
},
},
wantErr: true,
},
{
name: "error format of nfs",
res: &ResourcePolicies{
Version: "v1",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: "skip"},
Conditions: map[string]any{
"capacity": "0,10Gi",
"storageClass": []string{"gp2", "ebs-sc"},
"nfs": "aws.efs.csi.driver",
},
},
},
},
wantErr: true,
},
{
name: "supported format volume policies only csi driver",
res: &ResourcePolicies{
Version: "v1",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: "skip"},
Conditions: map[string]any{
"csi": any(
map[string]any{
"driver": "aws.efs.csi.driver",
}),
},
},
},
},
wantErr: false,
},
{
name: "unsupported format volume policies only csi volumeattributes",
res: &ResourcePolicies{
Version: "v1",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: "skip"},
Conditions: map[string]any{
"csi": any(
map[string]any{
"volumeAttributes": map[string]string{
"key1": "value1",
},
}),
},
},
},
},
wantErr: true,
},
{
name: "supported format volume policies with csi driver and volumeattributes",
res: &ResourcePolicies{
Version: "v1",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: "skip"},
Conditions: map[string]any{
"csi": any(
map[string]any{
"driver": "aws.efs.csi.driver",
"volumeAttributes": map[string]string{
"key1": "value1",
},
}),
},
},
},
},
wantErr: false,
},
{
name: "supported format volume policies",
res: &ResourcePolicies{
Version: "v1",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: "skip"},
Conditions: map[string]any{
"capacity": "0,10Gi",
"storageClass": []string{"gp2", "ebs-sc"},
"csi": any(
map[string]any{
"driver": "aws.efs.csi.driver",
}),
"nfs": any(
map[string]any{
"server": "192.168.20.90",
"path": "/mnt/data/",
}),
},
},
},
},
wantErr: false,
},
{
name: "supported format volume policies, action type snapshot",
res: &ResourcePolicies{
Version: "v1",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: "snapshot"},
Conditions: map[string]any{
"capacity": "0,10Gi",
"storageClass": []string{"gp2", "ebs-sc"},
"csi": any(
map[string]any{
"driver": "aws.efs.csi.driver",
}),
"nfs": any(
map[string]any{
"server": "192.168.20.90",
"path": "/mnt/data/",
}),
},
},
},
},
wantErr: false,
},
{
name: "supported format volume policies, action type fs-backup",
res: &ResourcePolicies{
Version: "v1",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: "fs-backup"},
Conditions: map[string]any{
"capacity": "0,10Gi",
"storageClass": []string{"gp2", "ebs-sc"},
"csi": any(
map[string]any{
"driver": "aws.efs.csi.driver",
}),
"nfs": any(
map[string]any{
"server": "192.168.20.90",
"path": "/mnt/data/",
}),
},
},
},
},
wantErr: false,
},
{
name: "supported format volume policies, action type fs-backup and snapshot",
res: &ResourcePolicies{
Version: "v1",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: Snapshot},
Conditions: map[string]any{
"storageClass": []string{"gp2"},
},
},
{
Action: Action{Type: FSBackup},
Conditions: map[string]any{
"nfs": any(
map[string]any{
"server": "192.168.20.90",
"path": "/mnt/data/",
}),
},
},
},
},
wantErr: false,
},
{
name: "supported format volume policies with pvcLabels (valid map)",
res: &ResourcePolicies{
Version: "v1",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: "skip"},
Conditions: map[string]any{
"pvcLabels": map[string]string{
"environment": "production",
"app": "database",
},
},
},
},
},
wantErr: false,
},
{
name: "error format volume policies with pvcLabels (not a map)",
res: &ResourcePolicies{
Version: "v1",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: "skip"},
Conditions: map[string]any{
"pvcLabels": "production",
},
},
},
},
wantErr: true,
},
{
name: " '*' in the filters of include exclude policy - 1",
res: &ResourcePolicies{
Version: "v1",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: "skip"},
Conditions: map[string]any{
"pvcLabels": map[string]string{
"environment": "production",
"app": "database",
},
},
},
},
IncludeExcludePolicy: &IncludeExcludePolicy{
IncludedClusterScopedResources: []string{"*"},
ExcludedClusterScopedResources: []string{"crds"},
IncludedNamespaceScopedResources: []string{"pods"},
ExcludedNamespaceScopedResources: []string{"secrets"},
},
},
wantErr: true,
},
{
name: " '*' in the filters of include exclude policy - 2",
res: &ResourcePolicies{
Version: "v1",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: "skip"},
Conditions: map[string]any{
"pvcLabels": map[string]string{
"environment": "production",
"app": "database",
},
},
},
},
IncludeExcludePolicy: &IncludeExcludePolicy{
IncludedClusterScopedResources: []string{"persistentvolumes"},
ExcludedClusterScopedResources: []string{"crds"},
IncludedNamespaceScopedResources: []string{"pods"},
ExcludedNamespaceScopedResources: []string{"*"},
},
},
wantErr: true,
},
{
name: " dup item in both the include and exclude filters of include exclude policy",
res: &ResourcePolicies{
Version: "v1",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: "skip"},
Conditions: map[string]any{
"pvcLabels": map[string]string{
"environment": "production",
"app": "database",
},
},
},
},
IncludeExcludePolicy: &IncludeExcludePolicy{
IncludedClusterScopedResources: []string{"persistentvolumes"},
ExcludedClusterScopedResources: []string{"crds"},
IncludedNamespaceScopedResources: []string{"pods", "configmaps"},
ExcludedNamespaceScopedResources: []string{"secrets", "pods"},
},
},
wantErr: true,
},
{
name: " valid volume policies and valid include/exclude policy",
res: &ResourcePolicies{
Version: "v1",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: "skip"},
Conditions: map[string]any{
"pvcLabels": map[string]string{
"environment": "production",
"app": "database",
},
},
},
},
IncludeExcludePolicy: &IncludeExcludePolicy{
IncludedClusterScopedResources: []string{"persistentvolumes"},
ExcludedClusterScopedResources: []string{"crds"},
IncludedNamespaceScopedResources: []string{"pods", "configmaps"},
ExcludedNamespaceScopedResources: []string{"secrets"},
},
},
wantErr: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
policies := &Policies{}
err1 := policies.BuildPolicy(tc.res)
err2 := policies.Validate()
if tc.wantErr {
if err1 == nil && err2 == nil {
t.Fatalf("Expected error %v, but not get error", tc.wantErr)
}
} else {
if err1 != nil || err2 != nil {
t.Fatalf("Expected error %v, but got error %v %v", tc.wantErr, err1, err2)
}
}
})
}
}