diff --git a/cmd/csi-snapshotter/create_crd.go b/cmd/csi-snapshotter/create_crd.go index 6e605b83..9e7d77a6 100644 --- a/cmd/csi-snapshotter/create_crd.go +++ b/cmd/csi-snapshotter/create_crd.go @@ -15,7 +15,6 @@ package main import ( "reflect" - "time" "github.com/golang/glog" crdv1 "github.com/kubernetes-csi/external-snapshotter/pkg/apis/volumesnapshot/v1alpha1" @@ -25,7 +24,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/rest" ) @@ -122,18 +120,3 @@ func CreateCRD(clientset apiextensionsclient.Interface) error { return nil } - -// WaitForSnapshotResource waits for the snapshot resource -func WaitForSnapshotResource(snapshotClient *rest.RESTClient) error { - return wait.Poll(100*time.Millisecond, 60*time.Second, func() (bool, error) { - _, err := snapshotClient.Get(). - Resource(crdv1.VolumeSnapshotContentResourcePlural).DoRaw() - if err == nil { - return true, nil - } - if apierrors.IsNotFound(err) { - return false, nil - } - return false, err - }) -} diff --git a/cmd/csi-snapshotter/main.go b/cmd/csi-snapshotter/main.go index df1e9ee4..1b7d9406 100644 --- a/cmd/csi-snapshotter/main.go +++ b/cmd/csi-snapshotter/main.go @@ -47,7 +47,7 @@ const ( // Command line flags var ( - snapshotter = flag.String("snapshotter", "", "Name of the snapshotter. The snapshotter will only create snapshot data for snapshot that request a StorageClass with a snapshotter field set equal to this name.") + snapshotter = flag.String("snapshotter", "", "Name of the snapshotter. The snapshotter will only create snapshot content for snapshot that requests a VolumeSnapshotClass with a snapshotter field set equal to this name.") kubeconfig = flag.String("kubeconfig", "", "Absolute path to the kubeconfig file. Required only when running out of cluster.") resync = flag.Duration("resync", 10*time.Second, "Resync interval of the controller.") connectionTimeout = flag.Duration("connection-timeout", 1*time.Minute, "Timeout for waiting for CSI driver socket.") @@ -56,7 +56,7 @@ var ( createSnapshotContentInterval = flag.Duration("createSnapshotContentInterval", 10*time.Second, "Interval between retries when we create a snapshot data object for a snapshot.") resyncPeriod = flag.Duration("resyncPeriod", 60*time.Second, "The period that should be used to re-sync the snapshot.") 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") + 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.") ) func main() { @@ -105,10 +105,20 @@ func main() { os.Exit(1) } - // Find driver name. + // Pass a context with a timeout ctx, cancel := context.WithTimeout(context.Background(), csiTimeout) defer cancel() + // Find driver name + if snapshotter == nil { + *snapshotter, err = csiConn.GetDriverName(ctx) + if err != nil { + glog.Error(err.Error()) + os.Exit(1) + } + } + glog.V(2).Infof("CSI driver name: %q", *snapshotter) + // Check it's ready if err = waitForDriverReady(csiConn, *connectionTimeout); err != nil { glog.Error(err.Error()) @@ -122,11 +132,11 @@ func main() { os.Exit(1) } if !supportsCreateSnapshot { - glog.Error("CSI driver does not support ControllerCreateSnapshot") + glog.Errorf("CSI driver %s does not support ControllerCreateSnapshot", *snapshotter) os.Exit(1) } - glog.V(2).Infof("Start NewCSISnapshotController with snapshotter %s", *snapshotter) + glog.V(2).Infof("Start NewCSISnapshotController with snapshotter [%s] kubeconfig [%s] resync [%+v] connectionTimeout [%+v] csiAddress [%s] createSnapshotContentRetryCount [%d] createSnapshotContentInterval [%+v] resyncPeriod [%+v] snapshotNamePrefix [%s] snapshotNameUUIDLength [%d]", *snapshotter, *kubeconfig, *resync, *connectionTimeout, *csiAddress, createSnapshotContentRetryCount, *createSnapshotContentInterval, *resyncPeriod, *snapshotNamePrefix, snapshotNameUUIDLength) ctrl := controller.NewCSISnapshotController( snapClient, diff --git a/pkg/apis/volumesnapshot/v1alpha1/types.go b/pkg/apis/volumesnapshot/v1alpha1/types.go index 63fb4cd9..9d91802f 100644 --- a/pkg/apis/volumesnapshot/v1alpha1/types.go +++ b/pkg/apis/volumesnapshot/v1alpha1/types.go @@ -80,6 +80,10 @@ type VolumeSnapshotSpec struct { // be used if it is available. // +optional VolumeSnapshotClassName string `json:"snapshotClassName" protobuf:"bytes,3,opt,name=snapshotClassName"` + + // A description of the volume snapshot's resources and size. + // +optional + Size core_v1.ResourceList `json:"size,omitempty" protobuf:"bytes,4,rep,name=size,casttype=ResourceList,castkey=ResourceName"` } // VolumeSnapshotStatus is the status of the VolumeSnapshot @@ -194,6 +198,11 @@ type VolumeSnapshotContentSpec struct { // taken from. It becomes non-nil when VolumeSnapshot and VolumeSnapshotContent are bound. // +optional PersistentVolumeRef *core_v1.ObjectReference `json:"persistentVolumeRef" protobuf:"bytes,3,opt,name=persistentVolumeRef"` + + // Name of the VolumeSnapshotClass used by the VolumeSnapshot. If not specified, a default snapshot class will + // be used if it is available. + // +optional + VolumeSnapshotClassName string `json:"snapshotClassName" protobuf:"bytes,4,opt,name=snapshotClassName"` } // VolumeSnapshotSource represents the actual location and type of the snapshot. Only one of its members may be specified. @@ -206,19 +215,26 @@ type VolumeSnapshotSource struct { // Represents the source from CSI volume snapshot type CSIVolumeSnapshotSource struct { // Driver is the name of the driver to use for this snapshot. + // This MUST be the same name returned by the CSI GetPluginName() call for + // that driver. // Required. - Driver string `json:"driver"` + Driver string `json:"driver" protobuf:"bytes,1,opt,name=driver"` // SnapshotHandle is the unique snapshot id returned by the CSI volume // plugin’s CreateSnapshot to refer to the snapshot on all subsequent calls. // Required. - SnapshotHandle string `json:"snapshotHandle"` + SnapshotHandle string `json:"snapshotHandle" protobuf:"bytes,2,opt,name=snapshotHandle"` // Timestamp when the point-in-time snapshot is taken on the storage // system. This timestamp will be generated by the CSI volume driver after // the snapshot is cut. The format of this field should be a Unix nanoseconds // time encoded as an int64. On Unix, the command `date +%s%N` returns // the current time in nanoseconds since 1970-01-01 00:00:00 UTC. - // This field is REQUIRED. + // This field is required in the CSI spec but optional here to support static binding. + // +optional CreatedAt int64 `json:"createdAt,omitempty" protobuf:"varint,3,opt,name=createdAt"` + + // A description of the volume snapshot's resources and size. + // +optional + Size core_v1.ResourceList `json:"size,omitempty" protobuf:"bytes,4,rep,name=size,casttype=ResourceList,castkey=ResourceName"` } diff --git a/pkg/apis/volumesnapshot/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/volumesnapshot/v1alpha1/zz_generated.deepcopy.go index a7dad6ef..26229359 100644 --- a/pkg/apis/volumesnapshot/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/volumesnapshot/v1alpha1/zz_generated.deepcopy.go @@ -29,6 +29,13 @@ import ( // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CSIVolumeSnapshotSource) DeepCopyInto(out *CSIVolumeSnapshotSource) { *out = *in + if in.Size != nil { + in, out := &in.Size, &out.Size + *out = make(v1.ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } return } @@ -278,7 +285,7 @@ func (in *VolumeSnapshotSource) DeepCopyInto(out *VolumeSnapshotSource) { if in.CSI != nil { in, out := &in.CSI, &out.CSI *out = new(CSIVolumeSnapshotSource) - **out = **in + (*in).DeepCopyInto(*out) } return } @@ -301,6 +308,13 @@ func (in *VolumeSnapshotSpec) DeepCopyInto(out *VolumeSnapshotSpec) { *out = new(TypedLocalObjectReference) **out = **in } + if in.Size != nil { + in, out := &in.Size, &out.Size + *out = make(v1.ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } return } diff --git a/pkg/connection/connection.go b/pkg/connection/connection.go index b2877e62..dc63c3a4 100644 --- a/pkg/connection/connection.go +++ b/pkg/connection/connection.go @@ -50,7 +50,7 @@ type CSIConnection interface { CreateSnapshot(ctx context.Context, snapshotName string, snapshot *crdv1.VolumeSnapshot, volume *v1.PersistentVolume, parameters map[string]string, snapshotterCredentials map[string]string) (driverName string, snapshotId string, timestamp int64, status *csi.SnapshotStatus, err error) // DeleteSnapshot deletes a snapshot from a volume - DeleteSnapshot(ctx context.Context, snapshotID string) (err error) + DeleteSnapshot(ctx context.Context, snapshotID string, snapshotterCredentials map[string]string) (err error) // GetSnapshotStatus lists snapshot from a volume GetSnapshotStatus(ctx context.Context, snapshotID string) (*csi.SnapshotStatus, int64, error) @@ -218,12 +218,12 @@ func (c *csiConnection) CreateSnapshot(ctx context.Context, snapshotName string, return driverName, rsp.Snapshot.Id, rsp.Snapshot.CreatedAt, rsp.Snapshot.Status, nil } -func (c *csiConnection) DeleteSnapshot(ctx context.Context, snapshotID string) (err error) { +func (c *csiConnection) DeleteSnapshot(ctx context.Context, snapshotID string, snapshotterCredentials map[string]string) (err error) { client := csi.NewControllerClient(c.conn) req := csi.DeleteSnapshotRequest{ SnapshotId: snapshotID, - DeleteSnapshotSecrets: nil, + DeleteSnapshotSecrets: snapshotterCredentials, } if _, err := client.DeleteSnapshot(ctx, &req); err != nil { diff --git a/pkg/controller/csi_handler.go b/pkg/controller/csi_handler.go index 56836aca..dd93b186 100644 --- a/pkg/controller/csi_handler.go +++ b/pkg/controller/csi_handler.go @@ -31,7 +31,7 @@ import ( // 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, int64, *csi.SnapshotStatus, error) - DeleteSnapshot(content *crdv1.VolumeSnapshotContent) error + DeleteSnapshot(content *crdv1.VolumeSnapshotContent, snapshotterCredentials map[string]string) error GetSnapshotStatus(content *crdv1.VolumeSnapshotContent) (*csi.SnapshotStatus, int64, error) } @@ -69,14 +69,14 @@ func (handler *csiHandler) CreateSnapshot(snapshot *crdv1.VolumeSnapshot, volume return handler.csiConnection.CreateSnapshot(ctx, snapshotName, snapshot, volume, parameters, snapshotterCredentials) } -func (handler *csiHandler) DeleteSnapshot(content *crdv1.VolumeSnapshotContent) error { +func (handler *csiHandler) DeleteSnapshot(content *crdv1.VolumeSnapshotContent, snapshotterCredentials map[string]string) error { if content.Spec.CSI == nil { return fmt.Errorf("CSISnapshot not defined in spec") } ctx, cancel := context.WithTimeout(context.Background(), handler.timeout) defer cancel() - err := handler.csiConnection.DeleteSnapshot(ctx, content.Spec.CSI.SnapshotHandle) + err := handler.csiConnection.DeleteSnapshot(ctx, content.Spec.CSI.SnapshotHandle, snapshotterCredentials) if err != nil { return fmt.Errorf("failed to delete snapshot data %s: %q", content.Name, err) } diff --git a/pkg/controller/csi_snapshot_controller.go b/pkg/controller/snapshot_controller.go similarity index 93% rename from pkg/controller/csi_snapshot_controller.go rename to pkg/controller/snapshot_controller.go index d2806c71..645113c5 100644 --- a/pkg/controller/csi_snapshot_controller.go +++ b/pkg/controller/snapshot_controller.go @@ -81,7 +81,7 @@ const snapshotterSecretNameKey = "csiSnapshotterSecretName" const snapshotterSecretNamespaceKey = "csiSnapshotterSecretNamespace" // syncContent deals with one key off the queue. It returns false when it's time to quit. -func (ctrl *CSISnapshotController) syncContent(content *crdv1.VolumeSnapshotContent) error { +func (ctrl *csiSnapshotController) syncContent(content *crdv1.VolumeSnapshotContent) error { glog.V(4).Infof("synchronizing VolumeSnapshotContent[%s]", content.Name) // VolumeSnapshotContent is not bind to any VolumeSnapshot, this case rare and we just return err @@ -134,7 +134,7 @@ func (ctrl *CSISnapshotController) syncContent(content *crdv1.VolumeSnapshotCont // created, updated or periodically synced. We do not differentiate between // these events. // For easier readability, it is split into syncCompleteSnapshot and syncUncompleteSnapshot -func (ctrl *CSISnapshotController) syncSnapshot(snapshot *crdv1.VolumeSnapshot) error { +func (ctrl *csiSnapshotController) syncSnapshot(snapshot *crdv1.VolumeSnapshot) error { glog.V(4).Infof("synchonizing VolumeSnapshot[%s]: %s", snapshotKey(snapshot), getSnapshotStatusForLogging(snapshot)) if !snapshot.Status.Ready { @@ -146,7 +146,7 @@ func (ctrl *CSISnapshotController) syncSnapshot(snapshot *crdv1.VolumeSnapshot) // syncCompleteSnapshot checks the snapshot which has been bound to snapshot content succesfully before. // If there is any problem with the binding (e.g., snapshot points to a non-exist snapshot content), update the snapshot status and emit event. -func (ctrl *CSISnapshotController) syncBoundSnapshot(snapshot *crdv1.VolumeSnapshot) error { +func (ctrl *csiSnapshotController) syncBoundSnapshot(snapshot *crdv1.VolumeSnapshot) error { if snapshot.Spec.SnapshotContentName == "" { if _, err := ctrl.updateSnapshotErrorStatusWithEvent(snapshot, v1.EventTypeWarning, "SnapshotLost", "Bound snapshot has lost reference to VolumeSnapshotContent"); err != nil { return err @@ -184,7 +184,7 @@ func (ctrl *CSISnapshotController) syncBoundSnapshot(snapshot *crdv1.VolumeSnaps } } -func (ctrl *CSISnapshotController) syncUnboundSnapshot(snapshot *crdv1.VolumeSnapshot) error { +func (ctrl *csiSnapshotController) syncUnboundSnapshot(snapshot *crdv1.VolumeSnapshot) error { uniqueSnapshotName := snapshotKey(snapshot) glog.V(4).Infof("syncSnapshot %s", uniqueSnapshotName) @@ -241,7 +241,7 @@ func (ctrl *CSISnapshotController) syncUnboundSnapshot(snapshot *crdv1.VolumeSna } // getMatchSnapshotContent looks up VolumeSnapshotContent for a VolumeSnapshot named snapshotName -func (ctrl *CSISnapshotController) getMatchSnapshotContent(vs *crdv1.VolumeSnapshot) *crdv1.VolumeSnapshotContent { +func (ctrl *csiSnapshotController) getMatchSnapshotContent(vs *crdv1.VolumeSnapshot) *crdv1.VolumeSnapshotContent { var snapshotDataObj *crdv1.VolumeSnapshotContent var found bool @@ -267,7 +267,7 @@ func (ctrl *CSISnapshotController) getMatchSnapshotContent(vs *crdv1.VolumeSnaps } // deleteSnapshotContent starts delete action. -func (ctrl *CSISnapshotController) deleteSnapshotContent(content *crdv1.VolumeSnapshotContent) { +func (ctrl *csiSnapshotController) deleteSnapshotContent(content *crdv1.VolumeSnapshotContent) { operationName := fmt.Sprintf("delete-%s[%s]", content.Name, string(content.UID)) glog.V(4).Infof("Snapshotter is about to delete volume snapshot and the operation named %s", operationName) ctrl.scheduleOperation(operationName, func() error { @@ -277,7 +277,7 @@ func (ctrl *CSISnapshotController) deleteSnapshotContent(content *crdv1.VolumeSn // scheduleOperation starts given asynchronous operation on given volume. It // makes sure the operation is already not running. -func (ctrl *CSISnapshotController) scheduleOperation(operationName string, operation func() error) { +func (ctrl *csiSnapshotController) scheduleOperation(operationName string, operation func() error) { glog.V(4).Infof("scheduleOperation[%s]", operationName) err := ctrl.runningOperations.Run(operationName, operation) @@ -293,16 +293,16 @@ func (ctrl *CSISnapshotController) scheduleOperation(operationName string, opera } } -func (ctrl *CSISnapshotController) storeSnapshotUpdate(vs interface{}) (bool, error) { +func (ctrl *csiSnapshotController) storeSnapshotUpdate(vs interface{}) (bool, error) { return storeObjectUpdate(ctrl.snapshotStore, vs, "vs") } -func (ctrl *CSISnapshotController) storeContentUpdate(content interface{}) (bool, error) { +func (ctrl *csiSnapshotController) storeContentUpdate(content interface{}) (bool, error) { return storeObjectUpdate(ctrl.contentStore, content, "content") } // createSnapshot starts new asynchronous operation to create snapshot data for snapshot -func (ctrl *CSISnapshotController) createSnapshot(snapshot *crdv1.VolumeSnapshot) error { +func (ctrl *csiSnapshotController) createSnapshot(snapshot *crdv1.VolumeSnapshot) error { glog.V(4).Infof("createSnapshot[%s]: started", snapshotKey(snapshot)) opName := fmt.Sprintf("create-%s[%s]", snapshotKey(snapshot), string(snapshot.UID)) ctrl.scheduleOperation(opName, func() error { @@ -323,7 +323,7 @@ func (ctrl *CSISnapshotController) createSnapshot(snapshot *crdv1.VolumeSnapshot return nil } -func (ctrl *CSISnapshotController) checkandUpdateSnapshotStatus(snapshot *crdv1.VolumeSnapshot, content *crdv1.VolumeSnapshotContent) error { +func (ctrl *csiSnapshotController) checkandUpdateSnapshotStatus(snapshot *crdv1.VolumeSnapshot, content *crdv1.VolumeSnapshotContent) error { glog.V(5).Infof("checkandUpdateSnapshotStatus[%s] started", snapshotKey(snapshot)) opName := fmt.Sprintf("check-%s[%s]", snapshotKey(snapshot), string(snapshot.UID)) ctrl.scheduleOperation(opName, func() error { @@ -349,7 +349,7 @@ func (ctrl *CSISnapshotController) checkandUpdateSnapshotStatus(snapshot *crdv1. // Parameters: // snapshot - snapshot to update // eventtype, reason, message - event to send, see EventRecorder.Event() -func (ctrl *CSISnapshotController) updateSnapshotErrorStatusWithEvent(snapshot *crdv1.VolumeSnapshot, eventtype, reason, message string) (*crdv1.VolumeSnapshot, error) { +func (ctrl *csiSnapshotController) updateSnapshotErrorStatusWithEvent(snapshot *crdv1.VolumeSnapshot, eventtype, reason, message string) (*crdv1.VolumeSnapshot, error) { glog.V(4).Infof("updateSnapshotStatusWithEvent[%s]", snapshotKey(snapshot)) if snapshot.Status.Error != nil && snapshot.Status.Ready == false { @@ -384,7 +384,7 @@ func (ctrl *CSISnapshotController) updateSnapshotErrorStatusWithEvent(snapshot * return newSnapshot, nil } -func (ctrl *CSISnapshotController) updateSnapshotBoundWithEvent(snapshot *crdv1.VolumeSnapshot, eventtype, reason, message string) (*crdv1.VolumeSnapshot, error) { +func (ctrl *csiSnapshotController) updateSnapshotBoundWithEvent(snapshot *crdv1.VolumeSnapshot, eventtype, reason, message string) (*crdv1.VolumeSnapshot, error) { glog.V(4).Infof("updateSnapshotBoundWithEvent[%s]", snapshotKey(snapshot)) if snapshot.Status.Ready && snapshot.Status.Error == nil { // Nothing to do. @@ -425,7 +425,7 @@ func IsSnapshotBound(snapshot *crdv1.VolumeSnapshot, content *crdv1.VolumeSnapsh return false } -func (ctrl *CSISnapshotController) bindSnapshotContent(snapshot *crdv1.VolumeSnapshot, content *crdv1.VolumeSnapshotContent) error { +func (ctrl *csiSnapshotController) bindSnapshotContent(snapshot *crdv1.VolumeSnapshot, content *crdv1.VolumeSnapshotContent) error { if content.Spec.VolumeSnapshotRef == nil || content.Spec.VolumeSnapshotRef.Name != snapshot.Name { return fmt.Errorf("Could not bind snapshot %s and content %s, the VolumeSnapshotRef does not match", snapshot.Name, content.Name) } else if content.Spec.VolumeSnapshotRef.UID != "" && content.Spec.VolumeSnapshotRef.UID != snapshot.UID { @@ -447,7 +447,7 @@ func (ctrl *CSISnapshotController) bindSnapshotContent(snapshot *crdv1.VolumeSna return nil } -func (ctrl *CSISnapshotController) checkandUpdateSnapshotStatusOperation(snapshot *crdv1.VolumeSnapshot, content *crdv1.VolumeSnapshotContent) (*crdv1.VolumeSnapshot, error) { +func (ctrl *csiSnapshotController) checkandUpdateSnapshotStatusOperation(snapshot *crdv1.VolumeSnapshot, content *crdv1.VolumeSnapshotContent) (*crdv1.VolumeSnapshot, error) { status, _, err := ctrl.handler.GetSnapshotStatus(content) if err != nil { return nil, fmt.Errorf("failed to check snapshot status %s with error %v", snapshot.Name, err) @@ -465,7 +465,7 @@ func (ctrl *CSISnapshotController) checkandUpdateSnapshotStatusOperation(snapsho // 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 *CSISnapshotController) createSnapshotOperation(snapshot *crdv1.VolumeSnapshot) (*crdv1.VolumeSnapshot, error) { +func (ctrl *csiSnapshotController) createSnapshotOperation(snapshot *crdv1.VolumeSnapshot) (*crdv1.VolumeSnapshot, error) { glog.Infof("createSnapshot: Creating snapshot %s through the plugin ...", snapshotKey(snapshot)) class, err := ctrl.GetClassFromVolumeSnapshot(snapshot) @@ -483,7 +483,7 @@ func (ctrl *CSISnapshotController) createSnapshotOperation(snapshot *crdv1.Volum contentName := GetSnapshotContentNameForSnapshot(snapshot) // Resolve snapshotting secret credentials. - snapshotterSecretRef, err := GetSecretReference(snapshotterSecretNameKey, snapshotterSecretNamespaceKey, class.Parameters, contentName, nil) + snapshotterSecretRef, err := GetSecretReference(snapshotterSecretNameKey, snapshotterSecretNamespaceKey, class.Parameters, contentName, snapshot) if err != nil { return nil, err } @@ -574,10 +574,28 @@ func (ctrl *CSISnapshotController) createSnapshotOperation(snapshot *crdv1.Volum // 3. Delete the SnapshotContent object // 4. Remove the Snapshot from vsStore // 5. Finish -func (ctrl *CSISnapshotController) DeleteSnapshotContentOperation(content *crdv1.VolumeSnapshotContent) error { +func (ctrl *csiSnapshotController) DeleteSnapshotContentOperation(content *crdv1.VolumeSnapshotContent) error { glog.V(4).Infof("deleteSnapshotOperation [%s] started", content.Name) - err := ctrl.handler.DeleteSnapshot(content) + // get secrets if VolumeSnapshotClass specifies it + var snapshotterCredentials map[string]string + snapshotClassName := content.Spec.VolumeSnapshotClassName + if len(snapshotClassName) != 0 { + if snapshotClass, err := ctrl.clientset.VolumesnapshotV1alpha1().VolumeSnapshotClasses().Get(snapshotClassName, metav1.GetOptions{}); err == nil { + // Resolve snapshotting secret credentials. + // No VolumeSnapshot is provided when resolving delete secret names, since the VolumeSnapshot may or may not exist at delete time. + snapshotterSecretRef, err := GetSecretReference(snapshotterSecretNameKey, snapshotterSecretNamespaceKey, snapshotClass.Parameters, content.Name, nil) + if err != nil { + return err + } + snapshotterCredentials, err = GetCredentials(ctrl.client, snapshotterSecretRef) + if err != nil { + return err + } + } + } + + err := ctrl.handler.DeleteSnapshot(content, snapshotterCredentials) if err != nil { return fmt.Errorf("failed to delete snapshot %#v, err: %v", content.Name, err) } @@ -590,7 +608,7 @@ func (ctrl *CSISnapshotController) DeleteSnapshotContentOperation(content *crdv1 return nil } -func (ctrl *CSISnapshotController) bindandUpdateVolumeSnapshot(snapshotContent *crdv1.VolumeSnapshotContent, snapshot *crdv1.VolumeSnapshot) (*crdv1.VolumeSnapshot, error) { +func (ctrl *csiSnapshotController) bindandUpdateVolumeSnapshot(snapshotContent *crdv1.VolumeSnapshotContent, snapshot *crdv1.VolumeSnapshot) (*crdv1.VolumeSnapshot, error) { glog.V(4).Infof("bindandUpdateVolumeSnapshot for snapshot [%s]: snapshotContent [%s]", snapshot.Name, snapshotContent.Name) snapshotObj, err := ctrl.clientset.VolumesnapshotV1alpha1().VolumeSnapshots(snapshot.Namespace).Get(snapshot.Name, metav1.GetOptions{}) if err != nil { @@ -622,7 +640,7 @@ func (ctrl *CSISnapshotController) bindandUpdateVolumeSnapshot(snapshotContent * } // UpdateSnapshotStatus converts snapshot status to crdv1.VolumeSnapshotCondition -func (ctrl *CSISnapshotController) updateSnapshotStatus(snapshot *crdv1.VolumeSnapshot, csistatus *csi.SnapshotStatus, timestamp time.Time, bound bool) (*crdv1.VolumeSnapshot, error) { +func (ctrl *csiSnapshotController) updateSnapshotStatus(snapshot *crdv1.VolumeSnapshot, csistatus *csi.SnapshotStatus, timestamp time.Time, bound bool) (*crdv1.VolumeSnapshot, error) { glog.V(4).Infof("updating VolumeSnapshot[]%s, set status %v, timestamp %v", snapshotKey(snapshot), csistatus, timestamp) status := snapshot.Status change := false @@ -668,7 +686,7 @@ func (ctrl *CSISnapshotController) updateSnapshotStatus(snapshot *crdv1.VolumeSn } // getVolumeFromVolumeSnapshot is a helper function to get PV from VolumeSnapshot. -func (ctrl *CSISnapshotController) getVolumeFromVolumeSnapshot(snapshot *crdv1.VolumeSnapshot) (*v1.PersistentVolume, error) { +func (ctrl *csiSnapshotController) getVolumeFromVolumeSnapshot(snapshot *crdv1.VolumeSnapshot) (*v1.PersistentVolume, error) { pvc, err := ctrl.getClaimFromVolumeSnapshot(snapshot) if err != nil { return nil, err @@ -686,7 +704,7 @@ func (ctrl *CSISnapshotController) getVolumeFromVolumeSnapshot(snapshot *crdv1.V } // GetClassFromVolumeSnapshot is a helper function to get storage class from VolumeSnapshot. -func (ctrl *CSISnapshotController) GetClassFromVolumeSnapshot(snapshot *crdv1.VolumeSnapshot) (*crdv1.VolumeSnapshotClass, error) { +func (ctrl *csiSnapshotController) GetClassFromVolumeSnapshot(snapshot *crdv1.VolumeSnapshot) (*crdv1.VolumeSnapshotClass, error) { className := snapshot.Spec.VolumeSnapshotClassName glog.V(5).Infof("getClassFromVolumeSnapshot [%s]: VolumeSnapshotClassName [%s]", snapshot.Name, className) if len(className) > 0 { @@ -734,7 +752,7 @@ func (ctrl *CSISnapshotController) GetClassFromVolumeSnapshot(snapshot *crdv1.Vo } // getClaimFromVolumeSnapshot is a helper function to get PV from VolumeSnapshot. -func (ctrl *CSISnapshotController) getClaimFromVolumeSnapshot(snapshot *crdv1.VolumeSnapshot) (*v1.PersistentVolumeClaim, error) { +func (ctrl *csiSnapshotController) getClaimFromVolumeSnapshot(snapshot *crdv1.VolumeSnapshot) (*v1.PersistentVolumeClaim, error) { if snapshot.Spec.Source == nil || snapshot.Spec.Source.Kind != pvcKind { return nil, fmt.Errorf("The snapshot source is not the right type. Expected %s, Got %v", pvcKind, snapshot.Spec.Source) } diff --git a/pkg/controller/controller.go b/pkg/controller/snapshot_controller_base.go similarity index 94% rename from pkg/controller/controller.go rename to pkg/controller/snapshot_controller_base.go index 4c9dd823..2f222f66 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/snapshot_controller_base.go @@ -39,7 +39,7 @@ import ( "k8s.io/kubernetes/pkg/util/goroutinemap" ) -type CSISnapshotController struct { +type csiSnapshotController struct { clientset clientset.Interface client kubernetes.Interface snapshotterName string @@ -66,7 +66,7 @@ type CSISnapshotController struct { resyncPeriod time.Duration } -// NewCSISnapshotController returns a new *CSISnapshotController +// NewCSISnapshotController returns a new *csiSnapshotController func NewCSISnapshotController( clientset clientset.Interface, client kubernetes.Interface, @@ -81,13 +81,13 @@ func NewCSISnapshotController( resyncPeriod time.Duration, snapshotNamePrefix string, snapshotNameUUIDLength int, -) *CSISnapshotController { +) *csiSnapshotController { broadcaster := record.NewBroadcaster() broadcaster.StartRecordingToSink(&corev1.EventSinkImpl{Interface: client.Core().Events(v1.NamespaceAll)}) var eventRecorder record.EventRecorder eventRecorder = broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: fmt.Sprintf("csi-snapshotter %s", snapshotterName)}) - ctrl := &CSISnapshotController{ + ctrl := &csiSnapshotController{ clientset: clientset, client: client, snapshotterName: snapshotterName, @@ -131,7 +131,7 @@ func NewCSISnapshotController( return ctrl } -func (ctrl *CSISnapshotController) Run(workers int, stopCh <-chan struct{}) { +func (ctrl *csiSnapshotController) Run(workers int, stopCh <-chan struct{}) { defer ctrl.snapshotQueue.ShutDown() defer ctrl.contentQueue.ShutDown() @@ -154,7 +154,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 *csiSnapshotController) enqueueSnapshotWork(obj interface{}) { // Beware of "xxx deleted" events if unknown, ok := obj.(cache.DeletedFinalStateUnknown); ok && unknown.Obj != nil { obj = unknown.Obj @@ -171,7 +171,7 @@ func (ctrl *CSISnapshotController) enqueueSnapshotWork(obj interface{}) { } // enqueueContentWork adds snapshot data to given work queue. -func (ctrl *CSISnapshotController) enqueueContentWork(obj interface{}) { +func (ctrl *csiSnapshotController) enqueueContentWork(obj interface{}) { // Beware of "xxx deleted" events if unknown, ok := obj.(cache.DeletedFinalStateUnknown); ok && unknown.Obj != nil { obj = unknown.Obj @@ -189,7 +189,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 *csiSnapshotController) snapshotWorker() { workFunc := func() bool { keyObj, quit := ctrl.snapshotQueue.Get() if quit { @@ -252,7 +252,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 *csiSnapshotController) contentWorker() { workFunc := func() bool { keyObj, quit := ctrl.contentQueue.Get() if quit { @@ -311,7 +311,7 @@ func (ctrl *CSISnapshotController) contentWorker() { // shouldProcessSnapshot detect if snapshotter in the VolumeSnapshotClass is the same as the snapshotter // in external controller. -func (ctrl *CSISnapshotController) shouldProcessSnapshot(snapshot *crdv1.VolumeSnapshot) bool { +func (ctrl *csiSnapshotController) shouldProcessSnapshot(snapshot *crdv1.VolumeSnapshot) bool { class, err := ctrl.GetClassFromVolumeSnapshot(snapshot) if err != nil { return false @@ -326,7 +326,7 @@ func (ctrl *CSISnapshotController) shouldProcessSnapshot(snapshot *crdv1.VolumeS // updateSnapshot runs in worker thread and handles "snapshot added", // "snapshot updated" and "periodic sync" events. -func (ctrl *CSISnapshotController) updateSnapshot(vs *crdv1.VolumeSnapshot) { +func (ctrl *csiSnapshotController) updateSnapshot(vs *crdv1.VolumeSnapshot) { // Store the new vs version in the cache and do not process it if this is // an old version. glog.V(5).Infof("updateSnapshot %q", snapshotKey(vs)) @@ -351,7 +351,7 @@ func (ctrl *CSISnapshotController) updateSnapshot(vs *crdv1.VolumeSnapshot) { // updateContent runs in worker thread and handles "content added", // "content updated" and "periodic sync" events. -func (ctrl *CSISnapshotController) updateContent(content *crdv1.VolumeSnapshotContent) { +func (ctrl *csiSnapshotController) updateContent(content *crdv1.VolumeSnapshotContent) { // Store the new vs version in the cache and do not process it if this is // an old version. new, err := ctrl.storeContentUpdate(content) @@ -374,7 +374,7 @@ func (ctrl *CSISnapshotController) updateContent(content *crdv1.VolumeSnapshotCo } // deleteSnapshot runs in worker thread and handles "snapshot deleted" event. -func (ctrl *CSISnapshotController) deleteSnapshot(vs *crdv1.VolumeSnapshot) { +func (ctrl *csiSnapshotController) deleteSnapshot(vs *crdv1.VolumeSnapshot) { _ = ctrl.snapshotStore.Delete(vs) glog.V(4).Infof("vs %q deleted", snapshotKey(vs)) @@ -391,7 +391,7 @@ func (ctrl *CSISnapshotController) deleteSnapshot(vs *crdv1.VolumeSnapshot) { } // deleteContent runs in worker thread and handles "snapshot deleted" event. -func (ctrl *CSISnapshotController) deleteContent(content *crdv1.VolumeSnapshotContent) { +func (ctrl *csiSnapshotController) deleteContent(content *crdv1.VolumeSnapshotContent) { _ = ctrl.contentStore.Delete(content) glog.V(4).Infof("content %q deleted", content.Name) @@ -410,7 +410,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 *csiSnapshotController) initializeCaches(snapshotLister storagelisters.VolumeSnapshotLister, contentLister storagelisters.VolumeSnapshotContentLister) { vsList, err := snapshotLister.List(labels.Everything()) if err != nil { glog.Errorf("CSISnapshotController can't initialize caches: %v", err) diff --git a/pkg/controller/util.go b/pkg/controller/util.go index 9ae3bc41..35fabb76 100644 --- a/pkg/controller/util.go +++ b/pkg/controller/util.go @@ -159,56 +159,57 @@ func GetSecretReference(nameKey, namespaceKey string, snapshotClassParams map[st ref := &v1.SecretReference{} - { - // Secret namespace template can make use of the VolumeSnapshotContent name or the VolumeSnapshot namespace. - // Note that neither of those things are under the control of the VolumeSnapshot user. - namespaceParams := map[string]string{"volumesnapshotcontent.name": snapContentName} - if snapshot != nil { - namespaceParams["volumesnapshot.namespace"] = snapshot.Namespace - } - - resolvedNamespace, err := resolveTemplate(namespaceTemplate, namespaceParams) - if err != nil { - return nil, fmt.Errorf("error resolving %s value %q: %v", namespaceKey, namespaceTemplate, err) - } - if len(validation.IsDNS1123Label(resolvedNamespace)) > 0 { - if namespaceTemplate != resolvedNamespace { - return nil, fmt.Errorf("%s parameter %q resolved to %q which is not a valid namespace name", namespaceKey, namespaceTemplate, resolvedNamespace) - } - return nil, fmt.Errorf("%s parameter %q is not a valid namespace name", namespaceKey, namespaceTemplate) - } - ref.Namespace = resolvedNamespace + // Secret namespace template can make use of the VolumeSnapshotContent name or the VolumeSnapshot 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 + // snapshot may or may not exist at delete time + if snapshot != nil { + namespaceParams["volumesnapshot.namespace"] = snapshot.Namespace } - { - // Secret name template can make use of the VolumeSnapshotContent name, VolumeSnapshot name or namespace, - // or a VolumeSnapshot annotation. - // Note that VolumeSnapshot name and annotations are under the VolumeSnapshot user's control. - nameParams := map[string]string{"volumesnapshotcontent.name": snapContentName} - if snapshot != nil { - nameParams["volumesnapshot.name"] = snapshot.Name - nameParams["volumesnapshot.namespace"] = snapshot.Namespace - for k, v := range snapshot.Annotations { - nameParams["volumesnapshot.annotations['"+k+"']"] = v - } - } - resolvedName, err := resolveTemplate(nameTemplate, nameParams) - if err != nil { - return nil, fmt.Errorf("error resolving %s value %q: %v", nameKey, nameTemplate, err) - } - if len(validation.IsDNS1123Subdomain(resolvedName)) > 0 { - if nameTemplate != resolvedName { - return nil, fmt.Errorf("%s parameter %q resolved to %q which is not a valid secret name", nameKey, nameTemplate, resolvedName) - } - return nil, fmt.Errorf("%s parameter %q is not a valid secret name", nameKey, nameTemplate) - } - ref.Name = resolvedName + resolvedNamespace, err := resolveTemplate(namespaceTemplate, namespaceParams) + if err != nil { + return nil, fmt.Errorf("error resolving %s value %q: %v", namespaceKey, namespaceTemplate, err) } + glog.V(4).Infof("GetSecretReference namespaceTemplate %s, namespaceParams: %+v, resolved %s", namespaceTemplate, namespaceParams, resolvedNamespace) + + if len(validation.IsDNS1123Label(resolvedNamespace)) > 0 { + if namespaceTemplate != resolvedNamespace { + return nil, fmt.Errorf("%s parameter %q resolved to %q which is not a valid namespace name", namespaceKey, namespaceTemplate, resolvedNamespace) + } + return nil, fmt.Errorf("%s parameter %q is not a valid namespace name", namespaceKey, namespaceTemplate) + } + ref.Namespace = resolvedNamespace + + // Secret name template can make use of the VolumeSnapshotContent name, VolumeSnapshot name or namespace, + // or a VolumeSnapshot annotation. + // Note that VolumeSnapshot name and annotations are under the VolumeSnapshot user's control. + nameParams := map[string]string{"volumesnapshotcontent.name": snapContentName} + if snapshot != nil { + nameParams["volumesnapshot.name"] = snapshot.Name + nameParams["volumesnapshot.namespace"] = snapshot.Namespace + for k, v := range snapshot.Annotations { + nameParams["volumesnapshot.annotations['"+k+"']"] = v + } + } + resolvedName, err := resolveTemplate(nameTemplate, nameParams) + if err != nil { + return nil, fmt.Errorf("error resolving %s value %q: %v", nameKey, nameTemplate, err) + } + if len(validation.IsDNS1123Subdomain(resolvedName)) > 0 { + if nameTemplate != resolvedName { + return nil, fmt.Errorf("%s parameter %q resolved to %q which is not a valid secret name", nameKey, nameTemplate, resolvedName) + } + return nil, fmt.Errorf("%s parameter %q is not a valid secret name", nameKey, nameTemplate) + } + ref.Name = resolvedName glog.V(4).Infof("GetSecretReference validated Secret: %+v", ref) return ref, nil } +// resolveTemplate resolves the template by checking if the value is missing for a key func resolveTemplate(template string, params map[string]string) (string, error) { missingParams := sets.NewString() resolved := os.Expand(template, func(k string) string { @@ -224,6 +225,7 @@ 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) { if ref == nil { return nil, nil