2018-02-05 16:05:59 -05:00
|
|
|
package devmapper // import "github.com/docker/docker/daemon/graphdriver/devmapper"
|
2017-02-16 15:33:03 -05:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
"reflect"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
2017-07-26 17:42:13 -04:00
|
|
|
"github.com/sirupsen/logrus"
|
2017-02-16 15:33:03 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
type directLVMConfig struct {
|
|
|
|
Device string
|
|
|
|
ThinpPercent uint64
|
|
|
|
ThinpMetaPercent uint64
|
|
|
|
AutoExtendPercent uint64
|
|
|
|
AutoExtendThreshold uint64
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
errThinpPercentMissing = errors.New("must set both `dm.thinp_percent` and `dm.thinp_metapercent` if either is specified")
|
|
|
|
errThinpPercentTooBig = errors.New("combined `dm.thinp_percent` and `dm.thinp_metapercent` must not be greater than 100")
|
|
|
|
errMissingSetupDevice = errors.New("must provide device path in `dm.setup_device` in order to configure direct-lvm")
|
|
|
|
)
|
|
|
|
|
|
|
|
func validateLVMConfig(cfg directLVMConfig) error {
|
|
|
|
if reflect.DeepEqual(cfg, directLVMConfig{}) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if cfg.Device == "" {
|
|
|
|
return errMissingSetupDevice
|
|
|
|
}
|
|
|
|
if (cfg.ThinpPercent > 0 && cfg.ThinpMetaPercent == 0) || cfg.ThinpMetaPercent > 0 && cfg.ThinpPercent == 0 {
|
|
|
|
return errThinpPercentMissing
|
|
|
|
}
|
|
|
|
|
|
|
|
if cfg.ThinpPercent+cfg.ThinpMetaPercent > 100 {
|
|
|
|
return errThinpPercentTooBig
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func checkDevAvailable(dev string) error {
|
|
|
|
lvmScan, err := exec.LookPath("lvmdiskscan")
|
|
|
|
if err != nil {
|
|
|
|
logrus.Debug("could not find lvmdiskscan")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
out, err := exec.Command(lvmScan).CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
logrus.WithError(err).Error(string(out))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if !bytes.Contains(out, []byte(dev)) {
|
|
|
|
return errors.Errorf("%s is not available for use with devicemapper", dev)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func checkDevInVG(dev string) error {
|
|
|
|
pvDisplay, err := exec.LookPath("pvdisplay")
|
|
|
|
if err != nil {
|
|
|
|
logrus.Debug("could not find pvdisplay")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
out, err := exec.Command(pvDisplay, dev).CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
logrus.WithError(err).Error(string(out))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
scanner := bufio.NewScanner(bytes.NewReader(bytes.TrimSpace(out)))
|
|
|
|
for scanner.Scan() {
|
|
|
|
fields := strings.SplitAfter(strings.TrimSpace(scanner.Text()), "VG Name")
|
|
|
|
if len(fields) > 1 {
|
|
|
|
// got "VG Name" line"
|
|
|
|
vg := strings.TrimSpace(fields[1])
|
|
|
|
if len(vg) > 0 {
|
|
|
|
return errors.Errorf("%s is already part of a volume group %q: must remove this device from any volume group or provide a different device", dev, vg)
|
|
|
|
}
|
|
|
|
logrus.Error(fields)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func checkDevHasFS(dev string) error {
|
|
|
|
blkid, err := exec.LookPath("blkid")
|
|
|
|
if err != nil {
|
|
|
|
logrus.Debug("could not find blkid")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
out, err := exec.Command(blkid, dev).CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
logrus.WithError(err).Error(string(out))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
fields := bytes.Fields(out)
|
|
|
|
for _, f := range fields {
|
|
|
|
kv := bytes.Split(f, []byte{'='})
|
|
|
|
if bytes.Equal(kv[0], []byte("TYPE")) {
|
|
|
|
v := bytes.Trim(kv[1], "\"")
|
|
|
|
if len(v) > 0 {
|
|
|
|
return errors.Errorf("%s has a filesystem already, use dm.directlvm_device_force=true if you want to wipe the device", dev)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func verifyBlockDevice(dev string, force bool) error {
|
|
|
|
if err := checkDevAvailable(dev); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := checkDevInVG(dev); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if force {
|
|
|
|
return nil
|
|
|
|
}
|
2018-01-14 18:42:25 -05:00
|
|
|
return checkDevHasFS(dev)
|
2017-02-16 15:33:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func readLVMConfig(root string) (directLVMConfig, error) {
|
|
|
|
var cfg directLVMConfig
|
|
|
|
|
|
|
|
p := filepath.Join(root, "setup-config.json")
|
|
|
|
b, err := ioutil.ReadFile(p)
|
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return cfg, nil
|
|
|
|
}
|
|
|
|
return cfg, errors.Wrap(err, "error reading existing setup config")
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if this is just an empty file, no need to produce a json error later if so
|
|
|
|
if len(b) == 0 {
|
|
|
|
return cfg, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
err = json.Unmarshal(b, &cfg)
|
|
|
|
return cfg, errors.Wrap(err, "error unmarshaling previous device setup config")
|
|
|
|
}
|
|
|
|
|
|
|
|
func writeLVMConfig(root string, cfg directLVMConfig) error {
|
|
|
|
p := filepath.Join(root, "setup-config.json")
|
|
|
|
b, err := json.Marshal(cfg)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "error marshalling direct lvm config")
|
|
|
|
}
|
|
|
|
err = ioutil.WriteFile(p, b, 0600)
|
|
|
|
return errors.Wrap(err, "error writing direct lvm config to file")
|
|
|
|
}
|
|
|
|
|
|
|
|
func setupDirectLVM(cfg directLVMConfig) error {
|
2017-08-11 11:00:00 -04:00
|
|
|
lvmProfileDir := "/etc/lvm/profile"
|
2017-08-14 06:06:16 -04:00
|
|
|
binaries := []string{"pvcreate", "vgcreate", "lvcreate", "lvconvert", "lvchange", "thin_check"}
|
2017-08-11 11:00:00 -04:00
|
|
|
|
2017-08-14 06:06:16 -04:00
|
|
|
for _, bin := range binaries {
|
|
|
|
if _, err := exec.LookPath(bin); err != nil {
|
|
|
|
return errors.Wrap(err, "error looking up command `"+bin+"` while setting up direct lvm")
|
|
|
|
}
|
2017-02-16 15:33:03 -05:00
|
|
|
}
|
|
|
|
|
2017-08-14 06:06:16 -04:00
|
|
|
err := os.MkdirAll(lvmProfileDir, 0755)
|
2017-08-11 11:00:00 -04:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "error creating lvm profile directory")
|
|
|
|
}
|
|
|
|
|
2017-02-16 15:33:03 -05:00
|
|
|
if cfg.AutoExtendPercent == 0 {
|
|
|
|
cfg.AutoExtendPercent = 20
|
|
|
|
}
|
|
|
|
|
|
|
|
if cfg.AutoExtendThreshold == 0 {
|
|
|
|
cfg.AutoExtendThreshold = 80
|
|
|
|
}
|
|
|
|
|
|
|
|
if cfg.ThinpPercent == 0 {
|
|
|
|
cfg.ThinpPercent = 95
|
|
|
|
}
|
|
|
|
if cfg.ThinpMetaPercent == 0 {
|
|
|
|
cfg.ThinpMetaPercent = 1
|
|
|
|
}
|
|
|
|
|
2017-08-14 06:06:16 -04:00
|
|
|
out, err := exec.Command("pvcreate", "-f", cfg.Device).CombinedOutput()
|
2017-02-16 15:33:03 -05:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, string(out))
|
|
|
|
}
|
|
|
|
|
2017-08-14 06:06:16 -04:00
|
|
|
out, err = exec.Command("vgcreate", "docker", cfg.Device).CombinedOutput()
|
2017-02-16 15:33:03 -05:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, string(out))
|
|
|
|
}
|
|
|
|
|
2017-08-14 06:06:16 -04:00
|
|
|
out, err = exec.Command("lvcreate", "--wipesignatures", "y", "-n", "thinpool", "docker", "--extents", fmt.Sprintf("%d%%VG", cfg.ThinpPercent)).CombinedOutput()
|
2017-02-16 15:33:03 -05:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, string(out))
|
|
|
|
}
|
2017-08-14 06:06:16 -04:00
|
|
|
out, err = exec.Command("lvcreate", "--wipesignatures", "y", "-n", "thinpoolmeta", "docker", "--extents", fmt.Sprintf("%d%%VG", cfg.ThinpMetaPercent)).CombinedOutput()
|
2017-02-16 15:33:03 -05:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, string(out))
|
|
|
|
}
|
|
|
|
|
2017-08-14 06:06:16 -04:00
|
|
|
out, err = exec.Command("lvconvert", "-y", "--zero", "n", "-c", "512K", "--thinpool", "docker/thinpool", "--poolmetadata", "docker/thinpoolmeta").CombinedOutput()
|
2017-02-16 15:33:03 -05:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, string(out))
|
|
|
|
}
|
|
|
|
|
|
|
|
profile := fmt.Sprintf("activation{\nthin_pool_autoextend_threshold=%d\nthin_pool_autoextend_percent=%d\n}", cfg.AutoExtendThreshold, cfg.AutoExtendPercent)
|
2017-08-11 11:00:00 -04:00
|
|
|
err = ioutil.WriteFile(lvmProfileDir+"/docker-thinpool.profile", []byte(profile), 0600)
|
2017-02-16 15:33:03 -05:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "error writing docker thinp autoextend profile")
|
|
|
|
}
|
|
|
|
|
2017-08-14 06:06:16 -04:00
|
|
|
out, err = exec.Command("lvchange", "--metadataprofile", "docker-thinpool", "docker/thinpool").CombinedOutput()
|
2017-02-16 15:33:03 -05:00
|
|
|
return errors.Wrap(err, string(out))
|
|
|
|
}
|