Merge remote-tracking branch 'origin/master' into prow-update-master

Manually resolved a merge conflict for vendor/modules.txt. This cannot
be done via a rebase because that would destroy the git submodule
history of `csi-release-tools`.
This commit is contained in:
Patrick Ohly
2019-11-11 21:25:56 +01:00
35 changed files with 4406 additions and 1866 deletions

View File

@@ -12,8 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
.PHONY: all csi-snapshotter clean test
.PHONY: all snapshot-controller csi-snapshotter clean test
CMDS=csi-snapshotter
CMDS=snapshot-controller csi-snapshotter
all: build
include release-tools/build.make

View File

@@ -1,6 +1,6 @@
FROM gcr.io/distroless/static:latest
LABEL maintainers="Kubernetes Authors"
LABEL description="CSI External Snapshotter"
LABEL description="CSI External Snapshotter Sidecar"
COPY ./bin/csi-snapshotter csi-snapshotter
ENTRYPOINT ["/csi-snapshotter"]

View File

@@ -37,7 +37,7 @@ import (
"github.com/kubernetes-csi/csi-lib-utils/connection"
"github.com/kubernetes-csi/csi-lib-utils/leaderelection"
csirpc "github.com/kubernetes-csi/csi-lib-utils/rpc"
"github.com/kubernetes-csi/external-snapshotter/pkg/controller"
controller "github.com/kubernetes-csi/external-snapshotter/pkg/sidecar-controller"
"github.com/kubernetes-csi/external-snapshotter/pkg/snapshotter"
clientset "github.com/kubernetes-csi/external-snapshotter/pkg/client/clientset/versioned"
@@ -56,17 +56,13 @@ const (
// Command line flags
var (
snapshotterName = flag.String("snapshotter", "", "This option is deprecated.")
kubeconfig = flag.String("kubeconfig", "", "Absolute path to the kubeconfig file. Required only when running out of cluster.")
connectionTimeout = flag.Duration("connection-timeout", 0, "The --connection-timeout flag is deprecated")
csiAddress = flag.String("csi-address", "/run/csi/socket", "Address of the CSI driver socket.")
createSnapshotContentRetryCount = flag.Int("create-snapshotcontent-retrycount", 5, "Number of retries when we create a snapshot content object for a snapshot.")
createSnapshotContentInterval = flag.Duration("create-snapshotcontent-interval", 10*time.Second, "Interval between retries when we create a snapshot content object for a snapshot.")
resyncPeriod = flag.Duration("resync-period", 60*time.Second, "Resync interval of the controller.")
snapshotNamePrefix = flag.String("snapshot-name-prefix", "snapshot", "Prefix to apply to the name of a created snapshot")
snapshotNameUUIDLength = flag.Int("snapshot-name-uuid-length", -1, "Length in characters for the generated uuid of a created snapshot. Defaults behavior is to NOT truncate.")
showVersion = flag.Bool("version", false, "Show version.")
csiTimeout = flag.Duration("timeout", defaultCSITimeout, "The timeout for any RPCs to the CSI driver. Default is 1 minute.")
kubeconfig = flag.String("kubeconfig", "", "Absolute path to the kubeconfig file. Required only when running out of cluster.")
csiAddress = flag.String("csi-address", "/run/csi/socket", "Address of the CSI driver socket.")
resyncPeriod = flag.Duration("resync-period", 60*time.Second, "Resync interval of the controller.")
snapshotNamePrefix = flag.String("snapshot-name-prefix", "snapshot", "Prefix to apply to the name of a created snapshot")
snapshotNameUUIDLength = flag.Int("snapshot-name-uuid-length", -1, "Length in characters for the generated uuid of a created snapshot. Defaults behavior is to NOT truncate.")
showVersion = flag.Bool("version", false, "Show version.")
csiTimeout = flag.Duration("timeout", defaultCSITimeout, "The timeout for any RPCs to the CSI driver. Default is 1 minute.")
leaderElection = flag.Bool("leader-election", false, "Enables leader election.")
leaderElectionNamespace = flag.String("leader-election-namespace", "", "The namespace where the leader election resource exists. Defaults to the pod namespace if not set.")
@@ -88,14 +84,6 @@ func main() {
}
klog.Infof("Version: %s", version)
if *connectionTimeout != 0 {
klog.Warning("--connection-timeout is deprecated and will have no effect")
}
if *snapshotterName != "" {
klog.Warning("--snapshotter is deprecated and will have no effect")
}
// Create the client config. Use kubeconfig if given, otherwise assume in-cluster.
config, err := buildConfig(*kubeconfig)
if err != nil {
@@ -133,13 +121,13 @@ func main() {
defer cancel()
// Find driver name
*snapshotterName, err = csirpc.GetDriverName(ctx, csiConn)
driverName, err := csirpc.GetDriverName(ctx, csiConn)
if err != nil {
klog.Errorf("error getting CSI driver name: %v", err)
os.Exit(1)
}
klog.V(2).Infof("CSI driver name: %q", *snapshotterName)
klog.V(2).Infof("CSI driver name: %q", driverName)
// Check it's ready
if err = csirpc.ProbeForever(csiConn, *csiTimeout); err != nil {
@@ -154,7 +142,7 @@ func main() {
os.Exit(1)
}
if !supportsCreateSnapshot {
klog.Errorf("CSI driver %s does not support ControllerCreateSnapshot", *snapshotterName)
klog.Errorf("CSI driver %s does not support ControllerCreateSnapshot", driverName)
os.Exit(1)
}
@@ -163,19 +151,15 @@ func main() {
os.Exit(1)
}
klog.V(2).Infof("Start NewCSISnapshotController with snapshotter [%s] kubeconfig [%s] csiTimeout [%+v] csiAddress [%s] createSnapshotContentRetryCount [%d] createSnapshotContentInterval [%+v] resyncPeriod [%+v] snapshotNamePrefix [%s] snapshotNameUUIDLength [%d]", *snapshotterName, *kubeconfig, *csiTimeout, *csiAddress, createSnapshotContentRetryCount, *createSnapshotContentInterval, *resyncPeriod, *snapshotNamePrefix, snapshotNameUUIDLength)
klog.V(2).Infof("Start NewCSISnapshotSideCarController with snapshotter [%s] kubeconfig [%s] csiTimeout [%+v] csiAddress [%s] resyncPeriod [%+v] snapshotNamePrefix [%s] snapshotNameUUIDLength [%d]", driverName, *kubeconfig, *csiTimeout, *csiAddress, *resyncPeriod, *snapshotNamePrefix, snapshotNameUUIDLength)
snapShotter := snapshotter.NewSnapshotter(csiConn)
ctrl := controller.NewCSISnapshotController(
ctrl := controller.NewCSISnapshotSideCarController(
snapClient,
kubeClient,
*snapshotterName,
factory.Snapshot().V1beta1().VolumeSnapshots(),
driverName,
factory.Snapshot().V1beta1().VolumeSnapshotContents(),
factory.Snapshot().V1beta1().VolumeSnapshotClasses(),
coreFactory.Core().V1().PersistentVolumeClaims(),
*createSnapshotContentRetryCount,
*createSnapshotContentInterval,
snapShotter,
*csiTimeout,
*resyncPeriod,
@@ -200,7 +184,7 @@ func main() {
if !*leaderElection {
run(context.TODO())
} else {
lockName := fmt.Sprintf("%s-%s", prefix, strings.Replace(*snapshotterName, "/", "-", -1))
lockName := fmt.Sprintf("%s-%s", prefix, strings.Replace(driverName, "/", "-", -1))
le := leaderelection.NewLeaderElection(kubeClient, lockName, run)
if *leaderElectionNamespace != "" {
le.WithNamespace(*leaderElectionNamespace)

View File

@@ -0,0 +1,6 @@
FROM gcr.io/distroless/static:latest
LABEL maintainers="Kubernetes Authors"
LABEL description="Snapshot Controller"
COPY ./bin/snapshot-controller snapshot-controller
ENTRYPOINT ["/snapshot-controller"]

View File

@@ -0,0 +1,146 @@
/*
Copyright 2018 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 main
import (
"context"
"flag"
"fmt"
"os"
"os/signal"
"time"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/klog"
"github.com/kubernetes-csi/csi-lib-utils/leaderelection"
controller "github.com/kubernetes-csi/external-snapshotter/pkg/common-controller"
clientset "github.com/kubernetes-csi/external-snapshotter/pkg/client/clientset/versioned"
snapshotscheme "github.com/kubernetes-csi/external-snapshotter/pkg/client/clientset/versioned/scheme"
informers "github.com/kubernetes-csi/external-snapshotter/pkg/client/informers/externalversions"
coreinformers "k8s.io/client-go/informers"
)
const (
// Number of worker threads
threads = 10
)
// Command line flags
var (
kubeconfig = flag.String("kubeconfig", "", "Absolute path to the kubeconfig file. Required only when running out of cluster.")
createSnapshotContentRetryCount = flag.Int("create-snapshotcontent-retrycount", 5, "Number of retries when we create a snapshot content object for a snapshot.")
createSnapshotContentInterval = flag.Duration("create-snapshotcontent-interval", 10*time.Second, "Interval between retries when we create a snapshot content object for a snapshot.")
resyncPeriod = flag.Duration("resync-period", 60*time.Second, "Resync interval of the controller.")
showVersion = flag.Bool("version", false, "Show version.")
leaderElection = flag.Bool("leader-election", false, "Enables leader election.")
leaderElectionNamespace = flag.String("leader-election-namespace", "", "The namespace where the leader election resource exists. Defaults to the pod namespace if not set.")
)
var (
version = "unknown"
)
func main() {
klog.InitFlags(nil)
flag.Set("logtostderr", "true")
flag.Parse()
if *showVersion {
fmt.Println(os.Args[0], version)
os.Exit(0)
}
klog.Infof("Version: %s", version)
// Create the client config. Use kubeconfig if given, otherwise assume in-cluster.
config, err := buildConfig(*kubeconfig)
if err != nil {
klog.Error(err.Error())
os.Exit(1)
}
kubeClient, err := kubernetes.NewForConfig(config)
if err != nil {
klog.Error(err.Error())
os.Exit(1)
}
snapClient, err := clientset.NewForConfig(config)
if err != nil {
klog.Errorf("Error building snapshot clientset: %s", err.Error())
os.Exit(1)
}
factory := informers.NewSharedInformerFactory(snapClient, *resyncPeriod)
coreFactory := coreinformers.NewSharedInformerFactory(kubeClient, *resyncPeriod)
// Add Snapshot types to the defualt Kubernetes so events can be logged for them
snapshotscheme.AddToScheme(scheme.Scheme)
klog.V(2).Infof("Start NewCSISnapshotController with kubeconfig [%s] createSnapshotContentRetryCount [%d] createSnapshotContentInterval [%d] resyncPeriod [%+v]", *kubeconfig, *createSnapshotContentRetryCount, *createSnapshotContentInterval, *resyncPeriod)
ctrl := controller.NewCSISnapshotCommonController(
snapClient,
kubeClient,
factory.Snapshot().V1beta1().VolumeSnapshots(),
factory.Snapshot().V1beta1().VolumeSnapshotContents(),
factory.Snapshot().V1beta1().VolumeSnapshotClasses(),
coreFactory.Core().V1().PersistentVolumeClaims(),
*createSnapshotContentRetryCount,
*createSnapshotContentInterval,
*resyncPeriod,
)
run := func(context.Context) {
// run...
stopCh := make(chan struct{})
factory.Start(stopCh)
coreFactory.Start(stopCh)
go ctrl.Run(threads, stopCh)
// ...until SIGINT
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
close(stopCh)
}
if !*leaderElection {
run(context.TODO())
} else {
lockName := "snapshot-controller-leader"
le := leaderelection.NewLeaderElection(kubeClient, lockName, run)
if *leaderElectionNamespace != "" {
le.WithNamespace(*leaderElectionNamespace)
}
if err := le.Run(); err != nil {
klog.Fatalf("failed to initialize leader election: %v", err)
}
}
}
func buildConfig(kubeconfig string) (*rest.Config, error) {
if kubeconfig != "" {
return clientcmd.BuildConfigFromFlags("", kubeconfig)
}
return rest.InClusterConfig()
}

View File

@@ -20,15 +20,6 @@ metadata:
# rename if there are conflicts
name: external-snapshotter-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["list", "watch", "create", "update", "patch"]
@@ -48,15 +39,6 @@ rules:
- apiGroups: ["snapshot.storage.k8s.io"]
resources: ["volumesnapshotcontents/status"]
verbs: ["update"]
- apiGroups: ["snapshot.storage.k8s.io"]
resources: ["volumesnapshots"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["snapshot.storage.k8s.io"]
resources: ["volumesnapshots/status"]
verbs: ["update"]
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["create", "list", "watch", "delete", "get", "update"]
---
kind: ClusterRoleBinding

View File

@@ -1,6 +1,6 @@
# This YAML file shows how to deploy the CSI snapshotter together
# with the hostpath CSI driver. It depends on the RBAC rules
# from rbac.yaml and rbac-external-provisioner.yaml.
# from rbac-csi-snapshotter.yaml and rbac-external-provisioner.yaml.
#
# Because external-snapshotter and external-provisioner get
# deployed in the same pod, we have to merge the permissions
@@ -72,11 +72,10 @@ spec:
serviceAccount: csi-snapshotter
containers:
- name: csi-provisioner
image: quay.io/k8scsi/csi-provisioner:v1.3.0
image: quay.io/k8scsi/csi-provisioner:v1.5.0-rc1
args:
- "--provisioner=csi-hostpath"
- "--v=5"
- "--csi-address=$(ADDRESS)"
- "--connection-timeout=15s"
env:
- name: ADDRESS
value: /csi/csi.sock
@@ -85,20 +84,21 @@ spec:
- name: socket-dir
mountPath: /csi
- name: csi-snapshotter
image: quay.io/k8scsi/csi-snapshotter:v1.2.0
# NOTE: replace with official image when released: quay.io/k8scsi/csi-snapshotter:v2.0.0
image: quay.io/k8scsi/csi-snapshotter:v2.0.0-rc2
args:
- "--v=5"
- "--csi-address=$(ADDRESS)"
- "--connection-timeout=15s"
- "--leader-election=false"
env:
- name: ADDRESS
value: /csi/csi.sock
imagePullPolicy: Always
imagePullPolicy: IfNotPresent #Always
volumeMounts:
- name: socket-dir
mountPath: /csi
- name: hostpath
image: quay.io/k8scsi/hostpathplugin:v1.1.0
image: quay.io/k8scsi/hostpathplugin:v1.2.0
args:
- "--v=5"
- "--endpoint=$(CSI_ENDPOINT)"

View File

@@ -0,0 +1,80 @@
# RBAC file for the snapshot controller.
apiVersion: v1
kind: ServiceAccount
metadata:
name: snapshot-controller
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
# rename if there are conflicts
name: snapshot-controller-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["list", "watch", "create", "update", "patch"]
- apiGroups: ["snapshot.storage.k8s.io"]
resources: ["volumesnapshotclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: ["snapshot.storage.k8s.io"]
resources: ["volumesnapshotcontents"]
verbs: ["create", "get", "list", "watch", "update", "delete"]
- apiGroups: ["snapshot.storage.k8s.io"]
resources: ["volumesnapshots"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["snapshot.storage.k8s.io"]
resources: ["volumesnapshots/status"]
verbs: ["update"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: snapshot-controller-role
subjects:
- kind: ServiceAccount
name: snapshot-controller
# replace with non-default namespace name
namespace: default
roleRef:
kind: ClusterRole
# change the name also here if the ClusterRole gets renamed
name: snapshot-controller-runner
apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: default # TODO: replace with the namespace you want for your sidecar
name: snapshot-controller-leaderelection
rules:
- apiGroups: ["coordination.k8s.io"]
resources: ["leases"]
verbs: ["get", "watch", "list", "delete", "update", "create"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: snapshot-controller-leaderelection
namespace: default # TODO: replace with the namespace you want for your sidecar
subjects:
- kind: ServiceAccount
name: snapshot-controller
namespace: default # TODO: replace with the namespace you want for your sidecar
roleRef:
kind: Role
name: snapshot-controller-leaderelection
apiGroup: rbac.authorization.k8s.io

View File

@@ -0,0 +1,27 @@
# This YAML file shows how to deploy the snapshot controller
---
kind: StatefulSet
apiVersion: apps/v1
metadata:
name: snapshot-controller
spec:
serviceName: "snapshot-controller"
replicas: 1
selector:
matchLabels:
app: snapshot-controller
template:
metadata:
labels:
app: snapshot-controller
spec:
serviceAccount: snapshot-controller
containers:
- name: snapshot-controller
# NOTE: replace with official image when released: quay.io/k8scsi/snapshot-controller:v2.0.0
image: quay.io/k8scsi/snapshot-controller:v2.0.0-rc2
args:
- "--v=5"
- "--leader-election=false"
imagePullPolicy: IfNotPresent #Always

View File

@@ -1,9 +1,8 @@
apiVersion: snapshot.storage.k8s.io/v1alpha1
apiVersion: snapshot.storage.k8s.io/v1beta1
kind: VolumeSnapshot
metadata:
name: new-snapshot-demo
spec:
snapshotClassName: csi-hostpath-snapclass
volumeSnapshotClassName: csi-hostpath-snapclass
source:
name: hpvc
kind: PersistentVolumeClaim
persistentVolumeClaimName: hpvc

View File

@@ -1,5 +1,6 @@
apiVersion: snapshot.storage.k8s.io/v1alpha1
apiVersion: snapshot.storage.k8s.io/v1beta1
kind: VolumeSnapshotClass
metadata:
name: csi-hostpath-snapclass
snapshotter: csi-hostpath
driver: hostpath.csi.k8s.io #csi-hostpath
deletionPolicy: Delete

View File

@@ -2,6 +2,6 @@ apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: csi-hostpath-sc
provisioner: csi-hostpath
provisioner: hostpath.csi.k8s.io #csi-hostpath
reclaimPolicy: Delete
volumeBindingMode: Immediate

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
package common_controller
import (
"context"
@@ -35,6 +35,7 @@ import (
snapshotscheme "github.com/kubernetes-csi/external-snapshotter/pkg/client/clientset/versioned/scheme"
informers "github.com/kubernetes-csi/external-snapshotter/pkg/client/informers/externalversions"
storagelisters "github.com/kubernetes-csi/external-snapshotter/pkg/client/listers/volumesnapshot/v1beta1"
"github.com/kubernetes-csi/external-snapshotter/pkg/utils"
v1 "k8s.io/api/core/v1"
storagev1 "k8s.io/api/storage/v1"
"k8s.io/apimachinery/pkg/api/resource"
@@ -115,7 +116,7 @@ type controllerTest struct {
expectSuccess bool
}
type testCall func(ctrl *csiSnapshotController, reactor *snapshotReactor, test controllerTest) error
type testCall func(ctrl *csiSnapshotCommonController, reactor *snapshotReactor, test controllerTest) error
const testNamespace = "default"
const mockDriverName = "csi-mock-plugin"
@@ -152,7 +153,7 @@ type snapshotReactor struct {
snapshots map[string]*crdv1.VolumeSnapshot
changedObjects []interface{}
changedSinceLastSync int
ctrl *csiSnapshotController
ctrl *csiSnapshotCommonController
fakeContentWatch *watch.FakeWatcher
fakeSnapshotWatch *watch.FakeWatcher
lock sync.Mutex
@@ -170,17 +171,19 @@ type reactorError struct {
}
func withSnapshotFinalizer(snapshot *crdv1.VolumeSnapshot) *crdv1.VolumeSnapshot {
snapshot.ObjectMeta.Finalizers = append(snapshot.ObjectMeta.Finalizers, VolumeSnapshotFinalizer)
snapshot.ObjectMeta.Finalizers = append(snapshot.ObjectMeta.Finalizers, utils.VolumeSnapshotAsSourceFinalizer)
snapshot.ObjectMeta.Finalizers = append(snapshot.ObjectMeta.Finalizers, utils.VolumeSnapshotBoundFinalizer)
return snapshot
}
func withContentFinalizer(content *crdv1.VolumeSnapshotContent) *crdv1.VolumeSnapshotContent {
content.ObjectMeta.Finalizers = append(content.ObjectMeta.Finalizers, VolumeSnapshotContentFinalizer)
content.ObjectMeta.Finalizers = append(content.ObjectMeta.Finalizers, utils.VolumeSnapshotContentFinalizer)
metav1.SetMetaDataAnnotation(&content.ObjectMeta, utils.AnnVolumeSnapshotBeingDeleted, "yes")
return content
}
func withPVCFinalizer(pvc *v1.PersistentVolumeClaim) *v1.PersistentVolumeClaim {
pvc.ObjectMeta.Finalizers = append(pvc.ObjectMeta.Finalizers, PVCFinalizer)
pvc.ObjectMeta.Finalizers = append(pvc.ObjectMeta.Finalizers, utils.PVCFinalizer)
return pvc
}
@@ -423,7 +426,9 @@ func (r *snapshotReactor) checkContents(expectedContents []*crdv1.VolumeSnapshot
v := v.DeepCopy()
v.ResourceVersion = ""
v.Spec.VolumeSnapshotRef.ResourceVersion = ""
v.Status.CreationTime = nil
if v.Status != nil {
v.Status.CreationTime = nil
}
expectedMap[v.Name] = v
}
for _, v := range r.contents {
@@ -432,7 +437,9 @@ func (r *snapshotReactor) checkContents(expectedContents []*crdv1.VolumeSnapshot
v := v.DeepCopy()
v.ResourceVersion = ""
v.Spec.VolumeSnapshotRef.ResourceVersion = ""
v.Status.CreationTime = nil
if v.Status != nil {
v.Status.CreationTime = nil
}
gotMap[v.Name] = v
}
if !reflect.DeepEqual(expectedMap, gotMap) {
@@ -480,7 +487,7 @@ func (r *snapshotReactor) checkSnapshots(expectedSnapshots []*crdv1.VolumeSnapsh
// checkEvents compares all expectedEvents with events generated during the test
// and reports differences.
func checkEvents(t *testing.T, expectedEvents []string, ctrl *csiSnapshotController) error {
func checkEvents(t *testing.T, expectedEvents []string, ctrl *csiSnapshotCommonController) error {
var err error
// Read recorded events - wait up to 1 minute to get all the expected ones
@@ -699,7 +706,7 @@ func (r *snapshotReactor) addSnapshotEvent(snapshot *crdv1.VolumeSnapshot) {
}
}
func newSnapshotReactor(kubeClient *kubefake.Clientset, client *fake.Clientset, ctrl *csiSnapshotController, fakeVolumeWatch, fakeClaimWatch *watch.FakeWatcher, errors []reactorError) *snapshotReactor {
func newSnapshotReactor(kubeClient *kubefake.Clientset, client *fake.Clientset, ctrl *csiSnapshotCommonController, fakeVolumeWatch, fakeClaimWatch *watch.FakeWatcher, errors []reactorError) *snapshotReactor {
reactor := &snapshotReactor{
secrets: make(map[string]*v1.Secret),
storageClasses: make(map[string]*storagev1.StorageClass),
@@ -732,36 +739,31 @@ func newSnapshotReactor(kubeClient *kubefake.Clientset, client *fake.Clientset,
func alwaysReady() bool { return true }
func newTestController(kubeClient kubernetes.Interface, clientset clientset.Interface,
informerFactory informers.SharedInformerFactory, t *testing.T, test controllerTest) (*csiSnapshotController, error) {
informerFactory informers.SharedInformerFactory, t *testing.T, test controllerTest) (*csiSnapshotCommonController, error) {
if informerFactory == nil {
informerFactory = informers.NewSharedInformerFactory(clientset, NoResyncPeriodFunc())
informerFactory = informers.NewSharedInformerFactory(clientset, utils.NoResyncPeriodFunc())
}
coreFactory := coreinformers.NewSharedInformerFactory(kubeClient, NoResyncPeriodFunc())
coreFactory := coreinformers.NewSharedInformerFactory(kubeClient, utils.NoResyncPeriodFunc())
// Construct controller
fakeSnapshot := &fakeSnapshotter{
t: t,
listCalls: test.expectedListCalls,
createCalls: test.expectedCreateCalls,
deleteCalls: test.expectedDeleteCalls,
}
//fakeSnapshot := &fakeSnapshotter{
// t: t,
// listCalls: test.expectedListCalls,
// createCalls: test.expectedCreateCalls,
// deleteCalls: test.expectedDeleteCalls,
//}
ctrl := NewCSISnapshotController(
ctrl := NewCSISnapshotCommonController(
clientset,
kubeClient,
mockDriverName,
informerFactory.Snapshot().V1beta1().VolumeSnapshots(),
informerFactory.Snapshot().V1beta1().VolumeSnapshotContents(),
informerFactory.Snapshot().V1beta1().VolumeSnapshotClasses(),
coreFactory.Core().V1().PersistentVolumeClaims(),
3,
5*time.Millisecond,
fakeSnapshot,
5*time.Millisecond,
60*time.Second,
"snapshot",
-1,
)
ctrl.eventRecorder = record.NewFakeRecorder(1000)
@@ -776,7 +778,8 @@ func newTestController(kubeClient kubernetes.Interface, clientset clientset.Inte
func newContent(contentName, boundToSnapshotUID, boundToSnapshotName, snapshotHandle, snapshotClassName, desiredSnapshotHandle, volumeHandle string,
deletionPolicy crdv1.DeletionPolicy, creationTime, size *int64,
withFinalizer bool) *crdv1.VolumeSnapshotContent {
withFinalizer bool, withStatus bool) *crdv1.VolumeSnapshotContent {
ready := true
content := crdv1.VolumeSnapshotContent{
ObjectMeta: metav1.ObjectMeta{
Name: contentName,
@@ -786,13 +789,17 @@ func newContent(contentName, boundToSnapshotUID, boundToSnapshotName, snapshotHa
Driver: mockDriverName,
DeletionPolicy: deletionPolicy,
},
Status: &crdv1.VolumeSnapshotContentStatus{
CreationTime: creationTime,
RestoreSize: size,
},
}
if snapshotHandle != "" {
if withStatus {
content.Status = &crdv1.VolumeSnapshotContentStatus{
CreationTime: creationTime,
RestoreSize: size,
ReadyToUse: &ready,
}
}
if withStatus && snapshotHandle != "" {
content.Status.SnapshotHandle = &snapshotHandle
}
@@ -830,14 +837,22 @@ func newContentArray(contentName, boundToSnapshotUID, boundToSnapshotName, snaps
deletionPolicy crdv1.DeletionPolicy, size, creationTime *int64,
withFinalizer bool) []*crdv1.VolumeSnapshotContent {
return []*crdv1.VolumeSnapshotContent{
newContent(contentName, boundToSnapshotUID, boundToSnapshotName, snapshotHandle, snapshotClassName, desiredSnapshotHandle, volumeHandle, deletionPolicy, creationTime, size, withFinalizer),
newContent(contentName, boundToSnapshotUID, boundToSnapshotName, snapshotHandle, snapshotClassName, desiredSnapshotHandle, volumeHandle, deletionPolicy, creationTime, size, withFinalizer, true),
}
}
func newContentArrayNoStatus(contentName, boundToSnapshotUID, boundToSnapshotName, snapshotHandle, snapshotClassName, desiredSnapshotHandle, volumeHandle string,
deletionPolicy crdv1.DeletionPolicy, size, creationTime *int64,
withFinalizer bool, withStatus bool) []*crdv1.VolumeSnapshotContent {
return []*crdv1.VolumeSnapshotContent{
newContent(contentName, boundToSnapshotUID, boundToSnapshotName, snapshotHandle, snapshotClassName, desiredSnapshotHandle, volumeHandle, deletionPolicy, creationTime, size, withFinalizer, withStatus),
}
}
func newContentArrayWithReadyToUse(contentName, boundToSnapshotUID, boundToSnapshotName, snapshotHandle, snapshotClassName, desiredSnapshotHandle, volumeHandle string,
deletionPolicy crdv1.DeletionPolicy, creationTime, size *int64, readyToUse *bool,
withFinalizer bool) []*crdv1.VolumeSnapshotContent {
content := newContent(contentName, boundToSnapshotUID, boundToSnapshotName, snapshotHandle, snapshotClassName, desiredSnapshotHandle, volumeHandle, deletionPolicy, creationTime, size, withFinalizer)
content := newContent(contentName, boundToSnapshotUID, boundToSnapshotName, snapshotHandle, snapshotClassName, desiredSnapshotHandle, volumeHandle, deletionPolicy, creationTime, size, withFinalizer, true)
content.Status.ReadyToUse = readyToUse
return []*crdv1.VolumeSnapshotContent{
content,
@@ -847,7 +862,7 @@ func newContentArrayWithReadyToUse(contentName, boundToSnapshotUID, boundToSnaps
func newContentWithUnmatchDriverArray(contentName, boundToSnapshotUID, boundToSnapshotName, snapshotHandle, snapshotClassName, desiredSnapshotHandle, volumeHandle string,
deletionPolicy crdv1.DeletionPolicy, size, creationTime *int64,
withFinalizer bool) []*crdv1.VolumeSnapshotContent {
content := newContent(contentName, boundToSnapshotUID, boundToSnapshotName, snapshotHandle, snapshotClassName, desiredSnapshotHandle, volumeHandle, deletionPolicy, size, creationTime, withFinalizer)
content := newContent(contentName, boundToSnapshotUID, boundToSnapshotName, snapshotHandle, snapshotClassName, desiredSnapshotHandle, volumeHandle, deletionPolicy, size, creationTime, withFinalizer, true)
content.Spec.Driver = "fake"
return []*crdv1.VolumeSnapshotContent{
content,
@@ -857,7 +872,7 @@ func newContentWithUnmatchDriverArray(contentName, boundToSnapshotUID, boundToSn
func newSnapshot(
snapshotName, snapshotUID, pvcName, targetContentName, snapshotClassName, boundContentName string,
readyToUse *bool, creationTime *metav1.Time, restoreSize *resource.Quantity,
err *crdv1.VolumeSnapshotError) *crdv1.VolumeSnapshot {
err *crdv1.VolumeSnapshotError, nilStatus bool) *crdv1.VolumeSnapshot {
snapshot := crdv1.VolumeSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: snapshotName,
@@ -869,12 +884,15 @@ func newSnapshot(
Spec: crdv1.VolumeSnapshotSpec{
VolumeSnapshotClassName: nil,
},
Status: &crdv1.VolumeSnapshotStatus{
}
if !nilStatus {
snapshot.Status = &crdv1.VolumeSnapshotStatus{
CreationTime: creationTime,
ReadyToUse: readyToUse,
Error: err,
RestoreSize: restoreSize,
},
}
}
if boundContentName != "" {
@@ -898,9 +916,9 @@ func newSnapshot(
func newSnapshotArray(
snapshotName, snapshotUID, pvcName, targetContentName, snapshotClassName, boundContentName string,
readyToUse *bool, creationTime *metav1.Time, restoreSize *resource.Quantity,
err *crdv1.VolumeSnapshotError) []*crdv1.VolumeSnapshot {
err *crdv1.VolumeSnapshotError, nilStatus bool) []*crdv1.VolumeSnapshot {
return []*crdv1.VolumeSnapshot{
newSnapshot(snapshotName, snapshotUID, pvcName, targetContentName, snapshotClassName, boundContentName, readyToUse, creationTime, restoreSize, err),
newSnapshot(snapshotName, snapshotUID, pvcName, targetContentName, snapshotClassName, boundContentName, readyToUse, creationTime, restoreSize, err, nilStatus),
}
}
@@ -1015,11 +1033,11 @@ func newVolumeError(message string) *crdv1.VolumeSnapshotError {
}
}
func testSyncSnapshot(ctrl *csiSnapshotController, reactor *snapshotReactor, test controllerTest) error {
func testSyncSnapshot(ctrl *csiSnapshotCommonController, reactor *snapshotReactor, test controllerTest) error {
return ctrl.syncSnapshot(test.initialSnapshots[0])
}
func testSyncSnapshotError(ctrl *csiSnapshotController, reactor *snapshotReactor, test controllerTest) error {
func testSyncSnapshotError(ctrl *csiSnapshotCommonController, reactor *snapshotReactor, test controllerTest) error {
err := ctrl.syncSnapshot(test.initialSnapshots[0])
if err != nil {
@@ -1028,16 +1046,16 @@ func testSyncSnapshotError(ctrl *csiSnapshotController, reactor *snapshotReactor
return fmt.Errorf("syncSnapshot succeeded when failure was expected")
}
func testSyncContent(ctrl *csiSnapshotController, reactor *snapshotReactor, test controllerTest) error {
func testSyncContent(ctrl *csiSnapshotCommonController, reactor *snapshotReactor, test controllerTest) error {
return ctrl.syncContent(test.initialContents[0])
}
func testAddPVCFinalizer(ctrl *csiSnapshotController, reactor *snapshotReactor, test controllerTest) error {
return ctrl.ensureSnapshotSourceFinalizer(test.initialSnapshots[0])
func testAddPVCFinalizer(ctrl *csiSnapshotCommonController, reactor *snapshotReactor, test controllerTest) error {
return ctrl.ensurePVCFinalizer(test.initialSnapshots[0])
}
func testRemovePVCFinalizer(ctrl *csiSnapshotController, reactor *snapshotReactor, test controllerTest) error {
return ctrl.checkandRemoveSnapshotSourceFinalizer(test.initialSnapshots[0])
func testRemovePVCFinalizer(ctrl *csiSnapshotCommonController, reactor *snapshotReactor, test controllerTest) error {
return ctrl.checkandRemovePVCFinalizer(test.initialSnapshots[0])
}
var (
@@ -1062,9 +1080,9 @@ var (
// injected function to simulate that something is happening when the
// controller waits for the operation lock. Controller is then resumed and we
// check how it behaves.
func wrapTestWithInjectedOperation(toWrap testCall, injectBeforeOperation func(ctrl *csiSnapshotController, reactor *snapshotReactor)) testCall {
func wrapTestWithInjectedOperation(toWrap testCall, injectBeforeOperation func(ctrl *csiSnapshotCommonController, reactor *snapshotReactor)) testCall {
return func(ctrl *csiSnapshotController, reactor *snapshotReactor, test controllerTest) error {
return func(ctrl *csiSnapshotCommonController, reactor *snapshotReactor, test controllerTest) error {
// Inject a hook before async operation starts
klog.V(4).Infof("reactor:injecting call")
injectBeforeOperation(ctrl, reactor)
@@ -1089,7 +1107,7 @@ func wrapTestWithInjectedOperation(toWrap testCall, injectBeforeOperation func(c
}
}
func evaluateTestResults(ctrl *csiSnapshotController, reactor *snapshotReactor, test controllerTest, t *testing.T) {
func evaluateTestResults(ctrl *csiSnapshotCommonController, reactor *snapshotReactor, test controllerTest, t *testing.T) {
// Evaluate results
if err := reactor.checkSnapshots(test.expectedSnapshots); err != nil {
t.Errorf("Test %q: %v", test.name, err)
@@ -1130,10 +1148,8 @@ func runSyncTests(t *testing.T, tests []controllerTest, snapshotClasses []*crdv1
reactor.snapshots[snapshot.Name] = snapshot
}
for _, content := range test.initialContents {
if ctrl.isDriverMatch(test.initialContents[0]) {
ctrl.contentStore.Add(content)
reactor.contents[content.Name] = content
}
ctrl.contentStore.Add(content)
reactor.contents[content.Name] = content
}
pvcIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
@@ -1162,7 +1178,7 @@ func runSyncTests(t *testing.T, tests []controllerTest, snapshotClasses []*crdv1
// Run the tested functions
err = test.test(ctrl, reactor, test)
if err != nil {
if test.expectSuccess && err != nil {
t.Errorf("Test %q failed: %v", test.name, err)
}
@@ -1176,7 +1192,7 @@ func runSyncTests(t *testing.T, tests []controllerTest, snapshotClasses []*crdv1
}
}
// This tests ensureSnapshotSourceFinalizer and checkandRemoveSnapshotSourceFinalizer
// This tests ensurePVCFinalizer and checkandRemovePVCFinalizer
func runPVCFinalizerTests(t *testing.T, tests []controllerTest, snapshotClasses []*crdv1.VolumeSnapshotClass) {
snapshotscheme.AddToScheme(scheme.Scheme)
for _, test := range tests {
@@ -1197,10 +1213,8 @@ func runPVCFinalizerTests(t *testing.T, tests []controllerTest, snapshotClasses
reactor.snapshots[snapshot.Name] = snapshot
}
for _, content := range test.initialContents {
if ctrl.isDriverMatch(test.initialContents[0]) {
ctrl.contentStore.Add(content)
reactor.contents[content.Name] = content
}
ctrl.contentStore.Add(content)
reactor.contents[content.Name] = content
}
pvcIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
@@ -1239,7 +1253,7 @@ func runPVCFinalizerTests(t *testing.T, tests []controllerTest, snapshotClasses
}
// Evaluate PVCFinalizer tests results
func evaluatePVCFinalizerTests(ctrl *csiSnapshotController, reactor *snapshotReactor, test controllerTest, t *testing.T) {
func evaluatePVCFinalizerTests(ctrl *csiSnapshotCommonController, reactor *snapshotReactor, test controllerTest, t *testing.T) {
// Evaluate results
bHasPVCFinalizer := false
name := sysruntime.FuncForPC(reflect.ValueOf(test.test).Pointer()).Name()
@@ -1255,7 +1269,7 @@ func evaluatePVCFinalizerTests(ctrl *csiSnapshotController, reactor *snapshotRea
if funcName == "testAddPVCFinalizer" {
for _, pvc := range reactor.claims {
if test.initialClaims[0].Name == pvc.Name {
if !slice.ContainsString(test.initialClaims[0].ObjectMeta.Finalizers, PVCFinalizer, nil) && slice.ContainsString(pvc.ObjectMeta.Finalizers, PVCFinalizer, nil) {
if !slice.ContainsString(test.initialClaims[0].ObjectMeta.Finalizers, utils.PVCFinalizer, nil) && slice.ContainsString(pvc.ObjectMeta.Finalizers, utils.PVCFinalizer, nil) {
klog.V(4).Infof("test %q succeeded. PVCFinalizer is added to PVC %s", test.name, pvc.Name)
bHasPVCFinalizer = true
}
@@ -1270,7 +1284,7 @@ func evaluatePVCFinalizerTests(ctrl *csiSnapshotController, reactor *snapshotRea
if funcName == "testRemovePVCFinalizer" {
for _, pvc := range reactor.claims {
if test.initialClaims[0].Name == pvc.Name {
if slice.ContainsString(test.initialClaims[0].ObjectMeta.Finalizers, PVCFinalizer, nil) && !slice.ContainsString(pvc.ObjectMeta.Finalizers, PVCFinalizer, nil) {
if slice.ContainsString(test.initialClaims[0].ObjectMeta.Finalizers, utils.PVCFinalizer, nil) && !slice.ContainsString(pvc.ObjectMeta.Finalizers, utils.PVCFinalizer, nil) {
klog.V(4).Infof("test %q succeeded. PVCFinalizer is removed from PVC %s", test.name, pvc.Name)
bHasPVCFinalizer = false
}
@@ -1310,23 +1324,23 @@ func secret() *v1.Secret {
func secretAnnotations() map[string]string {
return map[string]string{
AnnDeletionSecretRefName: "secret",
AnnDeletionSecretRefNamespace: "default",
utils.AnnDeletionSecretRefName: "secret",
utils.AnnDeletionSecretRefNamespace: "default",
}
}
func emptyNamespaceSecretAnnotations() map[string]string {
return map[string]string{
AnnDeletionSecretRefName: "name",
AnnDeletionSecretRefNamespace: "",
utils.AnnDeletionSecretRefName: "name",
utils.AnnDeletionSecretRefNamespace: "",
}
}
// this refers to emptySecret(), which is missing data.
func emptyDataSecretAnnotations() map[string]string {
return map[string]string{
AnnDeletionSecretRefName: "emptysecret",
AnnDeletionSecretRefNamespace: "default",
utils.AnnDeletionSecretRefName: "emptysecret",
utils.AnnDeletionSecretRefNamespace: "default",
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
/*
Copyright 2018 The Kubernetes Authors.
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.
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
package common_controller
import (
"fmt"
@@ -24,9 +24,9 @@ import (
clientset "github.com/kubernetes-csi/external-snapshotter/pkg/client/clientset/versioned"
storageinformers "github.com/kubernetes-csi/external-snapshotter/pkg/client/informers/externalversions/volumesnapshot/v1beta1"
storagelisters "github.com/kubernetes-csi/external-snapshotter/pkg/client/listers/volumesnapshot/v1beta1"
"github.com/kubernetes-csi/external-snapshotter/pkg/snapshotter"
"github.com/kubernetes-csi/external-snapshotter/pkg/utils"
v1 "k8s.io/api/core/v1"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/wait"
@@ -42,10 +42,9 @@ import (
"k8s.io/kubernetes/pkg/util/goroutinemap"
)
type csiSnapshotController struct {
type csiSnapshotCommonController struct {
clientset clientset.Interface
client kubernetes.Interface
driverName string
eventRecorder record.EventRecorder
snapshotQueue workqueue.RateLimitingInterface
contentQueue workqueue.RateLimitingInterface
@@ -62,7 +61,6 @@ type csiSnapshotController struct {
snapshotStore cache.Store
contentStore cache.Store
handler Handler
// Map of scheduled/running operations.
runningOperations goroutinemap.GoRoutineMap
@@ -71,43 +69,36 @@ type csiSnapshotController struct {
resyncPeriod time.Duration
}
// NewCSISnapshotController returns a new *csiSnapshotController
func NewCSISnapshotController(
// NewCSISnapshotController returns a new *csiSnapshotCommonController
func NewCSISnapshotCommonController(
clientset clientset.Interface,
client kubernetes.Interface,
driverName string,
volumeSnapshotInformer storageinformers.VolumeSnapshotInformer,
volumeSnapshotContentInformer storageinformers.VolumeSnapshotContentInformer,
volumeSnapshotClassInformer storageinformers.VolumeSnapshotClassInformer,
pvcInformer coreinformers.PersistentVolumeClaimInformer,
createSnapshotContentRetryCount int,
createSnapshotContentInterval time.Duration,
snapshotter snapshotter.Snapshotter,
timeout time.Duration,
resyncPeriod time.Duration,
snapshotNamePrefix string,
snapshotNameUUIDLength int,
) *csiSnapshotController {
) *csiSnapshotCommonController {
broadcaster := record.NewBroadcaster()
broadcaster.StartLogging(klog.Infof)
broadcaster.StartRecordingToSink(&corev1.EventSinkImpl{Interface: client.CoreV1().Events(v1.NamespaceAll)})
var eventRecorder record.EventRecorder
eventRecorder = broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: fmt.Sprintf("csi-snapshotter %s", driverName)})
eventRecorder = broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: fmt.Sprintf("snapshot-controller")})
ctrl := &csiSnapshotController{
ctrl := &csiSnapshotCommonController{
clientset: clientset,
client: client,
driverName: driverName,
eventRecorder: eventRecorder,
handler: NewCSIHandler(snapshotter, timeout, snapshotNamePrefix, snapshotNameUUIDLength),
runningOperations: goroutinemap.NewGoRoutineMap(true),
createSnapshotContentRetryCount: createSnapshotContentRetryCount,
createSnapshotContentInterval: createSnapshotContentInterval,
resyncPeriod: resyncPeriod,
snapshotStore: cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc),
contentStore: cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc),
snapshotQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "csi-snapshotter-snapshot"),
contentQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "csi-snapshotter-content"),
snapshotQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "snapshot-controller-snapshot"),
contentQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "snapshot-controller-content"),
}
ctrl.pvcLister = pvcInformer.Lister()
@@ -141,12 +132,12 @@ func NewCSISnapshotController(
return ctrl
}
func (ctrl *csiSnapshotController) Run(workers int, stopCh <-chan struct{}) {
func (ctrl *csiSnapshotCommonController) Run(workers int, stopCh <-chan struct{}) {
defer ctrl.snapshotQueue.ShutDown()
defer ctrl.contentQueue.ShutDown()
klog.Infof("Starting CSI snapshotter")
defer klog.Infof("Shutting CSI snapshotter")
klog.Infof("Starting snapshot controller")
defer klog.Infof("Shutting snapshot controller")
if !cache.WaitForCacheSync(stopCh, ctrl.snapshotListerSynced, ctrl.contentListerSynced, ctrl.classListerSynced, ctrl.pvcListerSynced) {
klog.Errorf("Cannot sync caches")
@@ -164,7 +155,7 @@ func (ctrl *csiSnapshotController) Run(workers int, stopCh <-chan struct{}) {
}
// enqueueSnapshotWork adds snapshot to given work queue.
func (ctrl *csiSnapshotController) enqueueSnapshotWork(obj interface{}) {
func (ctrl *csiSnapshotCommonController) enqueueSnapshotWork(obj interface{}) {
// Beware of "xxx deleted" events
if unknown, ok := obj.(cache.DeletedFinalStateUnknown); ok && unknown.Obj != nil {
obj = unknown.Obj
@@ -181,7 +172,7 @@ func (ctrl *csiSnapshotController) enqueueSnapshotWork(obj interface{}) {
}
// enqueueContentWork adds snapshot content to given work queue.
func (ctrl *csiSnapshotController) enqueueContentWork(obj interface{}) {
func (ctrl *csiSnapshotCommonController) enqueueContentWork(obj interface{}) {
// Beware of "xxx deleted" events
if unknown, ok := obj.(cache.DeletedFinalStateUnknown); ok && unknown.Obj != nil {
obj = unknown.Obj
@@ -199,7 +190,7 @@ func (ctrl *csiSnapshotController) enqueueContentWork(obj interface{}) {
// snapshotWorker processes items from snapshotQueue. It must run only once,
// syncSnapshot is not assured to be reentrant.
func (ctrl *csiSnapshotController) snapshotWorker() {
func (ctrl *csiSnapshotCommonController) snapshotWorker() {
workFunc := func() bool {
keyObj, quit := ctrl.snapshotQueue.Get()
if quit {
@@ -264,7 +255,7 @@ func (ctrl *csiSnapshotController) snapshotWorker() {
// contentWorker processes items from contentQueue. It must run only once,
// syncContent is not assured to be reentrant.
func (ctrl *csiSnapshotController) contentWorker() {
func (ctrl *csiSnapshotCommonController) contentWorker() {
workFunc := func() bool {
keyObj, quit := ctrl.contentQueue.Get()
if quit {
@@ -283,9 +274,7 @@ func (ctrl *csiSnapshotController) contentWorker() {
// The content still exists in informer cache, the event must have
// been add/update/sync
if err == nil {
if ctrl.isDriverMatch(content) {
ctrl.updateContent(content)
}
ctrl.updateContent(content)
return false
}
if !errors.IsNotFound(err) {
@@ -323,22 +312,16 @@ func (ctrl *csiSnapshotController) contentWorker() {
}
}
// verify whether the driver specified in VolumeSnapshotContent matches the controller's driver name
func (ctrl *csiSnapshotController) isDriverMatch(content *crdv1.VolumeSnapshotContent) bool {
return content.Spec.Driver == ctrl.driverName
}
// checkAndUpdateSnapshotClass gets the VolumeSnapshotClass from VolumeSnapshot. If it is not set,
// gets it from default VolumeSnapshotClass and sets it. It also detects if snapshotter in the
// VolumeSnapshotClass is the same as the snapshotter in external controller.
func (ctrl *csiSnapshotController) checkAndUpdateSnapshotClass(snapshot *crdv1.VolumeSnapshot) (*crdv1.VolumeSnapshot, error) {
// gets it from default VolumeSnapshotClass and sets it.
func (ctrl *csiSnapshotCommonController) checkAndUpdateSnapshotClass(snapshot *crdv1.VolumeSnapshot) (*crdv1.VolumeSnapshot, error) {
className := snapshot.Spec.VolumeSnapshotClassName
var class *crdv1.VolumeSnapshotClass
var err error
newSnapshot := snapshot
if className != nil {
klog.V(5).Infof("checkAndUpdateSnapshotClass [%s]: VolumeSnapshotClassName [%s]", snapshot.Name, *className)
class, err = ctrl.GetSnapshotClass(*className)
class, err = ctrl.getSnapshotClass(*className)
if err != nil {
klog.Errorf("checkAndUpdateSnapshotClass failed to getSnapshotClass %v", err)
ctrl.updateSnapshotErrorStatusWithEvent(snapshot, v1.EventTypeWarning, "GetSnapshotClassFailed", fmt.Sprintf("Failed to get snapshot class with error %v", err))
@@ -354,20 +337,19 @@ func (ctrl *csiSnapshotController) checkAndUpdateSnapshotClass(snapshot *crdv1.V
}
}
klog.V(5).Infof("VolumeSnapshotClass Driver [%s] Snapshot Controller driverName [%s]", class.Driver, ctrl.driverName)
if class.Driver != ctrl.driverName {
klog.V(4).Infof("Skipping VolumeSnapshot %s for snapshotter [%s] in VolumeSnapshotClass because it does not match with the snapshotter for controller [%s]", snapshotKey(snapshot), class.Driver, ctrl.driverName)
return nil, fmt.Errorf("volumeSnapshotClass does not match with the snapshotter for controller")
// For pre-provisioned snapshots, we may not have snapshot class
if class != nil {
klog.V(5).Infof("VolumeSnapshotClass [%s] Driver [%s]", class.Name, class.Driver)
}
return newSnapshot, nil
}
// updateSnapshot runs in worker thread and handles "snapshot added",
// "snapshot updated" and "periodic sync" events.
func (ctrl *csiSnapshotController) updateSnapshot(snapshot *crdv1.VolumeSnapshot) {
func (ctrl *csiSnapshotCommonController) updateSnapshot(snapshot *crdv1.VolumeSnapshot) {
// Store the new snapshot version in the cache and do not process it if this is
// an old version.
klog.V(5).Infof("updateSnapshot %q", snapshotKey(snapshot))
klog.V(5).Infof("updateSnapshot %q", utils.SnapshotKey(snapshot))
newSnapshot, err := ctrl.storeSnapshotUpdate(snapshot)
if err != nil {
klog.Errorf("%v", err)
@@ -380,16 +362,16 @@ func (ctrl *csiSnapshotController) updateSnapshot(snapshot *crdv1.VolumeSnapshot
if errors.IsConflict(err) {
// Version conflict error happens quite often and the controller
// recovers from it easily.
klog.V(3).Infof("could not sync claim %q: %+v", snapshotKey(snapshot), err)
klog.V(3).Infof("could not sync claim %q: %+v", utils.SnapshotKey(snapshot), err)
} else {
klog.Errorf("could not sync volume %q: %+v", snapshotKey(snapshot), err)
klog.Errorf("could not sync volume %q: %+v", utils.SnapshotKey(snapshot), err)
}
}
}
// updateContent runs in worker thread and handles "content added",
// "content updated" and "periodic sync" events.
func (ctrl *csiSnapshotController) updateContent(content *crdv1.VolumeSnapshotContent) {
func (ctrl *csiSnapshotCommonController) updateContent(content *crdv1.VolumeSnapshotContent) {
// Store the new content version in the cache and do not process it if this is
// an old version.
new, err := ctrl.storeContentUpdate(content)
@@ -412,28 +394,31 @@ func (ctrl *csiSnapshotController) updateContent(content *crdv1.VolumeSnapshotCo
}
// deleteSnapshot runs in worker thread and handles "snapshot deleted" event.
func (ctrl *csiSnapshotController) deleteSnapshot(snapshot *crdv1.VolumeSnapshot) {
func (ctrl *csiSnapshotCommonController) deleteSnapshot(snapshot *crdv1.VolumeSnapshot) {
_ = ctrl.snapshotStore.Delete(snapshot)
klog.V(4).Infof("snapshot %q deleted", snapshotKey(snapshot))
klog.V(4).Infof("snapshot %q deleted", utils.SnapshotKey(snapshot))
if snapshot.Status.BoundVolumeSnapshotContentName == nil {
klog.V(5).Infof("deleteSnapshot[%q]: content not bound", snapshotKey(snapshot))
snapshotContentName := ""
if snapshot.Status != nil && snapshot.Status.BoundVolumeSnapshotContentName != nil {
snapshotContentName = *snapshot.Status.BoundVolumeSnapshotContentName
}
if snapshotContentName == "" {
klog.V(5).Infof("deleteSnapshot[%q]: content not bound", utils.SnapshotKey(snapshot))
return
}
snapshotContentName := *snapshot.Status.BoundVolumeSnapshotContentName
// sync the content when its snapshot is deleted. Explicitly sync'ing the
// content here in response to snapshot deletion prevents the content from
// waiting until the next sync period for its Release.
klog.V(5).Infof("deleteSnapshot[%q]: scheduling sync of content %s", snapshotKey(snapshot), snapshotContentName)
klog.V(5).Infof("deleteSnapshot[%q]: scheduling sync of content %s", utils.SnapshotKey(snapshot), snapshotContentName)
ctrl.contentQueue.Add(snapshotContentName)
}
// deleteContent runs in worker thread and handles "content deleted" event.
func (ctrl *csiSnapshotController) deleteContent(content *crdv1.VolumeSnapshotContent) {
func (ctrl *csiSnapshotCommonController) deleteContent(content *crdv1.VolumeSnapshotContent) {
_ = ctrl.contentStore.Delete(content)
klog.V(4).Infof("content %q deleted", content.Name)
snapshotName := snapshotRefKey(content.Spec.VolumeSnapshotRef)
snapshotName := utils.SnapshotRefKey(&content.Spec.VolumeSnapshotRef)
if snapshotName == "" {
klog.V(5).Infof("deleteContent[%q]: content not bound", content.Name)
return
@@ -448,7 +433,7 @@ func (ctrl *csiSnapshotController) deleteContent(content *crdv1.VolumeSnapshotCo
// initializeCaches fills all controller caches with initial data from etcd in
// order to have the caches already filled when first addSnapshot/addContent to
// perform initial synchronization of the controller.
func (ctrl *csiSnapshotController) initializeCaches(snapshotLister storagelisters.VolumeSnapshotLister, contentLister storagelisters.VolumeSnapshotContentLister) {
func (ctrl *csiSnapshotCommonController) initializeCaches(snapshotLister storagelisters.VolumeSnapshotLister, contentLister storagelisters.VolumeSnapshotContentLister) {
snapshotList, err := snapshotLister.List(labels.Everything())
if err != nil {
klog.Errorf("CSISnapshotController can't initialize caches: %v", err)

View File

@@ -14,21 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
package common_controller
import (
"testing"
crdv1 "github.com/kubernetes-csi/external-snapshotter/pkg/apis/volumesnapshot/v1beta1"
"github.com/kubernetes-csi/external-snapshotter/pkg/utils"
"k8s.io/client-go/tools/cache"
)
var deletionPolicy = crdv1.VolumeSnapshotContentDelete
func storeVersion(t *testing.T, prefix string, c cache.Store, version string, expectedReturn bool) {
content := newContent("contentName", "snapuid1-1", "snap1-1", "sid1-1", classGold, "", "pv-handle-1-1", deletionPolicy, nil, nil, false)
content := newContent("contentName", "snapuid1-1", "snap1-1", "sid1-1", classGold, "", "pv-handle-1-1", deletionPolicy, nil, nil, false, true)
content.ResourceVersion = version
ret, err := storeObjectUpdate(c, content, "content")
ret, err := utils.StoreObjectUpdate(c, content, "content")
if err != nil {
t.Errorf("%s: expected storeObjectUpdate to succeed, got: %v", prefix, err)
}
@@ -84,9 +85,9 @@ func TestControllerCacheParsingError(t *testing.T) {
c := cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc)
// There must be something in the cache to compare with
storeVersion(t, "Step1", c, "1", true)
content := newContent("contentName", "snapuid1-1", "snap1-1", "sid1-1", classGold, "", "pv-handle-1-1", deletionPolicy, nil, nil, false)
content := newContent("contentName", "snapuid1-1", "snap1-1", "sid1-1", classGold, "", "pv-handle-1-1", deletionPolicy, nil, nil, false, true)
content.ResourceVersion = "xxx"
_, err := storeObjectUpdate(c, content, "content")
_, err := utils.StoreObjectUpdate(c, content, "content")
if err == nil {
t.Errorf("Expected parsing error, got nil instead")
}

View File

@@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
package common_controller
import (
"errors"
//"errors"
"testing"
"time"
@@ -71,12 +71,12 @@ func TestCreateSnapshotSync(t *testing.T) {
{
name: "6-1 - successful create snapshot with snapshot class gold",
initialContents: nocontents,
expectedContents: newContentArrayWithReadyToUse("snapcontent-snapuid6-1", "snapuid6-1", "snap6-1", "sid6-1", classGold, "", "pv-handle6-1", deletionPolicy, &timeNowStamp, &defaultSize, &True, false),
initialSnapshots: newSnapshotArray("snap6-1", "snapuid6-1", "claim6-1", "", classGold, "", &False, nil, nil, nil),
expectedSnapshots: newSnapshotArray("snap6-1", "snapuid6-1", "claim6-1", "", classGold, "snapcontent-snapuid6-1", &True, metaTimeNowUnix, getSize(defaultSize), nil),
expectedContents: newContentArrayNoStatus("snapcontent-snapuid6-1", "snapuid6-1", "snap6-1", "sid6-1", classGold, "", "pv-handle6-1", deletionPolicy, nil, nil, false, false),
initialSnapshots: newSnapshotArray("snap6-1", "snapuid6-1", "claim6-1", "", classGold, "", &False, nil, nil, nil, false),
expectedSnapshots: newSnapshotArray("snap6-1", "snapuid6-1", "claim6-1", "", classGold, "snapcontent-snapuid6-1", &False, nil, nil, nil, false),
initialClaims: newClaimArray("claim6-1", "pvc-uid6-1", "1Gi", "volume6-1", v1.ClaimBound, &classEmpty),
initialVolumes: newVolumeArray("volume6-1", "pv-uid6-1", "pv-handle6-1", "1Gi", "pvc-uid6-1", "claim6-1", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
expectedCreateCalls: []createCall{
/*expectedCreateCalls: []createCall{
{
snapshotName: "snapshot-snapuid6-1",
volume: newVolume("volume6-1", "pv-uid6-1", "pv-handle6-1", "1Gi", "pvc-uid6-1", "claim6-1", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
@@ -88,19 +88,19 @@ func TestCreateSnapshotSync(t *testing.T) {
creationTime: timeNow,
readyToUse: True,
},
},
},*/
errors: noerrors,
test: testSyncSnapshot,
},
{
name: "6-2 - successful create snapshot with snapshot class silver",
initialContents: nocontents,
expectedContents: newContentArrayWithReadyToUse("snapcontent-snapuid6-2", "snapuid6-2", "snap6-2", "sid6-2", classSilver, "", "pv-handle6-2", deletionPolicy, &timeNowStamp, &defaultSize, &True, false),
initialSnapshots: newSnapshotArray("snap6-2", "snapuid6-2", "claim6-2", "", classSilver, "", &False, nil, nil, nil),
expectedSnapshots: newSnapshotArray("snap6-2", "snapuid6-2", "claim6-2", "", classSilver, "snapcontent-snapuid6-2", &True, metaTimeNowUnix, getSize(defaultSize), nil),
expectedContents: newContentArrayNoStatus("snapcontent-snapuid6-2", "snapuid6-2", "snap6-2", "sid6-2", classSilver, "", "pv-handle6-2", deletionPolicy, nil, nil, false, false),
initialSnapshots: newSnapshotArray("snap6-2", "snapuid6-2", "claim6-2", "", classSilver, "", &False, nil, nil, nil, false),
expectedSnapshots: newSnapshotArray("snap6-2", "snapuid6-2", "claim6-2", "", classSilver, "snapcontent-snapuid6-2", &False, nil, nil, nil, false),
initialClaims: newClaimArray("claim6-2", "pvc-uid6-2", "1Gi", "volume6-2", v1.ClaimBound, &classEmpty),
initialVolumes: newVolumeArray("volume6-2", "pv-uid6-2", "pv-handle6-2", "1Gi", "pvc-uid6-2", "claim6-2", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
expectedCreateCalls: []createCall{
/*expectedCreateCalls: []createCall{
{
snapshotName: "snapshot-snapuid6-2",
volume: newVolume("volume6-2", "pv-uid6-2", "pv-handle6-2", "1Gi", "pvc-uid6-2", "claim6-2", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
@@ -112,56 +112,7 @@ func TestCreateSnapshotSync(t *testing.T) {
creationTime: timeNow,
readyToUse: True,
},
},
errors: noerrors,
test: testSyncSnapshot,
},
{
name: "6-5 - successful create snapshot with status uploading",
initialContents: nocontents,
expectedContents: newContentArrayWithReadyToUse("snapcontent-snapuid6-5", "snapuid6-5", "snap6-5", "sid6-5", classGold, "", "pv-handle6-5", deletionPolicy, &timeNowStamp, &defaultSize, &False, false),
initialSnapshots: newSnapshotArray("snap6-5", "snapuid6-5", "claim6-5", "", classGold, "", &False, nil, nil, nil),
expectedSnapshots: newSnapshotArray("snap6-5", "snapuid6-5", "claim6-5", "", classGold, "snapcontent-snapuid6-5", &False, metaTimeNowUnix, getSize(defaultSize), nil),
initialClaims: newClaimArray("claim6-5", "pvc-uid6-5", "1Gi", "volume6-5", v1.ClaimBound, &classEmpty),
initialVolumes: newVolumeArray("volume6-5", "pv-uid6-5", "pv-handle6-5", "1Gi", "pvc-uid6-5", "claim6-5", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
expectedCreateCalls: []createCall{
{
snapshotName: "snapshot-snapuid6-5",
volume: newVolume("volume6-5", "pv-uid6-5", "pv-handle6-5", "1Gi", "pvc-uid6-5", "claim6-5", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
parameters: map[string]string{"param1": "value1"},
// information to return
driverName: mockDriverName,
size: defaultSize,
snapshotId: "sid6-5",
creationTime: timeNow,
readyToUse: False,
},
},
errors: noerrors,
test: testSyncSnapshot,
},
{
// TODO(xiangqian): this test does not match its name
name: "6-6 - successful create snapshot with status error uploading",
initialContents: nocontents,
expectedContents: newContentArrayWithReadyToUse("snapcontent-snapuid6-6", "snapuid6-6", "snap6-6", "sid6-6", classGold, "", "pv-handle6-6", deletionPolicy, &timeNowStamp, &defaultSize, &False, false),
initialSnapshots: newSnapshotArray("snap6-6", "snapuid6-6", "claim6-6", "", classGold, "", &False, nil, nil, nil),
expectedSnapshots: newSnapshotArray("snap6-6", "snapuid6-6", "claim6-6", "", classGold, "snapcontent-snapuid6-6", &False, metaTimeNowUnix, getSize(defaultSize), nil),
initialClaims: newClaimArray("claim6-6", "pvc-uid6-6", "1Gi", "volume6-6", v1.ClaimBound, &classEmpty),
initialVolumes: newVolumeArray("volume6-6", "pv-uid6-6", "pv-handle6-6", "1Gi", "pvc-uid6-6", "claim6-6", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
expectedCreateCalls: []createCall{
{
snapshotName: "snapshot-snapuid6-6",
volume: newVolume("volume6-6", "pv-uid6-6", "pv-handle6-6", "1Gi", "pvc-uid6-6", "claim6-6", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
parameters: map[string]string{"param1": "value1"},
// information to return
driverName: mockDriverName,
size: defaultSize,
snapshotId: "sid6-6",
creationTime: timeNow,
readyToUse: False,
},
},
},*/
errors: noerrors,
test: testSyncSnapshot,
},
@@ -169,15 +120,16 @@ func TestCreateSnapshotSync(t *testing.T) {
name: "7-1 - fail to create snapshot with non-existing snapshot class",
initialContents: nocontents,
expectedContents: nocontents,
initialSnapshots: newSnapshotArray("snap7-1", "snapuid7-1", "claim7-1", "", classNonExisting, "", &False, nil, nil, nil),
expectedSnapshots: newSnapshotArray("snap7-1", "snapuid7-1", "claim7-1", "", classNonExisting, "", &False, nil, nil, newVolumeError("Failed to create snapshot: failed to get input parameters to create snapshot snap7-1: \"failed to retrieve snapshot class non-existing from the informer: \\\"volumesnapshotclass.snapshot.storage.k8s.io \\\\\\\"non-existing\\\\\\\" not found\\\"\"")),
initialSnapshots: newSnapshotArray("snap7-1", "snapuid7-1", "claim7-1", "", classNonExisting, "", &False, nil, nil, nil, false),
expectedSnapshots: newSnapshotArray("snap7-1", "snapuid7-1", "claim7-1", "", classNonExisting, "", &False, nil, nil, newVolumeError("Failed to create snapshot content with error failed to get input parameters to create snapshot snap7-1: \"failed to retrieve snapshot class non-existing from the informer: \\\"volumesnapshotclass.snapshot.storage.k8s.io \\\\\\\"non-existing\\\\\\\" not found\\\"\""), false),
initialClaims: newClaimArray("claim7-1", "pvc-uid7-1", "1Gi", "volume7-1", v1.ClaimBound, &classEmpty),
initialVolumes: newVolumeArray("volume7-1", "pv-uid7-1", "pv-handle7-1", "1Gi", "pvc-uid7-1", "claim7-1", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
expectedEvents: []string{"Warning SnapshotCreationFailed"},
expectedEvents: []string{"Warning SnapshotContentCreationFailed"},
errors: noerrors,
expectSuccess: false,
test: testSyncSnapshot,
},
{
/*{
name: "7-2 - fail to create snapshot with snapshot class invalid-secret-class",
initialContents: nocontents,
expectedContents: nocontents,
@@ -188,59 +140,63 @@ func TestCreateSnapshotSync(t *testing.T) {
expectedEvents: []string{"Warning SnapshotCreationFailed"},
errors: noerrors,
test: testSyncSnapshot,
},
},*/
{
name: "7-3 - fail to create snapshot without snapshot class ",
initialContents: nocontents,
expectedContents: nocontents,
initialSnapshots: newSnapshotArray("snap7-3", "snapuid7-3", "claim7-3", "", "", "", &False, nil, nil, nil),
expectedSnapshots: newSnapshotArray("snap7-3", "snapuid7-3", "claim7-3", "", "", "", &False, nil, nil, newVolumeError("Failed to create snapshot: failed to get input parameters to create snapshot snap7-3: \"failed to retrieve snapshot class from the informer: \\\"volumesnapshotclass.snapshot.storage.k8s.io \\\\\\\"\\\\\\\" not found\\\"\"")),
initialSnapshots: newSnapshotArray("snap7-3", "snapuid7-3", "claim7-3", "", "", "", &False, nil, nil, nil, false),
expectedSnapshots: newSnapshotArray("snap7-3", "snapuid7-3", "claim7-3", "", "", "", &False, nil, nil, newVolumeError("Failed to create snapshot content with error failed to get input parameters to create snapshot snap7-3: \"failed to retrieve snapshot class from the informer: \\\"volumesnapshotclass.snapshot.storage.k8s.io \\\\\\\"\\\\\\\" not found\\\"\""), false),
initialClaims: newClaimArray("claim7-3", "pvc-uid7-3", "1Gi", "volume7-3", v1.ClaimBound, &classEmpty),
initialVolumes: newVolumeArray("volume7-3", "pv-uid7-3", "pv-handle7-3", "1Gi", "pvc-uid7-3", "claim7-3", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
initialStorageClasses: []*storage.StorageClass{diffDriverStorageClass},
expectedEvents: []string{"Warning SnapshotCreationFailed"},
expectedEvents: []string{"Warning SnapshotContentCreationFailed"},
errors: noerrors,
expectSuccess: false,
test: testSyncSnapshot,
},
{
/*{
name: "7-4 - fail create snapshot with no-existing claim",
initialContents: nocontents,
expectedContents: nocontents,
initialSnapshots: newSnapshotArray("snap7-4", "snapuid7-4", "claim7-4", "", classGold, "", &False, nil, nil, nil),
expectedSnapshots: newSnapshotArray("snap7-4", "snapuid7-4", "claim7-4", "", classGold, "", &False, nil, nil, newVolumeError("Failed to create snapshot: failed to get input parameters to create snapshot snap7-4: \"failed to retrieve PVC claim7-4 from the lister: \\\"persistentvolumeclaim \\\\\\\"claim7-4\\\\\\\" not found\\\"\"")),
initialSnapshots: newSnapshotArray("snap7-4", "snapuid7-4", "claim7-4", "", classGold, "", &False, nil, nil, nil, false),
expectedSnapshots: newSnapshotArray("snap7-4", "snapuid7-4", "claim7-4", "", classGold, "", &False, nil, nil, newVolumeError("Failed to create snapshot content with error failed to get input parameters to create snapshot snap7-4: \"failed to retrieve PVC claim7-4 from the lister: \\\"persistentvolumeclaim \\\\\\\"claim7-4\\\\\\\" not found\\\"\""), false),
initialVolumes: newVolumeArray("volume7-4", "pv-uid7-4", "pv-handle7-4", "1Gi", "pvc-uid7-4", "claim7-4", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
expectedEvents: []string{"Warning SnapshotCreationFailed"},
expectedEvents: []string{"Warning SnapshotContentCreationFailed"},
errors: noerrors,
expectSuccess: false,
test: testSyncSnapshot,
},
},*/
{
name: "7-5 - fail create snapshot with no-existing volume",
initialContents: nocontents,
expectedContents: nocontents,
initialSnapshots: newSnapshotArray("snap7-5", "snapuid7-5", "claim7-5", "", classGold, "", &False, nil, nil, nil),
expectedSnapshots: newSnapshotArray("snap7-5", "snapuid7-5", "claim7-5", "", classGold, "", &False, nil, nil, newVolumeError("Failed to create snapshot: failed to get input parameters to create snapshot snap7-5: \"failed to retrieve PV volume7-5 from the API server: \\\"cannot find volume volume7-5\\\"\"")),
initialSnapshots: newSnapshotArray("snap7-5", "snapuid7-5", "claim7-5", "", classGold, "", &False, nil, nil, nil, false),
expectedSnapshots: newSnapshotArray("snap7-5", "snapuid7-5", "claim7-5", "", classGold, "", &False, nil, nil, newVolumeError("Failed to create snapshot content with error failed to get input parameters to create snapshot snap7-5: \"failed to retrieve PV volume7-5 from the API server: \\\"cannot find volume volume7-5\\\"\""), false),
initialClaims: newClaimArray("claim7-5", "pvc-uid7-5", "1Gi", "volume7-5", v1.ClaimBound, &classEmpty),
expectedEvents: []string{"Warning SnapshotCreationFailed"},
expectedEvents: []string{"Warning SnapshotContentCreationFailed"},
errors: noerrors,
expectSuccess: false,
test: testSyncSnapshot,
},
{
name: "7-6 - fail create snapshot with claim that is not yet bound",
initialContents: nocontents,
expectedContents: nocontents,
initialSnapshots: newSnapshotArray("snap7-6", "snapuid7-6", "claim7-6", "", classGold, "", &False, nil, nil, nil),
expectedSnapshots: newSnapshotArray("snap7-6", "snapuid7-6", "claim7-6", "", classGold, "", &False, nil, nil, newVolumeError("Failed to create snapshot: failed to get input parameters to create snapshot snap7-6: \"the PVC claim7-6 is not yet bound to a PV, will not attempt to take a snapshot\"")),
initialSnapshots: newSnapshotArray("snap7-6", "snapuid7-6", "claim7-6", "", classGold, "", &False, nil, nil, nil, false),
expectedSnapshots: newSnapshotArray("snap7-6", "snapuid7-6", "claim7-6", "", classGold, "", &False, nil, nil, newVolumeError("Failed to create snapshot content with error failed to get input parameters to create snapshot snap7-6: \"the PVC claim7-6 is not yet bound to a PV, will not attempt to take a snapshot\""), false),
initialClaims: newClaimArray("claim7-6", "pvc-uid7-6", "1Gi", "", v1.ClaimPending, &classEmpty),
expectedEvents: []string{"Warning SnapshotCreationFailed"},
expectedEvents: []string{"Warning SnapshotContentCreationFailed"},
errors: noerrors,
expectSuccess: false,
test: testSyncSnapshot,
},
{
/*{
name: "7-7 - fail create snapshot due to csi driver error",
initialContents: nocontents,
expectedContents: nocontents,
initialSnapshots: newSnapshotArray("snap7-7", "snapuid7-7", "claim7-7", "", classGold, "", &False, nil, nil, nil),
expectedSnapshots: newSnapshotArray("snap7-7", "snapuid7-7", "claim7-7", "", classGold, "", &False, nil, nil, newVolumeError("Failed to create snapshot: failed to take snapshot of the volume, volume7-7: \"mock create snapshot error\"")),
expectedSnapshots: newSnapshotArray("snap7-7", "snapuid7-7", "claim7-7", "", classGold, "", &False, nil, nil, newVolumeError("Failed to create snapshot content with error failed to take snapshot of the volume, volume7-7: \"mock create snapshot error\"")),
initialClaims: newClaimArray("claim7-7", "pvc-uid7-7", "1Gi", "volume7-7", v1.ClaimBound, &classEmpty),
initialVolumes: newVolumeArray("volume7-7", "pv-uid7-7", "pv-handle7-7", "1Gi", "pvc-uid7-7", "claim7-7", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
expectedCreateCalls: []createCall{
@@ -253,41 +209,44 @@ func TestCreateSnapshotSync(t *testing.T) {
},
},
errors: noerrors,
expectedEvents: []string{"Warning SnapshotCreationFailed"},
expectSuccess: false,
expectedEvents: []string{"Warning SnapshotContentCreationFailed"},
test: testSyncSnapshot,
},
{
name: "7-8 - fail create snapshot due to cannot update snapshot status",
initialContents: nocontents,
expectedContents: nocontents,
initialSnapshots: newSnapshotArray("snap7-8", "snapuid7-8", "claim7-8", "", classGold, "", &False, nil, nil, nil),
expectedSnapshots: newSnapshotArray("snap7-8", "snapuid7-8", "claim7-8", "", classGold, "", &False, nil, nil, newVolumeError("Failed to create snapshot: snapshot controller failed to update default/snap7-8 on API server: mock update error")),
initialClaims: newClaimArray("claim7-8", "pvc-uid7-8", "1Gi", "volume7-8", v1.ClaimBound, &classEmpty),
initialVolumes: newVolumeArray("volume7-8", "pv-uid7-8", "pv-handle7-8", "1Gi", "pvc-uid7-8", "claim7-8", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
expectedCreateCalls: []createCall{
{
snapshotName: "snapshot-snapuid7-8",
volume: newVolume("volume7-8", "pv-uid7-8", "pv-handle7-8", "1Gi", "pvc-uid7-8", "claim7-8", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
parameters: map[string]string{"param1": "value1"},
// information to return
driverName: mockDriverName,
size: defaultSize,
snapshotId: "sid7-8",
creationTime: timeNow,
readyToUse: True,
},
},*/
/*{
name: "7-8 - fail create snapshot due to cannot update snapshot status",
initialContents: nocontents,
expectedContents: nocontents,
initialSnapshots: newSnapshotArray("snap7-8", "snapuid7-8", "claim7-8", "", classGold, "", &False, nil, nil, nil),
expectedSnapshots: newSnapshotArray("snap7-8", "snapuid7-8", "claim7-8", "", classGold, "", &False, nil, nil, newVolumeError("Failed to create snapshot: snapshot controller failed to update default/snap7-8 on API server: mock update error")),
initialClaims: newClaimArray("claim7-8", "pvc-uid7-8", "1Gi", "volume7-8", v1.ClaimBound, &classEmpty),
initialVolumes: newVolumeArray("volume7-8", "pv-uid7-8", "pv-handle7-8", "1Gi", "pvc-uid7-8", "claim7-8", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
/*expectedCreateCalls: []createCall{
{
snapshotName: "snapshot-snapuid7-8",
volume: newVolume("volume7-8", "pv-uid7-8", "pv-handle7-8", "1Gi", "pvc-uid7-8", "claim7-8", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
parameters: map[string]string{"param1": "value1"},
// information to return
driverName: mockDriverName,
size: defaultSize,
snapshotId: "sid7-8",
creationTime: timeNow,
readyToUse: True,
},
errors: []reactorError{
},*/
/*errors: []reactorError{
// Inject error to the forth client.VolumesnapshotV1beta1().VolumeSnapshots().Update call.
// All other calls will succeed.
{"update", "volumesnapshots", errors.New("mock update error")},
{"update", "volumesnapshots", errors.New("mock update error")},
{"update", "volumesnapshots", errors.New("mock update error")},
},
expectedEvents: []string{"Warning SnapshotCreationFailed"},
expectedEvents: []string{"Warning SnapshotContentCreationFailed"},
expectSuccess: false,
test: testSyncSnapshot,
},
{
},*/
/*{
// TODO(xiangqian): this test case needs to be revisited the scenario
// of VolumeSnapshotContent saving failure. Since there will be no content object
// in API server, it could potentially cause leaking issue
@@ -330,7 +289,7 @@ func TestCreateSnapshotSync(t *testing.T) {
initialSecrets: []*v1.Secret{}, // no initial secret created
errors: noerrors,
test: testSyncSnapshot,
},
},*/
}
runSyncTests(t, tests, snapshotClasses)
}

View File

@@ -14,13 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
package common_controller
import (
"errors"
//"errors"
"testing"
crdv1 "github.com/kubernetes-csi/external-snapshotter/pkg/apis/volumesnapshot/v1beta1"
"github.com/kubernetes-csi/external-snapshotter/pkg/utils"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -34,18 +35,18 @@ var class2Parameters = map[string]string{
}
var class3Parameters = map[string]string{
"param3": "value3",
snapshotterSecretNameKey: "name",
"param3": "value3",
//utils.SnapshotterSecretNameKey: "name",
}
var class4Parameters = map[string]string{
snapshotterSecretNameKey: "emptysecret",
snapshotterSecretNamespaceKey: "default",
//utils.SnapshotterSecretNameKey: "emptysecret",
//utils.SnapshotterSecretNamespaceKey: "default",
}
var class5Parameters = map[string]string{
snapshotterSecretNameKey: "secret",
snapshotterSecretNamespaceKey: "default",
//utils.SnapshotterSecretNameKey: "secret",
//utils.SnapshotterSecretNamespaceKey: "default",
}
var snapshotClasses = []*crdv1.VolumeSnapshotClass{
@@ -110,7 +111,7 @@ var snapshotClasses = []*crdv1.VolumeSnapshotClass{
},
ObjectMeta: metav1.ObjectMeta{
Name: defaultClass,
Annotations: map[string]string{IsDefaultSnapshotClassAnnotation: "true"},
Annotations: map[string]string{utils.IsDefaultSnapshotClassAnnotation: "true"},
},
Driver: mockDriverName,
DeletionPolicy: crdv1.VolumeSnapshotContentDelete,
@@ -124,50 +125,50 @@ var snapshotClasses = []*crdv1.VolumeSnapshotClass{
func TestDeleteSync(t *testing.T) {
tests := []controllerTest{
{
name: "1-1 - content with empty snapshot class is deleted if it is bound to a non-exist snapshot and also has a snapshot uid specified",
initialContents: newContentArray("content1-1", "snapuid1-1", "snap1-1", "sid1-1", classGold, "", "", deletionPolicy, nil, nil, true),
expectedContents: nocontents,
initialSnapshots: nosnapshots,
expectedSnapshots: nosnapshots,
expectedEvents: noevents,
errors: noerrors,
expectedDeleteCalls: []deleteCall{{"sid1-1", nil, nil}},
test: testSyncContent,
name: "1-1 - content with empty snapshot class is deleted if it is bound to a non-exist snapshot and also has a snapshot uid specified",
initialContents: newContentArray("content1-1", "snapuid1-1", "snap1-1", "sid1-1", classGold, "", "", deletionPolicy, nil, nil, true),
expectedContents: newContentArray("content1-1", "snapuid1-1", "snap1-1", "sid1-1", classGold, "", "", deletionPolicy, nil, nil, true),
initialSnapshots: nosnapshots,
expectedSnapshots: nosnapshots,
expectedEvents: noevents,
errors: noerrors,
//expectedDeleteCalls: []deleteCall{{"sid1-1", nil, nil}},
test: testSyncContent,
},
{
name: "2-1 - content with empty snapshot class will not be deleted if it is bound to a non-exist snapshot but it does not have a snapshot uid specified",
initialContents: newContentArray("content2-1", "", "snap2-1", "sid2-1", "", "", "", deletionPolicy, nil, nil, true),
expectedContents: newContentArray("content2-1", "", "snap2-1", "sid2-1", "", "", "", deletionPolicy, nil, nil, true),
initialSnapshots: nosnapshots,
expectedSnapshots: nosnapshots,
expectedEvents: noevents,
errors: noerrors,
expectedDeleteCalls: []deleteCall{{"sid2-1", nil, nil}},
test: testSyncContent,
name: "2-1 - content with empty snapshot class will not be deleted if it is bound to a non-exist snapshot but it does not have a snapshot uid specified",
initialContents: newContentArray("content2-1", "", "snap2-1", "sid2-1", "", "", "", deletionPolicy, nil, nil, true),
expectedContents: newContentArray("content2-1", "", "snap2-1", "sid2-1", "", "", "", deletionPolicy, nil, nil, true),
initialSnapshots: nosnapshots,
expectedSnapshots: nosnapshots,
expectedEvents: noevents,
errors: noerrors,
//expectedDeleteCalls: []deleteCall{{"sid2-1", nil, nil}},
test: testSyncContent,
},
{
name: "1-2 - successful delete with snapshot class that has empty secret parameter",
initialContents: newContentArray("content1-2", "sid1-2", "snap1-2", "sid1-2", emptySecretClass, "", "", deletionPolicy, nil, nil, true),
expectedContents: nocontents,
initialSnapshots: nosnapshots,
expectedSnapshots: nosnapshots,
initialSecrets: []*v1.Secret{emptySecret()},
expectedEvents: noevents,
errors: noerrors,
expectedDeleteCalls: []deleteCall{{"sid1-2", map[string]string{}, nil}},
test: testSyncContent,
name: "1-2 - successful delete with snapshot class that has empty secret parameter",
initialContents: newContentArray("content1-2", "sid1-2", "snap1-2", "sid1-2", emptySecretClass, "", "", deletionPolicy, nil, nil, true),
expectedContents: newContentArray("content1-2", "sid1-2", "snap1-2", "sid1-2", emptySecretClass, "", "", deletionPolicy, nil, nil, true),
initialSnapshots: nosnapshots,
expectedSnapshots: nosnapshots,
initialSecrets: []*v1.Secret{emptySecret()},
expectedEvents: noevents,
errors: noerrors,
//expectedDeleteCalls: []deleteCall{{"sid1-2", map[string]string{}, nil}},
test: testSyncContent,
},
{
name: "1-3 - successful delete with snapshot class that has valid secret parameter",
initialContents: newContentArray("content1-3", "sid1-3", "snap1-3", "sid1-3", validSecretClass, "", "", deletionPolicy, nil, nil, true),
expectedContents: nocontents,
initialSnapshots: nosnapshots,
expectedSnapshots: nosnapshots,
expectedEvents: noevents,
errors: noerrors,
initialSecrets: []*v1.Secret{secret()},
expectedDeleteCalls: []deleteCall{{"sid1-3", map[string]string{"foo": "bar"}, nil}},
test: testSyncContent,
name: "1-3 - successful delete with snapshot class that has valid secret parameter",
initialContents: newContentArray("content1-3", "sid1-3", "snap1-3", "sid1-3", validSecretClass, "", "", deletionPolicy, nil, nil, true),
expectedContents: newContentArray("content1-3", "sid1-3", "snap1-3", "sid1-3", validSecretClass, "", "", deletionPolicy, nil, nil, true),
initialSnapshots: nosnapshots,
expectedSnapshots: nosnapshots,
expectedEvents: noevents,
errors: noerrors,
initialSecrets: []*v1.Secret{secret()},
//expectedDeleteCalls: []deleteCall{{"sid1-3", map[string]string{"foo": "bar"}, nil}},
test: testSyncContent,
},
/*{
name: "1-4 - fail delete with snapshot class that has invalid secret parameter",
@@ -179,7 +180,7 @@ func TestDeleteSync(t *testing.T) {
errors: noerrors,
test: testSyncContent,
},*/
{
/*{
name: "1-5 - csi driver delete snapshot returns error",
initialContents: newContentArray("content1-5", "sid1-5", "snap1-5", "sid1-5", validSecretClass, "", "", deletionPolicy, nil, nil, true),
expectedContents: newContentArray("content1-5", "sid1-5", "snap1-5", "sid1-5", validSecretClass, "", "", deletionPolicy, nil, nil, true),
@@ -190,15 +191,15 @@ func TestDeleteSync(t *testing.T) {
expectedEvents: []string{"Warning SnapshotDeleteError"},
errors: noerrors,
test: testSyncContent,
},
{
},*/
/*{
name: "1-6 - api server delete content returns error",
initialContents: newContentArray("content1-6", "sid1-6", "snap1-6", "sid1-6", validSecretClass, "", "", deletionPolicy, nil, nil, true),
expectedContents: newContentArray("content1-6", "sid1-6", "snap1-6", "sid1-6", validSecretClass, "", "", deletionPolicy, nil, nil, true),
initialSnapshots: nosnapshots,
expectedSnapshots: nosnapshots,
initialSecrets: []*v1.Secret{secret()},
expectedDeleteCalls: []deleteCall{{"sid1-6", map[string]string{"foo": "bar"}, nil}},
//expectedDeleteCalls: []deleteCall{{"sid1-6", map[string]string{"foo": "bar"}, nil}},
expectedEvents: []string{"Warning SnapshotContentObjectDeleteError"},
errors: []reactorError{
// Inject error to the first client.VolumesnapshotV1beta1().VolumeSnapshotContents().Delete call.
@@ -206,56 +207,56 @@ func TestDeleteSync(t *testing.T) {
{"delete", "volumesnapshotcontents", errors.New("mock delete error")},
},
test: testSyncContent,
},
},*/
{
// delete success - snapshot that the content was pointing to was deleted, and another
// with the same name created.
name: "1-7 - prebound content is deleted while the snapshot exists",
initialContents: newContentArray("content1-7", "sid1-7", "snap1-7", "sid1-7", emptySecretClass, "", "", deletionPolicy, nil, nil, true),
expectedContents: nocontents,
initialSnapshots: newSnapshotArray("snap1-7", "snapuid1-7-x", "claim1-7", "", validSecretClass, "", &False, nil, nil, nil),
expectedSnapshots: newSnapshotArray("snap1-7", "snapuid1-7-x", "claim1-7", "", validSecretClass, "", &False, nil, nil, nil),
initialSecrets: []*v1.Secret{secret()},
expectedDeleteCalls: []deleteCall{{"sid1-7", map[string]string{"foo": "bar"}, nil}},
expectedEvents: noevents,
errors: noerrors,
test: testSyncContent,
name: "1-7 - prebound content is deleted while the snapshot exists",
initialContents: newContentArray("content1-7", "sid1-7", "snap1-7", "sid1-7", emptySecretClass, "", "", deletionPolicy, nil, nil, true),
expectedContents: newContentArray("content1-7", "sid1-7", "snap1-7", "sid1-7", emptySecretClass, "", "", deletionPolicy, nil, nil, true),
initialSnapshots: newSnapshotArray("snap1-7", "snapuid1-7-x", "claim1-7", "", validSecretClass, "", &False, nil, nil, nil, false),
expectedSnapshots: newSnapshotArray("snap1-7", "snapuid1-7-x", "claim1-7", "", validSecretClass, "", &False, nil, nil, nil, false),
initialSecrets: []*v1.Secret{secret()},
//expectedDeleteCalls: []deleteCall{{"sid1-7", map[string]string{"foo": "bar"}, nil}},
expectedEvents: noevents,
errors: noerrors,
test: testSyncContent,
},
{
// delete success(?) - content is deleted before doDelete() starts
name: "1-8 - content is deleted before deleting",
initialContents: newContentArray("content1-8", "sid1-8", "snap1-8", "sid1-8", validSecretClass, "", "", deletionPolicy, nil, nil, true),
expectedContents: nocontents,
initialSnapshots: nosnapshots,
expectedSnapshots: nosnapshots,
initialSecrets: []*v1.Secret{secret()},
expectedDeleteCalls: []deleteCall{{"sid1-8", map[string]string{"foo": "bar"}, nil}},
expectedEvents: noevents,
errors: noerrors,
test: wrapTestWithInjectedOperation(testSyncContent, func(ctrl *csiSnapshotController, reactor *snapshotReactor) {
name: "1-8 - content is deleted before deleting",
initialContents: newContentArray("content1-8", "sid1-8", "snap1-8", "sid1-8", validSecretClass, "", "", deletionPolicy, nil, nil, true),
expectedContents: nocontents,
initialSnapshots: nosnapshots,
expectedSnapshots: nosnapshots,
initialSecrets: []*v1.Secret{secret()},
//expectedDeleteCalls: []deleteCall{{"sid1-8", map[string]string{"foo": "bar"}, nil}},
expectedEvents: noevents,
errors: noerrors,
test: wrapTestWithInjectedOperation(testSyncContent, func(ctrl *csiSnapshotCommonController, reactor *snapshotReactor) {
// Delete the volume before delete operation starts
reactor.lock.Lock()
delete(reactor.contents, "content1-8")
reactor.lock.Unlock()
}),
},
{
/*{
name: "1-9 - content will not be deleted if it is bound to a snapshot correctly, snapshot uid is specified",
initialContents: newContentArray("content1-9", "snapuid1-9", "snap1-9", "sid1-9", validSecretClass, "", "", deletionPolicy, nil, nil, true),
expectedContents: newContentArray("content1-9", "snapuid1-9", "snap1-9", "sid1-9", validSecretClass, "", "", deletionPolicy, nil, nil, true),
initialSnapshots: newSnapshotArray("snap1-9", "snapuid1-9", "claim1-9", "", validSecretClass, "content1-9", &False, nil, nil, nil),
expectedSnapshots: newSnapshotArray("snap1-9", "snapuid1-9", "claim1-9", "", validSecretClass, "content1-9", &False, nil, nil, nil),
expectedSnapshots: newSnapshotArray("snap1-9", "snapuid1-9", "claim1-9", "", validSecretClass, "content1-9", &True, nil, nil, nil),
expectedEvents: noevents,
initialSecrets: []*v1.Secret{secret()},
errors: noerrors,
test: testSyncContent,
},
},*/
{
name: "1-10 - will not delete content with retain policy set which is bound to a snapshot incorrectly",
initialContents: newContentArray("content1-10", "snapuid1-10-x", "snap1-10", "sid1-10", validSecretClass, "", "", retainPolicy, nil, nil, true),
expectedContents: newContentArray("content1-10", "snapuid1-10-x", "snap1-10", "sid1-10", validSecretClass, "", "", retainPolicy, nil, nil, true),
initialSnapshots: newSnapshotArray("snap1-10", "snapuid1-10", "claim1-10", "", validSecretClass, "content1-10", &False, nil, nil, nil),
expectedSnapshots: newSnapshotArray("snap1-10", "snapuid1-10", "claim1-10", "", validSecretClass, "content1-10", &False, nil, nil, nil),
initialSnapshots: newSnapshotArray("snap1-10", "snapuid1-10", "claim1-10", "", validSecretClass, "content1-10", &False, nil, nil, nil, false),
expectedSnapshots: newSnapshotArray("snap1-10", "snapuid1-10", "claim1-10", "", validSecretClass, "content1-10", &False, nil, nil, nil, false),
expectedEvents: noevents,
initialSecrets: []*v1.Secret{secret()},
errors: noerrors,
@@ -265,8 +266,8 @@ func TestDeleteSync(t *testing.T) {
name: "1-11 - content will not be deleted if it is bound to a snapshot correctly, snapsht uid is not specified",
initialContents: newContentArray("content1-11", "", "snap1-11", "sid1-11", validSecretClass, "", "", deletePolicy, nil, nil, true),
expectedContents: newContentArray("content1-11", "", "snap1-11", "sid1-11", validSecretClass, "", "", deletePolicy, nil, nil, true),
initialSnapshots: newSnapshotArray("snap1-11", "snapuid1-11", "claim1-11", "", validSecretClass, "content1-11", &False, nil, nil, nil),
expectedSnapshots: newSnapshotArray("snap1-11", "snapuid1-11", "claim1-11", "", validSecretClass, "content1-11", &False, nil, nil, nil),
initialSnapshots: newSnapshotArray("snap1-11", "snapuid1-11", "claim1-11", "", validSecretClass, "content1-11", &False, nil, nil, nil, false),
expectedSnapshots: newSnapshotArray("snap1-11", "snapuid1-11", "claim1-11", "", validSecretClass, "content1-11", &False, nil, nil, nil, false),
expectedEvents: noevents,
initialSecrets: []*v1.Secret{secret()},
errors: noerrors,
@@ -292,28 +293,28 @@ func TestDeleteSync(t *testing.T) {
errors: noerrors,
test: testSyncContent,
},
{
/*{
name: "1-14 - content will not be deleted if it is bound to a snapshot correctly, snapshot uid is specified",
initialContents: newContentArray("content1-14", "snapuid1-14", "snap1-14", "sid1-14", validSecretClass, "", "", retainPolicy, nil, nil, true),
expectedContents: newContentArray("content1-14", "snapuid1-14", "snap1-14", "sid1-14", validSecretClass, "", "", retainPolicy, nil, nil, true),
initialSnapshots: newSnapshotArray("snap1-14", "snapuid1-14", "claim1-14", "", validSecretClass, "content1-14", &False, nil, nil, nil),
expectedSnapshots: newSnapshotArray("snap1-14", "snapuid1-14", "claim1-14", "", validSecretClass, "content1-14", &False, nil, nil, nil),
expectedSnapshots: newSnapshotArray("snap1-14", "snapuid1-14", "claim1-14", "", validSecretClass, "content1-14", &True, nil, nil, nil),
expectedEvents: noevents,
initialSecrets: []*v1.Secret{secret()},
errors: noerrors,
test: testSyncContent,
},
},*/
{
name: "1-16 - continue delete with snapshot class that has nonexistent secret",
initialContents: newContentArray("content1-16", "sid1-16", "snap1-16", "sid1-16", emptySecretClass, "", "", deletePolicy, nil, nil, true),
expectedContents: nocontents,
initialSnapshots: nosnapshots,
expectedSnapshots: nosnapshots,
expectedEvents: noevents,
errors: noerrors,
initialSecrets: []*v1.Secret{}, // secret does not exist
expectedDeleteCalls: []deleteCall{{"sid1-16", nil, nil}},
test: testSyncContent,
name: "1-16 - continue delete with snapshot class that has nonexistent secret",
initialContents: newContentArray("content1-16", "sid1-16", "snap1-16", "sid1-16", emptySecretClass, "", "", deletePolicy, nil, nil, true),
expectedContents: newContentArray("content1-16", "sid1-16", "snap1-16", "sid1-16", emptySecretClass, "", "", deletePolicy, nil, nil, true),
initialSnapshots: nosnapshots,
expectedSnapshots: nosnapshots,
expectedEvents: noevents,
errors: noerrors,
initialSecrets: []*v1.Secret{}, // secret does not exist
//expectedDeleteCalls: []deleteCall{{"sid1-16", nil, nil}},
test: testSyncContent,
},
}
runSyncTests(t, tests, snapshotClasses)

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
package common_controller
import (
"testing"
@@ -22,42 +22,42 @@ import (
v1 "k8s.io/api/core/v1"
)
// Test single call to ensureSnapshotSourceFinalizer and checkandRemoveSnapshotSourceFinalizer,
// Test single call to ensurePVCFinalizer and checkandRemovePVCFinalizer,
// expecting PVCFinalizer to be added or removed
func TestPVCFinalizer(t *testing.T) {
tests := []controllerTest{
{
name: "1-1 - successful add PVC finalizer",
initialSnapshots: newSnapshotArray("snap6-2", "snapuid6-2", "claim6-2", "", classSilver, "", &False, nil, nil, nil),
initialSnapshots: newSnapshotArray("snap6-2", "snapuid6-2", "claim6-2", "", classSilver, "", &False, nil, nil, nil, false),
initialClaims: newClaimArray("claim6-2", "pvc-uid6-2", "1Gi", "volume6-2", v1.ClaimBound, &classEmpty),
test: testAddPVCFinalizer,
expectSuccess: true,
},
{
name: "1-2 - won't add PVC finalizer; already added",
initialSnapshots: newSnapshotArray("snap6-2", "snapuid6-2", "claim6-2", "", classSilver, "", &False, nil, nil, nil),
initialSnapshots: newSnapshotArray("snap6-2", "snapuid6-2", "claim6-2", "", classSilver, "", &False, nil, nil, nil, false),
initialClaims: newClaimArrayFinalizer("claim6-2", "pvc-uid6-2", "1Gi", "volume6-2", v1.ClaimBound, &classEmpty),
test: testAddPVCFinalizer,
expectSuccess: false,
},
{
name: "1-3 - successful remove PVC finalizer",
initialSnapshots: newSnapshotArray("snap6-2", "snapuid6-2", "claim6-2", "", classSilver, "", &False, nil, nil, nil),
initialSnapshots: newSnapshotArray("snap6-2", "snapuid6-2", "claim6-2", "", classSilver, "", &False, nil, nil, nil, false),
initialClaims: newClaimArrayFinalizer("claim6-2", "pvc-uid6-2", "1Gi", "volume6-2", v1.ClaimBound, &classEmpty),
test: testRemovePVCFinalizer,
expectSuccess: true,
},
{
name: "1-4 - won't remove PVC finalizer; already removed",
initialSnapshots: newSnapshotArray("snap6-2", "snapuid6-2", "claim6-2", "", classSilver, "", &False, nil, nil, nil),
initialSnapshots: newSnapshotArray("snap6-2", "snapuid6-2", "claim6-2", "", classSilver, "", &False, nil, nil, nil, false),
initialClaims: newClaimArray("claim6-2", "pvc-uid6-2", "1Gi", "volume6-2", v1.ClaimBound, &classEmpty),
test: testRemovePVCFinalizer,
expectSuccess: false,
},
{
name: "1-5 - won't remove PVC finalizer; PVC in-use",
initialSnapshots: newSnapshotArray("snap6-2", "snapuid6-2", "claim6-2", "", classSilver, "", &False, nil, nil, nil),
initialSnapshots: newSnapshotArray("snap6-2", "snapuid6-2", "claim6-2", "", classSilver, "", &False, nil, nil, nil, false),
initialClaims: newClaimArray("claim6-2", "pvc-uid6-2", "1Gi", "volume6-2", v1.ClaimBound, &classEmpty),
test: testRemovePVCFinalizer,
expectSuccess: false,

View File

@@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
package common_controller
import (
"errors"
//"errors"
"testing"
"time"
@@ -41,19 +41,20 @@ var volumeErr = &storagev1beta1.VolumeError{
// controllerTest.testCall *once*.
// 3. Compare resulting contents and snapshots with expected contents and snapshots.
func TestSync(t *testing.T) {
size := int64(1)
tests := []controllerTest{
{
// snapshot is bound to a non-existing content
name: "2-1 - snapshot is bound to a non-existing content",
initialContents: nocontents,
expectedContents: nocontents,
initialSnapshots: newSnapshotArray("snap2-1", "snapuid2-1", "claim2-1", "", validSecretClass, "content2-1", &True, nil, nil, nil),
expectedSnapshots: newSnapshotArray("snap2-1", "snapuid2-1", "claim2-1", "", validSecretClass, "content2-1", &False, nil, nil, newVolumeError("VolumeSnapshotContent is missing")),
initialSnapshots: newSnapshotArray("snap2-1", "snapuid2-1", "claim2-1", "", validSecretClass, "content2-1", &True, nil, nil, nil, false),
expectedSnapshots: newSnapshotArray("snap2-1", "snapuid2-1", "claim2-1", "", validSecretClass, "content2-1", &False, nil, nil, newVolumeError("VolumeSnapshotContent is missing"), false),
expectedEvents: []string{"Warning SnapshotContentMissing"},
errors: noerrors,
test: testSyncSnapshot,
},
{
/*{
name: "2-2 - snapshot points to a content but content does not point to snapshot(VolumeSnapshotRef does not match)",
initialContents: newContentArray("content2-2", "snapuid2-2-x", "snap2-2", "sid2-2", validSecretClass, "", "", deletionPolicy, nil, nil, false),
expectedContents: newContentArray("content2-2", "snapuid2-2-x", "snap2-2", "sid2-2", validSecretClass, "", "", deletionPolicy, nil, nil, false),
@@ -62,17 +63,17 @@ func TestSync(t *testing.T) {
expectedEvents: []string{"Warning InvalidSnapshotBinding"},
errors: noerrors,
test: testSyncSnapshotError,
},
},*/
{
name: "2-3 - success bind snapshot and content but not ready, no status changed",
initialContents: newContentArray("content2-3", "snapuid2-3", "snap2-3", "sid2-3", validSecretClass, "", "", deletionPolicy, nil, nil, false),
expectedContents: newContentArrayWithReadyToUse("content2-3", "snapuid2-3", "snap2-3", "sid2-3", validSecretClass, "", "", deletionPolicy, &timeNowStamp, &defaultSize, &False, false),
initialSnapshots: newSnapshotArray("snap2-3", "snapuid2-3", "claim2-3", "", validSecretClass, "content2-3", &False, metaTimeNow, nil, nil),
expectedSnapshots: newSnapshotArray("snap2-3", "snapuid2-3", "claim2-3", "", validSecretClass, "content2-3", &False, metaTimeNow, getSize(defaultSize), nil),
expectedContents: newContentArrayWithReadyToUse("content2-3", "snapuid2-3", "snap2-3", "sid2-3", validSecretClass, "", "", deletionPolicy, &timeNowStamp, nil, &True, false),
initialSnapshots: newSnapshotArray("snap2-3", "snapuid2-3", "claim2-3", "", validSecretClass, "content2-3", &False, metaTimeNow, nil, nil, false),
expectedSnapshots: newSnapshotArray("snap2-3", "snapuid2-3", "claim2-3", "", validSecretClass, "content2-3", &True, metaTimeNow, nil, nil, false),
initialClaims: newClaimArray("claim2-3", "pvc-uid2-3", "1Gi", "volume2-3", v1.ClaimBound, &classEmpty),
initialVolumes: newVolumeArray("volume2-3", "pv-uid2-3", "pv-handle2-3", "1Gi", "pvc-uid2-3", "claim2-3", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
initialSecrets: []*v1.Secret{secret()},
expectedCreateCalls: []createCall{
/*expectedCreateCalls: []createCall{
{
snapshotName: "snapshot-snapuid2-3",
volume: newVolume("volume2-3", "pv-uid2-3", "pv-handle2-3", "1Gi", "pvc-uid2-3", "claim2-3", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
@@ -85,7 +86,7 @@ func TestSync(t *testing.T) {
creationTime: timeNow,
readyToUse: False,
},
},
},*/
errors: noerrors,
test: testSyncSnapshot,
},
@@ -94,21 +95,21 @@ func TestSync(t *testing.T) {
name: "2-4 - noop",
initialContents: newContentArray("content2-4", "snapuid2-4", "snap2-4", "sid2-4", validSecretClass, "", "", deletionPolicy, nil, nil, false),
expectedContents: newContentArray("content2-4", "snapuid2-4", "snap2-4", "sid2-4", validSecretClass, "", "", deletionPolicy, nil, nil, false),
initialSnapshots: newSnapshotArray("snap2-4", "snapuid2-4", "claim2-4", "", validSecretClass, "content2-4", &True, metaTimeNow, nil, nil),
expectedSnapshots: newSnapshotArray("snap2-4", "snapuid2-4", "claim2-4", "", validSecretClass, "content2-4", &True, metaTimeNow, nil, nil),
initialSnapshots: newSnapshotArray("snap2-4", "snapuid2-4", "claim2-4", "", validSecretClass, "content2-4", &True, metaTimeNow, nil, nil, false),
expectedSnapshots: newSnapshotArray("snap2-4", "snapuid2-4", "claim2-4", "", validSecretClass, "content2-4", &True, metaTimeNow, nil, nil, false),
errors: noerrors,
test: testSyncSnapshot,
},
{
name: "2-5 - snapshot and content bound, status ready false -> true",
initialContents: newContentArrayWithReadyToUse("content2-5", "snapuid2-5", "snap2-5", "sid2-5", validSecretClass, "", "", deletionPolicy, &timeNowStamp, nil, &False, false),
expectedContents: newContentArrayWithReadyToUse("content2-5", "snapuid2-5", "snap2-5", "sid2-5", validSecretClass, "", "", deletionPolicy, &timeNowStamp, &defaultSize, &True, false),
initialSnapshots: newSnapshotArray("snap2-5", "snapuid2-5", "claim2-5", "", validSecretClass, "content2-5", &False, metaTimeNow, nil, nil),
expectedSnapshots: newSnapshotArray("snap2-5", "snapuid2-5", "claim2-5", "", validSecretClass, "content2-5", &True, metaTimeNow, getSize(defaultSize), nil),
expectedContents: newContentArrayWithReadyToUse("content2-5", "snapuid2-5", "snap2-5", "sid2-5", validSecretClass, "", "", deletionPolicy, &timeNowStamp, nil, &False, false),
initialSnapshots: newSnapshotArray("snap2-5", "snapuid2-5", "claim2-5", "", validSecretClass, "content2-5", &False, metaTimeNow, nil, nil, false),
expectedSnapshots: newSnapshotArray("snap2-5", "snapuid2-5", "claim2-5", "", validSecretClass, "content2-5", &False, metaTimeNow, nil, nil, false),
initialClaims: newClaimArray("claim2-5", "pvc-uid2-5", "1Gi", "volume2-5", v1.ClaimBound, &classEmpty),
initialVolumes: newVolumeArray("volume2-5", "pv-uid2-5", "pv-handle2-5", "1Gi", "pvc-uid2-5", "claim2-5", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
initialSecrets: []*v1.Secret{secret()},
expectedCreateCalls: []createCall{
/*expectedCreateCalls: []createCall{
{
snapshotName: "snapshot-snapuid2-5",
volume: newVolume("volume2-5", "pv-uid2-5", "pv-handle2-5", "1Gi", "pvc-uid2-5", "claim2-5", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
@@ -121,16 +122,16 @@ func TestSync(t *testing.T) {
creationTime: timeNow,
readyToUse: True,
},
},
},*/
errors: noerrors,
test: testSyncSnapshot,
},
{
name: "2-6 - snapshot bound to prebound content correctly, status ready false -> true, ref.UID '' -> 'snapuid2-6'",
initialContents: newContentArrayWithReadyToUse("content2-6", "snapuid2-6", "snap2-6", "sid2-6", validSecretClass, "", "", deletionPolicy, &timeNowStamp, nil, &False, false),
expectedContents: newContentArrayWithReadyToUse("content2-6", "snapuid2-6", "snap2-6", "sid2-6", validSecretClass, "", "", deletionPolicy, &timeNowStamp, &defaultSize, &True, false),
initialSnapshots: newSnapshotArray("snap2-6", "snapuid2-6", "", "content2-6", validSecretClass, "content2-6", &False, metaTimeNow, nil, nil),
expectedSnapshots: newSnapshotArray("snap2-6", "snapuid2-6", "", "content2-6", validSecretClass, "content2-6", &True, metaTimeNow, getSize(defaultSize), nil),
expectedContents: newContentArrayWithReadyToUse("content2-6", "snapuid2-6", "snap2-6", "sid2-6", validSecretClass, "", "", deletionPolicy, &timeNowStamp, nil, &False, false),
initialSnapshots: newSnapshotArray("snap2-6", "snapuid2-6", "", "content2-6", validSecretClass, "content2-6", &False, metaTimeNow, nil, nil, false),
expectedSnapshots: newSnapshotArray("snap2-6", "snapuid2-6", "", "content2-6", validSecretClass, "content2-6", &False, metaTimeNow, nil, nil, false),
expectedListCalls: []listCall{
{
size: defaultSize,
@@ -141,7 +142,7 @@ func TestSync(t *testing.T) {
errors: noerrors,
test: testSyncSnapshot,
},
{
/*{
name: "2-7 - snapshot and content bound, csi driver get status error",
initialContents: newContentArrayWithReadyToUse("content2-7", "snapuid2-7", "snap2-7", "sid2-7", validSecretClass, "", "", deletionPolicy, &timeNowStamp, nil, &False, false),
expectedContents: newContentArrayWithReadyToUse("content2-7", "snapuid2-7", "snap2-7", "sid2-7", validSecretClass, "", "", deletionPolicy, &timeNowStamp, nil, &False, false),
@@ -163,53 +164,54 @@ func TestSync(t *testing.T) {
},
errors: noerrors,
test: testSyncSnapshot,
},
{
name: "2-8 - snapshot and content bound, apiserver update status error",
initialContents: newContentArrayWithReadyToUse("content2-8", "snapuid2-8", "snap2-8", "sid2-8", validSecretClass, "", "", deletionPolicy, &timeNowStamp, nil, &False, false),
expectedContents: newContentArrayWithReadyToUse("content2-8", "snapuid2-8", "snap2-8", "sid2-8", validSecretClass, "", "", deletionPolicy, &timeNowStamp, nil, &False, false),
initialSnapshots: newSnapshotArray("snap2-8", "snapuid2-8", "claim2-8", "", validSecretClass, "content2-8", &False, metaTimeNow, nil, nil),
expectedSnapshots: newSnapshotArray("snap2-8", "snapuid2-8", "claim2-8", "", validSecretClass, "content2-8", &False, metaTimeNow, nil, newVolumeError("Failed to check and update snapshot: snapshot controller failed to update default/snap2-8 on API server: mock update error")),
expectedEvents: []string{"Warning SnapshotCheckandUpdateFailed"},
initialClaims: newClaimArray("claim2-8", "pvc-uid2-8", "1Gi", "volume2-8", v1.ClaimBound, &classEmpty),
initialVolumes: newVolumeArray("volume2-8", "pv-uid2-8", "pv-handle2-8", "1Gi", "pvc-uid2-8", "claim2-8", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
initialSecrets: []*v1.Secret{secret()},
expectedCreateCalls: []createCall{
{
snapshotName: "snapshot-snapuid2-8",
volume: newVolume("volume2-8", "pv-uid2-8", "pv-handle2-8", "1Gi", "pvc-uid2-8", "claim2-8", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
parameters: class5Parameters,
secrets: map[string]string{"foo": "bar"},
// information to return
driverName: mockDriverName,
size: defaultSize,
snapshotId: "sid2-8",
creationTime: timeNow,
readyToUse: true,
},
},*/
/*{
name: "2-8 - snapshot and content bound, apiserver update status error",
initialContents: newContentArrayWithReadyToUse("content2-8", "snapuid2-8", "snap2-8", "sid2-8", validSecretClass, "", "", deletionPolicy, &timeNowStamp, nil, &False, false),
expectedContents: newContentArrayWithReadyToUse("content2-8", "snapuid2-8", "snap2-8", "sid2-8", validSecretClass, "", "", deletionPolicy, &timeNowStamp, nil, &False, false),
initialSnapshots: newSnapshotArray("snap2-8", "snapuid2-8", "claim2-8", "", validSecretClass, "content2-8", &False, metaTimeNow, nil, nil),
expectedSnapshots: newSnapshotArray("snap2-8", "snapuid2-8", "claim2-8", "", validSecretClass, "content2-8", &False, metaTimeNow, nil, newVolumeError("Failed to check and update snapshot: snapshot controller failed to update default/snap2-8 on API server: mock update error")),
expectedEvents: []string{"Warning SnapshotCheckandUpdateFailed"},
initialClaims: newClaimArray("claim2-8", "pvc-uid2-8", "1Gi", "volume2-8", v1.ClaimBound, &classEmpty),
initialVolumes: newVolumeArray("volume2-8", "pv-uid2-8", "pv-handle2-8", "1Gi", "pvc-uid2-8", "claim2-8", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
initialSecrets: []*v1.Secret{secret()},
/*expectedCreateCalls: []createCall{
{
snapshotName: "snapshot-snapuid2-8",
volume: newVolume("volume2-8", "pv-uid2-8", "pv-handle2-8", "1Gi", "pvc-uid2-8", "claim2-8", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
parameters: class5Parameters,
secrets: map[string]string{"foo": "bar"},
// information to return
driverName: mockDriverName,
size: defaultSize,
snapshotId: "sid2-8",
creationTime: timeNow,
readyToUse: true,
},
},*/ /*
errors: []reactorError{
// Inject error to the first client.VolumesnapshotV1beta1().VolumeSnapshots().Update call.
// All other calls will succeed.
{"update", "volumesnapshots", errors.New("mock update error")},
},
test: testSyncSnapshot,
},
},*/
{
name: "2-9 - fail on status update as there is not pvc provided",
initialContents: newContentArray("content2-9", "snapuid2-9", "snap2-9", "sid2-9", validSecretClass, "", "", deletionPolicy, nil, nil, false),
expectedContents: newContentArray("content2-9", "snapuid2-9", "snap2-9", "sid2-9", validSecretClass, "", "", deletionPolicy, nil, nil, false),
initialSnapshots: newSnapshotArray("snap2-9", "snapuid2-9", "claim2-9", "", validSecretClass, "", &False, nil, nil, nil),
expectedSnapshots: newSnapshotArray("snap2-9", "snapuid2-9", "claim2-9", "", validSecretClass, "", &False, nil, nil, newVolumeError("Failed to check and update snapshot: failed to get input parameters to create snapshot snap2-9: \"failed to retrieve PVC claim2-9 from the lister: \\\"persistentvolumeclaim \\\\\\\"claim2-9\\\\\\\" not found\\\"\"")),
errors: noerrors,
test: testSyncSnapshot,
initialSnapshots: newSnapshotArray("snap2-9", "snapuid2-9", "claim2-9", "", validSecretClass, "", &False, nil, nil, nil, false),
expectedSnapshots: newSnapshotArray("snap2-9", "snapuid2-9", "claim2-9", "", validSecretClass, "content2-9", &True, nil, nil, nil, false),
//expectedSnapshots: newSnapshotArray("snap2-9", "snapuid2-9", "claim2-9", "", validSecretClass, "content2-9", &False, nil, nil, newVolumeError("Failed to check and update snapshot: failed to get input parameters to create snapshot snap2-9: \"failed to retrieve PVC claim2-9 from the lister: \\\"persistentvolumeclaim \\\\\\\"claim2-9\\\\\\\" not found\\\"\"")),
errors: noerrors,
test: testSyncSnapshot,
},
{
name: "2-10 - do not bind when snapshot and content not match",
initialContents: newContentArray("content2-10", "snapuid2-10-x", "snap2-10", "sid2-10", validSecretClass, "", "", deletionPolicy, nil, nil, false),
expectedContents: newContentArray("content2-10", "snapuid2-10-x", "snap2-10", "sid2-10", validSecretClass, "", "", deletionPolicy, nil, nil, false),
initialSnapshots: newSnapshotArray("snap2-10", "snapuid2-10", "claim2-10", "", validSecretClass, "", &False, nil, nil, newVolumeError("mock driver error")),
expectedSnapshots: newSnapshotArray("snap2-10", "snapuid2-10", "claim2-10", "", validSecretClass, "", &False, nil, nil, newVolumeError("mock driver error")),
initialSnapshots: newSnapshotArray("snap2-10", "snapuid2-10", "claim2-10", "", validSecretClass, "", &False, nil, nil, newVolumeError("mock driver error"), false),
expectedSnapshots: newSnapshotArray("snap2-10", "snapuid2-10", "claim2-10", "", validSecretClass, "", &False, nil, nil, newVolumeError("mock driver error"), false),
errors: noerrors,
test: testSyncSnapshot,
},
@@ -217,8 +219,8 @@ func TestSync(t *testing.T) {
name: "3-1 - ready snapshot lost reference to VolumeSnapshotContent",
initialContents: nocontents,
expectedContents: nocontents,
initialSnapshots: newSnapshotArray("snap3-1", "snapuid3-1", "claim3-1", "", validSecretClass, "content3-1", &True, metaTimeNow, nil, nil),
expectedSnapshots: newSnapshotArray("snap3-1", "snapuid3-1", "claim3-1", "", validSecretClass, "content3-1", &False, metaTimeNow, nil, newVolumeError("VolumeSnapshotContent is missing")),
initialSnapshots: newSnapshotArray("snap3-1", "snapuid3-1", "claim3-1", "", validSecretClass, "content3-1", &True, metaTimeNow, nil, nil, false),
expectedSnapshots: newSnapshotArray("snap3-1", "snapuid3-1", "claim3-1", "", validSecretClass, "content3-1", &False, metaTimeNow, nil, newVolumeError("VolumeSnapshotContent is missing"), false),
errors: noerrors,
expectedEvents: []string{"Warning SnapshotContentMissing"},
test: testSyncSnapshot,
@@ -227,8 +229,8 @@ func TestSync(t *testing.T) {
name: "3-2 - ready snapshot bound to none-exist content",
initialContents: nocontents,
expectedContents: nocontents,
initialSnapshots: newSnapshotArray("snap3-2", "snapuid3-2", "claim3-2", "", validSecretClass, "content3-2", &True, metaTimeNow, nil, nil),
expectedSnapshots: newSnapshotArray("snap3-2", "snapuid3-2", "claim3-2", "", validSecretClass, "content3-2", &False, metaTimeNow, nil, newVolumeError("VolumeSnapshotContent is missing")),
initialSnapshots: newSnapshotArray("snap3-2", "snapuid3-2", "claim3-2", "", validSecretClass, "content3-2", &True, metaTimeNow, nil, nil, false),
expectedSnapshots: newSnapshotArray("snap3-2", "snapuid3-2", "claim3-2", "", validSecretClass, "content3-2", &False, metaTimeNow, nil, newVolumeError("VolumeSnapshotContent is missing"), false),
errors: noerrors,
expectedEvents: []string{"Warning SnapshotContentMissing"},
test: testSyncSnapshot,
@@ -237,8 +239,8 @@ func TestSync(t *testing.T) {
name: "3-3 - ready snapshot(everything is well, do nothing)",
initialContents: newContentArray("content3-3", "snapuid3-3", "snap3-3", "sid3-3", validSecretClass, "", "", deletionPolicy, nil, nil, false),
expectedContents: newContentArray("content3-3", "snapuid3-3", "snap3-3", "sid3-3", validSecretClass, "", "", deletionPolicy, nil, nil, false),
initialSnapshots: newSnapshotArray("snap3-3", "snapuid3-3", "claim3-3", "", validSecretClass, "content3-3", &True, metaTimeNow, nil, nil),
expectedSnapshots: newSnapshotArray("snap3-3", "snapuid3-3", "claim3-3", "", validSecretClass, "content3-3", &True, metaTimeNow, nil, nil),
initialSnapshots: newSnapshotArray("snap3-3", "snapuid3-3", "claim3-3", "", validSecretClass, "content3-3", &True, metaTimeNow, nil, nil, false),
expectedSnapshots: newSnapshotArray("snap3-3", "snapuid3-3", "claim3-3", "", validSecretClass, "content3-3", &True, metaTimeNow, nil, nil, false),
errors: noerrors,
test: testSyncSnapshot,
},
@@ -246,12 +248,12 @@ func TestSync(t *testing.T) {
name: "3-4 - ready snapshot misbound to VolumeSnapshotContent",
initialContents: newContentArray("content3-4", "snapuid3-4-x", "snap3-4", "sid3-4", validSecretClass, "", "", deletionPolicy, nil, nil, false),
expectedContents: newContentArray("content3-4", "snapuid3-4-x", "snap3-4", "sid3-4", validSecretClass, "", "", deletionPolicy, nil, nil, false),
initialSnapshots: newSnapshotArray("snap3-4", "snapuid3-4", "claim3-4", "", validSecretClass, "content3-4", &True, metaTimeNow, nil, nil),
expectedSnapshots: newSnapshotArray("snap3-4", "snapuid3-4", "claim3-4", "", validSecretClass, "content3-4", &False, metaTimeNow, nil, newVolumeError("VolumeSnapshotContent is not bound to the VolumeSnapshot correctly")),
initialSnapshots: newSnapshotArray("snap3-4", "snapuid3-4", "claim3-4", "", validSecretClass, "content3-4", &True, metaTimeNow, nil, nil, false),
expectedSnapshots: newSnapshotArray("snap3-4", "snapuid3-4", "claim3-4", "", validSecretClass, "content3-4", &False, metaTimeNow, nil, newVolumeError("VolumeSnapshotContent is not bound to the VolumeSnapshot correctly"), false),
errors: noerrors,
test: testSyncSnapshot,
},
{
/*{
name: "3-5 - snapshot bound to content in which the driver does not match",
initialContents: newContentWithUnmatchDriverArray("content3-5", "snapuid3-5", "snap3-5", "sid3-5", validSecretClass, "", "", deletionPolicy, nil, nil, false),
expectedContents: nocontents,
@@ -260,6 +262,42 @@ func TestSync(t *testing.T) {
expectedEvents: []string{"Warning SnapshotContentMissing"},
errors: noerrors,
test: testSyncSnapshotError,
},*/
{
name: "4-1 - content bound to snapshot, snapshot status missing and rebuilt",
initialContents: newContentArrayWithReadyToUse("content2-5", "snapuid2-5", "snap2-5", "sid2-5", validSecretClass, "", "", deletionPolicy, nil, &size, &True, false),
expectedContents: newContentArrayWithReadyToUse("content2-5", "snapuid2-5", "snap2-5", "sid2-5", validSecretClass, "", "", deletionPolicy, nil, &size, &True, false),
initialSnapshots: newSnapshotArray("snap2-5", "snapuid2-5", "claim2-5", "", validSecretClass, "", &False, nil, nil, nil, true),
expectedSnapshots: newSnapshotArray("snap2-5", "snapuid2-5", "claim2-5", "", validSecretClass, "content2-5", &True, nil, getSize(1), nil, false),
initialClaims: newClaimArray("claim2-5", "pvc-uid2-5", "1Gi", "volume2-5", v1.ClaimBound, &classEmpty),
initialVolumes: newVolumeArray("volume2-5", "pv-uid2-5", "pv-handle2-5", "1Gi", "pvc-uid2-5", "claim2-5", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
initialSecrets: []*v1.Secret{secret()},
errors: noerrors,
test: testSyncSnapshot,
},
{
name: "4-2 - snapshot and content bound, ReadyToUse in snapshot status missing and rebuilt",
initialContents: newContentArrayWithReadyToUse("content2-5", "snapuid2-5", "snap2-5", "sid2-5", validSecretClass, "", "", deletionPolicy, nil, nil, &True, false),
expectedContents: newContentArrayWithReadyToUse("content2-5", "snapuid2-5", "snap2-5", "sid2-5", validSecretClass, "", "", deletionPolicy, nil, nil, &True, false),
initialSnapshots: newSnapshotArray("snap2-5", "snapuid2-5", "claim2-5", "", validSecretClass, "content2-5", &False, nil, nil, nil, false),
expectedSnapshots: newSnapshotArray("snap2-5", "snapuid2-5", "claim2-5", "", validSecretClass, "content2-5", &True, nil, nil, nil, false),
initialClaims: newClaimArray("claim2-5", "pvc-uid2-5", "1Gi", "volume2-5", v1.ClaimBound, &classEmpty),
initialVolumes: newVolumeArray("volume2-5", "pv-uid2-5", "pv-handle2-5", "1Gi", "pvc-uid2-5", "claim2-5", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
initialSecrets: []*v1.Secret{secret()},
errors: noerrors,
test: testSyncSnapshot,
},
{
name: "4-3 - content bound to snapshot, fields in snapshot status missing and rebuilt",
initialContents: newContentArrayWithReadyToUse("content2-5", "snapuid2-5", "snap2-5", "sid2-5", validSecretClass, "", "", deletionPolicy, nil, &size, &True, false),
expectedContents: newContentArrayWithReadyToUse("content2-5", "snapuid2-5", "snap2-5", "sid2-5", validSecretClass, "", "", deletionPolicy, nil, &size, &True, false),
initialSnapshots: newSnapshotArray("snap2-5", "snapuid2-5", "claim2-5", "", validSecretClass, "", &False, nil, nil, nil, false),
expectedSnapshots: newSnapshotArray("snap2-5", "snapuid2-5", "claim2-5", "", validSecretClass, "content2-5", &True, nil, getSize(1), nil, false),
initialClaims: newClaimArray("claim2-5", "pvc-uid2-5", "1Gi", "volume2-5", v1.ClaimBound, &classEmpty),
initialVolumes: newVolumeArray("volume2-5", "pv-uid2-5", "pv-handle2-5", "1Gi", "pvc-uid2-5", "claim2-5", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
initialSecrets: []*v1.Secret{secret()},
errors: noerrors,
test: testSyncSnapshot,
},
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,47 @@
/*
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 (
"testing"
"time"
)
func TestSyncContent(t *testing.T) {
var tests []controllerTest
tests = append(tests, controllerTest{
name: "Basic content create ready to use",
initialContents: newContentArrayWithReadyToUse("content1-1", "snapuid1-1", "snap1-1", "sid1-1", defaultClass, "", "", retainPolicy, nil, &defaultSize, &False, true),
expectedContents: newContentArrayWithReadyToUse("content1-1", "snapuid1-1", "snap1-1", "sid1-1", defaultClass, "", "", retainPolicy, nil, &defaultSize, &True, true),
expectedEvents: noevents,
expectedCreateCalls: []createCall{
{
snapshotName: "snapshot-snapuid1-1",
driverName: mockDriverName,
snapshotId: "snapuid1-1",
creationTime: timeNow,
readyToUse: true,
},
},
expectedListCalls: []listCall{{"sid1-1", true, time.Now(), 1, nil}},
errors: noerrors,
test: testSyncContent,
})
runSyncContentTests(t, tests, snapshotClasses)
}

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
package sidecar_controller
import (
"context"
@@ -24,13 +24,12 @@ import (
crdv1 "github.com/kubernetes-csi/external-snapshotter/pkg/apis/volumesnapshot/v1beta1"
"github.com/kubernetes-csi/external-snapshotter/pkg/snapshotter"
v1 "k8s.io/api/core/v1"
"github.com/kubernetes-csi/external-snapshotter/pkg/utils"
)
// Handler is responsible for handling VolumeSnapshot events from informer.
type Handler interface {
CreateSnapshot(snapshot *crdv1.VolumeSnapshot, volume *v1.PersistentVolume, parameters map[string]string, snapshotterCredentials map[string]string) (string, string, time.Time, int64, bool, error)
CreateSnapshot(content *crdv1.VolumeSnapshotContent, parameters map[string]string, snapshotterCredentials map[string]string) (string, string, time.Time, int64, bool, error)
DeleteSnapshot(content *crdv1.VolumeSnapshotContent, snapshotterCredentials map[string]string) error
GetSnapshotStatus(content *crdv1.VolumeSnapshotContent) (bool, time.Time, int64, error)
}
@@ -58,27 +57,45 @@ func NewCSIHandler(
}
}
func (handler *csiHandler) CreateSnapshot(snapshot *crdv1.VolumeSnapshot, volume *v1.PersistentVolume, parameters map[string]string, snapshotterCredentials map[string]string) (string, string, time.Time, int64, bool, error) {
func (handler *csiHandler) CreateSnapshot(content *crdv1.VolumeSnapshotContent, parameters map[string]string, snapshotterCredentials map[string]string) (string, string, time.Time, int64, bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), handler.timeout)
defer cancel()
snapshotName, err := makeSnapshotName(handler.snapshotNamePrefix, string(snapshot.UID), handler.snapshotNameUUIDLength)
if content.Spec.VolumeSnapshotRef.UID == "" {
return "", "", time.Time{}, 0, false, fmt.Errorf("cannot create snapshot. Snapshot content %s not bound to a snapshot", content.Name)
}
if content.Spec.Source.VolumeHandle == nil {
return "", "", time.Time{}, 0, false, fmt.Errorf("cannot create snapshot. Volume handle not found in snapshot content %s", content.Name)
}
snapshotName, err := makeSnapshotName(handler.snapshotNamePrefix, string(content.Spec.VolumeSnapshotRef.UID), handler.snapshotNameUUIDLength)
if err != nil {
return "", "", time.Time{}, 0, false, err
}
newParameters, err := removePrefixedParameters(parameters)
newParameters, err := utils.RemovePrefixedParameters(parameters)
if err != nil {
return "", "", time.Time{}, 0, false, fmt.Errorf("failed to remove CSI Parameters of prefixed keys: %v", err)
}
return handler.snapshotter.CreateSnapshot(ctx, snapshotName, volume, newParameters, snapshotterCredentials)
return handler.snapshotter.CreateSnapshot(ctx, snapshotName, *content.Spec.Source.VolumeHandle, newParameters, snapshotterCredentials)
}
func (handler *csiHandler) DeleteSnapshot(content *crdv1.VolumeSnapshotContent, snapshotterCredentials map[string]string) error {
ctx, cancel := context.WithTimeout(context.Background(), handler.timeout)
defer cancel()
err := handler.snapshotter.DeleteSnapshot(ctx, *content.Status.SnapshotHandle, snapshotterCredentials)
var snapshotHandle string
var err error
if content.Status != nil && content.Status.SnapshotHandle != nil {
snapshotHandle = *content.Status.SnapshotHandle
} else if content.Spec.Source.SnapshotHandle != nil {
snapshotHandle = *content.Spec.Source.SnapshotHandle
} else {
return fmt.Errorf("failed to delete snapshot content %s: snapshotHandle is missing", content.Name)
}
err = handler.snapshotter.DeleteSnapshot(ctx, snapshotHandle, snapshotterCredentials)
if err != nil {
return fmt.Errorf("failed to delete snapshot content %s: %q", content.Name, err)
}
@@ -90,9 +107,19 @@ func (handler *csiHandler) GetSnapshotStatus(content *crdv1.VolumeSnapshotConten
ctx, cancel := context.WithTimeout(context.Background(), handler.timeout)
defer cancel()
csiSnapshotStatus, timestamp, size, err := handler.snapshotter.GetSnapshotStatus(ctx, *content.Status.SnapshotHandle)
var snapshotHandle string
var err error
if content.Status != nil && content.Status.SnapshotHandle != nil {
snapshotHandle = *content.Status.SnapshotHandle
} else if content.Spec.Source.SnapshotHandle != nil {
snapshotHandle = *content.Spec.Source.SnapshotHandle
} else {
return false, time.Time{}, 0, fmt.Errorf("failed to list snapshot for content %s: snapshotHandle is missing", content.Name)
}
csiSnapshotStatus, timestamp, size, err := handler.snapshotter.GetSnapshotStatus(ctx, snapshotHandle)
if err != nil {
return false, time.Time{}, 0, fmt.Errorf("failed to list snapshot content %s: %q", content.Name, err)
return false, time.Time{}, 0, fmt.Errorf("failed to list snapshot for content %s: %q", content.Name, err)
}
return csiSnapshotStatus, timestamp, size, nil

View File

@@ -0,0 +1,937 @@
/*
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 (
"context"
"errors"
"fmt"
"reflect"
"strconv"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
crdv1 "github.com/kubernetes-csi/external-snapshotter/pkg/apis/volumesnapshot/v1beta1"
clientset "github.com/kubernetes-csi/external-snapshotter/pkg/client/clientset/versioned"
"github.com/kubernetes-csi/external-snapshotter/pkg/client/clientset/versioned/fake"
snapshotscheme "github.com/kubernetes-csi/external-snapshotter/pkg/client/clientset/versioned/scheme"
informers "github.com/kubernetes-csi/external-snapshotter/pkg/client/informers/externalversions"
storagelisters "github.com/kubernetes-csi/external-snapshotter/pkg/client/listers/volumesnapshot/v1beta1"
"github.com/kubernetes-csi/external-snapshotter/pkg/utils"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes"
kubefake "k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/kubernetes/scheme"
core "k8s.io/client-go/testing"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
"k8s.io/klog"
)
// This is a unit test framework for snapshot sidecar controller.
// It fills the controller with test contents and can simulate these
// scenarios:
// 1) Call syncContent once.
// 2) Call syncContent several times (both simulating "content
// modified" events and periodic sync), until the controller settles down and
// does not modify anything.
// 3) Simulate almost real API server/etcd and call add/update/delete
// content.
// In all these scenarios, when the test finishes, the framework can compare
// resulting contents with list of expected contents and report
// differences.
// controllerTest contains a single controller test input.
// Each test has initial set of contents that are filled into the
// controller before the test starts. The test then contains a reference to
// function to call as the actual test. Available functions are:
// - testSyncContent - calls syncContent on the first content in initialContents.
// - any custom function for specialized tests.
// The test then contains list of contents that are expected at the end
// of the test and list of generated events.
type controllerTest struct {
// Name of the test, for logging
name string
// Initial content of controller content cache.
initialContents []*crdv1.VolumeSnapshotContent
// Expected content of controller content cache at the end of the test.
expectedContents []*crdv1.VolumeSnapshotContent
// Initial content of controller Secret cache.
initialSecrets []*v1.Secret
// Expected events - any event with prefix will pass, we don't check full
// event message.
expectedEvents []string
// Errors to produce on matching action
errors []reactorError
// List of expected CSI Create snapshot calls
expectedCreateCalls []createCall
// List of expected CSI Delete snapshot calls
expectedDeleteCalls []deleteCall
// List of expected CSI list snapshot calls
expectedListCalls []listCall
// Function to call as the test.
test testCall
expectSuccess bool
}
type testCall func(ctrl *csiSnapshotSideCarController, reactor *snapshotReactor, test controllerTest) error
const testNamespace = "default"
const mockDriverName = "csi-mock-plugin"
var errVersionConflict = errors.New("VersionError")
var nocontents []*crdv1.VolumeSnapshotContent
var noevents = []string{}
var noerrors = []reactorError{}
// snapshotReactor is a core.Reactor that simulates etcd and API server. It
// stores:
// - Latest version of snapshots contents saved by the controller.
// - Queue of all saves (to simulate "content updated" events). This queue
// contains all intermediate state of an object. This queue will then contain both
// updates as separate entries.
// - Number of changes since the last call to snapshotReactor.syncAll().
// - Optionally, content watcher which should be the same ones
// used by the controller. Any time an event function like deleteContentEvent
// is called to simulate an event, the reactor's stores are updated and the
// controller is sent the event via the fake watcher.
// - Optionally, list of error that should be returned by reactor, simulating
// etcd / API server failures. These errors are evaluated in order and every
// error is returned only once. I.e. when the reactor finds matching
// reactorError, it return appropriate error and removes the reactorError from
// the list.
type snapshotReactor struct {
secrets map[string]*v1.Secret
contents map[string]*crdv1.VolumeSnapshotContent
changedObjects []interface{}
changedSinceLastSync int
ctrl *csiSnapshotSideCarController
fakeContentWatch *watch.FakeWatcher
lock sync.Mutex
errors []reactorError
}
// reactorError is an error that is returned by test reactor (=simulated
// etcd+/API server) when an action performed by the reactor matches given verb
// ("get", "update", "create", "delete" or "*"") on given resource
// ("volumesnapshotcontents" or "*").
type reactorError struct {
verb string
resource string
error error
}
func withContentFinalizer(content *crdv1.VolumeSnapshotContent) *crdv1.VolumeSnapshotContent {
content.ObjectMeta.Finalizers = append(content.ObjectMeta.Finalizers, utils.VolumeSnapshotContentFinalizer)
return content
}
// React is a callback called by fake kubeClient from the controller.
// In other words, every snapshot/content change performed by the controller ends
// here.
// This callback checks versions of the updated objects and refuse those that
// are too old (simulating real etcd).
// All updated objects are stored locally to keep track of object versions and
// to evaluate test results.
// All updated objects are also inserted into changedObjects queue and
// optionally sent back to the controller via its watchers.
func (r *snapshotReactor) React(action core.Action) (handled bool, ret runtime.Object, err error) {
r.lock.Lock()
defer r.lock.Unlock()
klog.V(4).Infof("reactor got operation %q on %q", action.GetVerb(), action.GetResource())
// Inject error when requested
err = r.injectReactError(action)
if err != nil {
return true, nil, err
}
// Test did not request to inject an error, continue simulating API server.
switch {
case action.Matches("create", "volumesnapshotcontents"):
obj := action.(core.UpdateAction).GetObject()
content := obj.(*crdv1.VolumeSnapshotContent)
// check the content does not exist
_, found := r.contents[content.Name]
if found {
return true, nil, fmt.Errorf("cannot create content %s: content already exists", content.Name)
}
// Store the updated object to appropriate places.
r.contents[content.Name] = content
r.changedObjects = append(r.changedObjects, content)
r.changedSinceLastSync++
klog.V(5).Infof("created content %s", content.Name)
return true, content, nil
case action.Matches("update", "volumesnapshotcontents"):
obj := action.(core.UpdateAction).GetObject()
content := obj.(*crdv1.VolumeSnapshotContent)
// Check and bump object version
storedContent, found := r.contents[content.Name]
if found {
storedVer, _ := strconv.Atoi(storedContent.ResourceVersion)
requestedVer, _ := strconv.Atoi(content.ResourceVersion)
if storedVer != requestedVer {
return true, obj, errVersionConflict
}
// Don't modify the existing object
content = content.DeepCopy()
content.ResourceVersion = strconv.Itoa(storedVer + 1)
} else {
return true, nil, fmt.Errorf("cannot update content %s: content not found", content.Name)
}
// Store the updated object to appropriate places.
r.contents[content.Name] = content
r.changedObjects = append(r.changedObjects, content)
r.changedSinceLastSync++
klog.V(4).Infof("saved updated content %s", content.Name)
return true, content, nil
case action.Matches("get", "volumesnapshotcontents"):
name := action.(core.GetAction).GetName()
content, found := r.contents[name]
if found {
klog.V(4).Infof("GetVolume: found %s", content.Name)
return true, content, nil
}
klog.V(4).Infof("GetVolume: content %s not found", name)
return true, nil, fmt.Errorf("cannot find content %s", name)
case action.Matches("delete", "volumesnapshotcontents"):
name := action.(core.DeleteAction).GetName()
klog.V(4).Infof("deleted content %s", name)
_, found := r.contents[name]
if found {
delete(r.contents, name)
r.changedSinceLastSync++
return true, nil, nil
}
return true, nil, fmt.Errorf("cannot delete content %s: not found", name)
case action.Matches("get", "secrets"):
name := action.(core.GetAction).GetName()
secret, found := r.secrets[name]
if found {
klog.V(4).Infof("GetSecret: found %s", secret.Name)
return true, secret, nil
}
klog.V(4).Infof("GetSecret: secret %s not found", name)
return true, nil, fmt.Errorf("cannot find secret %s", name)
}
return false, nil, nil
}
// injectReactError returns an error when the test requested given action to
// fail. nil is returned otherwise.
func (r *snapshotReactor) injectReactError(action core.Action) error {
if len(r.errors) == 0 {
// No more errors to inject, everything should succeed.
return nil
}
for i, expected := range r.errors {
klog.V(4).Infof("trying to match %q %q with %q %q", expected.verb, expected.resource, action.GetVerb(), action.GetResource())
if action.Matches(expected.verb, expected.resource) {
// That's the action we're waiting for, remove it from injectedErrors
r.errors = append(r.errors[:i], r.errors[i+1:]...)
klog.V(4).Infof("reactor found matching error at index %d: %q %q, returning %v", i, expected.verb, expected.resource, expected.error)
return expected.error
}
}
return nil
}
// checkContents compares all expectedContents with set of contents at the end of
// the test and reports differences.
func (r *snapshotReactor) checkContents(expectedContents []*crdv1.VolumeSnapshotContent) error {
r.lock.Lock()
defer r.lock.Unlock()
expectedMap := make(map[string]*crdv1.VolumeSnapshotContent)
gotMap := make(map[string]*crdv1.VolumeSnapshotContent)
// Clear any ResourceVersion from both sets
for _, v := range expectedContents {
// Don't modify the existing object
v := v.DeepCopy()
v.ResourceVersion = ""
v.Spec.VolumeSnapshotRef.ResourceVersion = ""
v.Status.CreationTime = nil
expectedMap[v.Name] = v
}
for _, v := range r.contents {
// We must clone the content because of golang race check - it was
// written by the controller without any locks on it.
v := v.DeepCopy()
v.ResourceVersion = ""
v.Spec.VolumeSnapshotRef.ResourceVersion = ""
v.Status.CreationTime = nil
gotMap[v.Name] = v
}
if !reflect.DeepEqual(expectedMap, gotMap) {
// Print ugly but useful diff of expected and received objects for
// easier debugging.
return fmt.Errorf("content check failed [A-expected, B-got]: %s", diff.ObjectDiff(expectedMap, gotMap))
}
return nil
}
// checkEvents compares all expectedEvents with events generated during the test
// and reports differences.
func checkEvents(t *testing.T, expectedEvents []string, ctrl *csiSnapshotSideCarController) error {
var err error
// Read recorded events - wait up to 1 minute to get all the expected ones
// (just in case some goroutines are slower with writing)
timer := time.NewTimer(time.Minute)
defer timer.Stop()
fakeRecorder := ctrl.eventRecorder.(*record.FakeRecorder)
gotEvents := []string{}
finished := false
for len(gotEvents) < len(expectedEvents) && !finished {
select {
case event, ok := <-fakeRecorder.Events:
if ok {
klog.V(5).Infof("event recorder got event %s", event)
gotEvents = append(gotEvents, event)
} else {
klog.V(5).Infof("event recorder finished")
finished = true
}
case _, _ = <-timer.C:
klog.V(5).Infof("event recorder timeout")
finished = true
}
}
// Evaluate the events
for i, expected := range expectedEvents {
if len(gotEvents) <= i {
t.Errorf("Event %q not emitted", expected)
err = fmt.Errorf("Events do not match")
continue
}
received := gotEvents[i]
if !strings.HasPrefix(received, expected) {
t.Errorf("Unexpected event received, expected %q, got %q", expected, received)
err = fmt.Errorf("Events do not match")
}
}
for i := len(expectedEvents); i < len(gotEvents); i++ {
t.Errorf("Unexpected event received: %q", gotEvents[i])
err = fmt.Errorf("Events do not match")
}
return err
}
// popChange returns one recorded updated object, either *crdv1.VolumeSnapshotContent
// or *crdv1.VolumeSnapshot. Returns nil when there are no changes.
func (r *snapshotReactor) popChange() interface{} {
r.lock.Lock()
defer r.lock.Unlock()
if len(r.changedObjects) == 0 {
return nil
}
// For debugging purposes, print the queue
for _, obj := range r.changedObjects {
switch obj.(type) {
case *crdv1.VolumeSnapshotContent:
vol, _ := obj.(*crdv1.VolumeSnapshotContent)
klog.V(4).Infof("reactor queue: %s", vol.Name)
}
}
// Pop the first item from the queue and return it
obj := r.changedObjects[0]
r.changedObjects = r.changedObjects[1:]
return obj
}
// syncAll simulates the controller periodic sync of contents. It
// simply adds all these objects to the internal queue of updates. This method
// should be used when the test manually calls syncContent. Test that
// use real controller loop (ctrl.Run()) will get periodic sync automatically.
func (r *snapshotReactor) syncAll() {
r.lock.Lock()
defer r.lock.Unlock()
for _, v := range r.contents {
r.changedObjects = append(r.changedObjects, v)
}
r.changedSinceLastSync = 0
}
func (r *snapshotReactor) getChangeCount() int {
r.lock.Lock()
defer r.lock.Unlock()
return r.changedSinceLastSync
}
// waitForIdle waits until all tests, controllers and other goroutines do their
// job and no new actions are registered for 10 milliseconds.
func (r *snapshotReactor) waitForIdle() {
r.ctrl.runningOperations.WaitForCompletion()
// Check every 10ms if the controller does something and stop if it's
// idle.
oldChanges := -1
for {
time.Sleep(10 * time.Millisecond)
changes := r.getChangeCount()
if changes == oldChanges {
// No changes for last 10ms -> controller must be idle.
break
}
oldChanges = changes
}
}
// waitTest waits until all tests, controllers and other goroutines do their
// job and list of current contents/snapshots is equal to list of expected
// contents/snapshots (with ~10 second timeout).
func (r *snapshotReactor) waitTest(test controllerTest) error {
// start with 10 ms, multiply by 2 each step, 10 steps = 10.23 seconds
backoff := wait.Backoff{
Duration: 10 * time.Millisecond,
Jitter: 0,
Factor: 2,
Steps: 10,
}
err := wait.ExponentialBackoff(backoff, func() (done bool, err error) {
// Finish all operations that are in progress
r.ctrl.runningOperations.WaitForCompletion()
// Return 'true' if the reactor reached the expected state
err1 := r.checkContents(test.expectedContents)
if err1 == nil {
return true, nil
}
return false, nil
})
return err
}
// deleteContentEvent simulates that a content has been deleted in etcd and
// the controller receives 'content deleted' event.
func (r *snapshotReactor) deleteContentEvent(content *crdv1.VolumeSnapshotContent) {
r.lock.Lock()
defer r.lock.Unlock()
// Remove the content from list of resulting contents.
delete(r.contents, content.Name)
// Generate deletion event. Cloned content is needed to prevent races (and we
// would get a clone from etcd too).
if r.fakeContentWatch != nil {
r.fakeContentWatch.Delete(content.DeepCopy())
}
}
// addContentEvent simulates that a content has been added in etcd and the
// controller receives 'content added' event.
func (r *snapshotReactor) addContentEvent(content *crdv1.VolumeSnapshotContent) {
r.lock.Lock()
defer r.lock.Unlock()
r.contents[content.Name] = content
// Generate event. No cloning is needed, this snapshot is not stored in the
// controller cache yet.
if r.fakeContentWatch != nil {
r.fakeContentWatch.Add(content)
}
}
// modifyContentEvent simulates that a content has been modified in etcd and the
// controller receives 'content modified' event.
func (r *snapshotReactor) modifyContentEvent(content *crdv1.VolumeSnapshotContent) {
r.lock.Lock()
defer r.lock.Unlock()
r.contents[content.Name] = content
// Generate deletion event. Cloned content is needed to prevent races (and we
// would get a clone from etcd too).
if r.fakeContentWatch != nil {
r.fakeContentWatch.Modify(content.DeepCopy())
}
}
func newSnapshotReactor(kubeClient *kubefake.Clientset, client *fake.Clientset, ctrl *csiSnapshotSideCarController, fakeVolumeWatch, fakeClaimWatch *watch.FakeWatcher, errors []reactorError) *snapshotReactor {
reactor := &snapshotReactor{
secrets: make(map[string]*v1.Secret),
contents: make(map[string]*crdv1.VolumeSnapshotContent),
ctrl: ctrl,
fakeContentWatch: fakeVolumeWatch,
errors: errors,
}
client.AddReactor("create", "volumesnapshotcontents", reactor.React)
client.AddReactor("update", "volumesnapshotcontents", reactor.React)
client.AddReactor("get", "volumesnapshotcontents", reactor.React)
client.AddReactor("delete", "volumesnapshotcontents", reactor.React)
return reactor
}
func alwaysReady() bool { return true }
func newTestController(kubeClient kubernetes.Interface, clientset clientset.Interface,
informerFactory informers.SharedInformerFactory, t *testing.T, test controllerTest) (*csiSnapshotSideCarController, error) {
if informerFactory == nil {
informerFactory = informers.NewSharedInformerFactory(clientset, utils.NoResyncPeriodFunc())
}
// Construct controller
fakeSnapshot := &fakeSnapshotter{
t: t,
listCalls: test.expectedListCalls,
createCalls: test.expectedCreateCalls,
deleteCalls: test.expectedDeleteCalls,
}
ctrl := NewCSISnapshotSideCarController(
clientset,
kubeClient,
mockDriverName,
informerFactory.Snapshot().V1beta1().VolumeSnapshotContents(),
informerFactory.Snapshot().V1beta1().VolumeSnapshotClasses(),
fakeSnapshot,
5*time.Millisecond,
60*time.Second,
"snapshot",
-1,
)
ctrl.eventRecorder = record.NewFakeRecorder(1000)
ctrl.contentListerSynced = alwaysReady
ctrl.classListerSynced = alwaysReady
return ctrl, nil
}
func newContent(contentName, boundToSnapshotUID, boundToSnapshotName, snapshotHandle, snapshotClassName, desiredSnapshotHandle, volumeHandle string,
deletionPolicy crdv1.DeletionPolicy, creationTime, size *int64,
withFinalizer bool, deletionTime *metav1.Time) *crdv1.VolumeSnapshotContent {
var annotations map[string]string
content := crdv1.VolumeSnapshotContent{
ObjectMeta: metav1.ObjectMeta{
Name: contentName,
ResourceVersion: "1",
DeletionTimestamp: deletionTime,
Annotations: annotations,
},
Spec: crdv1.VolumeSnapshotContentSpec{
Driver: mockDriverName,
DeletionPolicy: deletionPolicy,
Source: crdv1.VolumeSnapshotContentSource{
SnapshotHandle: &snapshotHandle,
VolumeHandle: &volumeHandle,
},
},
Status: &crdv1.VolumeSnapshotContentStatus{
CreationTime: creationTime,
RestoreSize: size,
},
}
if deletionTime != nil {
metav1.SetMetaDataAnnotation(&content.ObjectMeta, utils.AnnVolumeSnapshotBeingDeleted, "yes")
}
if snapshotHandle != "" {
content.Status.SnapshotHandle = &snapshotHandle
}
if snapshotClassName != "" {
content.Spec.VolumeSnapshotClassName = &snapshotClassName
}
if volumeHandle != "" {
content.Spec.Source = crdv1.VolumeSnapshotContentSource{
VolumeHandle: &volumeHandle,
}
} else if desiredSnapshotHandle != "" {
content.Spec.Source = crdv1.VolumeSnapshotContentSource{
SnapshotHandle: &desiredSnapshotHandle,
}
}
if boundToSnapshotName != "" {
content.Spec.VolumeSnapshotRef = v1.ObjectReference{
Kind: "VolumeSnapshot",
APIVersion: "snapshot.storage.k8s.io/v1beta1",
UID: types.UID(boundToSnapshotUID),
Namespace: testNamespace,
Name: boundToSnapshotName,
}
}
if withFinalizer {
return withContentFinalizer(&content)
}
return &content
}
func newContentArray(contentName, boundToSnapshotUID, boundToSnapshotName, snapshotHandle, snapshotClassName, desiredSnapshotHandle, volumeHandle string,
deletionPolicy crdv1.DeletionPolicy, size, creationTime *int64,
withFinalizer bool) []*crdv1.VolumeSnapshotContent {
return []*crdv1.VolumeSnapshotContent{
newContent(contentName, boundToSnapshotUID, boundToSnapshotName, snapshotHandle, snapshotClassName, desiredSnapshotHandle, volumeHandle, deletionPolicy, creationTime, size, withFinalizer, nil),
}
}
func newContentArrayWithReadyToUse(contentName, boundToSnapshotUID, boundToSnapshotName, snapshotHandle, snapshotClassName, desiredSnapshotHandle, volumeHandle string,
deletionPolicy crdv1.DeletionPolicy, creationTime, size *int64, readyToUse *bool,
withFinalizer bool) []*crdv1.VolumeSnapshotContent {
content := newContent(contentName, boundToSnapshotUID, boundToSnapshotName, snapshotHandle, snapshotClassName, desiredSnapshotHandle, volumeHandle, deletionPolicy, creationTime, size, withFinalizer, nil)
content.Status.ReadyToUse = readyToUse
return []*crdv1.VolumeSnapshotContent{
content,
}
}
func newContentWithUnmatchDriverArray(contentName, boundToSnapshotUID, boundToSnapshotName, snapshotHandle, snapshotClassName, desiredSnapshotHandle, volumeHandle string,
deletionPolicy crdv1.DeletionPolicy, size, creationTime *int64,
withFinalizer bool) []*crdv1.VolumeSnapshotContent {
content := newContent(contentName, boundToSnapshotUID, boundToSnapshotName, snapshotHandle, snapshotClassName, desiredSnapshotHandle, volumeHandle, deletionPolicy, size, creationTime, withFinalizer, nil)
content.Spec.Driver = "fake"
return []*crdv1.VolumeSnapshotContent{
content,
}
}
func newContentArrayWithDeletionTimestamp(contentName, boundToSnapshotUID, boundToSnapshotName, snapshotHandle, snapshotClassName, desiredSnapshotHandle, volumeHandle string,
deletionPolicy crdv1.DeletionPolicy, size, creationTime *int64,
withFinalizer bool, deletionTime *metav1.Time) []*crdv1.VolumeSnapshotContent {
return []*crdv1.VolumeSnapshotContent{
newContent(contentName, boundToSnapshotUID, boundToSnapshotName, snapshotHandle, snapshotClassName, desiredSnapshotHandle, volumeHandle, deletionPolicy, creationTime, size, withFinalizer, deletionTime),
}
}
func testSyncContent(ctrl *csiSnapshotSideCarController, reactor *snapshotReactor, test controllerTest) error {
return ctrl.syncContent(test.initialContents[0])
}
func testSyncContentError(ctrl *csiSnapshotSideCarController, reactor *snapshotReactor, test controllerTest) error {
err := ctrl.syncContent(test.initialContents[0])
if err != nil {
return nil
}
return fmt.Errorf("syncSnapshotContent succeeded when failure was expected")
}
var (
classEmpty string
classGold = "gold"
classSilver = "silver"
classNonExisting = "non-existing"
defaultClass = "default-class"
emptySecretClass = "empty-secret-class"
invalidSecretClass = "invalid-secret-class"
validSecretClass = "valid-secret-class"
sameDriver = "sameDriver"
diffDriver = "diffDriver"
noClaim = ""
noBoundUID = ""
noVolume = ""
)
// wrapTestWithInjectedOperation returns a testCall that:
// - starts the controller and lets it run original testCall until
// scheduleOperation() call. It blocks the controller there and calls the
// injected function to simulate that something is happening when the
// controller waits for the operation lock. Controller is then resumed and we
// check how it behaves.
func wrapTestWithInjectedOperation(toWrap testCall, injectBeforeOperation func(ctrl *csiSnapshotSideCarController, reactor *snapshotReactor)) testCall {
return func(ctrl *csiSnapshotSideCarController, reactor *snapshotReactor, test controllerTest) error {
// Inject a hook before async operation starts
klog.V(4).Infof("reactor:injecting call")
injectBeforeOperation(ctrl, reactor)
// Run the tested function (typically syncContent) in a
// separate goroutine.
var testError error
var testFinished int32
go func() {
testError = toWrap(ctrl, reactor, test)
// Let the "main" test function know that syncContent has finished.
atomic.StoreInt32(&testFinished, 1)
}()
// Wait for the controller to finish the test function.
for atomic.LoadInt32(&testFinished) == 0 {
time.Sleep(time.Millisecond * 10)
}
return testError
}
}
func evaluateTestResults(ctrl *csiSnapshotSideCarController, reactor *snapshotReactor, test controllerTest, t *testing.T) {
// Evaluate results
if test.expectedContents != nil {
if err := reactor.checkContents(test.expectedContents); err != nil {
t.Errorf("Test %q: %v", test.name, err)
}
}
if err := checkEvents(t, test.expectedEvents, ctrl); err != nil {
t.Errorf("Test %q: %v", test.name, err)
}
}
// Test single call to syncContent methods.
// For all tests:
// 1. Fill in the controller with initial data
// 2. Call the tested function (syncContent) via
// controllerTest.testCall *once*.
// 3. Compare resulting contents and snapshots with expected contents and snapshots.
func runSyncContentTests(t *testing.T, tests []controllerTest, snapshotClasses []*crdv1.VolumeSnapshotClass) {
snapshotscheme.AddToScheme(scheme.Scheme)
for _, test := range tests {
klog.V(4).Infof("starting test %q", test.name)
// Initialize the controller
kubeClient := &kubefake.Clientset{}
client := &fake.Clientset{}
ctrl, err := newTestController(kubeClient, client, nil, t, test)
if err != nil {
t.Fatalf("Test %q construct persistent content failed: %v", test.name, err)
}
reactor := newSnapshotReactor(kubeClient, client, ctrl, nil, nil, test.errors)
for _, content := range test.initialContents {
if ctrl.isDriverMatch(test.initialContents[0]) {
ctrl.contentStore.Add(content)
reactor.contents[content.Name] = content
}
}
for _, secret := range test.initialSecrets {
reactor.secrets[secret.Name] = secret
}
// Inject classes into controller via a custom lister.
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
for _, class := range snapshotClasses {
indexer.Add(class)
}
ctrl.classLister = storagelisters.NewVolumeSnapshotClassLister(indexer)
// Run the tested functions
err = test.test(ctrl, reactor, test)
if err != nil {
t.Errorf("Test %q failed: %v", test.name, err)
}
// Wait for the target state
err = reactor.waitTest(test)
if err != nil {
t.Errorf("Test %q failed: %v", test.name, err)
}
evaluateTestResults(ctrl, reactor, test, t)
}
}
func getSize(size int64) *resource.Quantity {
return resource.NewQuantity(size, resource.BinarySI)
}
func emptySecret() *v1.Secret {
return &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "emptysecret",
Namespace: "default",
},
}
}
func secret() *v1.Secret {
return &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "secret",
Namespace: "default",
},
Data: map[string][]byte{
"foo": []byte("bar"),
},
}
}
func secretAnnotations() map[string]string {
return map[string]string{
utils.AnnDeletionSecretRefName: "secret",
utils.AnnDeletionSecretRefNamespace: "default",
}
}
func emptyNamespaceSecretAnnotations() map[string]string {
return map[string]string{
utils.AnnDeletionSecretRefName: "name",
utils.AnnDeletionSecretRefNamespace: "",
}
}
// this refers to emptySecret(), which is missing data.
func emptyDataSecretAnnotations() map[string]string {
return map[string]string{
utils.AnnDeletionSecretRefName: "emptysecret",
utils.AnnDeletionSecretRefNamespace: "default",
}
}
type listCall struct {
snapshotID string
// information to return
readyToUse bool
createTime time.Time
size int64
err error
}
type deleteCall struct {
snapshotID string
secrets map[string]string
err error
}
type createCall struct {
// expected request parameter
snapshotName string
volumeHandle string
parameters map[string]string
secrets map[string]string
// information to return
driverName string
snapshotId string
creationTime time.Time
size int64
readyToUse bool
err error
}
// Fake SnapShotter implementation that check that Attach/Detach is called
// with the right parameters and it returns proper error code and metadata.
type fakeSnapshotter struct {
createCalls []createCall
createCallCounter int
deleteCalls []deleteCall
deleteCallCounter int
listCalls []listCall
listCallCounter int
t *testing.T
}
func (f *fakeSnapshotter) CreateSnapshot(ctx context.Context, snapshotName string, volumeHandle string, parameters map[string]string, snapshotterCredentials map[string]string) (string, string, time.Time, int64, bool, error) {
if f.createCallCounter >= len(f.createCalls) {
f.t.Errorf("Unexpected CSI Create Snapshot call: snapshotName=%s, volumeHandle=%v, index: %d, calls: %+v", snapshotName, volumeHandle, f.createCallCounter, f.createCalls)
return "", "", time.Time{}, 0, false, fmt.Errorf("unexpected call")
}
call := f.createCalls[f.createCallCounter]
f.createCallCounter++
var err error
if call.snapshotName != snapshotName {
f.t.Errorf("Wrong CSI Create Snapshot call: snapshotName=%s, volumeHandle=%s, expected snapshotName: %s", snapshotName, volumeHandle, call.snapshotName)
err = fmt.Errorf("unexpected create snapshot call")
}
if call.volumeHandle != volumeHandle {
f.t.Errorf("Wrong CSI Create Snapshot call: snapshotName=%s, volumeHandle=%s, expected volumeHandle: %s", snapshotName, volumeHandle, call.volumeHandle)
err = fmt.Errorf("unexpected create snapshot call")
}
if !reflect.DeepEqual(call.parameters, parameters) && !(len(call.parameters) == 0 && len(parameters) == 0) {
f.t.Errorf("Wrong CSI Create Snapshot call: snapshotName=%s, volumeHandle=%s, expected parameters %+v, got %+v", snapshotName, volumeHandle, call.parameters, parameters)
err = fmt.Errorf("unexpected create snapshot call")
}
if !reflect.DeepEqual(call.secrets, snapshotterCredentials) {
f.t.Errorf("Wrong CSI Create Snapshot call: snapshotName=%s, volumeHandle=%s, expected secrets %+v, got %+v", snapshotName, volumeHandle, call.secrets, snapshotterCredentials)
err = fmt.Errorf("unexpected create snapshot call")
}
if err != nil {
return "", "", time.Time{}, 0, false, fmt.Errorf("unexpected call")
}
return call.driverName, call.snapshotId, call.creationTime, call.size, call.readyToUse, call.err
}
func (f *fakeSnapshotter) DeleteSnapshot(ctx context.Context, snapshotID string, snapshotterCredentials map[string]string) error {
if f.deleteCallCounter >= len(f.deleteCalls) {
f.t.Errorf("Unexpected CSI Delete Snapshot call: snapshotID=%s, index: %d, calls: %+v", snapshotID, f.createCallCounter, f.createCalls)
return fmt.Errorf("unexpected DeleteSnapshot call")
}
call := f.deleteCalls[f.deleteCallCounter]
f.deleteCallCounter++
var err error
if call.snapshotID != snapshotID {
f.t.Errorf("Wrong CSI Create Snapshot call: snapshotID=%s, expected snapshotID: %s", snapshotID, call.snapshotID)
err = fmt.Errorf("unexpected Delete snapshot call")
}
if !reflect.DeepEqual(call.secrets, snapshotterCredentials) {
f.t.Errorf("Wrong CSI Delete Snapshot call: snapshotID=%s, expected secrets %+v, got %+v", snapshotID, call.secrets, snapshotterCredentials)
err = fmt.Errorf("unexpected Delete Snapshot call")
}
if err != nil {
return fmt.Errorf("unexpected call")
}
return call.err
}
func (f *fakeSnapshotter) GetSnapshotStatus(ctx context.Context, snapshotID string) (bool, time.Time, int64, error) {
if f.listCallCounter >= len(f.listCalls) {
f.t.Errorf("Unexpected CSI list Snapshot call: snapshotID=%s, index: %d, calls: %+v", snapshotID, f.createCallCounter, f.createCalls)
return false, time.Time{}, 0, fmt.Errorf("unexpected call")
}
call := f.listCalls[f.listCallCounter]
f.listCallCounter++
var err error
if call.snapshotID != snapshotID {
f.t.Errorf("Wrong CSI List Snapshot call: snapshotID=%s, expected snapshotID: %s", snapshotID, call.snapshotID)
err = fmt.Errorf("unexpected List snapshot call")
}
if err != nil {
return false, time.Time{}, 0, fmt.Errorf("unexpected call")
}
return call.readyToUse, call.createTime, call.size, call.err
}

View File

@@ -0,0 +1,537 @@
/*
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 (
"fmt"
"strings"
"time"
crdv1 "github.com/kubernetes-csi/external-snapshotter/pkg/apis/volumesnapshot/v1beta1"
"github.com/kubernetes-csi/external-snapshotter/pkg/utils"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog"
"k8s.io/kubernetes/pkg/util/goroutinemap"
"k8s.io/kubernetes/pkg/util/goroutinemap/exponentialbackoff"
"k8s.io/kubernetes/pkg/util/slice"
)
// Design:
//
// This is the sidecar controller that is responsible for creating and deleting a
// snapshot on the storage infrastructure through a csi volume driver. It watches
// the VolumeSnapshotContent object which is either created/deleted by the
// common snapshot controller in the case of dynamic provisioning or by the admin
// in the case of pre-provisioned snapshots.
// The snapshot creation through csi volume driver should return a snapshot after
// it is created successfully (however, the snapshot might not be ready to use yet if
// there is an uploading phase). The creationTime will be updated accordingly
// on the status of VolumeSnapshotContent.
// After that, the sidecar controller will keep checking the snapshot status
// through csi snapshot calls. When the snapshot is ready to use, the sidecar
// controller set the status "ReadyToUse" to true on the VolumeSnapshotContent object
// to indicate the snapshot is ready to use. If the creation failed for any reason,
// the Error status is set accordingly.
const controllerUpdateFailMsg = "snapshot controller failed to update"
// syncContent deals with one key off the queue. It returns false when it's time to quit.
func (ctrl *csiSnapshotSideCarController) syncContent(content *crdv1.VolumeSnapshotContent) error {
klog.V(5).Infof("synchronizing VolumeSnapshotContent[%s]", content.Name)
var err error
if ctrl.shouldDelete(content) {
switch content.Spec.DeletionPolicy {
case crdv1.VolumeSnapshotContentRetain:
klog.V(4).Infof("VolumeSnapshotContent[%s]: policy is Retain. Keep physical snapshot and remove content finalizer", content.Name)
// It is a deletion candidate if DeletionTimestamp is not nil and
// VolumeSnapshotContentFinalizer is set.
if utils.IsContentDeletionCandidate(content) {
// Volume snapshot content is a deletion candidate.
// Remove the content finalizer.
klog.V(5).Infof("syncContent: Content [%s] is a deletion candidate. Remove finalizer.", content.Name)
return ctrl.removeContentFinalizer(content)
}
case crdv1.VolumeSnapshotContentDelete:
klog.V(4).Infof("VolumeSnapshotContent[%s]: policy is Delete. Delete physical snapshot", content.Name)
err = ctrl.deleteCSISnapshot(content)
if err != nil {
return err
}
klog.V(5).Infof("syncContent: check if we should remove Finalizer for VolumeSnapshotContent[%s]", content.Name)
// It is a deletion candidate if DeletionTimestamp is not nil and
// VolumeSnapshotContentFinalizer is set.
if utils.IsContentDeletionCandidate(content) {
// Volume snapshot content is a deletion candidate.
// Remove the content finalizer.
klog.V(5).Infof("syncContent: Content [%s] is a deletion candidate. Remove finalizer.", content.Name)
return ctrl.removeContentFinalizer(content)
}
default:
// Unknown VolumeSnapshotDeletionPolicy
ctrl.eventRecorder.Event(content, v1.EventTypeWarning, "SnapshotUnknownDeletionPolicy", "Volume Snapshot Content has unrecognized deletion policy")
}
klog.V(4).Infof("VolumeSnapshotContent[%s]: the policy is %s", content.Name, content.Spec.DeletionPolicy)
} else {
var err error
klog.V(5).Infof("syncContent: Call CreateSnapshot for content %s", content.Name)
if content.Spec.Source.VolumeHandle != nil && content.Status == nil {
if err = ctrl.createSnapshot(content); err != nil {
ctrl.updateContentErrorStatusWithEvent(content, v1.EventTypeWarning, "SnapshotCreationFailed", fmt.Sprintf("Failed to create snapshot with error %v", err))
return err
}
} else {
if err = ctrl.checkandUpdateContentStatus(content); err != nil {
ctrl.updateContentErrorStatusWithEvent(content, v1.EventTypeWarning, "SnapshotContentStatusUpdateFailed", fmt.Sprintf("Failed to update snapshot content status with error %v", err))
return err
}
}
return nil
}
return nil
}
// deleteCSISnapshot starts delete action.
func (ctrl *csiSnapshotSideCarController) deleteCSISnapshot(content *crdv1.VolumeSnapshotContent) error {
operationName := fmt.Sprintf("delete-%s", content.Name)
klog.V(5).Infof("Snapshotter is about to delete volume snapshot content and the operation named %s", operationName)
ctrl.scheduleOperation(operationName, func() error {
return ctrl.deleteCSISnapshotOperation(content)
})
return nil
}
// scheduleOperation starts given asynchronous operation on given volume. It
// makes sure the operation is already not running.
func (ctrl *csiSnapshotSideCarController) scheduleOperation(operationName string, operation func() error) {
klog.V(5).Infof("scheduleOperation[%s]", operationName)
err := ctrl.runningOperations.Run(operationName, operation)
if err != nil {
switch {
case goroutinemap.IsAlreadyExists(err):
klog.V(4).Infof("operation %q is already running, skipping", operationName)
case exponentialbackoff.IsExponentialBackoff(err):
klog.V(4).Infof("operation %q postponed due to exponential backoff", operationName)
default:
klog.Errorf("error scheduling operation %q: %v", operationName, err)
}
}
}
func (ctrl *csiSnapshotSideCarController) storeContentUpdate(content interface{}) (bool, error) {
return utils.StoreObjectUpdate(ctrl.contentStore, content, "content")
}
// createSnapshot starts new asynchronous operation to create snapshot
func (ctrl *csiSnapshotSideCarController) createSnapshot(content *crdv1.VolumeSnapshotContent) error {
klog.V(5).Infof("createSnapshot for content [%s]: started", content.Name)
opName := fmt.Sprintf("create-%s", content.Name)
ctrl.scheduleOperation(opName, func() error {
contentObj, err := ctrl.createSnapshotOperation(content)
if err != nil {
ctrl.updateContentErrorStatusWithEvent(content, v1.EventTypeWarning, "SnapshotCreationFailed", fmt.Sprintf("Failed to create snapshot: %v", err))
klog.Errorf("createSnapshot [%s]: error occurred in createSnapshotOperation: %v", opName, err)
return err
}
_, updateErr := ctrl.storeContentUpdate(contentObj)
if updateErr != nil {
// We will get an "snapshot update" event soon, this is not a big error
klog.V(4).Infof("createSnapshot [%s]: cannot update internal content cache: %v", content.Name, updateErr)
}
return nil
})
return nil
}
func (ctrl *csiSnapshotSideCarController) checkandUpdateContentStatus(content *crdv1.VolumeSnapshotContent) error {
klog.V(5).Infof("checkandUpdateContentStatus[%s] started", content.Name)
opName := fmt.Sprintf("check-%s", content.Name)
ctrl.scheduleOperation(opName, func() error {
contentObj, err := ctrl.checkandUpdateContentStatusOperation(content)
if err != nil {
ctrl.updateContentErrorStatusWithEvent(content, v1.EventTypeWarning, "SnapshotContentCheckandUpdateFailed", fmt.Sprintf("Failed to check and update snapshot content: %v", err))
klog.Errorf("checkandUpdateContentStatus [%s]: error occured %v", content.Name, err)
return err
}
_, updateErr := ctrl.storeContentUpdate(contentObj)
if updateErr != nil {
// We will get an "snapshot update" event soon, this is not a big error
klog.V(4).Infof("checkandUpdateContentStatus [%s]: cannot update internal cache: %v", content.Name, updateErr)
}
return nil
})
return nil
}
// updateContentStatusWithEvent saves new content.Status to API server and emits
// given event on the content. It saves the status and emits the event only when
// the status has actually changed from the version saved in API server.
// Parameters:
// content - content to update
// eventtype, reason, message - event to send, see EventRecorder.Event()
func (ctrl *csiSnapshotSideCarController) updateContentErrorStatusWithEvent(content *crdv1.VolumeSnapshotContent, eventtype, reason, message string) error {
klog.V(5).Infof("updateContentStatusWithEvent[%s]", content.Name)
if content.Status != nil && content.Status.Error != nil && *content.Status.Error.Message == message {
klog.V(4).Infof("updateContentStatusWithEvent[%s]: the same error %v is already set", content.Name, content.Status.Error)
return nil
} else if content.Status == nil {
content.Status = &crdv1.VolumeSnapshotContentStatus{}
}
contentClone := content.DeepCopy()
statusError := &crdv1.VolumeSnapshotError{
Time: &metav1.Time{
Time: time.Now(),
},
Message: &message,
}
contentClone.Status.Error = statusError
ready := false
contentClone.Status.ReadyToUse = &ready
newContent, err := ctrl.clientset.SnapshotV1beta1().VolumeSnapshotContents().UpdateStatus(contentClone)
if err != nil {
klog.V(4).Infof("updating VolumeSnapshotContent[%s] error status failed %v", content.Name, err)
return err
}
// Emit the event only when the status change happens
ctrl.eventRecorder.Event(newContent, eventtype, reason, message)
_, err = ctrl.storeContentUpdate(newContent)
if err != nil {
klog.V(4).Infof("updating VolumeSnapshotContent[%s] error status: cannot update internal cache %v", content.Name, err)
return err
}
return nil
}
func (ctrl *csiSnapshotSideCarController) getCSISnapshotInput(content *crdv1.VolumeSnapshotContent) (*crdv1.VolumeSnapshotClass, map[string]string, error) {
className := content.Spec.VolumeSnapshotClassName
klog.V(5).Infof("getCSISnapshotInput for content [%s]: VolumeSnapshotClassName [%s]", content.Name, *className)
var class *crdv1.VolumeSnapshotClass
var err error
if className != nil {
class, err = ctrl.getSnapshotClass(*className)
if err != nil {
klog.Errorf("getCSISnapshotInput failed to getClassFromVolumeSnapshot %s", err)
return nil, nil, err
}
} else {
// If dynamic provisioning, return failure if no snapshot class
if content.Spec.Source.VolumeHandle != nil {
klog.Errorf("failed to getCSISnapshotInput %s without a snapshot class", content.Name)
return nil, nil, fmt.Errorf("failed to take snapshot %s without a snapshot class", content.Name)
}
// For pre-provisioned snapshot, snapshot class is not required
klog.V(5).Infof("getCSISnapshotInput for content [%s]: no VolumeSnapshotClassName provided for pre-provisioned snapshot", content.Name)
}
// Resolve snapshotting secret credentials.
snapshotterCredentials, err := ctrl.GetCredentialsFromAnnotation(content)
if err != nil {
return nil, nil, err
}
return class, snapshotterCredentials, nil
}
func (ctrl *csiSnapshotSideCarController) checkandUpdateContentStatusOperation(content *crdv1.VolumeSnapshotContent) (*crdv1.VolumeSnapshotContent, error) {
var err error
var creationTime time.Time
var size int64
var readyToUse = false
var driverName string
var snapshotID string
if content.Spec.Source.SnapshotHandle != nil {
klog.V(5).Infof("checkandUpdateContentStatusOperation: call GetSnapshotStatus for snapshot which is pre-bound to content [%s]", content.Name)
readyToUse, creationTime, size, err = ctrl.handler.GetSnapshotStatus(content)
if err != nil {
klog.Errorf("checkandUpdateContentStatusOperation: failed to call get snapshot status to check whether snapshot is ready to use %q", err)
return nil, err
}
driverName = content.Spec.Driver
snapshotID = *content.Spec.Source.SnapshotHandle
} else {
class, snapshotterCredentials, err := ctrl.getCSISnapshotInput(content)
if err != nil {
return nil, fmt.Errorf("failed to get input parameters to create snapshot %s: %q", content.Name, err)
}
driverName, snapshotID, creationTime, size, readyToUse, err = ctrl.handler.CreateSnapshot(content, class.Parameters, snapshotterCredentials)
if err != nil {
klog.Errorf("checkandUpdateContentStatusOperation: failed to call create snapshot to check whether the snapshot is ready to use %q", err)
return nil, err
}
}
klog.V(5).Infof("checkandUpdateContentStatusOperation: driver %s, snapshotId %s, creationTime %v, size %d, readyToUse %t", driverName, snapshotID, creationTime, size, readyToUse)
if creationTime.IsZero() {
creationTime = time.Now()
}
updateContent, err := ctrl.updateSnapshotContentStatus(content, snapshotID, readyToUse, creationTime.UnixNano(), size)
if err != nil {
return nil, err
}
return updateContent, nil
}
// The function goes through the whole snapshot creation process.
// 1. Trigger the snapshot through csi storage provider.
// 2. Update VolumeSnapshot status with creationtimestamp information
// 3. Create the VolumeSnapshotContent object with the snapshot id information.
// 4. Bind the VolumeSnapshot and VolumeSnapshotContent object
func (ctrl *csiSnapshotSideCarController) createSnapshotOperation(content *crdv1.VolumeSnapshotContent) (*crdv1.VolumeSnapshotContent, error) {
klog.Infof("createSnapshotOperation: Creating snapshot for content %s through the plugin ...", content.Name)
// content.Status will be created for the first time after a snapshot
// is created by the CSI driver. If content.Status is not nil,
// we should update content status without creating snapshot again.
if content.Status != nil && content.Status.Error != nil && content.Status.Error.Message != nil && !isControllerUpdateFailError(content.Status.Error) {
klog.V(4).Infof("error is already set in snapshot, do not retry to create: %s", *content.Status.Error.Message)
return content, nil
}
class, snapshotterCredentials, err := ctrl.getCSISnapshotInput(content)
if err != nil {
return nil, fmt.Errorf("failed to get input parameters to create snapshot for content %s: %q", content.Name, err)
}
driverName, snapshotID, creationTime, size, readyToUse, err := ctrl.handler.CreateSnapshot(content, class.Parameters, snapshotterCredentials)
if err != nil {
return nil, fmt.Errorf("failed to take snapshot of the volume, %s: %q", *content.Spec.Source.VolumeHandle, err)
}
if driverName != class.Driver {
return nil, fmt.Errorf("failed to take snapshot of the volume, %s: driver name %s returned from the driver is different from driver %s in snapshot class", *content.Spec.Source.VolumeHandle, driverName, class.Driver)
}
klog.V(5).Infof("Created snapshot: driver %s, snapshotId %s, creationTime %v, size %d, readyToUse %t", driverName, snapshotID, creationTime, size, readyToUse)
timestamp := creationTime.UnixNano()
newContent, err := ctrl.updateSnapshotContentStatus(content, snapshotID, readyToUse, timestamp, size)
if err != nil {
strerr := fmt.Sprintf("error updating volume snapshot content status for snapshot %s: %v.", content.Name, err)
klog.Error(strerr)
} else {
content = newContent
}
// Update content in the cache store
_, err = ctrl.storeContentUpdate(content)
if err != nil {
klog.Errorf("failed to update content store %v", err)
}
return content, nil
}
// Delete a snapshot: Ask the backend to remove the snapshot device
func (ctrl *csiSnapshotSideCarController) deleteCSISnapshotOperation(content *crdv1.VolumeSnapshotContent) error {
klog.V(5).Infof("deleteCSISnapshotOperation [%s] started", content.Name)
_, snapshotterCredentials, err := ctrl.getCSISnapshotInput(content)
if err != nil {
return fmt.Errorf("failed to get input parameters to delete snapshot for content %s: %q", content.Name, err)
}
err = ctrl.handler.DeleteSnapshot(content, snapshotterCredentials)
if err != nil {
ctrl.eventRecorder.Event(content, v1.EventTypeWarning, "SnapshotDeleteError", "Failed to delete snapshot")
return fmt.Errorf("failed to delete snapshot %#v, err: %v", content.Name, err)
}
return nil
}
func (ctrl *csiSnapshotSideCarController) updateSnapshotContentStatus(
content *crdv1.VolumeSnapshotContent,
snapshotHandle string,
readyToUse bool,
createdAt int64,
size int64) (*crdv1.VolumeSnapshotContent, error) {
klog.V(5).Infof("updateSnapshotContentStatus: updating VolumeSnapshotContent [%s], snapshotHandle %s, readyToUse %v, createdAt %v, size %d", content.Name, snapshotHandle, readyToUse, createdAt, size)
contentObj, err := ctrl.clientset.SnapshotV1beta1().VolumeSnapshotContents().Get(content.Name, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("error get snapshot content %s from api server: %v", content.Name, err)
}
var newStatus *crdv1.VolumeSnapshotContentStatus
updated := false
if contentObj.Status == nil {
newStatus = &crdv1.VolumeSnapshotContentStatus{
SnapshotHandle: &snapshotHandle,
ReadyToUse: &readyToUse,
CreationTime: &createdAt,
RestoreSize: &size,
}
updated = true
} else {
newStatus = contentObj.Status.DeepCopy()
if newStatus.SnapshotHandle == nil {
newStatus.SnapshotHandle = &snapshotHandle
updated = true
}
if newStatus.ReadyToUse == nil || *newStatus.ReadyToUse != readyToUse {
newStatus.ReadyToUse = &readyToUse
updated = true
if readyToUse && newStatus.Error != nil {
newStatus.Error = nil
}
}
if newStatus.CreationTime == nil {
newStatus.CreationTime = &createdAt
updated = true
}
if newStatus.RestoreSize == nil {
newStatus.RestoreSize = &size
updated = true
}
}
if updated {
contentClone := contentObj.DeepCopy()
contentClone.Status = newStatus
newContent, err := ctrl.clientset.SnapshotV1beta1().VolumeSnapshotContents().UpdateStatus(contentClone)
if err != nil {
return nil, newControllerUpdateError(content.Name, err.Error())
}
return newContent, nil
}
return contentObj, nil
}
// getSnapshotClass is a helper function to get snapshot class from the class name.
func (ctrl *csiSnapshotSideCarController) getSnapshotClass(className string) (*crdv1.VolumeSnapshotClass, error) {
klog.V(5).Infof("getSnapshotClass: VolumeSnapshotClassName [%s]", className)
class, err := ctrl.classLister.Get(className)
if err != nil {
klog.Errorf("failed to retrieve snapshot class %s from the informer: %q", className, err)
return nil, fmt.Errorf("failed to retrieve snapshot class %s from the informer: %q", className, err)
}
return class, nil
}
var _ error = controllerUpdateError{}
type controllerUpdateError struct {
message string
}
func newControllerUpdateError(name, message string) error {
return controllerUpdateError{
message: fmt.Sprintf("%s %s on API server: %s", controllerUpdateFailMsg, name, message),
}
}
func (e controllerUpdateError) Error() string {
return e.message
}
func isControllerUpdateFailError(err *crdv1.VolumeSnapshotError) bool {
if err != nil {
if strings.Contains(*err.Message, controllerUpdateFailMsg) {
return true
}
}
return false
}
func (ctrl *csiSnapshotSideCarController) GetCredentialsFromAnnotation(content *crdv1.VolumeSnapshotContent) (map[string]string, error) {
// get secrets if VolumeSnapshotClass specifies it
var snapshotterCredentials map[string]string
var err error
// Check if annotation exists
if metav1.HasAnnotation(content.ObjectMeta, utils.AnnDeletionSecretRefName) && metav1.HasAnnotation(content.ObjectMeta, utils.AnnDeletionSecretRefNamespace) {
annDeletionSecretName := content.Annotations[utils.AnnDeletionSecretRefName]
annDeletionSecretNamespace := content.Annotations[utils.AnnDeletionSecretRefNamespace]
snapshotterSecretRef := &v1.SecretReference{}
if annDeletionSecretName == "" || annDeletionSecretNamespace == "" {
return nil, fmt.Errorf("cannot retrieve secrets for snapshot content %#v, err: secret name or namespace not specified", content.Name)
}
snapshotterSecretRef.Name = annDeletionSecretName
snapshotterSecretRef.Namespace = annDeletionSecretNamespace
snapshotterCredentials, err = utils.GetCredentials(ctrl.client, snapshotterSecretRef)
if err != nil {
// Continue with deletion, as the secret may have already been deleted.
klog.Errorf("Failed to get credentials for snapshot %s: %s", content.Name, err.Error())
return nil, fmt.Errorf("cannot get credentials for snapshot content %#v", content.Name)
}
}
return snapshotterCredentials, nil
}
// removeContentFinalizer removes a Finalizer for VolumeSnapshotContent.
func (ctrl csiSnapshotSideCarController) removeContentFinalizer(content *crdv1.VolumeSnapshotContent) error {
contentClone := content.DeepCopy()
contentClone.ObjectMeta.Finalizers = slice.RemoveString(contentClone.ObjectMeta.Finalizers, utils.VolumeSnapshotContentFinalizer, nil)
_, err := ctrl.clientset.SnapshotV1beta1().VolumeSnapshotContents().Update(contentClone)
if err != nil {
return newControllerUpdateError(content.Name, err.Error())
}
_, err = ctrl.storeContentUpdate(contentClone)
if err != nil {
klog.Errorf("failed to update content store %v", err)
}
klog.V(5).Infof("Removed protection finalizer from volume snapshot content %s", content.Name)
return nil
}
// shouldDelete checks if content object should be deleted
// if DeletionTimestamp is set on the content
func (ctrl *csiSnapshotSideCarController) shouldDelete(content *crdv1.VolumeSnapshotContent) bool {
klog.V(5).Infof("Check if VolumeSnapshotContent[%s] should be deleted.", content.Name)
if content.ObjectMeta.DeletionTimestamp == nil {
return false
}
// 1) shouldDelete returns true if content is not bound
// (VolumeSnapshotRef.UID == "") for pre-provisioned snapshot
if content.Spec.Source.SnapshotHandle != nil && content.Spec.VolumeSnapshotRef.UID == "" {
return true
}
// 2) shouldDelete returns true if AnnVolumeSnapshotBeingDeleted annotation is set
if metav1.HasAnnotation(content.ObjectMeta, utils.AnnVolumeSnapshotBeingDeleted) {
return true
}
return false
}

View File

@@ -0,0 +1,280 @@
/*
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 (
"fmt"
"time"
crdv1 "github.com/kubernetes-csi/external-snapshotter/pkg/apis/volumesnapshot/v1beta1"
clientset "github.com/kubernetes-csi/external-snapshotter/pkg/client/clientset/versioned"
storageinformers "github.com/kubernetes-csi/external-snapshotter/pkg/client/informers/externalversions/volumesnapshot/v1beta1"
storagelisters "github.com/kubernetes-csi/external-snapshotter/pkg/client/listers/volumesnapshot/v1beta1"
"github.com/kubernetes-csi/external-snapshotter/pkg/snapshotter"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
"k8s.io/client-go/util/workqueue"
"k8s.io/klog"
"k8s.io/kubernetes/pkg/util/goroutinemap"
)
type csiSnapshotSideCarController struct {
clientset clientset.Interface
client kubernetes.Interface
driverName string
eventRecorder record.EventRecorder
contentQueue workqueue.RateLimitingInterface
contentLister storagelisters.VolumeSnapshotContentLister
contentListerSynced cache.InformerSynced
classLister storagelisters.VolumeSnapshotClassLister
classListerSynced cache.InformerSynced
contentStore cache.Store
handler Handler
// Map of scheduled/running operations.
runningOperations goroutinemap.GoRoutineMap
resyncPeriod time.Duration
}
// NewCSISnapshotSideCarController returns a new *csiSnapshotSideCarController
func NewCSISnapshotSideCarController(
clientset clientset.Interface,
client kubernetes.Interface,
driverName string,
volumeSnapshotContentInformer storageinformers.VolumeSnapshotContentInformer,
volumeSnapshotClassInformer storageinformers.VolumeSnapshotClassInformer,
snapshotter snapshotter.Snapshotter,
timeout time.Duration,
resyncPeriod time.Duration,
snapshotNamePrefix string,
snapshotNameUUIDLength int,
) *csiSnapshotSideCarController {
broadcaster := record.NewBroadcaster()
broadcaster.StartLogging(klog.Infof)
broadcaster.StartRecordingToSink(&corev1.EventSinkImpl{Interface: client.CoreV1().Events(v1.NamespaceAll)})
var eventRecorder record.EventRecorder
eventRecorder = broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: fmt.Sprintf("csi-snapshotter %s", driverName)})
ctrl := &csiSnapshotSideCarController{
clientset: clientset,
client: client,
driverName: driverName,
eventRecorder: eventRecorder,
handler: NewCSIHandler(snapshotter, timeout, snapshotNamePrefix, snapshotNameUUIDLength),
runningOperations: goroutinemap.NewGoRoutineMap(true),
resyncPeriod: resyncPeriod,
contentStore: cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc),
contentQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "csi-snapshotter-content"),
}
volumeSnapshotContentInformer.Informer().AddEventHandlerWithResyncPeriod(
cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) { ctrl.enqueueContentWork(obj) },
UpdateFunc: func(oldObj, newObj interface{}) { ctrl.enqueueContentWork(newObj) },
DeleteFunc: func(obj interface{}) { ctrl.enqueueContentWork(obj) },
},
ctrl.resyncPeriod,
)
ctrl.contentLister = volumeSnapshotContentInformer.Lister()
ctrl.contentListerSynced = volumeSnapshotContentInformer.Informer().HasSynced
ctrl.classLister = volumeSnapshotClassInformer.Lister()
ctrl.classListerSynced = volumeSnapshotClassInformer.Informer().HasSynced
return ctrl
}
func (ctrl *csiSnapshotSideCarController) Run(workers int, stopCh <-chan struct{}) {
defer ctrl.contentQueue.ShutDown()
klog.Infof("Starting CSI snapshotter")
defer klog.Infof("Shutting CSI snapshotter")
if !cache.WaitForCacheSync(stopCh, ctrl.contentListerSynced, ctrl.classListerSynced) {
klog.Errorf("Cannot sync caches")
return
}
ctrl.initializeCaches(ctrl.contentLister)
for i := 0; i < workers; i++ {
go wait.Until(ctrl.contentWorker, 0, stopCh)
}
<-stopCh
}
// enqueueContentWork adds snapshot content to given work queue.
func (ctrl *csiSnapshotSideCarController) enqueueContentWork(obj interface{}) {
// Beware of "xxx deleted" events
if unknown, ok := obj.(cache.DeletedFinalStateUnknown); ok && unknown.Obj != nil {
obj = unknown.Obj
}
if content, ok := obj.(*crdv1.VolumeSnapshotContent); ok {
objName, err := cache.DeletionHandlingMetaNamespaceKeyFunc(content)
if err != nil {
klog.Errorf("failed to get key from object: %v, %v", err, content)
return
}
klog.V(5).Infof("enqueued %q for sync", objName)
ctrl.contentQueue.Add(objName)
}
}
// contentWorker processes items from contentQueue. It must run only once,
// syncContent is not assured to be reentrant.
func (ctrl *csiSnapshotSideCarController) contentWorker() {
workFunc := func() bool {
keyObj, quit := ctrl.contentQueue.Get()
if quit {
return true
}
defer ctrl.contentQueue.Done(keyObj)
key := keyObj.(string)
klog.V(5).Infof("contentWorker[%s]", key)
_, name, err := cache.SplitMetaNamespaceKey(key)
if err != nil {
klog.V(4).Infof("error getting name of snapshotContent %q to get snapshotContent from informer: %v", key, err)
return false
}
content, err := ctrl.contentLister.Get(name)
// The content still exists in informer cache, the event must have
// been add/update/sync
if err == nil {
if ctrl.isDriverMatch(content) {
ctrl.updateContentInCacheStore(content)
}
return false
}
if !errors.IsNotFound(err) {
klog.V(2).Infof("error getting content %q from informer: %v", key, err)
return false
}
// The content is not in informer cache, the event must have been
// "delete"
contentObj, found, err := ctrl.contentStore.GetByKey(key)
if err != nil {
klog.V(2).Infof("error getting content %q from cache: %v", key, err)
return false
}
if !found {
// The controller has already processed the delete event and
// deleted the content from its cache
klog.V(2).Infof("deletion of content %q was already processed", key)
return false
}
content, ok := contentObj.(*crdv1.VolumeSnapshotContent)
if !ok {
klog.Errorf("expected content, got %+v", content)
return false
}
ctrl.deleteContentInCacheStore(content)
return false
}
for {
if quit := workFunc(); quit {
klog.Infof("content worker queue shutting down")
return
}
}
}
// verify whether the driver specified in VolumeSnapshotContent matches the controller's driver name
func (ctrl *csiSnapshotSideCarController) isDriverMatch(content *crdv1.VolumeSnapshotContent) bool {
if content.Spec.Source.VolumeHandle == nil && content.Spec.Source.SnapshotHandle == nil {
// Skip this snapshot content if it does not have a valid source
return false
}
if content.Spec.Driver != ctrl.driverName {
// Skip this snapshot content if the driver does not match
return false
}
snapshotClassName := content.Spec.VolumeSnapshotClassName
if snapshotClassName != nil {
if snapshotClass, err := ctrl.classLister.Get(*snapshotClassName); err == nil {
if snapshotClass.Driver != ctrl.driverName {
return false
}
}
}
return true
}
// updateContent runs in worker thread and handles "content added",
// "content updated" and "periodic sync" events.
func (ctrl *csiSnapshotSideCarController) updateContentInCacheStore(content *crdv1.VolumeSnapshotContent) {
// Store the new content version in the cache and do not process it if this is
// an old version.
new, err := ctrl.storeContentUpdate(content)
if err != nil {
klog.Errorf("%v", err)
}
if !new {
return
}
err = ctrl.syncContent(content)
if err != nil {
if errors.IsConflict(err) {
// Version conflict error happens quite often and the controller
// recovers from it easily.
klog.V(3).Infof("could not sync content %q: %+v", content.Name, err)
} else {
klog.Errorf("could not sync content %q: %+v", content.Name, err)
}
}
}
// deleteContent runs in worker thread and handles "content deleted" event.
func (ctrl *csiSnapshotSideCarController) deleteContentInCacheStore(content *crdv1.VolumeSnapshotContent) {
_ = ctrl.contentStore.Delete(content)
klog.V(4).Infof("content %q deleted", content.Name)
}
// initializeCaches fills all controller caches with initial data from etcd in
// order to have the caches already filled when first addSnapshot/addContent to
// perform initial synchronization of the controller.
func (ctrl *csiSnapshotSideCarController) initializeCaches(contentLister storagelisters.VolumeSnapshotContentLister) {
contentList, err := contentLister.List(labels.Everything())
if err != nil {
klog.Errorf("CSISnapshotController can't initialize caches: %v", err)
return
}
for _, content := range contentList {
if ctrl.isDriverMatch(content) {
contentClone := content.DeepCopy()
if _, err = ctrl.storeContentUpdate(contentClone); err != nil {
klog.Errorf("error updating volume snapshot content cache: %v", err)
}
}
}
klog.V(4).Infof("controller initialized")
}

View File

@@ -0,0 +1,91 @@
/*
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 (
"testing"
crdv1 "github.com/kubernetes-csi/external-snapshotter/pkg/apis/volumesnapshot/v1beta1"
"github.com/kubernetes-csi/external-snapshotter/pkg/utils"
"k8s.io/client-go/tools/cache"
)
var deletionPolicy = crdv1.VolumeSnapshotContentDelete
func storeVersion(t *testing.T, prefix string, c cache.Store, version string, expectedReturn bool) {
content := newContent("contentName", "snapuid1-1", "snap1-1", "sid1-1", classGold, "", "pv-handle-1-1", deletionPolicy, nil, nil, false, nil)
content.ResourceVersion = version
ret, err := utils.StoreObjectUpdate(c, content, "content")
if err != nil {
t.Errorf("%s: expected storeObjectUpdate to succeed, got: %v", prefix, err)
}
if expectedReturn != ret {
t.Errorf("%s: expected storeObjectUpdate to return %v, got: %v", prefix, expectedReturn, ret)
}
// find the stored version
contentObj, found, err := c.GetByKey("contentName")
if err != nil {
t.Errorf("expected content 'contentName' in the cache, got error instead: %v", err)
}
if !found {
t.Errorf("expected content 'contentName' in the cache but it was not found")
}
content, ok := contentObj.(*crdv1.VolumeSnapshotContent)
if !ok {
t.Errorf("expected content in the cache, got different object instead: %#v", contentObj)
}
if ret {
if content.ResourceVersion != version {
t.Errorf("expected content with version %s in the cache, got %s instead", version, content.ResourceVersion)
}
} else {
if content.ResourceVersion == version {
t.Errorf("expected content with version other than %s in the cache, got %s instead", version, content.ResourceVersion)
}
}
}
// TestControllerCache tests func storeObjectUpdate()
func TestControllerCache(t *testing.T) {
// Cache under test
c := cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc)
// Store new PV
storeVersion(t, "Step1", c, "1", true)
// Store the same PV
storeVersion(t, "Step2", c, "1", true)
// Store newer PV
storeVersion(t, "Step3", c, "2", true)
// Store older PV - simulating old "PV updated" event or periodic sync with
// old data
storeVersion(t, "Step4", c, "1", false)
// Store newer PV - test integer parsing ("2" > "10" as string,
// while 2 < 10 as integers)
storeVersion(t, "Step5", c, "10", true)
}
func TestControllerCacheParsingError(t *testing.T) {
c := cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc)
// There must be something in the cache to compare with
storeVersion(t, "Step1", c, "1", true)
content := newContent("contentName", "snapuid1-1", "snap1-1", "sid1-1", classGold, "", "pv-handle-1-1", deletionPolicy, nil, nil, false, nil)
content.ResourceVersion = "xxx"
_, err := utils.StoreObjectUpdate(c, content, "content")
if err == nil {
t.Errorf("Expected parsing error, got nil instead")
}
}

View File

@@ -0,0 +1,440 @@
/*
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 (
"fmt"
"testing"
"time"
"errors"
crdv1 "github.com/kubernetes-csi/external-snapshotter/pkg/apis/volumesnapshot/v1beta1"
"github.com/kubernetes-csi/external-snapshotter/pkg/utils"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var defaultSize int64 = 1000
var emptySize int64 = 0
var deletePolicy = crdv1.VolumeSnapshotContentDelete
var retainPolicy = crdv1.VolumeSnapshotContentRetain
var timeNow = time.Now()
var timeNowMetav1 = metav1.Now()
var False = false
var 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 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,
},
Driver: mockDriverName,
Parameters: class3Parameters,
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,
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, &timeNowMetav1),
expectedContents: newContentArrayWithDeletionTimestamp("content1-1", "snapuid1-1", "snap1-1", "sid1-1", classGold, "", "snap1-1-volumehandle", deletionPolicy, nil, nil, false, &timeNowMetav1),
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", true, time.Now(), 1, nil}},
expectedDeleteCalls: []deleteCall{{"sid1-1", nil, nil}},
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, &timeNowMetav1),
expectedContents: newContentArrayWithDeletionTimestamp("content1-2", "snapuid1-2", "snap1-2", "sid1-2", classGold, "", "snap1-2-volumehandle", retainPolicy, nil, nil, false, &timeNowMetav1),
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", true, time.Now(), 1, nil}},
expectedDeleteCalls: []deleteCall{{"sid1-2", nil, nil}},
test: testSyncContent,
},
{
name: "1-3 - delete snapshot error should result in an event",
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, false, &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", true, time.Now(), 1, nil}},
test: testSyncContent,
},
/*{
name: "1-4 - create snapshot error should result in an event",
initialContents: newContentArrayWithDeletionTimestamp("content1-4", "snapuid1-4", "snap1-4", "sid1-4", classGold, "", "snap1-4-volumehandle", deletePolicy, nil, nil, true, nil),
//expectedContents: newContentArrayWithDeletionTimestamp("content1-4", "snapuid1-4", "snap1-4", "sid1-4", classGold, "", "snap1-4-volumehandle", deletePolicy, nil, nil, true, nil),
errors: []reactorError{},
expectedCreateCalls: []createCall{
{
snapshotName: "snapshot-snapuid1-4",
volumeHandle: "snap1-4-volumehandle",
parameters: map[string]string{"param1": "value1"},
err: fmt.Errorf("Create failed"),
},
},
//expectedDeleteCalls: []deleteCall{{"sid1-4", nil, nil}},
expectedListCalls: []listCall{{"sid1-4", true, time.Now(), 1, nil}},
expectedEvents: []string{"Warning SnapshotContentCheckandUpdateFailed Failed to check and update snapshot content: Create failed"},
test: testSyncContent,
},*/
/*{
name: "2-1 - content with empty snapshot class will not be deleted if it is bound to a non-exist snapshot but it does not have a snapshot uid specified",
initialContents: newContentArray("content2-1", "", "snap2-1", "sid2-1", "", "", "", deletionPolicy, nil, nil, true),
expectedContents: newContentArray("content2-1", "", "snap2-1", "sid2-1", "", "", "", deletionPolicy, nil, nil, true),
expectedEvents: noevents,
errors: noerrors,
expectedDeleteCalls: []deleteCall{{"sid2-1", nil, nil}},
test: testSyncContent,
},*/
/*{
name: "1-2 - successful delete with snapshot class that has empty secret parameter",
initialContents: newContentArray("content1-2", "sid1-2", "snap1-2", "sid1-2", emptySecretClass, "", "volumeHandle", deletionPolicy, nil, nil, true),
expectedContents: nocontents,
initialSnapshots: nosnapshots,
expectedSnapshots: nosnapshots,
initialSecrets: []*v1.Secret{emptySecret()},
expectedEvents: noevents,
errors: noerrors,
expectedDeleteCalls: []deleteCall{{"sid1-2", map[string]string{}, nil}},
test: testSyncContent,
},*/
/*{
name: "1-3 - content non-nil DeletionTimestamp with delete policy will delete snapshot",
initialContents: newContentArrayWithDeletionTimestamp("content1-3", "snapuid1-1", "snap1-1", "sid1-3", classGold, "", "snap1-3-volumehandle", deletionPolicy, nil, nil, true, &timeNowMetav1),
expectedContents: newContentArrayWithDeletionTimestamp("content1-3", "snapuid1-1", "snap1-1", "sid1-3", classGold, "", "snap1-3-volumehandle", deletionPolicy, nil, nil, false, &timeNowMetav1),
initialSnapshots: newSnapshotArray("snap1-3", "snapuid1-3", "claim1-3", "", validSecretClass, "", &False, nil, nil, nil),
expectedSnapshots: newSnapshotArray("snap1-3", "snapuid1-3", "claim1-3", "", validSecretClass, "", &False, nil, nil, nil),
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", true, time.Now(), 1, nil}},
expectedDeleteCalls: []deleteCall{{"sid1-1", nil, nil}},
test: testSyncContent,
},*/
/* name: "1-3 - successful delete with snapshot class that has valid secret parameter",
initialContents: newContentArray("content1-3", "sid1-3", "snap1-3", "sid1-3", validSecretClass, "", "snap1-3-volumehandle", deletionPolicy, nil, nil, true),
expectedContents: newContentArray("content1-3", "sid1-3", "snap1-3", "sid1-3", validSecretClass, "", "snap1-3-volumehandle", deletionPolicy, nil, nil, false),
initialSnapshots: newSnapshotArray("snapshot-snapuid1-3", "snapuid1-3", "claim1-3", "", validSecretClass, "", &False, nil, nil, nil),
expectedSnapshots: newSnapshotArray("snapshot-snapuid1-3", "snapuid1-3", "claim1-3", "", validSecretClass, "", &False, nil, nil, nil),
expectedEvents: noevents,
errors: noerrors,
initialSecrets: []*v1.Secret{secret()},
expectedCreateCalls: []createCall{
{
snapshotName: "snap1-3",
volumeHandle: "snap1-3-volumehandle",
parameters: map[string]string{"param1": "value1"},
driverName: mockDriverName,
size: defaultSize,
snapshotId: "sid1-3",
creationTime: timeNow,
},
},
expectedListCalls: []listCall{{"sid1-3", true, time.Now(), 1, nil}},
expectedDeleteCalls: []deleteCall{{"sid1-3", map[string]string{"param1": "value1"}, nil}},
test: testSyncContent,
},
*/
{
name: "1-4 - fail delete with snapshot class that has invalid secret parameter",
initialContents: newContentArrayWithDeletionTimestamp("content1-1", "snapuid1-1", "snap1-1", "sid1-1", "invalid", "", "snap1-1-volumehandle", deletionPolicy, nil, nil, true, &timeNowMetav1),
expectedContents: newContentArrayWithDeletionTimestamp("content1-1", "snapuid1-1", "snap1-1", "sid1-1", "invalid", "", "snap1-1-volumehandle", deletionPolicy, nil, nil, false, &timeNowMetav1),
expectedEvents: noevents,
errors: noerrors,
test: testSyncContent,
},
{
name: "1-5 - csi driver delete snapshot returns error",
initialContents: newContentArrayWithDeletionTimestamp("content1-5", "sid1-5", "snap1-5", "sid1-5", validSecretClass, "", "", deletionPolicy, nil, &defaultSize, true, &timeNowMetav1),
expectedContents: newContentArrayWithDeletionTimestamp("content1-5", "sid1-5", "snap1-5", "sid1-5", validSecretClass, "", "", deletionPolicy, nil, &defaultSize, false, &timeNowMetav1),
expectedListCalls: []listCall{{"sid1-5", 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,
},
/*{
name: "1-6 - api server delete content returns error",
initialContents: newContentArray("content1-6", "sid1-6", "snap1-6", "sid1-6", classGold, "", "", deletionPolicy, nil, nil, true),
//expectedContents: newContentArray("content1-6", "sid1-6", "snap1-6", "sid1-6", classGold, "", "", deletionPolicy, nil, nil, true),
expectedDeleteCalls: []deleteCall{{"sid1-6", map[string]string{"foo": "bar"}, nil}},
expectedListCalls: []listCall{{"sid1-6", false, time.Now(), 0, nil}},
expectedEvents: []string{"Warning SnapshotContentObjectDeleteError"},
errors: []reactorError{
// Inject error to the first client.VolumesnapshotV1beta1().VolumeSnapshotContents().Delete call.
// All other calls will succeed.
{"delete", "volumesnapshotcontents", errors.New("mock delete error")},
},
test: testSyncContent,
},*/
/*
{
// delete success - snapshot that the content was pointing to was deleted, and another
// with the same name created.
name: "1-7 - prebound content is deleted while the snapshot exists",
initialContents: newContentArray("content1-7", "sid1-7", "snap1-7", "sid1-7", emptySecretClass, "", "", deletionPolicy, nil, nil, true),
expectedContents: nocontents,
initialSecrets: []*v1.Secret{secret()},
expectedDeleteCalls: []deleteCall{{"sid1-7", map[string]string{"foo": "bar"}, nil}},
expectedEvents: noevents,
errors: noerrors,
test: testSyncContent,
},*/
{
// delete success(?) - content is deleted before doDelete() starts
name: "1-8 - content is deleted before deleting",
initialContents: newContentArray("content1-8", "sid1-8", "snap1-8", "sid1-8", classGold, "", "", deletionPolicy, nil, nil, true),
expectedContents: nocontents,
expectedListCalls: []listCall{{"sid1-8", false, time.Now(), 0, nil}},
expectedDeleteCalls: []deleteCall{{"sid1-8", map[string]string{"foo": "bar"}, nil}},
expectedEvents: noevents,
errors: noerrors,
test: wrapTestWithInjectedOperation(testSyncContent, func(ctrl *csiSnapshotSideCarController, reactor *snapshotReactor) {
// Delete the volume before delete operation starts
reactor.lock.Lock()
delete(reactor.contents, "content1-8")
reactor.lock.Unlock()
}),
},
/*{
name: "1-9 - content will not be deleted if it is bound to a snapshot correctly, snapshot uid is specified",
expectedContents: newContentArrayWithDeletionTimestamp("content1-9", "snapuid1-9", "snap1-9", "sid1-9", classGold, "", "snap1-9-volumehandle", retainPolicy, nil, nil, false, &timeNowMetav1),
initialContents: newContentArrayWithDeletionTimestamp("content1-9", "snapuid1-9", "snap1-9", "sid1-9", classGold, "", "snap1-9-volumehandle", retainPolicy, nil, nil, false, &timeNowMetav1),
expectedEvents: noevents,
expectedListCalls: []listCall{{"sid1-9", true, time.Now(), 0, nil}},
initialSecrets: []*v1.Secret{secret()},
errors: noerrors,
test: testSyncContent,
},*/
/*
{
name: "1-10 - will not delete content with retain policy set which is bound to a snapshot incorrectly",
initialContents: newContentArray("content1-10", "snapuid1-10-x", "snap1-10", "sid1-10", validSecretClass, "", "", retainPolicy, nil, nil, true),
expectedContents: newContentArray("content1-10", "snapuid1-10-x", "snap1-10", "sid1-10", validSecretClass, "", "", retainPolicy, nil, nil, true),
initialSnapshots: newSnapshotArray("snap1-10", "snapuid1-10", "claim1-10", "", validSecretClass, "content1-10", &False, nil, nil, nil),
expectedSnapshots: newSnapshotArray("snap1-10", "snapuid1-10", "claim1-10", "", validSecretClass, "content1-10", &False, nil, nil, nil),
expectedEvents: noevents,
initialSecrets: []*v1.Secret{secret()},
errors: noerrors,
test: testSyncContent,
},*/
{
name: "1-11 - content will not be deleted if it is bound to a snapshot correctly, snapsht uid is not specified",
initialContents: newContentArrayWithReadyToUse("content1-11", "", "snap1-11", "sid1-11", validSecretClass, "", "", deletePolicy, nil, &defaultSize, &True, true),
expectedContents: newContentArrayWithReadyToUse("content1-11", "", "snap1-11", "sid1-11", validSecretClass, "", "", deletePolicy, nil, &defaultSize, &True, true),
expectedEvents: noevents,
expectedCreateCalls: []createCall{
{
snapshotName: "snap1-11",
volumeHandle: "snap1-11",
parameters: map[string]string{"param1": "value1"},
driverName: mockDriverName,
size: defaultSize,
snapshotId: "snapuid1-1-deleted",
creationTime: timeNow,
readyToUse: true,
},
},
expectedListCalls: []listCall{{"sid1-11", true, time.Now(), 1000, nil}},
initialSecrets: []*v1.Secret{secret()},
errors: noerrors,
test: testSyncContent,
},
{
name: "1-12 - 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-12", "sid1-12", "snap1-11", "sid1-11", validSecretClass, "", "", retainPolicy, nil, &defaultSize, &True, true),
expectedContents: newContentArrayWithReadyToUse("content1-12", "sid1-12", "snap1-11", "sid1-11", validSecretClass, "", "", retainPolicy, nil, &defaultSize, &True, true),
expectedEvents: noevents,
expectedListCalls: []listCall{{"sid1-11", true, time.Now(), 0, nil}},
errors: noerrors,
test: testSyncContent,
},
/*{
name: "1-13 - content with empty snapshot class is not deleted when Deletion policy is not set even if it is bound to a non-exist snapshot and also has a snapshot uid specified",
initialContents: newContentArray("content1-13", "sid1-13", "snap1-13", "sid1-13", validSecretClass, "", "", retainPolicy, nil, nil, true),
expectedContents: newContentArray("content1-13", "sid1-13", "snap1-13", "sid1-13", validSecretClass, "", "", retainPolicy, nil, nil, true),
expectedEvents: noevents,
errors: noerrors,
test: testSyncContent,
},
{
name: "1-14 - content will not be deleted if it is bound to a snapshot correctly, snapshot uid is specified",
initialContents: newContentArray("content1-14", "snapuid1-14", "snap1-14", "sid1-14", validSecretClass, "", "", retainPolicy, nil, nil, true),
expectedContents: newContentArray("content1-14", "snapuid1-14", "snap1-14", "sid1-14", validSecretClass, "", "", retainPolicy, nil, nil, true),
expectedEvents: noevents,
initialSecrets: []*v1.Secret{secret()},
errors: noerrors,
test: testSyncContent,
},*/
{
name: "1-16 - continue delete with snapshot class that has nonexistent secret",
initialContents: newContentArrayWithDeletionTimestamp("content1-16", "sid1-16", "snap1-16", "sid1-16", emptySecretClass, "", "", deletePolicy, nil, &defaultSize, true, &timeNowMetav1),
expectedContents: newContentArrayWithDeletionTimestamp("content1-16", "sid1-16", "snap1-16", "sid1-16", emptySecretClass, "", "", deletePolicy, nil, &defaultSize, false, &timeNowMetav1),
expectedEvents: noevents,
expectedListCalls: []listCall{{"sid1-16", true, time.Now(), 0, nil}},
errors: noerrors,
initialSecrets: []*v1.Secret{}, // secret does not exist
expectedDeleteCalls: []deleteCall{{"sid1-16", nil, nil}},
test: testSyncContent,
},
}
runSyncContentTests(t, tests, snapshotClasses)
}

View File

@@ -0,0 +1,34 @@
/*
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 (
"testing"
)
// Test single call to ensureSnapshotSourceFinalizer and checkandRemoveSnapshotSourceFinalizer,
// expecting PVCFinalizer to be added or removed
func TestContentFinalizer(t *testing.T) {
// GG TODO - add content finalizer tests
/*
tests := []controllerTest{
{},
}
runPVCFinalizerTests(t, tests, snapshotClasses)
*/
}

View File

@@ -27,14 +27,13 @@ import (
"google.golang.org/grpc"
"k8s.io/api/core/v1"
"k8s.io/klog"
)
// Snapshotter implements CreateSnapshot/DeleteSnapshot operations against a remote CSI driver.
type Snapshotter interface {
// CreateSnapshot creates a snapshot for a volume
CreateSnapshot(ctx context.Context, snapshotName string, volume *v1.PersistentVolume, parameters map[string]string, snapshotterCredentials map[string]string) (driverName string, snapshotId string, timestamp time.Time, size int64, readyToUse bool, err error)
CreateSnapshot(ctx context.Context, snapshotName string, volumeHandle string, parameters map[string]string, snapshotterCredentials map[string]string) (driverName string, snapshotId string, timestamp time.Time, size int64, readyToUse bool, err error)
// DeleteSnapshot deletes a snapshot from a volume
DeleteSnapshot(ctx context.Context, snapshotID string, snapshotterCredentials map[string]string) (err error)
@@ -53,12 +52,8 @@ func NewSnapshotter(conn *grpc.ClientConn) Snapshotter {
}
}
func (s *snapshot) CreateSnapshot(ctx context.Context, snapshotName string, volume *v1.PersistentVolume, parameters map[string]string, snapshotterCredentials map[string]string) (string, string, time.Time, int64, bool, error) {
func (s *snapshot) CreateSnapshot(ctx context.Context, snapshotName string, volumeHandle string, parameters map[string]string, snapshotterCredentials map[string]string) (string, string, time.Time, int64, bool, error) {
klog.V(5).Infof("CSI CreateSnapshot: %s", snapshotName)
if volume.Spec.CSI == nil {
return "", "", time.Time{}, 0, false, fmt.Errorf("CSIPersistentVolumeSource not defined in spec")
}
client := csi.NewControllerClient(s.conn)
driverName, err := csirpc.GetDriverName(ctx, s.conn)
@@ -67,7 +62,7 @@ func (s *snapshot) CreateSnapshot(ctx context.Context, snapshotName string, volu
}
req := csi.CreateSnapshotRequest{
SourceVolumeId: volume.Spec.CSI.VolumeHandle,
SourceVolumeId: volumeHandle,
Name: snapshotName,
Parameters: parameters,
Secrets: snapshotterCredentials,
@@ -118,6 +113,8 @@ func (s *snapshot) isListSnapshotsSupported(ctx context.Context) (bool, error) {
}
func (s *snapshot) GetSnapshotStatus(ctx context.Context, snapshotID string) (bool, time.Time, int64, error) {
klog.V(5).Infof("GetSnapshotStatus: %s", snapshotID)
client := csi.NewControllerClient(s.conn)
// If the driver does not support ListSnapshots, assume the snapshot ID is valid.

View File

@@ -79,7 +79,6 @@ func TestCreateSnapshot(t *testing.T) {
}
csiVolume := FakeCSIVolume()
volumeWithoutCSI := FakeVolume()
defaultRequest := &csi.CreateSnapshotRequest{
Name: defaultName,
@@ -135,7 +134,7 @@ func TestCreateSnapshot(t *testing.T) {
tests := []struct {
name string
snapshotName string
volume *v1.PersistentVolume
volumeHandle string
parameters map[string]string
secrets map[string]string
input *csi.CreateSnapshotRequest
@@ -147,7 +146,7 @@ func TestCreateSnapshot(t *testing.T) {
{
name: "success",
snapshotName: defaultName,
volume: csiVolume,
volumeHandle: csiVolume.Spec.CSI.VolumeHandle,
input: defaultRequest,
output: defaultResponse,
expectError: false,
@@ -156,7 +155,7 @@ func TestCreateSnapshot(t *testing.T) {
{
name: "attributes",
snapshotName: defaultName,
volume: csiVolume,
volumeHandle: csiVolume.Spec.CSI.VolumeHandle,
parameters: defaultParameter,
input: attributesRequest,
output: defaultResponse,
@@ -166,25 +165,17 @@ func TestCreateSnapshot(t *testing.T) {
{
name: "secrets",
snapshotName: defaultName,
volume: csiVolume,
volumeHandle: csiVolume.Spec.CSI.VolumeHandle,
secrets: createSecrets,
input: secretsRequest,
output: defaultResponse,
expectError: false,
expectResult: result,
},
{
name: "fail for volume without csi source",
snapshotName: defaultName,
volume: volumeWithoutCSI,
input: nil,
output: nil,
expectError: true,
},
{
name: "gRPC transient error",
snapshotName: defaultName,
volume: csiVolume,
volumeHandle: csiVolume.Spec.CSI.VolumeHandle,
input: defaultRequest,
output: nil,
injectError: codes.DeadlineExceeded,
@@ -193,7 +184,7 @@ func TestCreateSnapshot(t *testing.T) {
{
name: "gRPC final error",
snapshotName: defaultName,
volume: csiVolume,
volumeHandle: csiVolume.Spec.CSI.VolumeHandle,
input: defaultRequest,
output: nil,
injectError: codes.NotFound,
@@ -224,7 +215,7 @@ func TestCreateSnapshot(t *testing.T) {
}
s := NewSnapshotter(csiConn)
driverName, snapshotId, timestamp, size, readyToUse, err := s.CreateSnapshot(context.Background(), test.snapshotName, test.volume, test.parameters, test.secrets)
driverName, snapshotId, timestamp, size, readyToUse, err := s.CreateSnapshot(context.Background(), test.snapshotName, test.volumeHandle, test.parameters, test.secrets)
if test.expectError && err == nil {
t.Errorf("test %q: Expected error, got none", test.name)
}
@@ -509,29 +500,3 @@ func FakeCSIVolume() *v1.PersistentVolume {
return &volume
}
func FakeVolume() *v1.PersistentVolume {
volume := v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "fake-csi-volume",
},
Spec: v1.PersistentVolumeSpec{
ClaimRef: &v1.ObjectReference{
Kind: "PersistentVolumeClaim",
APIVersion: "v1",
UID: types.UID("uid123"),
Namespace: "default",
Name: "test-claim",
},
PersistentVolumeSource: v1.PersistentVolumeSource{
GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
},
StorageClassName: "default",
},
Status: v1.PersistentVolumeStatus{
Phase: v1.VolumeBound,
},
}
return &volume
}

View File

@@ -14,18 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
package utils
import (
"fmt"
"strings"
"os"
"strconv"
"time"
crdv1 "github.com/kubernetes-csi/external-snapshotter/pkg/apis/volumesnapshot/v1beta1"
v1 "k8s.io/api/core/v1"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
@@ -34,18 +30,19 @@ import (
"k8s.io/client-go/tools/cache"
"k8s.io/klog"
"k8s.io/kubernetes/pkg/util/slice"
"os"
"strconv"
"time"
)
var (
keyFunc = cache.DeletionHandlingMetaNamespaceKeyFunc
)
type deprecatedSecretParamsMap struct {
name string
deprecatedSecretNameKey string
deprecatedSecretNamespaceKey string
secretNameKey string
secretNamespaceKey string
type secretParamsMap struct {
name string
secretNameKey string
secretNamespaceKey string
}
const (
@@ -58,14 +55,24 @@ const (
prefixedSnapshotterSecretNameKey = csiParameterPrefix + "snapshotter-secret-name"
prefixedSnapshotterSecretNamespaceKey = csiParameterPrefix + "snapshotter-secret-namespace"
// [Deprecated] CSI Parameters that are put into fields but
// NOT stripped from the parameters passed to CreateSnapshot
snapshotterSecretNameKey = "csiSnapshotterSecretName"
snapshotterSecretNamespaceKey = "csiSnapshotterSecretNamespace"
// Name of finalizer on VolumeSnapshotContents that are bound by VolumeSnapshots
VolumeSnapshotContentFinalizer = "snapshot.storage.kubernetes.io/volumesnapshotcontent-protection"
VolumeSnapshotFinalizer = "snapshot.storage.kubernetes.io/volumesnapshot-protection"
VolumeSnapshotContentFinalizer = "snapshot.storage.kubernetes.io/volumesnapshotcontent-bound-protection"
// Name of finalizer on VolumeSnapshot that is being used as a source to create a PVC
VolumeSnapshotBoundFinalizer = "snapshot.storage.kubernetes.io/volumesnapshot-bound-protection"
// Name of finalizer on VolumeSnapshot that is used as a source to create a PVC
VolumeSnapshotAsSourceFinalizer = "snapshot.storage.kubernetes.io/volumesnapshot-as-source-protection"
// Name of finalizer on PVCs that is being used as a source to create VolumeSnapshots
PVCFinalizer = "snapshot.storage.kubernetes.io/pvc-as-source-protection"
IsDefaultSnapshotClassAnnotation = "snapshot.storage.kubernetes.io/is-default-class"
// AnnVolumeSnapshotBeingDeleted annotation applies to VolumeSnapshotContents.
// It indicates that the common snapshot controller has verified that volume
// snapshot has a deletion timestamp and is being deleted.
// Sidecar controller needs to check the deletion policy on the
// VolumeSnapshotContentand and decide whether to delete the volume snapshot
// backing the snapshot content.
AnnVolumeSnapshotBeingDeleted = "snapshot.storage.kubernetes.io/volumesnapshot-being-deleted"
// Annotation for secret name and namespace will be added to the content
// and used at snapshot content deletion time.
@@ -73,22 +80,17 @@ const (
AnnDeletionSecretRefNamespace = "snapshot.storage.kubernetes.io/deletion-secret-namespace"
)
var snapshotterSecretParams = deprecatedSecretParamsMap{
name: "Snapshotter",
deprecatedSecretNameKey: snapshotterSecretNameKey,
deprecatedSecretNamespaceKey: snapshotterSecretNamespaceKey,
secretNameKey: prefixedSnapshotterSecretNameKey,
secretNamespaceKey: prefixedSnapshotterSecretNamespaceKey,
var snapshotterSecretParams = secretParamsMap{
name: "Snapshotter",
secretNameKey: prefixedSnapshotterSecretNameKey,
secretNamespaceKey: prefixedSnapshotterSecretNamespaceKey,
}
// Name of finalizer on PVCs that have been used as a source to create VolumeSnapshots
const PVCFinalizer = "snapshot.storage.kubernetes.io/pvc-protection"
func snapshotKey(vs *crdv1.VolumeSnapshot) string {
func SnapshotKey(vs *crdv1.VolumeSnapshot) string {
return fmt.Sprintf("%s/%s", vs.Namespace, vs.Name)
}
func snapshotRefKey(vsref v1.ObjectReference) string {
func SnapshotRefKey(vsref *v1.ObjectReference) string {
return fmt.Sprintf("%s/%s", vsref.Namespace, vsref.Name)
}
@@ -96,7 +98,7 @@ func snapshotRefKey(vsref v1.ObjectReference) string {
// callback (i.e. with events from etcd) or with an object modified by the
// controller itself. Returns "true", if the cache was updated, false if the
// object is an old version and should be ignored.
func storeObjectUpdate(store cache.Store, obj interface{}, className string) (bool, error) {
func StoreObjectUpdate(store cache.Store, obj interface{}, className string) (bool, error) {
objName, err := keyFunc(obj)
if err != nil {
return false, fmt.Errorf("Couldn't get key for object %+v: %v", obj, err)
@@ -172,19 +174,9 @@ func IsDefaultAnnotation(obj metav1.ObjectMeta) bool {
// verifyAndGetSecretNameAndNamespaceTemplate gets the values (templates) associated
// with the parameters specified in "secret" and verifies that they are specified correctly.
func verifyAndGetSecretNameAndNamespaceTemplate(secret deprecatedSecretParamsMap, snapshotClassParams map[string]string) (nameTemplate, namespaceTemplate string, err error) {
func verifyAndGetSecretNameAndNamespaceTemplate(secret secretParamsMap, snapshotClassParams map[string]string) (nameTemplate, namespaceTemplate string, err error) {
numName := 0
numNamespace := 0
if t, ok := snapshotClassParams[secret.deprecatedSecretNameKey]; ok {
nameTemplate = t
numName++
klog.Warning(deprecationWarning(secret.deprecatedSecretNameKey, secret.secretNameKey, ""))
}
if t, ok := snapshotClassParams[secret.deprecatedSecretNamespaceKey]; ok {
namespaceTemplate = t
numNamespace++
klog.Warning(deprecationWarning(secret.deprecatedSecretNamespaceKey, secret.secretNamespaceKey, ""))
}
if t, ok := snapshotClassParams[secret.secretNameKey]; ok {
nameTemplate = t
numName++
@@ -194,10 +186,7 @@ func verifyAndGetSecretNameAndNamespaceTemplate(secret deprecatedSecretParamsMap
numNamespace++
}
if numName > 1 || numNamespace > 1 {
// Double specified error
return "", "", fmt.Errorf("%s secrets specified in paramaters with both \"csi\" and \"%s\" keys", secret.name, csiParameterPrefix)
} else if numName != numNamespace {
if numName != numNamespace {
// Not both 0 or both 1
return "", "", fmt.Errorf("either name and namespace for %s secrets specified, Both must be specified", secret.name)
} else if numName == 1 {
@@ -232,7 +221,7 @@ func verifyAndGetSecretNameAndNamespaceTemplate(secret deprecatedSecretParamsMap
// - the nameTemplate or namespaceTemplate contains a token that cannot be resolved
// - the resolved name is not a valid secret name
// - the resolved namespace is not a valid namespace name
func getSecretReference(snapshotClassParams map[string]string, snapContentName string, snapshot *crdv1.VolumeSnapshot) (*v1.SecretReference, error) {
func GetSecretReference(snapshotClassParams map[string]string, snapContentName string, snapshot *crdv1.VolumeSnapshot) (*v1.SecretReference, error) {
nameTemplate, namespaceTemplate, err := verifyAndGetSecretNameAndNamespaceTemplate(snapshotterSecretParams, snapshotClassParams)
if err != nil {
return nil, fmt.Errorf("failed to get name and namespace template from params: %v", err)
@@ -244,7 +233,7 @@ func getSecretReference(snapshotClassParams map[string]string, snapContentName s
ref := &v1.SecretReference{}
// Secret namespace template can make use of the VolumeSnapshotContent name or the VolumeSnapshot namespace.
// Secret namespace template can make use of the VolumeSnapshotContent name, VolumeSnapshot name or namespace.
// Note that neither of those things are under the control of the VolumeSnapshot user.
namespaceParams := map[string]string{"volumesnapshotcontent.name": snapContentName}
// snapshot may be nil when resolving create/delete snapshot secret names because the
@@ -268,7 +257,7 @@ func getSecretReference(snapshotClassParams map[string]string, snapContentName s
ref.Namespace = resolvedNamespace
// Secret name template can make use of the VolumeSnapshotContent name, VolumeSnapshot name or namespace.
// Note that VolumeSnapshot name and annotations are under the VolumeSnapshot user's control.
// Note that VolumeSnapshot name and namespace are under the VolumeSnapshot user's control.
nameParams := map[string]string{"volumesnapshotcontent.name": snapContentName}
if snapshot != nil {
nameParams["volumesnapshot.name"] = snapshot.Name
@@ -306,8 +295,8 @@ func resolveTemplate(template string, params map[string]string) (string, error)
return resolved, nil
}
// getCredentials retrieves credentials stored in v1.SecretReference
func getCredentials(k8s kubernetes.Interface, ref *v1.SecretReference) (map[string]string, error) {
// GetCredentials retrieves credentials stored in v1.SecretReference
func GetCredentials(k8s kubernetes.Interface, ref *v1.SecretReference) (map[string]string, error) {
if ref == nil {
return nil, nil
}
@@ -330,23 +319,31 @@ func NoResyncPeriodFunc() time.Duration {
}
// isContentDeletionCandidate checks if a volume snapshot content is a deletion candidate.
func isContentDeletionCandidate(content *crdv1.VolumeSnapshotContent) bool {
// It is a deletion candidate if DeletionTimestamp is not nil and
// VolumeSnapshotContentFinalizer is set.
func IsContentDeletionCandidate(content *crdv1.VolumeSnapshotContent) bool {
return content.ObjectMeta.DeletionTimestamp != nil && slice.ContainsString(content.ObjectMeta.Finalizers, VolumeSnapshotContentFinalizer, nil)
}
// needToAddContentFinalizer checks if a Finalizer needs to be added for the volume snapshot content.
func needToAddContentFinalizer(content *crdv1.VolumeSnapshotContent) bool {
func NeedToAddContentFinalizer(content *crdv1.VolumeSnapshotContent) bool {
return content.ObjectMeta.DeletionTimestamp == nil && !slice.ContainsString(content.ObjectMeta.Finalizers, VolumeSnapshotContentFinalizer, nil)
}
// isSnapshotDeletionCandidate checks if a volume snapshot is a deletion candidate.
func isSnapshotDeletionCandidate(snapshot *crdv1.VolumeSnapshot) bool {
return snapshot.ObjectMeta.DeletionTimestamp != nil && slice.ContainsString(snapshot.ObjectMeta.Finalizers, VolumeSnapshotFinalizer, nil)
// isSnapshotDeletionCandidate checks if a volume snapshot deletionTimestamp
// is set and any finalizer is on the snapshot.
func IsSnapshotDeletionCandidate(snapshot *crdv1.VolumeSnapshot) bool {
return snapshot.ObjectMeta.DeletionTimestamp != nil && (slice.ContainsString(snapshot.ObjectMeta.Finalizers, VolumeSnapshotAsSourceFinalizer, nil) || slice.ContainsString(snapshot.ObjectMeta.Finalizers, VolumeSnapshotBoundFinalizer, nil))
}
// needToAddSnapshotFinalizer checks if a Finalizer needs to be added for the volume snapshot.
func needToAddSnapshotFinalizer(snapshot *crdv1.VolumeSnapshot) bool {
return snapshot.ObjectMeta.DeletionTimestamp == nil && !slice.ContainsString(snapshot.ObjectMeta.Finalizers, VolumeSnapshotFinalizer, nil)
// needToAddSnapshotAsSourceFinalizer checks if a Finalizer needs to be added for the volume snapshot as a source for PVC.
func NeedToAddSnapshotAsSourceFinalizer(snapshot *crdv1.VolumeSnapshot) bool {
return snapshot.ObjectMeta.DeletionTimestamp == nil && !slice.ContainsString(snapshot.ObjectMeta.Finalizers, VolumeSnapshotAsSourceFinalizer, nil)
}
// needToAddSnapshotBoundFinalizer checks if a Finalizer needs to be added for the bound volume snapshot.
func NeedToAddSnapshotBoundFinalizer(snapshot *crdv1.VolumeSnapshot) bool {
return snapshot.ObjectMeta.DeletionTimestamp == nil && !slice.ContainsString(snapshot.ObjectMeta.Finalizers, VolumeSnapshotBoundFinalizer, nil) && snapshot.Status != nil && snapshot.Status.BoundVolumeSnapshotContentName != nil
}
func deprecationWarning(deprecatedParam, newParam, removalVersion string) string {
@@ -360,7 +357,7 @@ func deprecationWarning(deprecatedParam, newParam, removalVersion string) string
return fmt.Sprintf("\"%s\" is deprecated and will be removed in %s%s", deprecatedParam, removalVersion, newParamPhrase)
}
func removePrefixedParameters(param map[string]string) (map[string]string, error) {
func RemovePrefixedParameters(param map[string]string) (map[string]string, error) {
newParam := map[string]string{}
for k, v := range param {
if strings.HasPrefix(k, csiParameterPrefix) {
@@ -379,3 +376,47 @@ func removePrefixedParameters(param map[string]string) (map[string]string, error
}
return newParam, nil
}
// Stateless functions
func GetSnapshotStatusForLogging(snapshot *crdv1.VolumeSnapshot) string {
snapshotContentName := ""
if snapshot.Status != nil && snapshot.Status.BoundVolumeSnapshotContentName != nil {
snapshotContentName = *snapshot.Status.BoundVolumeSnapshotContentName
}
ready := false
if snapshot.Status != nil && snapshot.Status.ReadyToUse != nil {
ready = *snapshot.Status.ReadyToUse
}
return fmt.Sprintf("bound to: %q, Completed: %v", snapshotContentName, ready)
}
// IsSnapshotBound returns true/false if snapshot is bound
func IsSnapshotBound(snapshot *crdv1.VolumeSnapshot, content *crdv1.VolumeSnapshotContent) bool {
if IsVolumeSnapshotRefSet(snapshot, content) && IsBoundVolumeSnapshotContentNameSet(snapshot) {
return true
}
return false
}
func IsVolumeSnapshotRefSet(snapshot *crdv1.VolumeSnapshot, content *crdv1.VolumeSnapshotContent) bool {
if content.Spec.VolumeSnapshotRef.Name == snapshot.Name &&
content.Spec.VolumeSnapshotRef.Namespace == snapshot.Namespace &&
content.Spec.VolumeSnapshotRef.UID == snapshot.UID {
return true
}
return false
}
func IsBoundVolumeSnapshotContentNameSet(snapshot *crdv1.VolumeSnapshot) bool {
if snapshot.Status == nil || snapshot.Status.BoundVolumeSnapshotContentName == nil || *snapshot.Status.BoundVolumeSnapshotContentName == "" {
return false
}
return true
}
func IsSnapshotReady(snapshot *crdv1.VolumeSnapshot) bool {
if snapshot.Status == nil || snapshot.Status.ReadyToUse == nil || *snapshot.Status.ReadyToUse == false {
return false
}
return true
}

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
package utils
import (
crdv1 "github.com/kubernetes-csi/external-snapshotter/pkg/apis/volumesnapshot/v1beta1"
@@ -36,14 +36,6 @@ func TestGetSecretReference(t *testing.T) {
params: nil,
expectRef: nil,
},
"empty err": {
params: map[string]string{snapshotterSecretNameKey: "", snapshotterSecretNamespaceKey: ""},
expectErr: true,
},
"[deprecated] name, no namespace": {
params: map[string]string{snapshotterSecretNameKey: "foo"},
expectErr: true,
},
"namespace, no name": {
params: map[string]string{prefixedSnapshotterSecretNamespaceKey: "foo"},
expectErr: true,
@@ -53,23 +45,12 @@ func TestGetSecretReference(t *testing.T) {
snapshot: &crdv1.VolumeSnapshot{},
expectRef: &v1.SecretReference{Name: "name", Namespace: "ns"},
},
"[deprecated] simple - valid, no pvc": {
params: map[string]string{snapshotterSecretNameKey: "name", snapshotterSecretNamespaceKey: "ns"},
snapshot: nil,
expectRef: &v1.SecretReference{Name: "name", Namespace: "ns"},
},
"simple - invalid name": {
params: map[string]string{prefixedSnapshotterSecretNameKey: "bad name", prefixedSnapshotterSecretNamespaceKey: "ns"},
snapshot: &crdv1.VolumeSnapshot{},
expectRef: nil,
expectErr: true,
},
"[deprecated] simple - invalid namespace": {
params: map[string]string{snapshotterSecretNameKey: "name", snapshotterSecretNamespaceKey: "bad ns"},
snapshot: &crdv1.VolumeSnapshot{},
expectRef: nil,
expectErr: true,
},
"template - invalid": {
params: map[string]string{
prefixedSnapshotterSecretNameKey: "static-${volumesnapshotcontent.name}-${volumesnapshot.namespace}-${volumesnapshot.name}-${volumesnapshot.annotations['akey']}",
@@ -86,29 +67,11 @@ func TestGetSecretReference(t *testing.T) {
expectRef: nil,
expectErr: true,
},
"template - invalid namespace tokens": {
params: map[string]string{
snapshotterSecretNameKey: "myname",
snapshotterSecretNamespaceKey: "mynamespace${bar}",
},
snapshot: &crdv1.VolumeSnapshot{},
expectRef: nil,
expectErr: true,
},
"template - invalid name tokens": {
params: map[string]string{
snapshotterSecretNameKey: "myname${foo}",
snapshotterSecretNamespaceKey: "mynamespace",
},
snapshot: &crdv1.VolumeSnapshot{},
expectRef: nil,
expectErr: true,
},
}
for k, tc := range testcases {
t.Run(k, func(t *testing.T) {
ref, err := getSecretReference(tc.params, tc.snapContentName, tc.snapshot)
ref, err := GetSecretReference(tc.params, tc.snapContentName, tc.snapshot)
if err != nil {
if tc.expectErr {
return
@@ -152,17 +115,6 @@ func TestRemovePrefixedCSIParams(t *testing.T) {
},
expectedParams: map[string]string{},
},
{
name: "all known deprecated params not stripped",
params: map[string]string{
snapshotterSecretNameKey: "csiBar",
snapshotterSecretNamespaceKey: "csiBar",
},
expectedParams: map[string]string{
snapshotterSecretNameKey: "csiBar",
snapshotterSecretNamespaceKey: "csiBar",
},
},
{
name: "unknown prefixed var",
params: map[string]string{csiParameterPrefix + "bim": "baz"},
@@ -176,7 +128,7 @@ func TestRemovePrefixedCSIParams(t *testing.T) {
}
for _, tc := range testcases {
t.Logf("test: %v", tc.name)
newParams, err := removePrefixedParameters(tc.params)
newParams, err := RemovePrefixedParameters(tc.params)
if err != nil {
if tc.expectErr {
continue