From 6b2de1dbf686a030d4fcadf636c5b0a4726985ed Mon Sep 17 00:00:00 2001
From: Vicente Cheng <vicente.cheng@suse.com>
Date: Tue, 23 Jul 2024 00:49:04 +0800
Subject: [PATCH] controller: support lvm type `dm-thin`

    - also drop linear/mirror

Signed-off-by: Vicente Cheng <vicente.cheng@suse.com>
---
 cmd/provisioner/createsnap.go | 10 +++-
 pkg/lvm/controllerserver.go   |  5 +-
 pkg/lvm/lvm.go                | 93 ++++++++++++++++++++++++++++-------
 3 files changed, 87 insertions(+), 21 deletions(-)

diff --git a/cmd/provisioner/createsnap.go b/cmd/provisioner/createsnap.go
index 7f045d81..f865aedc 100644
--- a/cmd/provisioner/createsnap.go
+++ b/cmd/provisioner/createsnap.go
@@ -30,6 +30,10 @@ func createSnapCmd() *cli.Command {
 				Name:  flagLVName,
 				Usage: "Required. the name of the volumegroup",
 			},
+			&cli.StringFlag{
+				Name:  flagLVMType,
+				Usage: "Required. the type of the source lvm",
+			},
 		},
 		Action: func(c *cli.Context) error {
 			if err := createSnap(c); err != nil {
@@ -58,6 +62,10 @@ func createSnap(c *cli.Context) error {
 	if snapName == "" {
 		return fmt.Errorf("invalid empty flag %v", flagLVMType)
 	}
+	lvType := c.String(flagLVMType)
+	if lvType == "" {
+		return fmt.Errorf("invalid empty flag %v", flagLVMType)
+	}
 
 	klog.Infof("create snapshot: %s source size: %d source lv: %s/%s", snapName, lvSize, vgName, lvName)
 
@@ -69,7 +77,7 @@ func createSnap(c *cli.Context) error {
 		}
 	}
 
-	output, err := lvm.CreateSnapshot(snapName, lvName, vgName, lvSize)
+	output, err := lvm.CreateSnapshot(snapName, lvName, vgName, lvSize, lvType)
 	if err != nil {
 		return fmt.Errorf("unable to create Snapshot: %w output:%s", err, output)
 	}
diff --git a/pkg/lvm/controllerserver.go b/pkg/lvm/controllerserver.go
index 0f38d32b..036c2a38 100644
--- a/pkg/lvm/controllerserver.go
+++ b/pkg/lvm/controllerserver.go
@@ -116,7 +116,7 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
 	}
 
 	lvmType := req.GetParameters()["type"]
-	if !(lvmType == "linear" || lvmType == "mirror" || lvmType == "striped") {
+	if !(lvmType == "striped" || lvmType == "dm-thin") {
 		return nil, status.Errorf(codes.Internal, "lvmType is incorrect: %s", lvmType)
 	}
 
@@ -430,6 +430,7 @@ func (cs *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS
 	ns := volume.Spec.NodeAffinity.Required.NodeSelectorTerms
 	node := ns[0].MatchExpressions[0].Values[0]
 	vgName := volume.Spec.CSI.VolumeAttributes["vgName"]
+	lvType := volume.Spec.CSI.VolumeAttributes["type"]
 	//snapSize := strconv.FormatUint(volume.Spec.CSI.VolumeAttributes["RequiredBytes"], 10)
 	snapSizeStr := volume.Spec.CSI.VolumeAttributes["RequiredBytes"]
 	snapSize, err := strconv.ParseInt(snapSizeStr, 10, 64)
@@ -449,6 +450,7 @@ func (cs *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS
 		nodeName:         node,
 		snapSize:         snapSize,
 		vgName:           vgName,
+		lvType:           lvType,
 		hostWritePath:    cs.hostWritePath,
 		kubeClient:       cs.kubeClient,
 		namespace:        cs.namespace,
@@ -503,6 +505,7 @@ func (cs *controllerServer) DeleteSnapshot(ctx context.Context, req *csi.DeleteS
 		snapshotName:     snapName,
 		nodeName:         node,
 		vgName:           vgName,
+		lvType:           "", // not used
 		hostWritePath:    cs.hostWritePath,
 		kubeClient:       cs.kubeClient,
 		namespace:        cs.namespace,
diff --git a/pkg/lvm/lvm.go b/pkg/lvm/lvm.go
index db3ce685..238ed783 100644
--- a/pkg/lvm/lvm.go
+++ b/pkg/lvm/lvm.go
@@ -86,13 +86,13 @@ type snapshotAction struct {
 	kubeClient       kubernetes.Clientset
 	namespace        string
 	vgName           string
+	lvType           string
 	hostWritePath    string
 }
 
 const (
-	linearType         = "linear"
 	stripedType        = "striped"
-	mirrorType         = "mirror"
+	dmThinType         = "dm-thin"
 	actionTypeCreate   = "create"
 	actionTypeDelete   = "delete"
 	actionTypeClone    = "clone"
@@ -279,7 +279,7 @@ func createSnapshotterPod(ctx context.Context, sa snapshotAction) (err error) {
 	args := []string{}
 	switch sa.action {
 	case actionTypeCreate:
-		args = append(args, "createsnap", "--snapname", sa.snapshotName, "--lvname", sa.srcVolName, "--vgname", sa.vgName, "--lvsize", fmt.Sprintf("%d", sa.snapSize))
+		args = append(args, "createsnap", "--snapname", sa.snapshotName, "--lvname", sa.srcVolName, "--vgname", sa.vgName, "--lvsize", fmt.Sprintf("%d", sa.snapSize), "--lvmtype", sa.lvType)
 	case actionTypeDelete:
 		args = append(args, "deletesnap", "--snapname", sa.snapshotName, "--vgname", sa.vgName)
 	default:
@@ -436,40 +436,51 @@ func CreateLVS(vg string, name string, size uint64, lvmType string) (string, err
 		return "", fmt.Errorf("size must be greater than 0")
 	}
 
-	if !(lvmType == "linear" || lvmType == "mirror" || lvmType == "striped") {
-		return "", fmt.Errorf("lvmType is incorrect: %s", lvmType)
-	}
-
 	// TODO: check available capacity, fail if request doesn't fit
 
-	args := []string{"-v", "--yes", "-n", name, "-W", "y", "-L", fmt.Sprintf("%db", size)}
+	executor := cmd.NewExecutor()
+	thinPoolName := ""
+	// we need to create thin pool first if the lvmType is dm-thin
+	if lvmType == dmThinType {
+		thinPoolName = fmt.Sprintf("%s-thinpool", vg)
+		found, err := getThinPool(vg, thinPoolName)
+		if err != nil {
+			return "", fmt.Errorf("unable to determine if thinpool exists: %w", err)
+		}
+		if !found {
+			args := []string{"-l90%FREE", "--thinpool", thinPoolName, vg}
+			klog.Infof("lvcreate %s", args)
+			_, err := executor.Execute("lvcreate", args)
+			if err != nil {
+				return "", fmt.Errorf("unable to create thinpool: %w", err)
+			}
+		}
+	}
+
+	args := []string{"-v", "--yes", "-n", name, "-W", "y"}
 
 	pvs, err := pvCount(vg)
 	if err != nil {
 		return "", fmt.Errorf("unable to determine pv count of vg: %w", err)
 	}
 
-	if pvs < 2 {
-		klog.Warning("pvcount is <2 only linear is supported")
-		lvmType = linearType
+	if pvs < 2 && lvmType == stripedType {
+		klog.Warning("pvcount is <2, the striped does not meaningful.")
 	}
 
 	switch lvmType {
 	case stripedType:
-		args = append(args, "--type", "striped", "--stripes", fmt.Sprintf("%d", pvs))
-	case mirrorType:
-		args = append(args, "--type", "raid1", "--mirrors", "1", "--nosync")
-	case linearType:
+		args = append(args, "-L", fmt.Sprintf("%db", size), "--type", "striped", "--stripes", fmt.Sprintf("%d", pvs), vg)
+	case dmThinType:
+		args = append(args, "-V", fmt.Sprintf("%db", size), "--thin-pool", thinPoolName, vg)
 	default:
 		return "", fmt.Errorf("unsupported lvmtype: %s", lvmType)
 	}
 
-	executor := cmd.NewExecutor()
 	tags := []string{"harvester-csi-lvm"}
 	for _, tag := range tags {
 		args = append(args, "--addtag", tag)
 	}
-	args = append(args, vg)
 	klog.Infof("lvcreate %s", args)
 	out, err := executor.Execute("lvcreate", args)
 	return out, err
@@ -535,7 +546,7 @@ func RemoveLVS(name string) (string, error) {
 	return out, err
 }
 
-func CreateSnapshot(snapshotName, srcVolName, vgName string, volSize int64) (string, error) {
+func CreateSnapshot(snapshotName, srcVolName, vgName string, volSize int64, lvType string) (string, error) {
 	if snapshotName == "" || srcVolName == "" {
 		return "", fmt.Errorf("invalid empty name or path")
 	}
@@ -552,7 +563,16 @@ func CreateSnapshot(snapshotName, srcVolName, vgName string, volSize int64) (str
 	// Names starting "snapshot" are reserved for internal use by LVM
 	// we patch new snapName as "lvm-<snapshotName>"
 	snapshotName = fmt.Sprintf("lvm-%s", snapshotName)
-	args := []string{"-s", "-y", "-n", snapshotName, "-L", fmt.Sprintf("%db", volSize)}
+	args := []string{"-s", "-y", "-n", snapshotName}
+	switch lvType {
+	case stripedType:
+		args = append(args, "-L", fmt.Sprintf("%db", volSize))
+	case dmThinType:
+		// no-size option for the dm-thin
+		break
+	default:
+		return "", fmt.Errorf("unsupported lvmtype: %s", lvType)
+	}
 	args = append(args, fmt.Sprintf("/dev/%s/%s", vgName, srcVolName))
 	klog.Infof("lvcreate %s", args)
 	out, err := executor.Execute("lvcreate", args)
@@ -593,6 +613,41 @@ func pvCount(vgname string) (int, error) {
 	return count, nil
 }
 
+func getThinPool(vgName, thinpoolName string) (bool, error) {
+	executor := cmd.NewExecutor()
+	// we would like to get the segtype, name as below:
+	// thin thinvol01    <-- this is volume
+	// thin-pool vg02-thinpool  <-- this is thin-pool
+	args := []string{"--noheadings", "-o", "segtype,name", vgName}
+	out, err := executor.Execute("lvs", args)
+	if err != nil {
+		klog.Infof("execute lvs %s, err: %v", args, err)
+		return false, err
+	}
+	lines := strings.Split(out, "\n")
+	// type[Name]
+	// type: thin -> vol name
+	//       thin-pool -> pool name
+	thinInfo := make(map[string]string)
+	for _, line := range lines {
+		if line == "" {
+			continue
+		}
+		parts := strings.Fields(line)
+		if len(parts) != 2 {
+			klog.Warningf("unexpected output from lvs: %s", line)
+			continue
+		}
+		thinInfo[parts[0]] = parts[1]
+	}
+	for typeName, val := range thinInfo {
+		if typeName == "thin-pool" && val == thinpoolName {
+			return true, nil
+		}
+	}
+	return false, nil
+}
+
 func getRelatedVG(lvname string) (string, error) {
 	executor := cmd.NewExecutor()
 	// we would like to get the lvname, vgname as below: