device_setup.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  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/Sirupsen/logrus"
  14. "github.com/pkg/errors"
  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. if err := checkDevHasFS(dev); err != nil {
  120. return err
  121. }
  122. return nil
  123. }
  124. func readLVMConfig(root string) (directLVMConfig, error) {
  125. var cfg directLVMConfig
  126. p := filepath.Join(root, "setup-config.json")
  127. b, err := ioutil.ReadFile(p)
  128. if err != nil {
  129. if os.IsNotExist(err) {
  130. return cfg, nil
  131. }
  132. return cfg, errors.Wrap(err, "error reading existing setup config")
  133. }
  134. // check if this is just an empty file, no need to produce a json error later if so
  135. if len(b) == 0 {
  136. return cfg, nil
  137. }
  138. err = json.Unmarshal(b, &cfg)
  139. return cfg, errors.Wrap(err, "error unmarshaling previous device setup config")
  140. }
  141. func writeLVMConfig(root string, cfg directLVMConfig) error {
  142. p := filepath.Join(root, "setup-config.json")
  143. b, err := json.Marshal(cfg)
  144. if err != nil {
  145. return errors.Wrap(err, "error marshalling direct lvm config")
  146. }
  147. err = ioutil.WriteFile(p, b, 0600)
  148. return errors.Wrap(err, "error writing direct lvm config to file")
  149. }
  150. func setupDirectLVM(cfg directLVMConfig) error {
  151. pvCreate, err := exec.LookPath("pvcreate")
  152. if err != nil {
  153. return errors.Wrap(err, "error lookuping up command `pvcreate` while setting up direct lvm")
  154. }
  155. vgCreate, err := exec.LookPath("vgcreate")
  156. if err != nil {
  157. return errors.Wrap(err, "error lookuping up command `vgcreate` while setting up direct lvm")
  158. }
  159. lvCreate, err := exec.LookPath("lvcreate")
  160. if err != nil {
  161. return errors.Wrap(err, "error lookuping up command `lvcreate` while setting up direct lvm")
  162. }
  163. lvConvert, err := exec.LookPath("lvconvert")
  164. if err != nil {
  165. return errors.Wrap(err, "error lookuping up command `lvconvert` while setting up direct lvm")
  166. }
  167. lvChange, err := exec.LookPath("lvchange")
  168. if err != nil {
  169. return errors.Wrap(err, "error lookuping up command `lvchange` while setting up direct lvm")
  170. }
  171. if cfg.AutoExtendPercent == 0 {
  172. cfg.AutoExtendPercent = 20
  173. }
  174. if cfg.AutoExtendThreshold == 0 {
  175. cfg.AutoExtendThreshold = 80
  176. }
  177. if cfg.ThinpPercent == 0 {
  178. cfg.ThinpPercent = 95
  179. }
  180. if cfg.ThinpMetaPercent == 0 {
  181. cfg.ThinpMetaPercent = 1
  182. }
  183. out, err := exec.Command(pvCreate, "-f", cfg.Device).CombinedOutput()
  184. if err != nil {
  185. return errors.Wrap(err, string(out))
  186. }
  187. out, err = exec.Command(vgCreate, "docker", cfg.Device).CombinedOutput()
  188. if err != nil {
  189. return errors.Wrap(err, string(out))
  190. }
  191. out, err = exec.Command(lvCreate, "--wipesignatures", "y", "-n", "thinpool", "docker", "--extents", fmt.Sprintf("%d%%VG", cfg.ThinpPercent)).CombinedOutput()
  192. if err != nil {
  193. return errors.Wrap(err, string(out))
  194. }
  195. out, err = exec.Command(lvCreate, "--wipesignatures", "y", "-n", "thinpoolmeta", "docker", "--extents", fmt.Sprintf("%d%%VG", cfg.ThinpMetaPercent)).CombinedOutput()
  196. if err != nil {
  197. return errors.Wrap(err, string(out))
  198. }
  199. out, err = exec.Command(lvConvert, "-y", "--zero", "n", "-c", "512K", "--thinpool", "docker/thinpool", "--poolmetadata", "docker/thinpoolmeta").CombinedOutput()
  200. if err != nil {
  201. return errors.Wrap(err, string(out))
  202. }
  203. profile := fmt.Sprintf("activation{\nthin_pool_autoextend_threshold=%d\nthin_pool_autoextend_percent=%d\n}", cfg.AutoExtendThreshold, cfg.AutoExtendPercent)
  204. err = ioutil.WriteFile("/etc/lvm/profile/docker-thinpool.profile", []byte(profile), 0600)
  205. if err != nil {
  206. return errors.Wrap(err, "error writing docker thinp autoextend profile")
  207. }
  208. out, err = exec.Command(lvChange, "--metadataprofile", "docker-thinpool", "docker/thinpool").CombinedOutput()
  209. return errors.Wrap(err, string(out))
  210. }