Files
external-snapshotter/pkg/common-controller/groupsnapshot_controller_helper.go
2024-01-30 17:40:58 -05:00

1241 lines
64 KiB
Go

/*
Copyright 2023 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 common_controller
import (
"context"
"fmt"
"time"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
apierrs "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/kubernetes/scheme"
ref "k8s.io/client-go/tools/reference"
klog "k8s.io/klog/v2"
crdv1alpha1 "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumegroupsnapshot/v1alpha1"
crdv1 "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1"
"github.com/kubernetes-csi/external-snapshotter/v7/pkg/utils"
)
func (ctrl *csiSnapshotCommonController) storeGroupSnapshotUpdate(groupsnapshot interface{}) (bool, error) {
return utils.StoreObjectUpdate(ctrl.groupSnapshotStore, groupsnapshot, "groupsnapshot")
}
func (ctrl *csiSnapshotCommonController) storeGroupSnapshotContentUpdate(groupsnapshotcontent interface{}) (bool, error) {
return utils.StoreObjectUpdate(ctrl.groupSnapshotContentStore, groupsnapshotcontent, "groupsnapshotcontent")
}
// getGroupSnapshotClass is a helper function to get group snapshot class from the group snapshot class name.
func (ctrl *csiSnapshotCommonController) getGroupSnapshotClass(className string) (*crdv1alpha1.VolumeGroupSnapshotClass, error) {
klog.V(5).Infof("getGroupSnapshotClass: VolumeGroupSnapshotClassName [%s]", className)
groupSnapshotClass, err := ctrl.groupSnapshotClassLister.Get(className)
if err != nil {
klog.Errorf("failed to retrieve group snapshot class %s from the informer: %q", className, err)
return nil, err
}
return groupSnapshotClass, nil
}
// updateGroupSnapshotErrorStatusWithEvent saves new groupsnapshot.Status to API
// server and emits given event on the group snapshot. It saves the status and
// emits the event only when the status has actually changed from the version
// saved in API server.
//
// Parameters:
//
// - groupSnapshot - group snapshot to update
// - setReadyToFalse bool - indicates whether to set the group snapshot's
// ReadyToUse status to false.
// if true, ReadyToUse will be set to false;
// otherwise, ReadyToUse will not be changed.
// - eventtype, reason, message - event to send, see EventRecorder.Event()
func (ctrl *csiSnapshotCommonController) updateGroupSnapshotErrorStatusWithEvent(groupSnapshot *crdv1alpha1.VolumeGroupSnapshot, setReadyToFalse bool, eventtype, reason, message string) error {
klog.V(5).Infof("updateGroupSnapshotErrorStatusWithEvent[%s]", utils.GroupSnapshotKey(groupSnapshot))
if groupSnapshot.Status != nil && groupSnapshot.Status.Error != nil && *groupSnapshot.Status.Error.Message == message {
klog.V(4).Infof("updateGroupSnapshotErrorStatusWithEvent[%s]: the same error %v is already set", groupSnapshot.Name, groupSnapshot.Status.Error)
return nil
}
groupSnapshotClone := groupSnapshot.DeepCopy()
if groupSnapshotClone.Status == nil {
groupSnapshotClone.Status = &crdv1alpha1.VolumeGroupSnapshotStatus{}
}
statusError := &crdv1.VolumeSnapshotError{
Time: &metav1.Time{
Time: time.Now(),
},
Message: &message,
}
groupSnapshotClone.Status.Error = statusError
// Only update ReadyToUse in VolumeGroupSnapshot's Status to false if setReadyToFalse is true.
if setReadyToFalse {
ready := false
groupSnapshotClone.Status.ReadyToUse = &ready
}
newSnapshot, err := ctrl.clientset.GroupsnapshotV1alpha1().VolumeGroupSnapshots(groupSnapshotClone.Namespace).UpdateStatus(context.TODO(), groupSnapshotClone, metav1.UpdateOptions{})
// Emit the event even if the status update fails so that user can see the error
ctrl.eventRecorder.Event(newSnapshot, eventtype, reason, message)
if err != nil {
klog.V(4).Infof("updating VolumeGroupSnapshot[%s] error status failed %v", utils.GroupSnapshotKey(groupSnapshot), err)
return err
}
_, err = ctrl.storeGroupSnapshotUpdate(newSnapshot)
if err != nil {
klog.V(4).Infof("updating VolumeGroupSnapshot[%s] error status: cannot update internal cache %v", utils.GroupSnapshotKey(groupSnapshot), err)
return err
}
return nil
}
// SetDefaultGroupSnapshotClass is a helper function to figure out the default
// group snapshot class.
// For pre-provisioned case, it's an no-op.
// For dynamic provisioning, it gets the default GroupSnapshotClasses in the
// system if there is any (could be multiple), and finds the one with the same
// CSI Driver as a PV from which a group snapshot will be taken.
func (ctrl *csiSnapshotCommonController) SetDefaultGroupSnapshotClass(groupSnapshot *crdv1alpha1.VolumeGroupSnapshot) (*crdv1alpha1.VolumeGroupSnapshotClass, *crdv1alpha1.VolumeGroupSnapshot, error) {
klog.V(5).Infof("SetDefaultGroupSnapshotClass for group snapshot [%s]", groupSnapshot.Name)
if groupSnapshot.Spec.Source.VolumeGroupSnapshotContentName != nil {
// don't return error for pre-provisioned group snapshots
klog.V(5).Infof("Don't need to find GroupSnapshotClass for pre-provisioned group snapshot [%s]", groupSnapshot.Name)
return nil, groupSnapshot, nil
}
// Find default group snapshot class if available
list, err := ctrl.groupSnapshotClassLister.List(labels.Everything())
if err != nil {
return nil, groupSnapshot, err
}
pvDriver, err := ctrl.pvDriverFromGroupSnapshot(groupSnapshot)
if err != nil {
klog.Errorf("failed to get pv csi driver from group snapshot %s/%s: %q", groupSnapshot.Namespace, groupSnapshot.Name, err)
return nil, groupSnapshot, err
}
defaultClasses := []*crdv1alpha1.VolumeGroupSnapshotClass{}
for _, groupSnapshotClass := range list {
if utils.IsDefaultAnnotation(groupSnapshotClass.ObjectMeta) && pvDriver == groupSnapshotClass.Driver {
defaultClasses = append(defaultClasses, groupSnapshotClass)
klog.V(5).Infof("get defaultGroupClass added: %s, driver: %s", groupSnapshotClass.Name, pvDriver)
}
}
if len(defaultClasses) == 0 {
return nil, groupSnapshot, fmt.Errorf("cannot find default group snapshot class")
}
if len(defaultClasses) > 1 {
klog.V(4).Infof("get DefaultGroupSnapshotClass %d defaults found", len(defaultClasses))
return nil, groupSnapshot, fmt.Errorf("%d default snapshot classes were found", len(defaultClasses))
}
klog.V(5).Infof("setDefaultGroupSnapshotClass [%s]: default VolumeGroupSnapshotClassName [%s]", groupSnapshot.Name, defaultClasses[0].Name)
groupSnapshotClone := groupSnapshot.DeepCopy()
groupSnapshotClone.Spec.VolumeGroupSnapshotClassName = &(defaultClasses[0].Name)
newGroupSnapshot, err := ctrl.clientset.GroupsnapshotV1alpha1().VolumeGroupSnapshots(groupSnapshotClone.Namespace).Update(context.TODO(), groupSnapshotClone, metav1.UpdateOptions{})
if err != nil {
klog.V(4).Infof("updating VolumeGroupSnapshot[%s] default group snapshot class failed %v", utils.GroupSnapshotKey(groupSnapshot), err)
}
_, updateErr := ctrl.storeGroupSnapshotUpdate(newGroupSnapshot)
if updateErr != nil {
// We will get a "group snapshot update" event soon, this is not a big error
klog.V(4).Infof("setDefaultSnapshotClass [%s]: cannot update internal cache: %v", utils.GroupSnapshotKey(groupSnapshot), updateErr)
}
return defaultClasses[0], newGroupSnapshot, nil
}
// pvDriverFromGroupSnapshot is a helper function to get the CSI driver name from the targeted persistent volume.
// It looks up every PVC from which the group snapshot is specified to be created from, and looks for the PVC's
// corresponding PV. Bi-directional binding will be verified between PVC and PV before the PV's CSI driver is returned.
// For an non-CSI volume, it returns an error immediately as it's not supported.
func (ctrl *csiSnapshotCommonController) pvDriverFromGroupSnapshot(groupSnapshot *crdv1alpha1.VolumeGroupSnapshot) (string, error) {
pvs, err := ctrl.getVolumesFromVolumeGroupSnapshot(groupSnapshot)
if err != nil {
return "", err
}
// Take any volume to get the driver
if pvs[0].Spec.PersistentVolumeSource.CSI == nil {
return "", fmt.Errorf("snapshotting non-CSI volumes is not supported, group snapshot:%s/%s", groupSnapshot.Namespace, groupSnapshot.Name)
}
return pvs[0].Spec.PersistentVolumeSource.CSI.Driver, nil
}
// getVolumesFromVolumeGroupSnapshot returns the list of PersistentVolume from a VolumeGroupSnapshot.
func (ctrl *csiSnapshotCommonController) getVolumesFromVolumeGroupSnapshot(groupSnapshot *crdv1alpha1.VolumeGroupSnapshot) ([]*v1.PersistentVolume, error) {
var pvReturnList []*v1.PersistentVolume
pvcs, err := ctrl.getClaimsFromVolumeGroupSnapshot(groupSnapshot)
if err != nil {
return nil, err
}
for _, pvc := range pvcs {
if pvc.Status.Phase != v1.ClaimBound {
return nil, fmt.Errorf("the PVC %s is not yet bound to a PV, will not attempt to take a group snapshot", pvc.Name)
}
pvName := pvc.Spec.VolumeName
pv, err := ctrl.client.CoreV1().PersistentVolumes().Get(context.TODO(), pvName, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("failed to retrieve PV %s from the API server: %q", pvName, err)
}
// Verify binding between PV/PVC is still valid
bound := ctrl.isVolumeBoundToClaim(pv, &pvc)
if bound == false {
klog.Warningf("binding between PV %s and PVC %s is broken", pvName, pvc.Name)
return nil, fmt.Errorf("claim in dataSource not bound or invalid")
}
pvReturnList = append(pvReturnList, pv)
klog.V(5).Infof("getVolumeFromVolumeGroupSnapshot: group snapshot [%s] PV name [%s]", groupSnapshot.Name, pvName)
}
return pvReturnList, nil
}
// getClaimsFromVolumeGroupSnapshot is a helper function to get a list of PVCs from VolumeGroupSnapshot.
func (ctrl *csiSnapshotCommonController) getClaimsFromVolumeGroupSnapshot(groupSnapshot *crdv1alpha1.VolumeGroupSnapshot) ([]v1.PersistentVolumeClaim, error) {
labelSelector := groupSnapshot.Spec.Source.Selector
// Get PVC that has group snapshot label applied.
pvcList, err := ctrl.client.CoreV1().PersistentVolumeClaims(groupSnapshot.Namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: labels.Set(labelSelector.MatchLabels).String()})
if err != nil {
return nil, fmt.Errorf("failed to list PVCs with label selector %s: %q", labelSelector.String(), err)
}
if len(pvcList.Items) == 0 {
return nil, fmt.Errorf("label selector %s for group snapshot not applied to any PVC", labelSelector.String())
}
return pvcList.Items, nil
}
// updateGroupSnapshot runs in worker thread and handles "groupsnapshot added",
// "groupsnapshot updated" and "periodic sync" events.
func (ctrl *csiSnapshotCommonController) updateGroupSnapshot(groupSnapshot *crdv1alpha1.VolumeGroupSnapshot) error {
// Store the new group snapshot version in the cache and do not process it
// if this is an old version.
klog.V(5).Infof("updateGroupSnapshot %q", utils.GroupSnapshotKey(groupSnapshot))
newGroupSnapshot, err := ctrl.storeGroupSnapshotUpdate(groupSnapshot)
if err != nil {
klog.Errorf("%v", err)
}
if !newGroupSnapshot {
return nil
}
err = ctrl.syncGroupSnapshot(groupSnapshot)
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 group snapshot %q: %+v", utils.GroupSnapshotKey(groupSnapshot), err)
} else {
klog.Errorf("could not sync group snapshot %q: %+v", utils.GroupSnapshotKey(groupSnapshot), err)
}
return err
}
return nil
}
// deleteGroupSnapshot runs in worker thread and handles "groupsnapshot deleted" event.
func (ctrl *csiSnapshotCommonController) deleteGroupSnapshot(groupSnapshot *crdv1alpha1.VolumeGroupSnapshot) {
_ = ctrl.snapshotStore.Delete(groupSnapshot)
klog.V(4).Infof("group snapshot %q deleted", utils.GroupSnapshotKey(groupSnapshot))
groupSnapshotContentName := ""
if groupSnapshot.Status != nil && groupSnapshot.Status.BoundVolumeGroupSnapshotContentName != nil {
groupSnapshotContentName = *groupSnapshot.Status.BoundVolumeGroupSnapshotContentName
}
if groupSnapshotContentName == "" {
klog.V(5).Infof("deleteGroupSnapshot[%q]: group snapshot content not bound", utils.GroupSnapshotKey(groupSnapshot))
return
}
// sync the group snapshot content when its group snapshot is deleted. Explicitly sync'ing
// the group snapshot content here in response to group snapshot deletion prevents the group
// snapshot content from waiting until the next sync period for its release.
klog.V(5).Infof("deleteGroupSnapshot[%q]: scheduling sync of group snapshot content %s", utils.GroupSnapshotKey(groupSnapshot), groupSnapshotContentName)
ctrl.groupSnapshotContentQueue.Add(groupSnapshotContentName)
}
// syncGroupSnapshot is the main controller method to decide what to do with a
// group snapshot. It's invoked by appropriate cache.Controller callbacks when
// a group snapshot is created, updated or periodically synced. We do not
// differentiate between these events.
// For easier readability, it is split into syncUnreadyGroupSnapshot and syncReadyGroupSnapshot
func (ctrl *csiSnapshotCommonController) syncGroupSnapshot(groupSnapshot *crdv1alpha1.VolumeGroupSnapshot) error {
klog.V(5).Infof("synchronizing VolumeGroupSnapshot[%s]", utils.GroupSnapshotKey(groupSnapshot))
klog.V(5).Infof("syncGroupSnapshot [%s]: check if we should remove finalizer on group snapshot PVC source and remove it if we can", utils.GroupSnapshotKey(groupSnapshot))
// Proceed with group snapshot deletion and remove finalizers when needed
if groupSnapshot.ObjectMeta.DeletionTimestamp != nil {
return ctrl.processGroupSnapshotWithDeletionTimestamp(groupSnapshot)
}
// Keep this check in the controller since the validation webhook may not have been deployed.
klog.V(5).Infof("syncGroupSnapshot[%s]: validate group snapshot to make sure source has been correctly specified", utils.GroupSnapshotKey(groupSnapshot))
if (groupSnapshot.Spec.Source.Selector == nil && groupSnapshot.Spec.Source.VolumeGroupSnapshotContentName == nil) ||
(groupSnapshot.Spec.Source.Selector != nil && groupSnapshot.Spec.Source.VolumeGroupSnapshotContentName != nil) {
err := fmt.Errorf("Exactly one of Selector and VolumeGroupSnapshotContentName should be specified")
klog.Errorf("syncGroupSnapshot[%s]: validation error, %s", utils.GroupSnapshotKey(groupSnapshot), err.Error())
ctrl.updateGroupSnapshotErrorStatusWithEvent(groupSnapshot, true, v1.EventTypeWarning, "GroupSnapshotValidationError", err.Error())
return err
}
klog.V(5).Infof("syncGroupSnapshot: check if we should add finalizers on group snapshot [%s]", utils.GroupSnapshotKey(groupSnapshot))
if err := ctrl.checkandAddGroupSnapshotFinalizers(groupSnapshot); err != nil {
klog.Errorf("error checkandAddGroupSnapshotFinalizers for group snapshot [%s]: %v", utils.GroupSnapshotKey(groupSnapshot), err)
ctrl.eventRecorder.Event(groupSnapshot, v1.EventTypeWarning, "GroupSnapshotFinalizerError", fmt.Sprintf("Failed to check and update group snapshot: %s", err.Error()))
return err
}
// Need to build or update groupSnapshot.Status in following cases:
// 1) groupSnapshot.Status is nil
// 2) groupSnapshot.Status.ReadyToUse is false
// 3) groupSnapshot.Status.IsBoundVolumeGroupSnapshotContentNameSet is not set
// 4) groupSnapshot.Status.IsVolumeSnapshotRefListSet is not set
if !utils.IsGroupSnapshotReady(groupSnapshot) || !utils.IsBoundVolumeGroupSnapshotContentNameSet(groupSnapshot) || !utils.IsVolumeSnapshotRefListSet(groupSnapshot) {
return ctrl.syncUnreadyGroupSnapshot(groupSnapshot)
}
return ctrl.syncReadyGroupSnapshot(groupSnapshot)
}
// syncReadyGroupSnapshot checks the group snapshot which has been bound to group
// snapshot content successfully before.
// If there is any problem with the binding (e.g., group snapshot points to a
// non-existent group snapshot content), update the group snapshot status and emit event.
func (ctrl *csiSnapshotCommonController) syncReadyGroupSnapshot(groupSnapshot *crdv1alpha1.VolumeGroupSnapshot) error {
if !utils.IsBoundVolumeGroupSnapshotContentNameSet(groupSnapshot) {
return fmt.Errorf("group snapshot %s is not bound to a group snapshot content", utils.GroupSnapshotKey(groupSnapshot))
}
groupSnapshotContent, err := ctrl.getGroupSnapshotContentFromStore(*groupSnapshot.Status.BoundVolumeGroupSnapshotContentName)
if err != nil {
return nil
}
if groupSnapshotContent == nil {
// this meant there is no matching group snapshot content in cache found
// update status of the group snapshot and return
return ctrl.updateGroupSnapshotErrorStatusWithEvent(groupSnapshot, true, v1.EventTypeWarning, "GroupSnapshotContentMissing", "VolumeGroupSnapshotContent is missing")
}
klog.V(5).Infof("syncReadyGroupSnapshot[%s]: VolumeGroupSnapshotContent %q found", utils.GroupSnapshotKey(groupSnapshot), groupSnapshotContent.Name)
// check binding from group snapshot content side to make sure the binding is still valid
if !utils.IsVolumeGroupSnapshotRefSet(groupSnapshot, groupSnapshotContent) {
// group snapshot is bound but group snapshot content is not pointing to the group snapshot
return ctrl.updateGroupSnapshotErrorStatusWithEvent(groupSnapshot, true, v1.EventTypeWarning, "GroupSnapshotMisbound", "VolumeGroupSnapshotContent is not bound to the VolumeGroupSnapshot correctly")
}
// everything is verified, return
return nil
}
// getGroupSnapshotContentFromStore tries to find a VolumeGroupSnapshotContent from group
// snapshot content cache store by name.
// Note that if no VolumeGroupSnapshotContent exists in the cache store and no error
// encountered, it returns (nil, nil)
func (ctrl *csiSnapshotCommonController) getGroupSnapshotContentFromStore(contentName string) (*crdv1alpha1.VolumeGroupSnapshotContent, error) {
obj, exist, err := ctrl.groupSnapshotContentStore.GetByKey(contentName)
if err != nil {
// should never reach here based on implementation at:
// https://github.com/kubernetes/client-go/blob/master/tools/cache/store.go#L226
return nil, err
}
if !exist {
// not able to find a matching group snapshot content
return nil, nil
}
groupSnapshotContent, ok := obj.(*crdv1alpha1.VolumeGroupSnapshotContent)
if !ok {
return nil, fmt.Errorf("expected VolumeGroupSnapshotContent, got %+v", obj)
}
return groupSnapshotContent, nil
}
// syncUnreadyGroupSnapshot is the main controller method to decide what to do
// with a group snapshot which is not set to ready.
func (ctrl *csiSnapshotCommonController) syncUnreadyGroupSnapshot(groupSnapshot *crdv1alpha1.VolumeGroupSnapshot) error {
uniqueGroupSnapshotName := utils.GroupSnapshotKey(groupSnapshot)
klog.V(5).Infof("syncUnreadyGroupSnapshot %s", uniqueGroupSnapshotName)
/*
TODO: Add metrics
*/
// Pre-provisioned snapshot
if groupSnapshot.Spec.Source.VolumeGroupSnapshotContentName != nil {
groupSnapshotContent, err := ctrl.getPreprovisionedGroupSnapshotContentFromStore(groupSnapshot)
if err != nil {
return err
}
// if no group snapshot content found yet, update status and return
if groupSnapshotContent == nil {
// can not find the desired VolumeGroupSnapshotContent from cache store
ctrl.updateGroupSnapshotErrorStatusWithEvent(groupSnapshot, true, v1.EventTypeWarning, "GroupSnapshotContentMissing", "VolumeGroupSnapshotContent is missing")
klog.V(4).Infof("syncUnreadyGroupSnapshot[%s]: group snapshot content %q requested but not found, will try again", utils.GroupSnapshotKey(groupSnapshot), *groupSnapshot.Spec.Source.VolumeGroupSnapshotContentName)
return fmt.Errorf("group snapshot %s requests an non-existing group snapshot content %s", utils.GroupSnapshotKey(groupSnapshot), *groupSnapshot.Spec.Source.VolumeGroupSnapshotContentName)
}
// Set VolumeGroupSnapshotRef UID
newGroupSnapshotContent, err := ctrl.checkAndBindGroupSnapshotContent(groupSnapshot, groupSnapshotContent)
if err != nil {
// group snapshot is bound but group snapshot content is not bound to group snapshot correctly
ctrl.updateGroupSnapshotErrorStatusWithEvent(groupSnapshot, true, v1.EventTypeWarning, "GroupSnapshotBindFailed", fmt.Sprintf("GroupSnapshot failed to bind VolumeGroupSnapshotContent, %v", err))
return fmt.Errorf("group snapshot %s is bound, but VolumeGroupSnapshotContent %s is not bound to the VolumeGroupSnapshot correctly, %v", uniqueGroupSnapshotName, groupSnapshotContent.Name, err)
}
// update group snapshot status
klog.V(5).Infof("syncUnreadyGroupSnapshot [%s]: trying to update group snapshot status", utils.GroupSnapshotKey(groupSnapshot))
if _, err = ctrl.updateGroupSnapshotStatus(groupSnapshot, newGroupSnapshotContent); err != nil {
// update group snapshot status failed
klog.V(4).Infof("failed to update group snapshot %s status: %v", utils.GroupSnapshotKey(groupSnapshot), err)
ctrl.updateGroupSnapshotErrorStatusWithEvent(groupSnapshot, false, v1.EventTypeWarning, "GroupSnapshotStatusUpdateFailed", fmt.Sprintf("GroupSnapshot status update failed, %v", err))
return err
}
return nil
}
// groupSnapshot.Spec.Source.VolumeGroupSnapshotContentName == nil - dynamically created group snapshot
klog.V(5).Infof("getDynamicallyProvisionedGroupContentFromStore for snapshot %s", uniqueGroupSnapshotName)
contentObj, err := ctrl.getDynamicallyProvisionedGroupContentFromStore(groupSnapshot)
if err != nil {
klog.V(4).Infof("getDynamicallyProvisionedGroupContentFromStore[%s]: error when getting group snapshot content for group snapshot %v", uniqueGroupSnapshotName, err)
return err
}
if contentObj != nil {
klog.V(5).Infof("Found VolumeGroupSnapshotContent object %s for group snapshot %s", contentObj.Name, uniqueGroupSnapshotName)
if contentObj.Spec.Source.GroupSnapshotHandles != nil {
ctrl.updateGroupSnapshotErrorStatusWithEvent(groupSnapshot, true, v1.EventTypeWarning, "GroupSnapshotHandleSet", fmt.Sprintf("GroupSnapshot handle should not be set in group snapshot content %s for dynamic provisioning", uniqueGroupSnapshotName))
return fmt.Errorf("VolumeGroupSnapshotHandle should not be set in the group snapshot content for dynamic provisioning for group snapshot %s", uniqueGroupSnapshotName)
}
newGroupSnapshot, err := ctrl.bindandUpdateVolumeGroupSnapshot(contentObj, groupSnapshot)
if err != nil {
klog.V(4).Infof("bindandUpdateVolumeGroupSnapshot[%s]: failed to bind group snapshot content [%s] to group snapshot %v", uniqueGroupSnapshotName, contentObj.Name, err)
return err
}
klog.V(5).Infof("bindandUpdateVolumeGroupSnapshot %v", newGroupSnapshot)
return nil
}
// If we reach here, it is a dynamically provisioned group snapshot, and the VolumeGroupSnapshotContent object is not yet created.
var groupSnapshotContent *crdv1alpha1.VolumeGroupSnapshotContent
if groupSnapshotContent, err = ctrl.createGroupSnapshotContent(groupSnapshot); err != nil {
ctrl.updateGroupSnapshotErrorStatusWithEvent(groupSnapshot, true, v1.EventTypeWarning, "GroupSnapshotContentCreationFailed", fmt.Sprintf("failed to create group snapshot content with error %v", err))
return err
}
// Update group snapshot status with BoundVolumeGroupSnapshotContentName
klog.V(5).Infof("syncUnreadyGroupSnapshot [%s]: trying to update group snapshot status", utils.GroupSnapshotKey(groupSnapshot))
if _, err = ctrl.updateGroupSnapshotStatus(groupSnapshot, groupSnapshotContent); err != nil {
// update group snapshot status failed
ctrl.updateGroupSnapshotErrorStatusWithEvent(groupSnapshot, false, v1.EventTypeWarning, "GroupSnapshotStatusUpdateFailed", fmt.Sprintf("GroupSnapshot status update failed, %v", err))
return err
}
return nil
}
// getPreprovisionedGroupSnapshotContentFromStore tries to find a pre-provisioned
// volume group snapshot content object from group snapshot content cache store
// for the passed in VolumeGroupSnapshot.
// Note that this function assumes the passed in VolumeGroupSnapshot is a pre-provisioned
// one, i.e., groupSnapshot.Spec.Source.VolumeGroupSnapshotContentName != nil.
// If no matching group snapshot content is found, it returns (nil, nil).
// If it found a group snapshot content which is not a pre-provisioned one, it
// updates the status of the group snapshot with an event and returns an error.
// If it found a group snapshot content which does not point to the passed in
// VolumeGroupSnapshot, it updates the status of the group snapshot with an event
// and returns an error.
// Otherwise, the found group snapshot content will be returned.
func (ctrl *csiSnapshotCommonController) getPreprovisionedGroupSnapshotContentFromStore(groupSnapshot *crdv1alpha1.VolumeGroupSnapshot) (*crdv1alpha1.VolumeGroupSnapshotContent, error) {
contentName := *groupSnapshot.Spec.Source.VolumeGroupSnapshotContentName
if contentName == "" {
return nil, fmt.Errorf("empty VolumeGroupSnapshotContentName for group snapshot %s", utils.GroupSnapshotKey(groupSnapshot))
}
groupSnapshotContent, err := ctrl.getGroupSnapshotContentFromStore(contentName)
if err != nil {
return nil, err
}
if groupSnapshotContent == nil {
// can not find the desired VolumeGroupSnapshotContent from cache store
return nil, nil
}
// check whether the content is a pre-provisioned VolumeGroupSnapshotContent
if groupSnapshotContent.Spec.Source.GroupSnapshotHandles == nil {
// found a group snapshot content which represents a dynamically provisioned group snapshot
// update the group snapshot and return an error
ctrl.updateGroupSnapshotErrorStatusWithEvent(groupSnapshot, true, v1.EventTypeWarning, "GroupSnapshotContentMismatch", "VolumeGroupSnapshotContent is dynamically provisioned while expecting a pre-provisioned one")
klog.V(4).Infof("sync group snapshot[%s]: group snapshot content %q is dynamically provisioned while expecting a pre-provisioned one", utils.GroupSnapshotKey(groupSnapshot), contentName)
return nil, fmt.Errorf("group snapshot %s expects a pre-provisioned VolumeGroupSnapshotContent %s but gets a dynamically provisioned one", utils.GroupSnapshotKey(groupSnapshot), contentName)
}
// verify the group snapshot content points back to the group snapshot
ref := groupSnapshotContent.Spec.VolumeGroupSnapshotRef
if ref.Name != groupSnapshot.Name || ref.Namespace != groupSnapshot.Namespace || (ref.UID != "" && ref.UID != groupSnapshot.UID) {
klog.V(4).Infof("sync group snapshot[%s]: VolumeGroupSnapshotContent %s is bound to another group snapshot %v", utils.GroupSnapshotKey(groupSnapshot), contentName, ref)
msg := fmt.Sprintf("VolumeGroupSnapshotContent [%s] is bound to a different group snapshot", contentName)
ctrl.updateGroupSnapshotErrorStatusWithEvent(groupSnapshot, true, v1.EventTypeWarning, "GroupSnapshotContentMisbound", msg)
return nil, fmt.Errorf(msg)
}
return groupSnapshotContent, nil
}
// checkandBindGroupSnapshotContent checks whether the VolumeGroupSnapshotRef in
// the group snapshot content matches the given group snapshot. If match, it binds
// the group snapshot content with the group snapshot. This is for static binding where
// user has specified group snapshot name but not UID of the group snapshot in
// groupSnapshotContent.Spec.VolumeGroupSnapshotRef.
func (ctrl *csiSnapshotCommonController) checkAndBindGroupSnapshotContent(groupSnapshot *crdv1alpha1.VolumeGroupSnapshot, groupSnapshotContent *crdv1alpha1.VolumeGroupSnapshotContent) (*crdv1alpha1.VolumeGroupSnapshotContent, error) {
if groupSnapshotContent.Spec.VolumeGroupSnapshotRef.Name != groupSnapshot.Name {
return nil, fmt.Errorf("Could not bind group snapshot %s and group snapshot content %s, the VolumeGroupSnapshotRef does not match", groupSnapshot.Name, groupSnapshotContent.Name)
} else if groupSnapshotContent.Spec.VolumeGroupSnapshotRef.UID != "" && groupSnapshotContent.Spec.VolumeGroupSnapshotRef.UID != groupSnapshot.UID {
return nil, fmt.Errorf("Could not bind group snapshot %s and group snapshot content %s, the VolumeGroupSnapshotRef does not match", groupSnapshot.Name, groupSnapshotContent.Name)
} else if groupSnapshotContent.Spec.VolumeGroupSnapshotRef.UID != "" && groupSnapshotContent.Spec.VolumeGroupSnapshotClassName != nil {
return groupSnapshotContent, nil
}
patches := []utils.PatchOp{
{
Op: "replace",
Path: "/spec/volumeGroupSnapshotRef/uid",
Value: string(groupSnapshot.UID),
},
}
if groupSnapshot.Spec.VolumeGroupSnapshotClassName != nil {
className := *(groupSnapshot.Spec.VolumeGroupSnapshotClassName)
patches = append(patches, utils.PatchOp{
Op: "replace",
Path: "/spec/volumeGroupSnapshotClassName",
Value: className,
})
}
newContent, err := utils.PatchVolumeGroupSnapshotContent(groupSnapshotContent, patches, ctrl.clientset)
if err != nil {
klog.V(4).Infof("updating VolumeGroupSnapshotContent[%s] error status failed %v", groupSnapshotContent.Name, err)
return groupSnapshotContent, err
}
_, err = ctrl.storeGroupSnapshotContentUpdate(newContent)
if err != nil {
klog.V(4).Infof("updating VolumeGroupSnapshotContent[%s] error status: cannot update internal cache %v", newContent.Name, err)
return newContent, err
}
return newContent, nil
}
// updateGroupSnapshotStatus updates group snapshot status based on group snapshot content status
func (ctrl *csiSnapshotCommonController) updateGroupSnapshotStatus(groupSnapshot *crdv1alpha1.VolumeGroupSnapshot, groupSnapshotContent *crdv1alpha1.VolumeGroupSnapshotContent) (*crdv1alpha1.VolumeGroupSnapshot, error) {
klog.V(5).Infof("updateGroupSnapshotStatus[%s]", utils.GroupSnapshotKey(groupSnapshot))
boundContentName := groupSnapshotContent.Name
var createdAt *time.Time
if groupSnapshotContent.Status != nil && groupSnapshotContent.Status.CreationTime != nil {
unixTime := time.Unix(0, *groupSnapshotContent.Status.CreationTime)
createdAt = &unixTime
}
var readyToUse bool
if groupSnapshotContent.Status != nil && groupSnapshotContent.Status.ReadyToUse != nil {
readyToUse = *groupSnapshotContent.Status.ReadyToUse
}
var volumeSnapshotErr *crdv1.VolumeSnapshotError
if groupSnapshotContent.Status != nil && groupSnapshotContent.Status.Error != nil {
volumeSnapshotErr = groupSnapshotContent.Status.Error.DeepCopy()
}
var volumeSnapshotRefList []v1.ObjectReference
if groupSnapshotContent.Status != nil && len(groupSnapshotContent.Status.VolumeSnapshotContentRefList) != 0 {
for _, contentRef := range groupSnapshotContent.Status.VolumeSnapshotContentRefList {
groupSnapshotContent, err := ctrl.contentLister.Get(contentRef.Name)
if err != nil {
return nil, fmt.Errorf("failed to get group snapshot content %s from group snapshot content store: %v", contentRef.Name, err)
}
volumeSnapshotRefList = append(volumeSnapshotRefList, groupSnapshotContent.Spec.VolumeSnapshotRef)
}
}
klog.V(5).Infof("updateGroupSnapshotStatus: updating VolumeGroupSnapshot [%+v] based on VolumeGroupSnapshotContentStatus [%+v]", groupSnapshot, groupSnapshotContent.Status)
groupSnapshotObj, err := ctrl.clientset.GroupsnapshotV1alpha1().VolumeGroupSnapshots(groupSnapshot.Namespace).Get(context.TODO(), groupSnapshot.Name, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("error get group snapshot %s from api server: %v", utils.GroupSnapshotKey(groupSnapshot), err)
}
var newStatus *crdv1alpha1.VolumeGroupSnapshotStatus
updated := false
if groupSnapshotObj.Status == nil {
newStatus = &crdv1alpha1.VolumeGroupSnapshotStatus{
BoundVolumeGroupSnapshotContentName: &boundContentName,
ReadyToUse: &readyToUse,
}
if createdAt != nil {
newStatus.CreationTime = &metav1.Time{Time: *createdAt}
}
if volumeSnapshotErr != nil {
newStatus.Error = volumeSnapshotErr
}
if len(volumeSnapshotRefList) == 0 {
newStatus.VolumeSnapshotRefList = volumeSnapshotRefList
}
updated = true
} else {
newStatus = groupSnapshotObj.Status.DeepCopy()
if newStatus.BoundVolumeGroupSnapshotContentName == nil {
newStatus.BoundVolumeGroupSnapshotContentName = &boundContentName
updated = true
}
if newStatus.CreationTime == nil && createdAt != nil {
newStatus.CreationTime = &metav1.Time{Time: *createdAt}
updated = true
}
if newStatus.ReadyToUse == nil || *newStatus.ReadyToUse != readyToUse {
newStatus.ReadyToUse = &readyToUse
updated = true
if readyToUse && newStatus.Error != nil {
newStatus.Error = nil
}
}
if (newStatus.Error == nil && volumeSnapshotErr != nil) || (newStatus.Error != nil && volumeSnapshotErr != nil && newStatus.Error.Time != nil && volumeSnapshotErr.Time != nil && &newStatus.Error.Time != &volumeSnapshotErr.Time) || (newStatus.Error != nil && volumeSnapshotErr == nil) {
newStatus.Error = volumeSnapshotErr
updated = true
}
if len(newStatus.VolumeSnapshotRefList) == 0 {
newStatus.VolumeSnapshotRefList = volumeSnapshotRefList
updated = true
}
}
if updated {
groupSnapshotClone := groupSnapshotObj.DeepCopy()
groupSnapshotClone.Status = newStatus
// Must meet the following criteria to emit a successful CreateGroupSnapshot status
// 1. Previous status was nil OR Previous status had a nil CreationTime
// 2. New status must be non-nil with a non-nil CreationTime
if !utils.IsGroupSnapshotCreated(groupSnapshotObj) && utils.IsGroupSnapshotCreated(groupSnapshotClone) {
msg := fmt.Sprintf("GroupSnapshot %s was successfully created by the CSI driver.", utils.GroupSnapshotKey(groupSnapshot))
ctrl.eventRecorder.Event(groupSnapshot, v1.EventTypeNormal, "GroupSnapshotCreated", msg)
}
// Must meet the following criteria to emit a successful CreateGroupSnapshotAndReady status
// 1. Previous status was nil OR Previous status had a nil ReadyToUse OR Previous status had a false ReadyToUse
// 2. New status must be non-nil with a ReadyToUse as true
if !utils.IsGroupSnapshotReady(groupSnapshotObj) && utils.IsGroupSnapshotReady(groupSnapshotClone) {
msg := fmt.Sprintf("GroupSnapshot %s is ready to use.", utils.GroupSnapshotKey(groupSnapshot))
ctrl.eventRecorder.Event(groupSnapshot, v1.EventTypeNormal, "GroupSnapshotReady", msg)
}
newGroupSnapshotObj, err := ctrl.clientset.GroupsnapshotV1alpha1().VolumeGroupSnapshots(groupSnapshotClone.Namespace).UpdateStatus(context.TODO(), groupSnapshotClone, metav1.UpdateOptions{})
if err != nil {
return nil, newControllerUpdateError(utils.GroupSnapshotKey(groupSnapshot), err.Error())
}
return newGroupSnapshotObj, nil
}
return groupSnapshotObj, nil
}
// getDynamicallyProvisionedGroupContentFromStore tries to find a dynamically created
// group snapshot content object for the passed in VolumeGroupSnapshot from the
// group snapshot content store.
// Note that this function assumes the passed in VolumeGroupSnapshot is a dynamic
// one which requests creating a group snapshot from a group of PVCs.
// If no matching VolumeGroupSnapshotContent exists in the group snapshot content
// cache store, it returns (nil, nil)
// If a group snapshot content is found but it's not dynamically provisioned,
// the passed in group snapshot status will be updated with an error along with
// an event, and an error will be returned.
// If a group snapshot content is found but it does not point to the passed in VolumeGroupSnapshot,
// the passed in group snapshot will be updated with an error along with an event,
// and an error will be returned.
func (ctrl *csiSnapshotCommonController) getDynamicallyProvisionedGroupContentFromStore(groupSnapshot *crdv1alpha1.VolumeGroupSnapshot) (*crdv1alpha1.VolumeGroupSnapshotContent, error) {
contentName := utils.GetDynamicSnapshotContentNameForGroupSnapshot(groupSnapshot)
groupSnapshotContent, err := ctrl.getGroupSnapshotContentFromStore(contentName)
if err != nil {
return nil, err
}
if groupSnapshotContent == nil {
// no matching group snapshot content with the desired name has been found in cache
return nil, nil
}
// check whether the group snapshot content represents a dynamically provisioned snapshot
if groupSnapshotContent.Spec.Source.GroupSnapshotHandles != nil {
ctrl.updateGroupSnapshotErrorStatusWithEvent(groupSnapshot, true, v1.EventTypeWarning, "GroupSnapshotContentMismatch", "VolumeGroupSnapshotContent "+contentName+" is pre-provisioned while expecting a dynamically provisioned one")
klog.V(4).Infof("sync group snapshot[%s]: group snapshot content %s is pre-provisioned while expecting a dynamically provisioned one", utils.GroupSnapshotKey(groupSnapshot), contentName)
return nil, fmt.Errorf("group snapshot %s expects a dynamically provisioned VolumeGroupSnapshotContent %s but gets a pre-provisioned one", utils.GroupSnapshotKey(groupSnapshot), contentName)
}
// check whether the group snapshot content points back to the passed in VolumeGroupSnapshot
ref := groupSnapshotContent.Spec.VolumeGroupSnapshotRef
// Unlike a pre-provisioned group snapshot content, whose Spec.VolumeGroupSnapshotRef.UID will be
// left to be empty to allow binding to a group snapshot, a dynamically provisioned
// group snapshot content MUST have its Spec.VolumeGroupSnapshotRef.UID set to the group snapshot's
// UID from which it's been created, thus ref.UID == "" is not a legit case here.
if ref.Name != groupSnapshot.Name || ref.Namespace != groupSnapshot.Namespace || ref.UID != groupSnapshot.UID {
klog.V(4).Infof("sync group snapshot[%s]: VolumeGroupSnapshotContent %s is bound to another group snapshot %v", utils.GroupSnapshotKey(groupSnapshot), contentName, ref)
msg := fmt.Sprintf("VolumeGroupSnapshotContent [%s] is bound to a different group snapshot", contentName)
ctrl.updateGroupSnapshotErrorStatusWithEvent(groupSnapshot, true, v1.EventTypeWarning, "GroupSnapshotContentMisbound", msg)
return nil, fmt.Errorf(msg)
}
return groupSnapshotContent, nil
}
// This routine sets snapshot.Spec.Source.VolumeGroupSnapshotContentName
func (ctrl *csiSnapshotCommonController) bindandUpdateVolumeGroupSnapshot(groupSnapshotContent *crdv1alpha1.VolumeGroupSnapshotContent, groupSnapshot *crdv1alpha1.VolumeGroupSnapshot) (*crdv1alpha1.VolumeGroupSnapshot, error) {
klog.V(5).Infof("bindandUpdateVolumeGroupSnapshot for group snapshot [%s]: groupSnapshotContent [%s]", groupSnapshot.Name, groupSnapshotContent.Name)
groupSnapshotObj, err := ctrl.clientset.GroupsnapshotV1alpha1().VolumeGroupSnapshots(groupSnapshot.Namespace).Get(context.TODO(), groupSnapshot.Name, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("error get group snapshot %s from api server: %v", utils.GroupSnapshotKey(groupSnapshot), err)
}
// Copy the group snapshot object before updating it
groupSnapshotCopy := groupSnapshotObj.DeepCopy()
// update group snapshot status
var updateGroupSnapshot *crdv1alpha1.VolumeGroupSnapshot
klog.V(5).Infof("bindandUpdateVolumeGroupSnapshot [%s]: trying to update group snapshot status", utils.GroupSnapshotKey(groupSnapshotCopy))
updateGroupSnapshot, err = ctrl.updateGroupSnapshotStatus(groupSnapshotCopy, groupSnapshotContent)
if err == nil {
groupSnapshotCopy = updateGroupSnapshot
}
if err != nil {
// update group snapshot status failed
klog.V(4).Infof("failed to update group snapshot %s status: %v", utils.GroupSnapshotKey(groupSnapshot), err)
ctrl.updateGroupSnapshotErrorStatusWithEvent(groupSnapshotCopy, true, v1.EventTypeWarning, "GroupSnapshotStatusUpdateFailed", fmt.Sprintf("GroupSnapshot status update failed, %v", err))
return nil, err
}
_, err = ctrl.storeGroupSnapshotUpdate(groupSnapshotCopy)
if err != nil {
klog.Errorf("%v", err)
}
klog.V(5).Infof("bindandUpdateVolumeGroupSnapshot for group snapshot completed [%#v]", groupSnapshotCopy)
return groupSnapshotCopy, nil
}
// createGroupSnapshotContent will only be called for dynamic provisioning
func (ctrl *csiSnapshotCommonController) createGroupSnapshotContent(groupSnapshot *crdv1alpha1.VolumeGroupSnapshot) (*crdv1alpha1.VolumeGroupSnapshotContent, error) {
klog.Infof("createSnapshotContent: Creating group snapshot content for group snapshot %s through the plugin ...", utils.GroupSnapshotKey(groupSnapshot))
/*
TODO: Add PVC finalizer
*/
groupSnapshotClass, volumes, contentName, err := ctrl.getCreateGroupSnapshotInput(groupSnapshot)
if err != nil {
return nil, fmt.Errorf("failed to get input parameters to create group snapshot %s: %q", groupSnapshot.Name, err)
}
snapshotRef, err := ref.GetReference(scheme.Scheme, groupSnapshot)
if err != nil {
return nil, err
}
var volumeHandles []string
for _, pv := range volumes {
volumeHandles = append(volumeHandles, pv.Spec.CSI.VolumeHandle)
}
groupSnapshotContent := &crdv1alpha1.VolumeGroupSnapshotContent{
ObjectMeta: metav1.ObjectMeta{
Name: contentName,
},
Spec: crdv1alpha1.VolumeGroupSnapshotContentSpec{
VolumeGroupSnapshotRef: *snapshotRef,
Source: crdv1alpha1.VolumeGroupSnapshotContentSource{
VolumeHandles: volumeHandles,
},
VolumeGroupSnapshotClassName: &(groupSnapshotClass.Name),
DeletionPolicy: groupSnapshotClass.DeletionPolicy,
Driver: groupSnapshotClass.Driver,
},
}
/*
TODO: Add secret reference details
*/
var updateGroupSnapshotContent *crdv1alpha1.VolumeGroupSnapshotContent
klog.V(5).Infof("volume group snapshot content %#v", groupSnapshotContent)
// Try to create the VolumeGroupSnapshotContent object
klog.V(5).Infof("createGroupSnapshotContent [%s]: trying to save volume group snapshot content %s", utils.GroupSnapshotKey(groupSnapshot), groupSnapshotContent.Name)
if updateGroupSnapshotContent, err = ctrl.clientset.GroupsnapshotV1alpha1().VolumeGroupSnapshotContents().Create(context.TODO(), groupSnapshotContent, metav1.CreateOptions{}); err == nil || apierrs.IsAlreadyExists(err) {
// Save succeeded.
if err != nil {
klog.V(3).Infof("volume group snapshot content %q for group snapshot %q already exists, reusing", groupSnapshotContent.Name, utils.GroupSnapshotKey(groupSnapshot))
err = nil
updateGroupSnapshotContent = groupSnapshotContent
} else {
klog.V(3).Infof("volume group snapshot content %q for group snapshot %q saved, %v", groupSnapshotContent.Name, utils.GroupSnapshotKey(groupSnapshot), groupSnapshotContent)
}
}
if err != nil {
strerr := fmt.Sprintf("Error creating volume group snapshot content object for group snapshot %s: %v.", utils.GroupSnapshotKey(groupSnapshot), err)
klog.Error(strerr)
ctrl.eventRecorder.Event(groupSnapshot, v1.EventTypeWarning, "CreateGroupSnapshotContentFailed", strerr)
return nil, newControllerUpdateError(utils.GroupSnapshotKey(groupSnapshot), err.Error())
}
msg := fmt.Sprintf("Waiting for a group snapshot %s to be created by the CSI driver.", utils.GroupSnapshotKey(groupSnapshot))
ctrl.eventRecorder.Event(groupSnapshot, v1.EventTypeNormal, "CreatingGroupSnapshot", msg)
// Update group snapshot content in the cache store
_, err = ctrl.storeGroupSnapshotContentUpdate(updateGroupSnapshotContent)
if err != nil {
klog.Errorf("failed to update group snapshot content store %v", err)
}
return updateGroupSnapshotContent, nil
}
func (ctrl *csiSnapshotCommonController) getCreateGroupSnapshotInput(groupSnapshot *crdv1alpha1.VolumeGroupSnapshot) (*crdv1alpha1.VolumeGroupSnapshotClass, []*v1.PersistentVolume, string, error) {
className := groupSnapshot.Spec.VolumeGroupSnapshotClassName
klog.V(5).Infof("getCreateGroupSnapshotInput [%s]", groupSnapshot.Name)
var groupSnapshotClass *crdv1alpha1.VolumeGroupSnapshotClass
var err error
if className != nil {
groupSnapshotClass, err = ctrl.getGroupSnapshotClass(*className)
if err != nil {
klog.Errorf("getCreateGroupSnapshotInput failed to getClassFromVolumeGroupSnapshot %s", err)
return nil, nil, "", err
}
} else {
klog.Errorf("failed to getCreateGroupSnapshotInput %s without a group snapshot class", groupSnapshot.Name)
return nil, nil, "", fmt.Errorf("failed to take group snapshot %s without a group snapshot class", groupSnapshot.Name)
}
volumes, err := ctrl.getVolumesFromVolumeGroupSnapshot(groupSnapshot)
if err != nil {
klog.Errorf("getCreateGroupSnapshotInput failed to get PersistentVolume objects [%s]: Error: [%#v]", groupSnapshot.Name, err)
return nil, nil, "", err
}
// Create VolumeGroupSnapshotContent name
contentName := utils.GetDynamicSnapshotContentNameForGroupSnapshot(groupSnapshot)
return groupSnapshotClass, volumes, contentName, nil
}
// syncGroupSnapshotContent deals with one key off the queue
func (ctrl *csiSnapshotCommonController) syncGroupSnapshotContent(groupSnapshotContent *crdv1alpha1.VolumeGroupSnapshotContent) error {
groupSnapshotName := utils.GroupSnapshotRefKey(&groupSnapshotContent.Spec.VolumeGroupSnapshotRef)
klog.V(4).Infof("synchronizing VolumeGroupSnapshotContent[%s]: group snapshot content is bound to group snapshot %s", groupSnapshotContent.Name, groupSnapshotName)
klog.V(5).Infof("syncGroupSnapshotContent[%s]: check if we should add invalid label on group snapshot content", groupSnapshotContent.Name)
// Keep this check in the controller since the validation webhook may not have been deployed.
if (groupSnapshotContent.Spec.Source.GroupSnapshotHandles == nil && len(groupSnapshotContent.Spec.Source.VolumeHandles) == 0) ||
(groupSnapshotContent.Spec.Source.GroupSnapshotHandles != nil && len(groupSnapshotContent.Spec.Source.VolumeHandles) > 0) {
err := fmt.Errorf("Exactly one of GroupSnapshotHandles and VolumeHandles should be specified")
klog.Errorf("syncGroupSnapshotContent[%s]: validation error, %s", groupSnapshotContent.Name, err.Error())
ctrl.eventRecorder.Event(groupSnapshotContent, v1.EventTypeWarning, "GroupContentValidationError", err.Error())
return err
}
// The VolumeGroupSnapshotContent is reserved for a VolumeGroupSnapshot;
// that VolumeGroupSnapshot has not yet been bound to this VolumeGroupSnapshotContent;
// syncGroupSnapshot will handle it.
if groupSnapshotContent.Spec.VolumeGroupSnapshotRef.UID == "" {
klog.V(4).Infof("syncGroupSnapshotContent [%s]: VolumeGroupSnapshotContent is pre-bound to VolumeGroupSnapshot %s", groupSnapshotContent.Name, groupSnapshotName)
return nil
}
if utils.NeedToAddGroupSnapshotContentFinalizer(groupSnapshotContent) {
// Group Snapshot Content is not being deleted -> it should have the finalizer.
klog.V(5).Infof("syncGroupSnapshotContent [%s]: Add Finalizer for VolumeGroupSnapshotContent", groupSnapshotContent.Name)
return ctrl.addGroupSnapshotContentFinalizer(groupSnapshotContent)
}
// Check if group snapshot exists in cache store
// If getGroupSnapshotFromStore returns (nil, nil), it means group snapshot not found
// and it may have already been deleted, and it will fall into the
// group snapshot == nil case below
var groupSnapshot *crdv1alpha1.VolumeGroupSnapshot
groupSnapshot, err := ctrl.getGroupSnapshotFromStore(groupSnapshotName)
if err != nil {
return err
}
if groupSnapshot != nil && groupSnapshot.UID != groupSnapshotContent.Spec.VolumeGroupSnapshotRef.UID {
// The group snapshot that the group snapshot content was pointing to was deleted, and another
// with the same name created.
klog.V(4).Infof("syncGroupSnapshotContent [%s]: group snapshot %s has different UID, the old one must have been deleted", groupSnapshotContent.Name, groupSnapshotName)
// Treat the group snapshot content as bound to a missing snapshot.
groupSnapshot = nil
} else {
// Check if groupSnapshot.Status is different from groupSnapshotContent.Status
// and add group snapshot to queue if there is a difference and it is worth
// triggering a group snapshot status update.
if groupSnapshot != nil && ctrl.needsUpdateGroupSnapshotStatus(groupSnapshot, groupSnapshotContent) {
klog.V(4).Infof("synchronizing VolumeGroupSnapshotContent for group snapshot [%s]: update group snapshot status to true if needed.", groupSnapshotName)
// Manually trigger a group snapshot status update to happen
// right away so that it is in-sync with the group snapshot content status
ctrl.groupSnapshotQueue.Add(groupSnapshotName)
}
}
return nil
}
// getGroupSnapshotFromStore finds group snapshot from the cache store.
// If getGroupSnapshotFromStore returns (nil, nil), it means group snapshot not
// found and it may have already been deleted.
func (ctrl *csiSnapshotCommonController) getGroupSnapshotFromStore(groupSnapshotName string) (*crdv1alpha1.VolumeGroupSnapshot, error) {
// Get the VolumeGroupSnapshot by _name_
var groupSnapshot *crdv1alpha1.VolumeGroupSnapshot
obj, found, err := ctrl.groupSnapshotStore.GetByKey(groupSnapshotName)
if err != nil {
return nil, err
}
if !found {
klog.V(4).Infof("getGroupSnapshotFromStore: group snapshot %s not found", groupSnapshotName)
// Fall through with group snapshot = nil
return nil, nil
}
var ok bool
groupSnapshot, ok = obj.(*crdv1alpha1.VolumeGroupSnapshot)
if !ok {
return nil, fmt.Errorf("cannot convert object from group snapshot cache to group snapshot %q!?: %#v", groupSnapshotName, obj)
}
klog.V(4).Infof("getGroupSnapshotFromStore: group snapshot %s found", groupSnapshotName)
return groupSnapshot, nil
}
// needsUpdateGroupSnapshotStatus compares group snapshot status with the group snapshot content
// status and decide if group snapshot status needs to be updated based on group snapshot content
// status
func (ctrl *csiSnapshotCommonController) needsUpdateGroupSnapshotStatus(groupSnapshot *crdv1alpha1.VolumeGroupSnapshot, groupSnapshotContent *crdv1alpha1.VolumeGroupSnapshotContent) bool {
klog.V(5).Infof("needsUpdateGroupSnapshotStatus[%s]", utils.GroupSnapshotKey(groupSnapshot))
if groupSnapshot.Status == nil && groupSnapshotContent.Status != nil {
return true
}
if groupSnapshotContent.Status == nil {
return false
}
if groupSnapshot.Status.BoundVolumeGroupSnapshotContentName == nil {
return true
}
if groupSnapshot.Status.CreationTime == nil && groupSnapshotContent.Status.CreationTime != nil {
return true
}
if groupSnapshot.Status.ReadyToUse == nil && groupSnapshotContent.Status.ReadyToUse != nil {
return true
}
if groupSnapshot.Status.ReadyToUse != nil && groupSnapshotContent.Status.ReadyToUse != nil && groupSnapshot.Status.ReadyToUse != groupSnapshotContent.Status.ReadyToUse {
return true
}
return false
}
// addGroupSnapshotContentFinalizer adds a Finalizer for VolumeGroupSnapshotContent.
func (ctrl *csiSnapshotCommonController) addGroupSnapshotContentFinalizer(groupSnapshotContent *crdv1alpha1.VolumeGroupSnapshotContent) error {
var patches []utils.PatchOp
if len(groupSnapshotContent.Finalizers) > 0 {
// Add to the end of the finalizers if we have any other finalizers
patches = append(patches, utils.PatchOp{
Op: "add",
Path: "/metadata/finalizers/-",
Value: utils.VolumeGroupSnapshotContentFinalizer,
})
} else {
// Replace finalizers with new array if there are no other finalizers
patches = append(patches, utils.PatchOp{
Op: "add",
Path: "/metadata/finalizers",
Value: []string{utils.VolumeGroupSnapshotContentFinalizer},
})
}
newGroupSnapshotContent, err := utils.PatchVolumeGroupSnapshotContent(groupSnapshotContent, patches, ctrl.clientset)
if err != nil {
return newControllerUpdateError(groupSnapshotContent.Name, err.Error())
}
_, err = ctrl.storeGroupSnapshotContentUpdate(newGroupSnapshotContent)
if err != nil {
klog.Errorf("failed to update group snapshot content store %v", err)
}
klog.V(5).Infof("Added protection finalizer to volume group snapshot content %s", newGroupSnapshotContent.Name)
return nil
}
// checkandAddGroupSnapshotFinalizers checks and adds group snapshot finailzers when needed
func (ctrl *csiSnapshotCommonController) checkandAddGroupSnapshotFinalizers(groupSnapshot *crdv1alpha1.VolumeGroupSnapshot) error {
// get the group snapshot content for this group snapshot
var (
groupSnapshotContent *crdv1alpha1.VolumeGroupSnapshotContent
err error
)
if groupSnapshot.Spec.Source.VolumeGroupSnapshotContentName != nil {
groupSnapshotContent, err = ctrl.getPreprovisionedGroupSnapshotContentFromStore(groupSnapshot)
} else {
groupSnapshotContent, err = ctrl.getDynamicallyProvisionedGroupContentFromStore(groupSnapshot)
}
if err != nil {
return err
}
// A bound finalizer is needed ONLY when all following conditions are satisfied:
// 1. the VolumeGroupSnapshot is bound to a VolumeGroupSnapshotContent
// 2. the VolumeGroupSnapshot does not have deletion timestamp set
// 3. the matching VolumeGroupSnapshotContent has a deletion policy to be Delete
// Note that if a matching VolumeGroupSnapshotContent is found, it must point back to the VolumeGroupSnapshot
if groupSnapshotContent != nil && utils.NeedToAddGroupSnapshotBoundFinalizer(groupSnapshot) && (groupSnapshotContent.Spec.DeletionPolicy == crdv1.VolumeSnapshotContentDelete) {
// Snapshot is not being deleted -> it should have the finalizer.
klog.V(5).Infof("checkandAddGroupSnapshotFinalizers: Add Finalizer for VolumeGroupSnapshot[%s]", utils.GroupSnapshotKey(groupSnapshot))
return ctrl.addGroupSnapshotFinalizer(groupSnapshot, true)
}
return nil
}
// addGroupSnapshotFinalizer adds a Finalizer to a VolumeGroupSnapshot.
func (ctrl *csiSnapshotCommonController) addGroupSnapshotFinalizer(groupSnapshot *crdv1alpha1.VolumeGroupSnapshot, addBoundFinalizer bool) error {
var updatedGroupSnapshot *crdv1alpha1.VolumeGroupSnapshot
var err error
// NOTE(ggriffiths): Must perform an update if no finalizers exist.
// Unable to find a patch that correctly updated the finalizers if none currently exist.
if len(groupSnapshot.ObjectMeta.Finalizers) == 0 {
groupSnapshotClone := groupSnapshot.DeepCopy()
if addBoundFinalizer {
groupSnapshotClone.ObjectMeta.Finalizers = append(groupSnapshotClone.ObjectMeta.Finalizers, utils.VolumeGroupSnapshotBoundFinalizer)
}
updatedGroupSnapshot, err = ctrl.clientset.GroupsnapshotV1alpha1().VolumeGroupSnapshots(groupSnapshotClone.Namespace).Update(context.TODO(), groupSnapshotClone, metav1.UpdateOptions{})
if err != nil {
return newControllerUpdateError(utils.GroupSnapshotKey(groupSnapshot), err.Error())
}
} else {
// Otherwise, perform a patch
var patches []utils.PatchOp
if addBoundFinalizer {
patches = append(patches, utils.PatchOp{
Op: "add",
Path: "/metadata/finalizers/-",
Value: utils.VolumeGroupSnapshotBoundFinalizer,
})
}
updatedGroupSnapshot, err = utils.PatchVolumeGroupSnapshot(groupSnapshot, patches, ctrl.clientset)
if err != nil {
return newControllerUpdateError(utils.GroupSnapshotKey(groupSnapshot), err.Error())
}
}
_, err = ctrl.storeGroupSnapshotUpdate(updatedGroupSnapshot)
if err != nil {
klog.Errorf("failed to update group snapshot store %v", err)
}
klog.V(5).Infof("Added protection finalizer to volume group snapshot %s", utils.GroupSnapshotKey(updatedGroupSnapshot))
return nil
}
// processGroupSnapshotWithDeletionTimestamp processes finalizers and deletes the
// group snapshot content when appropriate. It has the following steps:
// 1. Get the VolumeGroupSnapshotContent which the to-be-deleted VolumeGroupSnapshot
// points to and verifies bi-directional binding.
// 2. Call checkandRemoveGroupSnapshotFinalizersAndCheckandDeleteGroupSnapshotContent()
// with information obtained from step 1. This function name is very long but the
// name suggests what it does. It determines whether to remove finalizers on group
// snapshot and whether to delete group snapshot content.
func (ctrl *csiSnapshotCommonController) processGroupSnapshotWithDeletionTimestamp(groupSnapshot *crdv1alpha1.VolumeGroupSnapshot) error {
klog.V(5).Infof("processGroupSnapshotWithDeletionTimestamp VolumeGroupSnapshot[%s]: %s", utils.GroupSnapshotKey(groupSnapshot), utils.GetGroupSnapshotStatusForLogging(groupSnapshot))
var groupSnapshotContentName string
if groupSnapshot.Status != nil && groupSnapshot.Status.BoundVolumeGroupSnapshotContentName != nil {
groupSnapshotContentName = *groupSnapshot.Status.BoundVolumeGroupSnapshotContentName
}
// for a dynamically created group snapshot, it's possible that a group snapshot
// content has been created however the Status of the group snapshot has not
// been updated yet, i.e., failed right after group snapshot content creation.
// In this case, use the fixed naming scheme to get the group snapshot content
// name and search
if groupSnapshotContentName == "" && &groupSnapshot.Spec.Source.VolumeGroupSnapshotContentName == nil {
groupSnapshotContentName = utils.GetDynamicSnapshotContentNameForGroupSnapshot(groupSnapshot)
}
// find a group snapshot content from cache store, note that it's completely legit
// that no group snapshot content has been found from group snapshot content
// cache store
groupSnapshotContent, err := ctrl.getGroupSnapshotContentFromStore(groupSnapshotContentName)
if err != nil {
return err
}
// check whether the group snapshot content points back to the passed in group
// snapshot, note that binding should always be bi-directional to trigger the
// deletion on group snapshot content or adding any annotation to the group
// snapshot content
var deleteGroupSnapshotContent bool
if groupSnapshotContent != nil && utils.IsVolumeGroupSnapshotRefSet(groupSnapshot, groupSnapshotContent) {
// group snapshot content points back to group snapshot, whether or not
// to delete a group snapshot content now depends on the deletion policy
// of it.
deleteGroupSnapshotContent = (groupSnapshotContent.Spec.DeletionPolicy == crdv1.VolumeSnapshotContentDelete)
} else {
// the group snapshot content is nil or points to a different group snapshot, reset group snapshot content to nil
// such that there is no operation done on the found group snapshot content in
// checkandRemoveSnapshotFinalizersAndCheckandDeleteContent
groupSnapshotContent = nil
}
klog.V(5).Infof("processGroupSnapshotWithDeletionTimestamp[%s]: check if group snapshot is a candidate for deletion", utils.GroupSnapshotKey(groupSnapshot))
if !utils.IsGroupSnapshotDeletionCandidate(groupSnapshot) {
return nil
}
// check if an individual snapshot belonging to the group snapshot is being
// used for restore a PVC
// If yes, do nothing and wait until PVC restoration finishes
for _, snapshotRef := range groupSnapshot.Status.VolumeSnapshotRefList {
snapshot, err := ctrl.snapshotLister.VolumeSnapshots(snapshotRef.Namespace).Get(snapshotRef.Name)
if err != nil {
if apierrs.IsNotFound(err) {
continue
}
return err
}
if ctrl.isVolumeBeingCreatedFromSnapshot(snapshot) {
msg := fmt.Sprintf("Snapshot %s belonging to VolumeGroupSnapshot %s is being used to restore a PVC", utils.SnapshotKey(snapshot), utils.GroupSnapshotKey(groupSnapshot))
klog.V(4).Info(msg)
ctrl.eventRecorder.Event(groupSnapshot, v1.EventTypeWarning, "SnapshotDeletePending", msg)
// TODO(@xiangqian): should requeue this?
return nil
}
}
// regardless of the deletion policy, set VolumeGroupSnapshotBeingDeleted on
// group snapshot content object, this is to allow snapshotter sidecar controller
// to conduct a delete operation whenever the group snapshot content has deletion
// timestamp set.
if groupSnapshotContent != nil {
klog.V(5).Infof("processGroupSnapshotWithDeletionTimestamp[%s]: Set VolumeGroupSnapshotBeingDeleted annotation on the group snapshot content [%s]", utils.GroupSnapshotKey(groupSnapshot), groupSnapshotContent.Name)
updatedGroupSnapshotContent, err := ctrl.setAnnVolumeGroupSnapshotBeingDeleted(groupSnapshotContent)
if err != nil {
klog.V(4).Infof("processGroupSnapshotWithDeletionTimestamp[%s]: failed to set VolumeGroupSnapshotBeingDeleted annotation on the group snapshot content [%s]: %v", utils.GroupSnapshotKey(groupSnapshot), groupSnapshotContent.Name, err)
return err
}
groupSnapshotContent = updatedGroupSnapshotContent
}
// VolumeGroupSnapshot should be deleted. Check and remove finalizers
// If group snapshot content exists and has a deletion policy of Delete, set
// DeletionTimeStamp on the group snapshot content;
// VolumeGroupSnapshotContent won't be deleted immediately due to the VolumeGroupSnapshotContentFinalizer
if groupSnapshotContent != nil && deleteGroupSnapshotContent {
klog.V(5).Infof("processGroupSnapshotWithDeletionTimestamp[%s]: set DeletionTimeStamp on group snapshot content [%s].", utils.GroupSnapshotKey(groupSnapshot), groupSnapshotContent.Name)
err := ctrl.clientset.GroupsnapshotV1alpha1().VolumeGroupSnapshotContents().Delete(context.TODO(), groupSnapshotContent.Name, metav1.DeleteOptions{})
if err != nil {
ctrl.eventRecorder.Event(groupSnapshot, v1.EventTypeWarning, "GroupSnapshotContentObjectDeleteError", "Failed to delete group snapshot content API object")
return fmt.Errorf("failed to delete VolumeGroupSnapshotContent %s from API server: %q", groupSnapshotContent.Name, err)
}
}
klog.V(5).Infof("processGroupSnapshotWithDeletionTimestamp[%s]: Delete individual snapshots that are part of the group snapshot", utils.GroupSnapshotKey(groupSnapshot))
// Delete the individual snapshots part of the group snapshot
for _, snapshot := range groupSnapshot.Status.VolumeSnapshotRefList {
err := ctrl.clientset.SnapshotV1().VolumeSnapshots(snapshot.Namespace).Delete(context.TODO(), snapshot.Name, metav1.DeleteOptions{})
if err != nil && !apierrs.IsNotFound(err) {
msg := fmt.Sprintf("failed to delete snapshot API object %s/%s part of group snapshot %s: %v", snapshot.Namespace, snapshot.Name, utils.GroupSnapshotKey(groupSnapshot), err)
klog.Error(msg)
ctrl.eventRecorder.Event(groupSnapshot, v1.EventTypeWarning, "SnapshotDeleteError", msg)
return fmt.Errorf(msg)
}
}
klog.V(5).Infof("processGroupSnapshotWithDeletionTimestamp[%s] : Remove Finalizer for VolumeGroupSnapshot", utils.GroupSnapshotKey(groupSnapshot))
// remove VolumeSnapshotBoundFinalizer on the VolumeGroupSnapshot object:
// a. If there is no group snapshot content found, remove the finalizer.
// b. If the group snapshot content is being deleted, i.e., with deleteGroupSnapshotContent == true,
// keep this finalizer until the group snapshot content object is removed
// from API server by group snapshot sidecar controller.
// c. If deletion will not cascade to the group snapshot content, remove
// the finalizer on the group snapshot such that it can be removed from
// the API server.
removeBoundFinalizer := !(groupSnapshotContent != nil && deleteGroupSnapshotContent)
return ctrl.removeGroupSnapshotFinalizer(groupSnapshot, removeBoundFinalizer)
}
func (ctrl *csiSnapshotCommonController) setAnnVolumeGroupSnapshotBeingDeleted(groupSnapshotContent *crdv1alpha1.VolumeGroupSnapshotContent) (*crdv1alpha1.VolumeGroupSnapshotContent, error) {
if groupSnapshotContent == nil {
return groupSnapshotContent, nil
}
// Set AnnVolumeGroupSnapshotBeingDeleted if it is not set yet
if !metav1.HasAnnotation(groupSnapshotContent.ObjectMeta, utils.AnnVolumeGroupSnapshotBeingDeleted) {
klog.V(5).Infof("setAnnVolumeGroupSnapshotBeingDeleted: set annotation [%s] on group snapshot content [%s].", utils.AnnVolumeGroupSnapshotBeingDeleted, groupSnapshotContent.Name)
var patches []utils.PatchOp
metav1.SetMetaDataAnnotation(&groupSnapshotContent.ObjectMeta, utils.AnnVolumeGroupSnapshotBeingDeleted, "yes")
patches = append(patches, utils.PatchOp{
Op: "replace",
Path: "/metadata/annotations",
Value: groupSnapshotContent.ObjectMeta.GetAnnotations(),
})
patchedGroupSnapshotContent, err := utils.PatchVolumeGroupSnapshotContent(groupSnapshotContent, patches, ctrl.clientset)
if err != nil {
return groupSnapshotContent, newControllerUpdateError(groupSnapshotContent.Name, err.Error())
}
// update group snapshot content if update is successful
groupSnapshotContent = patchedGroupSnapshotContent
_, err = ctrl.storeGroupSnapshotContentUpdate(groupSnapshotContent)
if err != nil {
klog.V(4).Infof("setAnnVolumeGroupSnapshotBeingDeleted for group snapshot content [%s]: cannot update internal cache %v", groupSnapshotContent.Name, err)
return groupSnapshotContent, err
}
klog.V(5).Infof("setAnnVolumeGroupSnapshotBeingDeleted: volume group snapshot content %+v", groupSnapshotContent)
}
return groupSnapshotContent, nil
}
// removeGroupSnapshotFinalizer removes a Finalizer for VolumeGroupSnapshot.
func (ctrl *csiSnapshotCommonController) removeGroupSnapshotFinalizer(groupSnapshot *crdv1alpha1.VolumeGroupSnapshot, removeBoundFinalizer bool) error {
if !removeBoundFinalizer {
return nil
}
// TODO: Remove PVC Finalizer
groupSnapshotClone := groupSnapshot.DeepCopy()
groupSnapshotClone.ObjectMeta.Finalizers = utils.RemoveString(groupSnapshotClone.ObjectMeta.Finalizers, utils.VolumeGroupSnapshotBoundFinalizer)
newGroupSnapshot, err := ctrl.clientset.GroupsnapshotV1alpha1().VolumeGroupSnapshots(groupSnapshotClone.Namespace).Update(context.TODO(), groupSnapshotClone, metav1.UpdateOptions{})
if err != nil {
return newControllerUpdateError(groupSnapshot.Name, err.Error())
}
_, err = ctrl.storeGroupSnapshotUpdate(newGroupSnapshot)
if err != nil {
klog.Errorf("failed to update group snapshot store %v", err)
}
klog.V(5).Infof("Removed protection finalizer from volume group snapshot %s", utils.GroupSnapshotKey(groupSnapshot))
return nil
}