From 6a591d7cba6f3c6a00fdacaa4e82ba3b333fd247 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Tue, 20 Oct 2020 15:03:00 -0400 Subject: [PATCH 1/3] c-i: Install thin-provisioning-tools The stock ubuntu had lvm2, but did not have thin-provisioning-tools. The newly added test needs thin-provisioning-tools. --- .github/workflows/build.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5d3a326..38aa2d1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,6 +18,11 @@ jobs: run: | go mod download + - name: Install Deps + run: | + sudo apt-get update --quiet + sudo apt-get install --quiet --assume-yes --no-install-recommends lvm2 thin-provisioning-tools + - name: build run: | make build From 3b158b989d22982112a3f3e21315410c7861b68f Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Tue, 20 Oct 2020 16:05:56 -0400 Subject: [PATCH 2/3] Add support for CreateLV with thin or thinpool. This adds a limited support to CreateLV for THIN or THINPOOL types. A few quirks that allow us to keep the same old CreateLV interface: * The caller of CreateLV has to know that when creating a THIN lv, they have to pass the VG name as /. * For THINPOOL types, the name given is the name of the thin pool. We accept the defaults from lvm for tdata and tmeta pool names. (creation of MyThinPool will have hidden volumes created named mythinpool_tdata and mythinpool_tmeta). * The MetaData pool size (--poolmetadatasize=) is hard coded to 1GiB and a spare pool is created (--poolmetadataspare=y). --- linux/lvm.go | 62 +++++++++++++++++++++++------- linux/root_test.go | 95 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 14 deletions(-) diff --git a/linux/lvm.go b/linux/lvm.go index 7e23b83..0aaef4e 100644 --- a/linux/lvm.go +++ b/linux/lvm.go @@ -9,6 +9,9 @@ import ( "github.com/anuvu/disko" ) +const pvMetaDataSize = 128 * disko.Mebibyte +const thinPoolMetaDataSize = 1024 * disko.Mebibyte + // VolumeManager returns the linux implementation of disko.VolumeManager interface. func VolumeManager() disko.VolumeManager { return &linuxLVM{} @@ -201,9 +204,8 @@ func (ls *linuxLVM) HasPV(name string) bool { } func (ls *linuxLVM) CreateVG(name string, pvs ...disko.PV) (disko.VG, error) { - const mdSize = 128 * disko.Mebibyte cmd := []string{"lvm", "vgcreate", - fmt.Sprintf("--metadatasize=%dB", mdSize), + fmt.Sprintf("--metadatasize=%dB", pvMetaDataSize), "--zero=y", name} for _, p := range pvs { @@ -269,6 +271,25 @@ func (ls *linuxLVM) CryptClose(vgName string, lvName string, return runCommand("cryptsetup", "close", decryptedName) } +func createLVCmd(args ...string) error { + return runCommandSettled( + append([]string{"lvm", "lvcreate", "--ignoremonitoring", "--yes", "--activate=y", + "--setactivationskip=n"}, args...)...) +} + +func createThinPool(name string, vgName string, size uint64, mdSize uint64) error { + // thinpool takes up size + 2*mdSize + // https://www.redhat.com/archives/linux-lvm/2020-October/thread.html#00016 + args := []string{} + // if mdSize is zero, let lvcreate choose the size. That is documented as: + // (Pool_LV_size / Pool_LV_chunk_size * 64) + if mdSize != 0 { + args = append(args, fmt.Sprintf("--poolmetadatasize=%dB", mdSize)) + } + + return createLVCmd(append(args, fmt.Sprintf("--size=%dB", size), "--thinpool="+name, vgName)...) +} + func (ls *linuxLVM) CreateLV(vgName string, name string, size uint64, lvType disko.LVType) (disko.LV, error) { nilLV := disko.LV{} @@ -277,22 +298,35 @@ func (ls *linuxLVM) CreateLV(vgName string, name string, size uint64, return nilLV, err } - if lvType == disko.THIN { - // thin lv creation would require creating a pool - return nilLV, fmt.Errorf("not supported. Thin LV create not implemented") - } + nameFlag := "--name=" + name + sizeB := fmt.Sprintf("%dB", size) + vglv := vgLv(vgName, name) - err := runCommandSettled( - "lvm", "lvcreate", "--ignoremonitoring", "--yes", "--activate=y", - "--zero=y", - "--setactivationskip=n", fmt.Sprintf("--size=%dB", size), - fmt.Sprintf("--name=%s", name), vgName) + switch lvType { + case disko.THIN: + // When creating THIN LV, the VG must be / + if !strings.Contains(vgName, "/") { + return nilLV, + fmt.Errorf("%s: vgName input for THIN LV name in format /thinDataName", vgName) + } - if err != nil { - return nilLV, err + vglv = vgLv(strings.Split(vgName, "/")[0], name) + + if err := createLVCmd("--virtualsize="+sizeB, nameFlag, vgName); err != nil { + return nilLV, err + } + case disko.THICK: + if err := createLVCmd("--size="+sizeB, nameFlag, vgName); err != nil { + return nilLV, err + } + case disko.THINPOOL: + // When creating a THINPOOL, the name is the thin pool name. + if err := createThinPool(name, vgName, size, thinPoolMetaDataSize); err != nil { + return nilLV, err + } } - lvs, err := ls.scanLVs(func(d disko.LV) bool { return true }, vgLv(vgName, name)) + lvs, err := ls.scanLVs(func(d disko.LV) bool { return true }, vglv) if err != nil { return nilLV, err diff --git a/linux/root_test.go b/linux/root_test.go index 75fc8ba..a35168c 100644 --- a/linux/root_test.go +++ b/linux/root_test.go @@ -234,3 +234,98 @@ func TestRootLVMExtend(t *testing.T) { foundLv = vgs[vgname].Volumes[lvname] ast.Equalf(size2, foundLv.Size, "extended volume size incorrect") } + +func runShow(args ...string) { + out, err, rc := runCommandWithOutputErrorRc(args...) + fmt.Print(cmdString(args, out, err, rc)) +} + +func TestRootLVMCreate(t *testing.T) { + iSkipOrFail(t, isRoot, canUseLoop, canUseLVM) + + ast := assert.New(t) + + var cl = cleanList{} + defer cl.Cleanup(t) + + var pv disko.PV + var vg disko.VG + var lv disko.LV + var c cleaner + var tmpFile string + + lvthick := "diskot-thick" + randStr(8) + lvthinpool := "diskot-pool" + randStr(8) + lvthin := "diskot-thin" + randStr(8) + vgname := "diskot-vg" + randStr(8) + + c, tmpFile = getTempFile(4 * GiB) + cl.Add(c) + + lCleanup, disk, err := singlePartDisk(tmpFile) + cl.AddF(lCleanup, "singlePartdisk") + + if err != nil { + t.Fatalf("Failed to create a single part disk: %s", err) + } + + lvm := linux.VolumeManager() + + pv, err = lvm.CreatePV(disk.Path + "p1") + if err != nil { + t.Fatalf("Failed to create pv on %s: %s\n", disk.Path, err) + } + + cl.AddF(func() error { return lvm.DeletePV(pv) }, "remove pv") + + vg, err = lvm.CreateVG(vgname, pv) + + if err != nil { + t.Fatalf("Failed to create %s with %s: %s", vgname, pv.Path, err) + } + + cl.AddF(func() error { return lvm.RemoveVG(vgname) }, "remove VG") + + ast.Equal(vgname, vg.Name) + + thickSize := uint64(12 * MiB) + + lv, err = lvm.CreateLV(vgname, lvthick, thickSize, disko.THICK) + if err != nil { + t.Fatalf("Failed to create lv %s/%s: %s", vgname, lvthick, err) + } + + cl.AddF(func() error { return lvm.RemoveLV(vgname, lvthick) }, "remove LV") + + ast.Equal(lvthick, lv.Name) + ast.Equal(thickSize, lv.Size) + + thinPoolSize, thinSize := uint64(500*MiB), uint64(200*MiB) + + // create a THINPOOL volume + lv, err = lvm.CreateLV(vgname, lvthinpool, thinPoolSize, disko.THINPOOL) + if err != nil { + t.Fatalf("Failed to create lv %s/%s: %s", vgname, lvthick, err) + } + + cl.AddF(func() error { return lvm.RemoveLV(vgname, lvthinpool) }, "remove thin pool LV") + + ast.Equal(lvthinpool, lv.Name) + ast.Equal(thinPoolSize, lv.Size) + + lv, err = lvm.CreateLV(vgname+"/"+lvthinpool, lvthin, thinSize, disko.THIN) + if err != nil { + runShow("lvm", "lvdisplay", "--unit=m", vgname) + t.Fatalf("Failed to create THIN lv %s on %s/%s: %s", lvthin, vgname, lvthinpool, err) + } + + ast.Equal(lvthin, lv.Name) + ast.Equal(thinSize, lv.Size) + + vgs, errScan := lvm.ScanVGs(func(v disko.VG) bool { return v.Name == vgname }) + if errScan != nil { + t.Fatalf("Failed to scan VGs: %s\n", err) + } + + ast.Equal(len(vgs), 1) +} From 0b8990d07697d6d619ea495a6c669a88a1ad63b7 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Tue, 20 Oct 2020 16:07:44 -0400 Subject: [PATCH 3/3] Add --metadatasize on pvcreate. If the caller did a CreatePV and then a CreateLV, then the explicitly created PV would not have the metadatasize that we wanted (128MiB). Change here is to just add the --metadatasize=128MiB to the 'lvm pvcreate' call. --- linux/lvm.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/linux/lvm.go b/linux/lvm.go index 0aaef4e..072cb2c 100644 --- a/linux/lvm.go +++ b/linux/lvm.go @@ -171,7 +171,8 @@ func (ls *linuxLVM) CreatePV(name string) (disko.PV, error) { return nilPV, err } - err = runCommandSettled("lvm", "pvcreate", "--zero=y", path) + err = runCommandSettled("lvm", "pvcreate", "--zero=y", + fmt.Sprintf("--metadatasize=%dB", pvMetaDataSize), path) if err != nil { return nilPV, err