github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controllerutil/volumesnapshot.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package controllerutil
    21  
    22  import (
    23  	"context"
    24  	"encoding/json"
    25  
    26  	snapshotv1beta1 "github.com/kubernetes-csi/external-snapshotter/client/v3/apis/volumesnapshot/v1beta1"
    27  	snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1"
    28  	corev1 "k8s.io/api/core/v1"
    29  	"k8s.io/apimachinery/pkg/types"
    30  	"sigs.k8s.io/controller-runtime/pkg/client"
    31  
    32  	roclient "github.com/1aal/kubeblocks/pkg/controller/client"
    33  	viper "github.com/1aal/kubeblocks/pkg/viperx"
    34  )
    35  
    36  func InVolumeSnapshotV1Beta1() bool {
    37  	return viper.GetBool("VOLUMESNAPSHOT_API_BETA")
    38  }
    39  
    40  // VolumeSnapshotCompatClient client is compatible with VolumeSnapshot v1 and v1beta1
    41  type VolumeSnapshotCompatClient struct {
    42  	client.Client
    43  	roclient.ReadonlyClient
    44  	Ctx context.Context
    45  }
    46  
    47  func (c *VolumeSnapshotCompatClient) Create(obj client.Object, opts ...client.CreateOption) error {
    48  	if InVolumeSnapshotV1Beta1() {
    49  		objV1Beta1 := typeofV1Beta1(obj).(client.Object)
    50  		if err := convertObjectBetweenAPIVersion(obj, objV1Beta1); err != nil {
    51  			return err
    52  		}
    53  		return c.Client.Create(c.Ctx, objV1Beta1, opts...)
    54  	}
    55  	return c.Client.Create(c.Ctx, obj, opts...)
    56  }
    57  
    58  func (c *VolumeSnapshotCompatClient) Get(key client.ObjectKey, snapshot client.Object, opts ...client.GetOption) error {
    59  	if c.ReadonlyClient == nil {
    60  		c.ReadonlyClient = c.Client
    61  	}
    62  	if InVolumeSnapshotV1Beta1() {
    63  		snapshotV1Beta1 := typeofV1Beta1(snapshot).(client.Object)
    64  		err := c.ReadonlyClient.Get(c.Ctx, key, snapshotV1Beta1, opts...)
    65  		if err != nil {
    66  			return err
    67  		}
    68  		if err = convertObjectBetweenAPIVersion(snapshotV1Beta1, snapshot); err != nil {
    69  			return err
    70  		}
    71  		return nil
    72  	}
    73  	return c.ReadonlyClient.Get(c.Ctx, key, snapshot, opts...)
    74  }
    75  
    76  func (c *VolumeSnapshotCompatClient) Delete(snapshot client.Object) error {
    77  	if InVolumeSnapshotV1Beta1() {
    78  		snapshotV1Beta1 := typeofV1Beta1(snapshot).(client.Object)
    79  		if err := convertObjectBetweenAPIVersion(snapshot, snapshotV1Beta1); err != nil {
    80  			return err
    81  		}
    82  		return BackgroundDeleteObject(c.Client, c.Ctx, snapshotV1Beta1)
    83  	}
    84  	return BackgroundDeleteObject(c.Client, c.Ctx, snapshot)
    85  }
    86  
    87  func (c *VolumeSnapshotCompatClient) Patch(snapshot client.Object, deepCopy client.Object, opts ...client.PatchOption) error {
    88  	if InVolumeSnapshotV1Beta1() {
    89  		snapshotV1Beta1 := typeofV1Beta1(snapshot).(client.Object)
    90  		if err := convertObjectBetweenAPIVersion(snapshot, snapshotV1Beta1); err != nil {
    91  			return err
    92  		}
    93  		snapshotV1Beta1Patch := typeofV1Beta1(deepCopy).(client.Object)
    94  		if err := convertObjectBetweenAPIVersion(deepCopy, snapshotV1Beta1Patch); err != nil {
    95  			return err
    96  		}
    97  		patch := client.MergeFrom(snapshotV1Beta1Patch)
    98  		return c.Client.Patch(c.Ctx, snapshotV1Beta1, patch, opts...)
    99  	}
   100  	snapPatch := client.MergeFrom(deepCopy)
   101  	return c.Client.Patch(c.Ctx, snapshot, snapPatch, opts...)
   102  }
   103  
   104  func (c *VolumeSnapshotCompatClient) List(objList client.ObjectList, opts ...client.ListOption) error {
   105  	if c.ReadonlyClient == nil {
   106  		c.ReadonlyClient = c.Client
   107  	}
   108  	if InVolumeSnapshotV1Beta1() {
   109  		objV1Beta1List := typeofV1Beta1(objList).(client.ObjectList)
   110  		err := c.ReadonlyClient.List(c.Ctx, objV1Beta1List, opts...)
   111  		if err != nil {
   112  			return err
   113  		}
   114  		if err = convertObjectBetweenAPIVersion(objV1Beta1List, objList); err != nil {
   115  			return err
   116  		}
   117  		return nil
   118  	}
   119  	return c.ReadonlyClient.List(c.Ctx, objList, opts...)
   120  }
   121  
   122  // CheckResourceExists checks whether resource exist or not.
   123  func (c *VolumeSnapshotCompatClient) CheckResourceExists(key client.ObjectKey, obj client.Object) (bool, error) {
   124  	if err := c.Get(key, obj); err != nil {
   125  		return false, client.IgnoreNotFound(err)
   126  	}
   127  	// if found, return true
   128  	return true, nil
   129  }
   130  
   131  func convertObjectBetweenAPIVersion[T1 any, T2 any](from T1, to T2) error {
   132  	fromJSONBytes, err := json.Marshal(from)
   133  	if err != nil {
   134  		return err
   135  	}
   136  	if err = json.Unmarshal(fromJSONBytes, to); err != nil {
   137  		return err
   138  	}
   139  	return nil
   140  }
   141  
   142  func typeofV1Beta1(v any) any {
   143  	switch v.(type) {
   144  	// object
   145  	case *snapshotv1.VolumeSnapshot:
   146  		return &snapshotv1beta1.VolumeSnapshot{}
   147  	case *snapshotv1.VolumeSnapshotClass:
   148  		return &snapshotv1beta1.VolumeSnapshotClass{}
   149  	case *snapshotv1beta1.VolumeSnapshot:
   150  		return &snapshotv1.VolumeSnapshot{}
   151  	case *snapshotv1beta1.VolumeSnapshotClass:
   152  		return &snapshotv1.VolumeSnapshotClass{}
   153  	// object list
   154  	case *snapshotv1.VolumeSnapshotList:
   155  		return &snapshotv1beta1.VolumeSnapshotList{}
   156  	case *snapshotv1.VolumeSnapshotClassList:
   157  		return &snapshotv1beta1.VolumeSnapshotClassList{}
   158  	case *snapshotv1beta1.VolumeSnapshotList:
   159  		return &snapshotv1.VolumeSnapshotList{}
   160  	case *snapshotv1beta1.VolumeSnapshotClassList:
   161  		return &snapshotv1.VolumeSnapshotClassList{}
   162  	default:
   163  		return nil
   164  	}
   165  }
   166  
   167  // IsVolumeSnapshotEnabled checks if the CSI supports the volume snapshot.
   168  func IsVolumeSnapshotEnabled(ctx context.Context, cli client.Client, pvName string) (bool, error) {
   169  	if len(pvName) == 0 {
   170  		return false, nil
   171  	}
   172  	pv := &corev1.PersistentVolume{}
   173  	if err := cli.Get(ctx, types.NamespacedName{Name: pvName}, pv); err != nil {
   174  		return false, err
   175  	}
   176  	if pv.Spec.CSI == nil {
   177  		return false, nil
   178  	}
   179  	vsCli := VolumeSnapshotCompatClient{
   180  		Client: cli,
   181  		Ctx:    ctx,
   182  	}
   183  	vscList := snapshotv1.VolumeSnapshotClassList{}
   184  	if err := vsCli.List(&vscList); err != nil {
   185  		return false, err
   186  	}
   187  	for _, vsc := range vscList.Items {
   188  		if vsc.Driver == pv.Spec.CSI.Driver {
   189  			return true, nil
   190  		}
   191  	}
   192  	return false, nil
   193  }