device_setup.go 6.2 KB

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