github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/daemon/graphdriver/devmapper/device_setup.go (about) 1 package devmapper // import "github.com/demonoid81/moby/daemon/graphdriver/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.directlvm_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 if force { 133 return nil 134 } 135 return checkDevHasFS(dev) 136 } 137 138 func readLVMConfig(root string) (directLVMConfig, error) { 139 var cfg directLVMConfig 140 141 p := filepath.Join(root, "setup-config.json") 142 b, err := ioutil.ReadFile(p) 143 if err != nil { 144 if os.IsNotExist(err) { 145 return cfg, nil 146 } 147 return cfg, errors.Wrap(err, "error reading existing setup config") 148 } 149 150 // check if this is just an empty file, no need to produce a json error later if so 151 if len(b) == 0 { 152 return cfg, nil 153 } 154 155 err = json.Unmarshal(b, &cfg) 156 return cfg, errors.Wrap(err, "error unmarshaling previous device setup config") 157 } 158 159 func writeLVMConfig(root string, cfg directLVMConfig) error { 160 p := filepath.Join(root, "setup-config.json") 161 b, err := json.Marshal(cfg) 162 if err != nil { 163 return errors.Wrap(err, "error marshalling direct lvm config") 164 } 165 err = ioutil.WriteFile(p, b, 0600) 166 return errors.Wrap(err, "error writing direct lvm config to file") 167 } 168 169 func setupDirectLVM(cfg directLVMConfig) error { 170 lvmProfileDir := "/etc/lvm/profile" 171 binaries := []string{"pvcreate", "vgcreate", "lvcreate", "lvconvert", "lvchange", "thin_check"} 172 173 for _, bin := range binaries { 174 if _, err := exec.LookPath(bin); err != nil { 175 return errors.Wrap(err, "error looking up command `"+bin+"` while setting up direct lvm") 176 } 177 } 178 179 err := os.MkdirAll(lvmProfileDir, 0755) 180 if err != nil { 181 return errors.Wrap(err, "error creating lvm profile directory") 182 } 183 184 if cfg.AutoExtendPercent == 0 { 185 cfg.AutoExtendPercent = 20 186 } 187 188 if cfg.AutoExtendThreshold == 0 { 189 cfg.AutoExtendThreshold = 80 190 } 191 192 if cfg.ThinpPercent == 0 { 193 cfg.ThinpPercent = 95 194 } 195 if cfg.ThinpMetaPercent == 0 { 196 cfg.ThinpMetaPercent = 1 197 } 198 199 out, err := exec.Command("pvcreate", "-f", cfg.Device).CombinedOutput() 200 if err != nil { 201 return errors.Wrap(err, string(out)) 202 } 203 204 out, err = exec.Command("vgcreate", "docker", cfg.Device).CombinedOutput() 205 if err != nil { 206 return errors.Wrap(err, string(out)) 207 } 208 209 out, err = exec.Command("lvcreate", "--wipesignatures", "y", "-n", "thinpool", "docker", "--extents", fmt.Sprintf("%d%%VG", cfg.ThinpPercent)).CombinedOutput() 210 if err != nil { 211 return errors.Wrap(err, string(out)) 212 } 213 out, err = exec.Command("lvcreate", "--wipesignatures", "y", "-n", "thinpoolmeta", "docker", "--extents", fmt.Sprintf("%d%%VG", cfg.ThinpMetaPercent)).CombinedOutput() 214 if err != nil { 215 return errors.Wrap(err, string(out)) 216 } 217 218 out, err = exec.Command("lvconvert", "-y", "--zero", "n", "-c", "512K", "--thinpool", "docker/thinpool", "--poolmetadata", "docker/thinpoolmeta").CombinedOutput() 219 if err != nil { 220 return errors.Wrap(err, string(out)) 221 } 222 223 profile := fmt.Sprintf("activation{\nthin_pool_autoextend_threshold=%d\nthin_pool_autoextend_percent=%d\n}", cfg.AutoExtendThreshold, cfg.AutoExtendPercent) 224 err = ioutil.WriteFile(lvmProfileDir+"/docker-thinpool.profile", []byte(profile), 0600) 225 if err != nil { 226 return errors.Wrap(err, "error writing docker thinp autoextend profile") 227 } 228 229 out, err = exec.Command("lvchange", "--metadataprofile", "docker-thinpool", "docker/thinpool").CombinedOutput() 230 return errors.Wrap(err, string(out)) 231 }