From aaa080a520dfc119e73bfb4710c4192064c56088 Mon Sep 17 00:00:00 2001 From: Santosh Pillai Date: Mon, 18 Apr 2022 15:38:09 +0530 Subject: [PATCH] feat: create thin pool Signed-off-by: Santosh Pillai --- pkg/vgmanager/lvm.go | 42 +++++++++++++++++++ pkg/vgmanager/vgmanager_controller.go | 59 +++++++++++++++++++++++---- 2 files changed, 94 insertions(+), 7 deletions(-) diff --git a/pkg/vgmanager/lvm.go b/pkg/vgmanager/lvm.go index 765c35175..b3a9917d3 100644 --- a/pkg/vgmanager/lvm.go +++ b/pkg/vgmanager/lvm.go @@ -37,6 +37,7 @@ const ( vgExtendCmd = "/usr/sbin/vgextend" vgRemoveCmd = "/usr/sbin/vgremove" pvRemoveCmd = "/usr/sbin/pvremove" + lvCreateCmd = "/usr/sbin/lvcreate" ) // vgsOutput represents the output of the `vgs --reportformat json` command @@ -58,6 +59,18 @@ type pvsOutput struct { } `json:"report"` } +// lvsOutput represents the output of the `lvs --reportformat json` command +type lvsOutput struct { + Report []struct { + Lv []struct { + Name string `json:"lv_name"` + VgName string `json:"vg_name"` + PoolName string `json:"pool_lv"` + LvAttr string `json:"lv_attr"` + } `json:"pv"` + } `json:"report"` +} + // VolumeGroup represents a volume group of linux lvm. type VolumeGroup struct { // Name is the name of the volume group @@ -214,6 +227,35 @@ func ListVolumeGroups(exec internal.Executor) ([]VolumeGroup, error) { return vgList, nil } +// ListLogicalVolumes returns list of logical volumes for a volume group +func ListLogicalVolumes(exec internal.Executor, vgName string) ([]string, error) { + res, err := GetLVSOutput(exec, vgName) + if err != nil { + return []string{}, err + } + + lvs := []string{} + for _, report := range res.Report { + for _, lv := range report.Lv { + lvs = append(lvs, lv.Name) + } + } + return lvs, nil +} + +// GetLVSOutput returns the output for `lvs` command in json format +func GetLVSOutput(exec internal.Executor, vgName string) (*lvsOutput, error) { + res := new(lvsOutput) + args := []string{ + "lvs", "-S", fmt.Sprintf("vgname=%s", vgName), "--reportformat", "json", + } + if err := execute(exec, res, args...); err != nil { + return nil, err + } + + return res, nil +} + func execute(exec internal.Executor, v interface{}, args ...string) error { output, err := exec.ExecuteCommandWithOutputAsHost(lvmCmd, args...) if err != nil { diff --git a/pkg/vgmanager/vgmanager_controller.go b/pkg/vgmanager/vgmanager_controller.go index 495db61cb..ac667c582 100644 --- a/pkg/vgmanager/vgmanager_controller.go +++ b/pkg/vgmanager/vgmanager_controller.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "os" + "strings" "time" "github.com/go-logr/logr" @@ -43,7 +44,8 @@ import ( ) const ( - ControllerName = "vg-manager" + ControllerName = "vg-manager" + DefaultChunkSize = "512" ) // SetupWithManager sets up the controller with the Manager. @@ -188,16 +190,30 @@ func (r *VGReconciler) reconcile(ctx context.Context, req ctrl.Request, volumeGr } if !found { - lvmdConfig.DeviceClasses = append(lvmdConfig.DeviceClasses, &lvmd.DeviceClass{ - Name: volumeGroup.Name, - VolumeGroup: volumeGroup.Name, - Default: true, - }) + dc := &lvmd.DeviceClass{ + Name: volumeGroup.Name, + VolumeGroup: volumeGroup.Name, + Default: true, + ThinPoolConfig: &lvmd.ThinPoolConfig{}, + } + + if volumeGroup.Spec.ThinPoolConfig != nil { + dc.Type = lvmd.TypeThin + dc.ThinPoolConfig.Name = volumeGroup.Spec.ThinPoolConfig.Name + dc.ThinPoolConfig.OverprovisionRatio = float64(volumeGroup.Spec.ThinPoolConfig.OverprovisionRatio) + } + + lvmdConfig.DeviceClasses = append(lvmdConfig.DeviceClasses, dc) + } + + // Create thin pool + err = r.addThinPoolToVG(volumeGroup.Name, volumeGroup.Spec.ThinPoolConfig) + if err != nil { + r.Log.Error(err, "failed to create thin pool", "VGName", "ThinPool", volumeGroup.Name, volumeGroup.Spec.ThinPoolConfig.Name) } // apply and save lvmconfig // pass config to configChannel only if config has changed - if !cmp.Equal(existingLvmdConfig, lvmdConfig) { err := saveLVMDConfig(lvmdConfig) if err != nil { @@ -224,6 +240,35 @@ func (r *VGReconciler) reconcile(ctx context.Context, req ctrl.Request, volumeGr return ctrl.Result{RequeueAfter: requeueAfter}, nil } +func (r *VGReconciler) addThinPoolToVG(vgName string, config *lvmv1alpha1.ThinPoolConfig) error { + resp, err := GetLVSOutput(r.executor, vgName) + if err != nil { + return fmt.Errorf("failed to list logical volumes in the volume group %q. %v", vgName, err) + } + + for _, report := range resp.Report { + for _, lv := range report.Lv { + if lv.Name == config.Name { + if strings.Contains(lv.LvAttr, "t") { + r.Log.Info("lvm thinpool already exists", "VGName", vgName, "ThinPool", config.Name) + return nil + } + + return fmt.Errorf("failed to create thin pool %q. Logical volume with same name already exists", config.Name) + } + } + } + + args := []string{"-l", fmt.Sprintf("%d%%FREE", config.SizePercent), "-c", DefaultChunkSize, "-T", fmt.Sprintf("%s/%s", vgName, config.Name)} + + _, err = r.executor.ExecuteCommandWithOutputAsHost(lvCreateCmd, args...) + if err != nil { + return fmt.Errorf("failed to create thin pool %q in the volume group %q. %v", config.Name, vgName, err) + } + + return nil +} + func (r *VGReconciler) processDelete(ctx context.Context, volumeGroup *lvmv1alpha1.LVMVolumeGroup) error { // Read the lvmd config file