Skip to content

Commit

Permalink
Merge pull request #95 from smoser/feature/add-thinpool
Browse files Browse the repository at this point in the history
Add support for CreateLV with thin or thinpool.
  • Loading branch information
hallyn authored Oct 21, 2020
2 parents a57c250 + 0b8990d commit f05e034
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 15 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
65 changes: 50 additions & 15 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 @@ -168,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
Expand Down Expand Up @@ -201,9 +205,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 +272,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 +299,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 f05e034

Please sign in to comment.