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