device_setup.go 6.2 KB

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