@@ -5,9 +5,11 @@ import (
55 "errors"
66 "fmt"
77 "os"
8+ "slices"
89 "strings"
910 "time"
1011
12+ "github.com/cybozu-go/mantle/internal/ceph"
1113 batchv1 "k8s.io/api/batch/v1"
1214 corev1 "k8s.io/api/core/v1"
1315 storagev1 "k8s.io/api/storage/v1"
@@ -19,6 +21,55 @@ import (
1921
2022var errEmptyClusterID error = errors .New ("cluster ID is empty" )
2123
24+ // createCloneByPV creates a clone RBD image from the snapshot of the volume bound to PV.
25+ // If the clone image already exists and is a clone of the snapshot, it does nothing.
26+ func createCloneByPV (ctx context.Context , cephCmd ceph.CephCmd , pv * corev1.PersistentVolume , snapshotName , cloneName string ) error {
27+ logger := log .FromContext (ctx )
28+
29+ bkImage := pv .Spec .CSI .VolumeAttributes ["imageName" ]
30+ if bkImage == "" {
31+ return fmt .Errorf ("imageName not found in PV manifest" )
32+ }
33+ pool := pv .Spec .CSI .VolumeAttributes ["pool" ]
34+ if pool == "" {
35+ return fmt .Errorf ("pool not found in PV manifest" )
36+ }
37+
38+ images , err := cephCmd .RBDLs (pool )
39+ if err != nil {
40+ return fmt .Errorf ("failed to list RBD images: %w" , err )
41+ }
42+
43+ // check if the image already exists
44+ if slices .Contains (images , cloneName ) {
45+ info , err := cephCmd .RBDInfo (pool , cloneName )
46+ if err != nil {
47+ return fmt .Errorf ("failed to get RBD info: %w" , err )
48+ }
49+ if info .Parent == nil {
50+ return fmt .Errorf ("failed to get RBD info: parent field is empty" )
51+ }
52+
53+ if info .Parent .Pool == pool && info .Parent .Image == bkImage && info .Parent .Snapshot == snapshotName {
54+ logger .Info ("image already exists" , "image" , cloneName )
55+ return nil
56+ } else {
57+ // If the clone image already exists but is not a clone of the snapshot, it returns an error.
58+ return fmt .Errorf ("image already exists but not a clone of the backup: %s" , cloneName )
59+ }
60+ }
61+
62+ features := pv .Spec .CSI .VolumeAttributes ["imageFeatures" ]
63+ if features == "" {
64+ features = "deep-flatten"
65+ } else {
66+ features += ",deep-flatten"
67+ }
68+
69+ // create a clone image from the backup
70+ return cephCmd .RBDClone (pool , bkImage , snapshotName , cloneName , features )
71+ }
72+
2273func getCephClusterIDFromSCName (ctx context.Context , k8sClient client.Client , storageClassName string ) (string , error ) {
2374 var storageClass storagev1.StorageClass
2475 err := k8sClient .Get (ctx , types.NamespacedName {Name : storageClassName }, & storageClass )
0 commit comments