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 }