github.com/vrothberg/storage@v1.12.13/drivers/devmapper/device_setup.go (about)

     1  package devmapper
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"strings"
    13  
    14  	"github.com/pkg/errors"
    15  	"github.com/sirupsen/logrus"
    16  )
    17  
    18  type directLVMConfig struct {
    19  	Device              string
    20  	ThinpPercent        uint64
    21  	ThinpMetaPercent    uint64
    22  	AutoExtendPercent   uint64
    23  	AutoExtendThreshold uint64
    24  }
    25  
    26  var (
    27  	errThinpPercentMissing = errors.New("must set both `dm.thinp_percent` and `dm.thinp_metapercent` if either is specified")
    28  	errThinpPercentTooBig  = errors.New("combined `dm.thinp_percent` and `dm.thinp_metapercent` must not be greater than 100")
    29  	errMissingSetupDevice  = errors.New("must provide device path in `dm.directlvm_device` in order to configure direct-lvm")
    30  )
    31  
    32  func validateLVMConfig(cfg directLVMConfig) error {
    33  	if cfg.Device == "" {
    34  		return errMissingSetupDevice
    35  	}
    36  	if (cfg.ThinpPercent > 0 && cfg.ThinpMetaPercent == 0) || cfg.ThinpMetaPercent > 0 && cfg.ThinpPercent == 0 {
    37  		return errThinpPercentMissing
    38  	}
    39  
    40  	if cfg.ThinpPercent+cfg.ThinpMetaPercent > 100 {
    41  		return errThinpPercentTooBig
    42  	}
    43  	return nil
    44  }
    45  
    46  func checkDevAvailable(dev string) error {
    47  	lvmScan, err := exec.LookPath("lvmdiskscan")
    48  	if err != nil {
    49  		logrus.Debug("could not find lvmdiskscan")
    50  		return nil
    51  	}
    52  
    53  	out, err := exec.Command(lvmScan).CombinedOutput()
    54  	if err != nil {
    55  		logrus.WithError(err).Error(string(out))
    56  		return nil
    57  	}
    58  
    59  	if !bytes.Contains(out, []byte(dev)) {
    60  		return errors.Errorf("%s is not available for use with devicemapper", dev)
    61  	}
    62  	return nil
    63  }
    64  
    65  func checkDevInVG(dev string) error {
    66  	pvDisplay, err := exec.LookPath("pvdisplay")
    67  	if err != nil {
    68  		logrus.Debug("could not find pvdisplay")
    69  		return nil
    70  	}
    71  
    72  	out, err := exec.Command(pvDisplay, dev).CombinedOutput()
    73  	if err != nil {
    74  		logrus.WithError(err).Error(string(out))
    75  		return nil
    76  	}
    77  
    78  	scanner := bufio.NewScanner(bytes.NewReader(bytes.TrimSpace(out)))
    79  	for scanner.Scan() {
    80  		fields := strings.SplitAfter(strings.TrimSpace(scanner.Text()), "VG Name")
    81  		if len(fields) > 1 {
    82  			// got "VG Name" line"
    83  			vg := strings.TrimSpace(fields[1])
    84  			if len(vg) > 0 {
    85  				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)
    86  			}
    87  			logrus.Error(fields)
    88  			break
    89  		}
    90  	}
    91  	return nil
    92  }
    93  
    94  func checkDevHasFS(dev string) error {
    95  	blkid, err := exec.LookPath("blkid")
    96  	if err != nil {
    97  		logrus.Debug("could not find blkid")
    98  		return nil
    99  	}
   100  
   101  	out, err := exec.Command(blkid, dev).CombinedOutput()
   102  	if err != nil {
   103  		logrus.WithError(err).Error(string(out))
   104  		return nil
   105  	}
   106  
   107  	fields := bytes.Fields(out)
   108  	for _, f := range fields {
   109  		kv := bytes.Split(f, []byte{'='})
   110  		if bytes.Equal(kv[0], []byte("TYPE")) {
   111  			v := bytes.Trim(kv[1], "\"")
   112  			if len(v) > 0 {
   113  				return errors.Errorf("%s has a filesystem already, use dm.directlvm_device_force=true if you want to wipe the device", dev)
   114  			}
   115  			return nil
   116  		}
   117  	}
   118  	return nil
   119  }
   120  
   121  func verifyBlockDevice(dev string, force bool) error {
   122  	realPath, err := filepath.Abs(dev)
   123  	if err != nil {
   124  		return errors.Errorf("unable to get absolute path for %s: %s", dev, err)
   125  	}
   126  	if realPath, err = filepath.EvalSymlinks(realPath); err != nil {
   127  		return errors.Errorf("failed to canonicalise path for %s: %s", dev, err)
   128  	}
   129  	if err := checkDevAvailable(realPath); err != nil {
   130  		return err
   131  	}
   132  	if err := checkDevInVG(realPath); err != nil {
   133  		return err
   134  	}
   135  
   136  	if force {
   137  		return nil
   138  	}
   139  
   140  	if err := checkDevHasFS(realPath); err != nil {
   141  		return err
   142  	}
   143  	return nil
   144  }
   145  
   146  func readLVMConfig(root string) (directLVMConfig, error) {
   147  	var cfg directLVMConfig
   148  
   149  	p := filepath.Join(root, "setup-config.json")
   150  	b, err := ioutil.ReadFile(p)
   151  	if err != nil {
   152  		if os.IsNotExist(err) {
   153  			return cfg, nil
   154  		}
   155  		return cfg, errors.Wrap(err, "error reading existing setup config")
   156  	}
   157  
   158  	// check if this is just an empty file, no need to produce a json error later if so
   159  	if len(b) == 0 {
   160  		return cfg, nil
   161  	}
   162  
   163  	err = json.Unmarshal(b, &cfg)
   164  	return cfg, errors.Wrap(err, "error unmarshaling previous device setup config")
   165  }
   166  
   167  func writeLVMConfig(root string, cfg directLVMConfig) error {
   168  	p := filepath.Join(root, "setup-config.json")
   169  	b, err := json.Marshal(cfg)
   170  	if err != nil {
   171  		return errors.Wrap(err, "error marshalling direct lvm config")
   172  	}
   173  	err = ioutil.WriteFile(p, b, 0600)
   174  	return errors.Wrap(err, "error writing direct lvm config to file")
   175  }
   176  
   177  func setupDirectLVM(cfg directLVMConfig) error {
   178  	lvmProfileDir := "/etc/lvm/profile"
   179  	binaries := []string{"pvcreate", "vgcreate", "lvcreate", "lvconvert", "lvchange", "thin_check"}
   180  
   181  	for _, bin := range binaries {
   182  		if _, err := exec.LookPath(bin); err != nil {
   183  			return errors.Wrap(err, "error looking up command `"+bin+"` while setting up direct lvm")
   184  		}
   185  	}
   186  
   187  	err := os.MkdirAll(lvmProfileDir, 0755)
   188  	if err != nil {
   189  		return errors.Wrap(err, "error creating lvm profile directory")
   190  	}
   191  
   192  	if cfg.AutoExtendPercent == 0 {
   193  		cfg.AutoExtendPercent = 20
   194  	}
   195  
   196  	if cfg.AutoExtendThreshold == 0 {
   197  		cfg.AutoExtendThreshold = 80
   198  	}
   199  
   200  	if cfg.ThinpPercent == 0 {
   201  		cfg.ThinpPercent = 95
   202  	}
   203  	if cfg.ThinpMetaPercent == 0 {
   204  		cfg.ThinpMetaPercent = 1
   205  	}
   206  
   207  	out, err := exec.Command("pvcreate", "-f", cfg.Device).CombinedOutput()
   208  	if err != nil {
   209  		return errors.Wrap(err, string(out))
   210  	}
   211  
   212  	out, err = exec.Command("vgcreate", "storage", cfg.Device).CombinedOutput()
   213  	if err != nil {
   214  		return errors.Wrap(err, string(out))
   215  	}
   216  
   217  	out, err = exec.Command("lvcreate", "--wipesignatures", "y", "-n", "thinpool", "storage", "--extents", fmt.Sprintf("%d%%VG", cfg.ThinpPercent)).CombinedOutput()
   218  	if err != nil {
   219  		return errors.Wrap(err, string(out))
   220  	}
   221  	out, err = exec.Command("lvcreate", "--wipesignatures", "y", "-n", "thinpoolmeta", "storage", "--extents", fmt.Sprintf("%d%%VG", cfg.ThinpMetaPercent)).CombinedOutput()
   222  	if err != nil {
   223  		return errors.Wrap(err, string(out))
   224  	}
   225  
   226  	out, err = exec.Command("lvconvert", "-y", "--zero", "n", "-c", "512K", "--thinpool", "storage/thinpool", "--poolmetadata", "storage/thinpoolmeta").CombinedOutput()
   227  	if err != nil {
   228  		return errors.Wrap(err, string(out))
   229  	}
   230  
   231  	profile := fmt.Sprintf("activation{\nthin_pool_autoextend_threshold=%d\nthin_pool_autoextend_percent=%d\n}", cfg.AutoExtendThreshold, cfg.AutoExtendPercent)
   232  	err = ioutil.WriteFile(lvmProfileDir+"/storage-thinpool.profile", []byte(profile), 0600)
   233  	if err != nil {
   234  		return errors.Wrap(err, "error writing storage thinp autoextend profile")
   235  	}
   236  
   237  	out, err = exec.Command("lvchange", "--metadataprofile", "storage-thinpool", "storage/thinpool").CombinedOutput()
   238  	return errors.Wrap(err, string(out))
   239  }