Skip to content

Commit

Permalink
Add support for CreateLV with thin or thinpool.
Browse files Browse the repository at this point in the history
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 <vgname>/<thinpoolName>.
 * 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).
  • Loading branch information
Scott Moser committed Oct 20, 2020
1 parent 6a591d7 commit 3b158b9
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 14 deletions.
62 changes: 48 additions & 14 deletions linux/lvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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{}
Expand All @@ -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 <vgname>/<thinLVName>
if !strings.Contains(vgName, "/") {
return nilLV,
fmt.Errorf("%s: vgName input for THIN LV name in format <vgname>/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
Expand Down
95 changes: 95 additions & 0 deletions linux/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

0 comments on commit 3b158b9

Please sign in to comment.