360 lines
17 KiB
Go
360 lines
17 KiB
Go
/*
|
|
Copyright 2019 The Kubernetes Authors.
|
|
|
|
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 sidecar_controller
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
crdv1 "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1"
|
|
"github.com/kubernetes-csi/external-snapshotter/v7/pkg/utils"
|
|
v1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
)
|
|
|
|
var (
|
|
defaultSize int64 = 1000
|
|
emptySize int64
|
|
deletePolicy = crdv1.VolumeSnapshotContentDelete
|
|
retainPolicy = crdv1.VolumeSnapshotContentRetain
|
|
timeNow = time.Now()
|
|
timeNowMetav1 = metav1.Now()
|
|
nonFractionalTime = metav1.NewTime(time.Now().Truncate(time.Second))
|
|
False = false
|
|
True = true
|
|
)
|
|
|
|
var class1Parameters = map[string]string{
|
|
"param1": "value1",
|
|
}
|
|
|
|
var class2Parameters = map[string]string{
|
|
"param2": "value2",
|
|
}
|
|
|
|
var class3Parameters = map[string]string{
|
|
"param3": "value3",
|
|
utils.AnnDeletionSecretRefName: "name",
|
|
}
|
|
|
|
var class4Parameters = map[string]string{
|
|
utils.AnnDeletionSecretRefName: "emptysecret",
|
|
utils.AnnDeletionSecretRefNamespace: "default",
|
|
}
|
|
|
|
var class5Parameters = map[string]string{
|
|
utils.AnnDeletionSecretRefName: "secret",
|
|
utils.AnnDeletionSecretRefNamespace: "default",
|
|
}
|
|
|
|
var class6Parameters = map[string]string{
|
|
utils.PrefixedSnapshotterSecretNameKey: "secret",
|
|
utils.PrefixedSnapshotterSecretNamespaceKey: "default",
|
|
utils.PrefixedSnapshotterListSecretNameKey: "secret",
|
|
utils.PrefixedSnapshotterListSecretNamespaceKey: "default",
|
|
}
|
|
|
|
var class7Annotations = map[string]string{
|
|
utils.AnnDeletionSecretRefName: "secret-x",
|
|
utils.AnnDeletionSecretRefNamespace: "default-x",
|
|
}
|
|
|
|
var snapshotClasses = []*crdv1.VolumeSnapshotClass{
|
|
{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "VolumeSnapshotClass",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: classGold,
|
|
},
|
|
Driver: mockDriverName,
|
|
Parameters: class1Parameters,
|
|
DeletionPolicy: crdv1.VolumeSnapshotContentDelete,
|
|
},
|
|
{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "VolumeSnapshotClass",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: classSilver,
|
|
},
|
|
Driver: mockDriverName,
|
|
Parameters: class2Parameters,
|
|
DeletionPolicy: crdv1.VolumeSnapshotContentDelete,
|
|
},
|
|
{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "VolumeSnapshotClass",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: emptySecretClass,
|
|
},
|
|
Driver: mockDriverName,
|
|
Parameters: class4Parameters,
|
|
DeletionPolicy: crdv1.VolumeSnapshotContentDelete,
|
|
},
|
|
{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "VolumeSnapshotClass",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: invalidSecretClass,
|
|
Annotations: class7Annotations,
|
|
},
|
|
Driver: mockDriverName,
|
|
Parameters: class2Parameters,
|
|
DeletionPolicy: crdv1.VolumeSnapshotContentDelete,
|
|
},
|
|
{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "VolumeSnapshotClass",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: validSecretClass,
|
|
},
|
|
Driver: mockDriverName,
|
|
Parameters: class5Parameters,
|
|
DeletionPolicy: crdv1.VolumeSnapshotContentDelete,
|
|
},
|
|
{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "VolumeSnapshotClass",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: defaultClass,
|
|
Annotations: map[string]string{utils.IsDefaultSnapshotClassAnnotation: "true"},
|
|
},
|
|
Driver: mockDriverName,
|
|
Parameters: class6Parameters,
|
|
DeletionPolicy: crdv1.VolumeSnapshotContentDelete,
|
|
},
|
|
}
|
|
|
|
// Test single call to syncContent, expecting deleting to happen.
|
|
// 1. Fill in the controller with initial data
|
|
// 2. Call the syncContent *once*.
|
|
// 3. Compare resulting contents with expected contents.
|
|
func TestDeleteSync(t *testing.T) {
|
|
tests := []controllerTest{
|
|
{
|
|
name: "1-1 - content non-nil DeletionTimestamp with delete policy will delete snapshot",
|
|
initialContents: newContentArrayWithDeletionTimestamp("content1-1", "snapuid1-1", "snap1-1", "sid1-1", classGold, "", "snap1-1-volumehandle", deletionPolicy, nil, nil, true, &nonFractionalTime),
|
|
expectedContents: newContentArrayWithDeletionTimestamp("content1-1", "snapuid1-1", "snap1-1", "", classGold, "", "snap1-1-volumehandle", deletionPolicy, nil, nil, false, &nonFractionalTime),
|
|
expectedEvents: noevents,
|
|
errors: noerrors,
|
|
initialSecrets: []*v1.Secret{secret()},
|
|
expectedCreateCalls: []createCall{
|
|
{
|
|
snapshotName: "snapshot-snapuid1-1",
|
|
volumeHandle: "snap1-1-volumehandle",
|
|
parameters: map[string]string{"param1": "value1"},
|
|
driverName: mockDriverName,
|
|
size: defaultSize,
|
|
snapshotId: "snapuid1-1-deleted",
|
|
creationTime: timeNow,
|
|
readyToUse: true,
|
|
},
|
|
},
|
|
expectedListCalls: []listCall{{"sid1-1", map[string]string{}, true, time.Now(), 1, nil, ""}},
|
|
expectedDeleteCalls: []deleteCall{{"sid1-1", nil, nil}},
|
|
expectSuccess: true,
|
|
test: testSyncContent,
|
|
},
|
|
{
|
|
name: "1-2 - content non-nil DeletionTimestamp with retain policy will not delete snapshot",
|
|
initialContents: newContentArrayWithDeletionTimestamp("content1-2", "snapuid1-2", "snap1-2", "sid1-2", classGold, "", "snap1-2-volumehandle", retainPolicy, nil, nil, true, &nonFractionalTime),
|
|
expectedContents: newContentArrayWithDeletionTimestamp("content1-2", "snapuid1-2", "snap1-2", "sid1-2", classGold, "", "snap1-2-volumehandle", retainPolicy, nil, nil, false, &nonFractionalTime),
|
|
expectedEvents: noevents,
|
|
errors: noerrors,
|
|
expectedCreateCalls: []createCall{
|
|
{
|
|
snapshotName: "snapshot-snapuid1-2",
|
|
volumeHandle: "snap1-2-volumehandle",
|
|
parameters: map[string]string{"param1": "value1"},
|
|
driverName: mockDriverName,
|
|
size: defaultSize,
|
|
snapshotId: "snapuid1-2-deleted",
|
|
creationTime: timeNow,
|
|
readyToUse: true,
|
|
},
|
|
},
|
|
expectedListCalls: []listCall{{"sid1-2", map[string]string{}, true, time.Now(), 1, nil, ""}},
|
|
expectedDeleteCalls: []deleteCall{{"sid1-2", nil, nil}},
|
|
expectSuccess: true,
|
|
test: testSyncContent,
|
|
},
|
|
{
|
|
name: "1-3 - delete snapshot error should result in an event, bound finalizer should remain",
|
|
initialContents: newContentArrayWithDeletionTimestamp("content1-3", "snapuid1-3", "snap1-3", "sid1-3", validSecretClass, "", "snap1-3-volumehandle", deletePolicy, nil, nil, true, &timeNowMetav1),
|
|
expectedContents: newContentArrayWithDeletionTimestamp("content1-3", "snapuid1-3", "snap1-3", "sid1-3", validSecretClass, "", "snap1-3-volumehandle", deletePolicy, nil, nil, true, &timeNowMetav1),
|
|
errors: noerrors,
|
|
expectedCreateCalls: []createCall{
|
|
{
|
|
snapshotName: "snapshot-snapuid1-3",
|
|
volumeHandle: "snap1-3-volumehandle",
|
|
parameters: map[string]string{"foo": "bar"},
|
|
driverName: mockDriverName,
|
|
size: defaultSize,
|
|
snapshotId: "snapuid1-3-deleted",
|
|
creationTime: timeNow,
|
|
readyToUse: true,
|
|
},
|
|
},
|
|
expectedDeleteCalls: []deleteCall{{"sid1-3", nil, fmt.Errorf("mock csi driver delete error")}},
|
|
expectedEvents: []string{"Warning SnapshotDeleteError"},
|
|
expectedListCalls: []listCall{{"sid1-3", map[string]string{}, true, time.Now(), 1, nil, ""}},
|
|
test: testSyncContent,
|
|
},
|
|
{
|
|
name: "1-4 - fail to delete with a snapshot class which has invalid secret parameter, bound finalizer should remain",
|
|
initialContents: newContentArrayWithDeletionTimestamp("content1-1", "snapuid1-1", "snap1-1", "sid1-1", "invalid", "", "snap1-4-volumehandle", deletionPolicy, nil, nil, true, &timeNowMetav1),
|
|
expectedContents: newContentArrayWithDeletionTimestamp("content1-1", "snapuid1-1", "snap1-1", "sid1-1", "invalid", "", "snap1-4-volumehandle", deletionPolicy, nil, nil, true, &timeNowMetav1),
|
|
expectedEvents: noevents,
|
|
expectedDeleteCalls: []deleteCall{{"sid1-1", nil, fmt.Errorf("mock csi driver delete error")}},
|
|
errors: []reactorError{
|
|
// Inject error to the first client.VolumesnapshotV1().VolumeSnapshotContents().Delete call.
|
|
// All other calls will succeed.
|
|
{"get", "secrets", errors.New("mock get invalid secret error")},
|
|
},
|
|
test: testSyncContent,
|
|
},
|
|
{
|
|
name: "1-5 - csi driver delete snapshot returns error, bound finalizer should remain",
|
|
initialContents: newContentArrayWithDeletionTimestamp("content1-5", "sid1-5", "snap1-5", "sid1-5", validSecretClass, "", "snap1-5-volumehandle", deletionPolicy, nil, &defaultSize, true, &timeNowMetav1),
|
|
expectedContents: newContentArrayWithDeletionTimestamp("content1-5", "sid1-5", "snap1-5", "sid1-5", validSecretClass, "", "snap1-5-volumehandle", deletionPolicy, nil, &defaultSize, true, &timeNowMetav1),
|
|
expectedListCalls: []listCall{{"sid1-5", map[string]string{}, true, time.Now(), 1000, nil, ""}},
|
|
expectedDeleteCalls: []deleteCall{{"sid1-5", nil, errors.New("mock csi driver delete error")}},
|
|
expectedEvents: []string{"Warning SnapshotDeleteError"},
|
|
errors: noerrors,
|
|
test: testSyncContent,
|
|
},
|
|
{
|
|
// delete success(?) - content is deleted before deleteSnapshot starts
|
|
name: "1-6 - content is deleted before deleting",
|
|
initialContents: newContentArray("content1-6", "sid1-6", "snap1-6", "sid1-6", classGold, "sid1-6", "", deletionPolicy, nil, nil, true),
|
|
expectedContents: nocontents,
|
|
expectedListCalls: []listCall{{"sid1-6", nil, false, time.Now(), 0, nil, ""}},
|
|
expectedDeleteCalls: []deleteCall{{"sid1-6", map[string]string{"foo": "bar"}, nil}},
|
|
expectedEvents: noevents,
|
|
errors: noerrors,
|
|
test: wrapTestWithInjectedOperation(testSyncContent, func(ctrl *csiSnapshotSideCarController, reactor *snapshotReactor) {
|
|
// Delete the content before delete operation starts
|
|
reactor.lock.Lock()
|
|
delete(reactor.contents, "content1-6")
|
|
reactor.lock.Unlock()
|
|
}),
|
|
},
|
|
{
|
|
name: "1-7 - content will not be deleted if it is bound to a snapshot correctly, snapshot uid is not specified",
|
|
initialContents: newContentArrayWithReadyToUse("content1-7", "", "snap1-7", "sid1-7", validSecretClass, "sid1-7", "", deletePolicy, nil, &defaultSize, &True, true),
|
|
expectedContents: newContentArrayWithReadyToUse("content1-7", "", "snap1-7", "sid1-7", validSecretClass, "sid1-7", "", deletePolicy, nil, &defaultSize, &True, true),
|
|
expectedEvents: noevents,
|
|
expectedListCalls: []listCall{{"sid1-7", map[string]string{}, true, time.Now(), 1000, nil, ""}},
|
|
expectSuccess: true,
|
|
initialSecrets: []*v1.Secret{secret()},
|
|
errors: noerrors,
|
|
test: testSyncContent,
|
|
},
|
|
{
|
|
name: "1-8 - content with retain policy will not be deleted if it is bound to a non-exist snapshot and also has a snapshot uid specified",
|
|
initialContents: newContentArrayWithReadyToUse("content1-8", "sid1-8", "none-existed-snapshot", "sid1-8", validSecretClass, "sid1-8", "", retainPolicy, nil, &defaultSize, &True, true),
|
|
expectedContents: newContentArrayWithReadyToUse("content1-8", "sid1-8", "none-existed-snapshot", "sid1-8", validSecretClass, "sid1-8", "", retainPolicy, nil, &defaultSize, &True, true),
|
|
expectedEvents: noevents,
|
|
expectedListCalls: []listCall{{"sid1-8", map[string]string{}, true, time.Now(), 0, nil, ""}},
|
|
expectSuccess: true,
|
|
errors: noerrors,
|
|
test: testSyncContent,
|
|
},
|
|
{
|
|
name: "1-9 - continue deletion with snapshot class that has nonexistent secret, bound finalizer removed",
|
|
initialContents: newContentArrayWithDeletionTimestamp("content1-9", "sid1-9", "snap1-9", "sid1-9", emptySecretClass, "", "snap1-9-volumehandle", deletePolicy, nil, &defaultSize, true, &nonFractionalTime),
|
|
expectedContents: newContentArrayWithDeletionTimestamp("content1-9", "sid1-9", "snap1-9", "", emptySecretClass, "", "snap1-9-volumehandle", deletePolicy, nil, &defaultSize, false, &nonFractionalTime),
|
|
expectedEvents: noevents,
|
|
expectedListCalls: []listCall{{"sid1-9", map[string]string{}, true, time.Now(), 0, nil, ""}},
|
|
expectSuccess: true,
|
|
errors: noerrors,
|
|
initialSecrets: []*v1.Secret{}, // secret does not exist
|
|
expectedDeleteCalls: []deleteCall{{"sid1-9", nil, nil}},
|
|
test: testSyncContent,
|
|
},
|
|
{
|
|
name: "1-10 - (dynamic)deletion of content with retain policy should not trigger CSI call, not update status, but remove bound finalizer",
|
|
initialContents: newContentArrayWithDeletionTimestamp("content1-10", "sid1-10", "snap1-10", "sid1-10", emptySecretClass, "", "snap1-10-volumehandle", retainPolicy, nil, &defaultSize, true, &nonFractionalTime),
|
|
expectedContents: newContentArrayWithDeletionTimestamp("content1-10", "sid1-10", "snap1-10", "sid1-10", emptySecretClass, "", "snap1-10-volumehandle", retainPolicy, nil, &defaultSize, false, &nonFractionalTime),
|
|
expectedEvents: noevents,
|
|
expectedListCalls: []listCall{{"sid1-10", map[string]string{}, true, time.Now(), 0, nil, ""}},
|
|
expectSuccess: true,
|
|
errors: noerrors,
|
|
initialSecrets: []*v1.Secret{},
|
|
test: testSyncContent,
|
|
},
|
|
{
|
|
name: "1-11 - (dynamic)deletion of content with deletion policy should trigger CSI call, update status, and remove bound finalizer removed.",
|
|
initialContents: newContentArrayWithDeletionTimestamp("content1-11", "sid1-11", "snap1-11", "sid1-11", emptySecretClass, "", "snap1-11-volumehandle", deletePolicy, nil, &defaultSize, true, &nonFractionalTime),
|
|
expectedContents: newContentArrayWithDeletionTimestamp("content1-11", "sid1-11", "snap1-11", "", emptySecretClass, "", "snap1-11-volumehandle", deletePolicy, nil, nil, false, &nonFractionalTime),
|
|
expectedEvents: noevents,
|
|
expectSuccess: true,
|
|
errors: noerrors,
|
|
expectedDeleteCalls: []deleteCall{{"sid1-11", nil, nil}},
|
|
test: testSyncContent,
|
|
},
|
|
{
|
|
name: "1-12 - (pre-provision)deletion of content with retain policy should not trigger CSI call, not update status, but remove bound finalizer",
|
|
initialContents: newContentArrayWithDeletionTimestamp("content1-12", "sid1-12", "snap1-12", "sid1-12", emptySecretClass, "sid1-12", "", retainPolicy, nil, &defaultSize, true, &nonFractionalTime),
|
|
expectedContents: newContentArrayWithDeletionTimestamp("content1-12", "sid1-12", "snap1-12", "sid1-12", emptySecretClass, "sid1-12", "", retainPolicy, nil, &defaultSize, false, &nonFractionalTime),
|
|
expectedEvents: noevents,
|
|
expectedListCalls: []listCall{{"sid1-12", map[string]string{}, true, time.Now(), 0, nil, ""}},
|
|
expectSuccess: true,
|
|
errors: noerrors,
|
|
initialSecrets: []*v1.Secret{},
|
|
test: testSyncContent,
|
|
},
|
|
{
|
|
name: "1-13 - (pre-provision)deletion of content with deletion policy should trigger CSI call, update status, and remove bound finalizer removed.",
|
|
initialContents: newContentArrayWithDeletionTimestamp("content1-13", "sid1-13", "snap1-13", "sid1-13", emptySecretClass, "sid1-13", "", deletePolicy, nil, &defaultSize, true, &nonFractionalTime),
|
|
expectedContents: newContentArrayWithDeletionTimestamp("content1-13", "sid1-13", "snap1-13", "", emptySecretClass, "sid1-13", "", deletePolicy, nil, nil, false, &nonFractionalTime),
|
|
expectedEvents: noevents,
|
|
expectSuccess: true,
|
|
errors: noerrors,
|
|
expectedDeleteCalls: []deleteCall{{"sid1-13", nil, nil}},
|
|
test: testSyncContent,
|
|
},
|
|
{
|
|
name: "1-14 - (pre-provision)deletion of content with deletion policy and no snapshotclass should trigger CSI call, update status, and remove bound finalizer removed.",
|
|
initialContents: newContentArrayWithDeletionTimestamp("content1-14", "sid1-14", "snap1-14", "sid1-14", "", "sid1-14", "", deletePolicy, nil, &defaultSize, true, &nonFractionalTime),
|
|
expectedContents: newContentArrayWithDeletionTimestamp("content1-14", "sid1-14", "snap1-14", "", "", "sid1-14", "", deletePolicy, nil, nil, false, &nonFractionalTime),
|
|
expectedEvents: noevents,
|
|
expectSuccess: true,
|
|
errors: noerrors,
|
|
expectedDeleteCalls: []deleteCall{{"sid1-14", nil, nil}},
|
|
test: testSyncContent,
|
|
},
|
|
{
|
|
name: "1-15 - (dynamic)deletion of content with no snapshotclass should succeed",
|
|
initialContents: newContentArrayWithDeletionTimestamp("content1-15", "sid1-15", "snap1-15", "sid1-15", "", "", "snap1-15-volumehandle", deletePolicy, nil, &defaultSize, true, &nonFractionalTime),
|
|
expectedContents: newContentArrayWithDeletionTimestamp("content1-15", "sid1-15", "snap1-15", "", "", "", "snap1-15-volumehandle", deletePolicy, nil, &defaultSize, false, &nonFractionalTime),
|
|
expectSuccess: true,
|
|
errors: noerrors,
|
|
expectedDeleteCalls: []deleteCall{{"sid1-15", nil, nil}},
|
|
test: testSyncContent,
|
|
},
|
|
}
|
|
runSyncContentTests(t, tests, snapshotClasses)
|
|
}
|