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 }