github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/daemon/graphdriver/devmapper/device_setup.go (about)

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