asteris-llc/converge

View on GitHub
resource/lvm/lowlevel/util.go

Summary

Maintainability
A
0 mins
Test Coverage
// Copyright © 2016 Asteris, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package lowlevel

import (
    "fmt"

    "github.com/asteris-llc/converge/resource/wait"
    "github.com/pkg/errors"
)

// LVM is a public interface to LVM guts for converge highlevel modules
type LVM interface {
    // Check for LVM tools installed and useable
    Check() error

    // Check for mkfs.* tools installed and useable
    CheckFilesystemTools(fstype string) error

    QueryLogicalVolumes(vg string) (map[string]*LogicalVolume, error)
    QueryPhysicalVolumes() (map[string]*PhysicalVolume, error)
    QueryVolumeGroups() (map[string]*VolumeGroup, error)
    CreateVolumeGroup(vg string, devs []string) error
    ExtendVolumeGroup(vg string, dev string) error
    ReduceVolumeGroup(vg string, dev string) error
    CreatePhysicalVolume(dev string) error
    RemovePhysicalVolume(dev string, force bool) error
    CreateLogicalVolume(group string, volume string, size *LvmSize) error
    Mkfs(dev string, fstype string) error
    Mountpoint(path string) (bool, error)
    Blkid(dev string) (string, error)
    WaitForDevice(path string) error

    // systemd units
    CheckUnit(filename string, content string) (bool, error)
    UpdateUnit(filename string, content string) error
    StartUnit(filename string) error
}

type realLVM struct {
    backend Exec
}

// MakeLvmBackend creates default LVM backend
func MakeLvmBackend() LVM {
    backend := MakeOsExec()
    return &realLVM{backend: backend}
}

// MakeRealLVM is actually kludge for DI (intended for creating test-backed RealLVM, and unpublish type inself)
func MakeRealLVM(backend Exec) LVM {
    return &realLVM{backend: backend}
}

func (lvm *realLVM) CreateVolumeGroup(vg string, devs []string) error {
    args := []string{vg}
    var canonicalDevs []string
    for _, dev := range devs {
        canonicalDev, err := lvm.backend.EvalSymlinks(dev)
        if err != nil {
            return errors.Wrap(err, "resolving symlink for "+dev)
        }
        canonicalDevs = append(canonicalDevs, canonicalDev)
    }
    args = append(args, canonicalDevs...)
    return lvm.backend.Run("vgcreate", args)
}

func (lvm *realLVM) ExtendVolumeGroup(vg string, dev string) error {
    canonicalDev, err := lvm.backend.EvalSymlinks(dev)
    if err != nil {
        return err
    }
    return lvm.backend.Run("vgextend", []string{vg, canonicalDev})
}

func (lvm *realLVM) ReduceVolumeGroup(vg string, dev string) error {
    canonicalDev, err := lvm.backend.EvalSymlinks(dev)
    if err != nil {
        return err
    }
    return lvm.backend.Run("vgreduce", []string{vg, canonicalDev})
}

func (lvm *realLVM) CreatePhysicalVolume(dev string) error {
    canonicalDev, err := lvm.backend.EvalSymlinks(dev)
    if err != nil {
        return err
    }
    return lvm.backend.Run("pvcreate", []string{canonicalDev})
}

func (lvm *realLVM) RemovePhysicalVolume(dev string, force bool) error {
    canonicalDev, err := lvm.backend.EvalSymlinks(dev)
    if err != nil {
        return err
    }
    args := []string{}
    if force {
        args = append(args, "--force", "--force", "--yes")
    }
    args = append(args, canonicalDev)
    return lvm.backend.Run("pvremove", args)
}

func (lvm *realLVM) CreateLogicalVolume(group string, volume string, size *LvmSize) error {
    sizeStr := size.String()
    option := size.Option()
    return lvm.backend.Run("lvcreate", []string{"-n", volume, option, sizeStr, group})
}

func (lvm *realLVM) Mkfs(dev string, fstype string) error {
    canonicalDev, err := lvm.backend.EvalSymlinks(dev)
    if err != nil {
        return err
    }
    return lvm.backend.Run("mkfs", []string{"-t", fstype, canonicalDev})
}

func (lvm *realLVM) Mountpoint(path string) (bool, error) {
    rc, err := lvm.backend.RunWithExitCode("mountpoint", []string{"-q", path})
    if err != nil {
        return false, err
    }
    if rc == 0 {
        return true, nil
    }
    return false, nil
}

func (lvm *realLVM) Check() error {
    if uid := lvm.backend.Getuid(); uid != 0 {
        return fmt.Errorf("lvm require root permissions (uid == 0), but converge run from user id (uid == %d)", uid)
    }
    // NB: extend list to all used tools or wrap all calls via `lvm $subcommand` and check for lvm only
    //     second way need careful check, if `lvm $subcommand` and just `$subcommand`  accepot exact same parameters
    // Related issue: https://github.com/asteris-llc/converge/issues/457
    for _, tool := range []string{"lvs", "vgs", "pvs", "lvcreate", "lvreduce", "lvremove", "vgcreate", "vgreduce", "pvcreate"} {
        if err := lvm.backend.Lookup(tool); err != nil {
            return errors.Wrapf(err, "lvm: can't find required tool %s in $PATH", tool)
        }
    }
    return nil
}

func (lvm *realLVM) CheckFilesystemTools(fstype string) error {
    // Root check just copied from .Check() because lvm.fs can be used w/o lvm utils,  but require root and mkfs.*
    if uid := lvm.backend.Getuid(); uid != 0 {
        return fmt.Errorf("lvm require root permissions (uid == 0), but converge run from user id (uid == %d)", uid)
    }

    tool := fmt.Sprintf("mkfs.%s", fstype)
    if err := lvm.backend.Lookup(tool); err != nil {
        return errors.Wrapf(err, "lvm: can't find required tool %s in $PATH", tool)
    }
    return nil
}

func (lvm *realLVM) WaitForDevice(path string) error {
    retrier := wait.PrepareRetrier(nil, nil, nil)
    ok, err := retrier.RetryUntil(func() (bool, error) {
        return lvm.backend.Exists(path)
    })
    if err != nil {
        return err
    }
    if !ok {
        return fmt.Errorf("device path %s not appeared after %s seconds", path, retrier.Duration.String())
    }
    return nil
}