Tizen_4.0 base
[platform/upstream/docker-engine.git] / daemon / graphdriver / devmapper / device_setup.go
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         "reflect"
13         "strings"
14
15         "github.com/Sirupsen/logrus"
16         "github.com/pkg/errors"
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.setup_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
133         if force {
134                 return nil
135         }
136
137         if err := checkDevHasFS(dev); err != nil {
138                 return err
139         }
140         return nil
141 }
142
143 func readLVMConfig(root string) (directLVMConfig, error) {
144         var cfg directLVMConfig
145
146         p := filepath.Join(root, "setup-config.json")
147         b, err := ioutil.ReadFile(p)
148         if err != nil {
149                 if os.IsNotExist(err) {
150                         return cfg, nil
151                 }
152                 return cfg, errors.Wrap(err, "error reading existing setup config")
153         }
154
155         // check if this is just an empty file, no need to produce a json error later if so
156         if len(b) == 0 {
157                 return cfg, nil
158         }
159
160         err = json.Unmarshal(b, &cfg)
161         return cfg, errors.Wrap(err, "error unmarshaling previous device setup config")
162 }
163
164 func writeLVMConfig(root string, cfg directLVMConfig) error {
165         p := filepath.Join(root, "setup-config.json")
166         b, err := json.Marshal(cfg)
167         if err != nil {
168                 return errors.Wrap(err, "error marshalling direct lvm config")
169         }
170         err = ioutil.WriteFile(p, b, 0600)
171         return errors.Wrap(err, "error writing direct lvm config to file")
172 }
173
174 func setupDirectLVM(cfg directLVMConfig) error {
175         pvCreate, err := exec.LookPath("pvcreate")
176         if err != nil {
177                 return errors.Wrap(err, "error lookuping up command `pvcreate` while setting up direct lvm")
178         }
179
180         vgCreate, err := exec.LookPath("vgcreate")
181         if err != nil {
182                 return errors.Wrap(err, "error lookuping up command `vgcreate` while setting up direct lvm")
183         }
184
185         lvCreate, err := exec.LookPath("lvcreate")
186         if err != nil {
187                 return errors.Wrap(err, "error lookuping up command `lvcreate` while setting up direct lvm")
188         }
189
190         lvConvert, err := exec.LookPath("lvconvert")
191         if err != nil {
192                 return errors.Wrap(err, "error lookuping up command `lvconvert` while setting up direct lvm")
193         }
194
195         lvChange, err := exec.LookPath("lvchange")
196         if err != nil {
197                 return errors.Wrap(err, "error lookuping up command `lvchange` while setting up direct lvm")
198         }
199
200         if cfg.AutoExtendPercent == 0 {
201                 cfg.AutoExtendPercent = 20
202         }
203
204         if cfg.AutoExtendThreshold == 0 {
205                 cfg.AutoExtendThreshold = 80
206         }
207
208         if cfg.ThinpPercent == 0 {
209                 cfg.ThinpPercent = 95
210         }
211         if cfg.ThinpMetaPercent == 0 {
212                 cfg.ThinpMetaPercent = 1
213         }
214
215         out, err := exec.Command(pvCreate, "-f", cfg.Device).CombinedOutput()
216         if err != nil {
217                 return errors.Wrap(err, string(out))
218         }
219
220         out, err = exec.Command(vgCreate, "docker", cfg.Device).CombinedOutput()
221         if err != nil {
222                 return errors.Wrap(err, string(out))
223         }
224
225         out, err = exec.Command(lvCreate, "--wipesignatures", "y", "-n", "thinpool", "docker", "--extents", fmt.Sprintf("%d%%VG", cfg.ThinpPercent)).CombinedOutput()
226         if err != nil {
227                 return errors.Wrap(err, string(out))
228         }
229         out, err = exec.Command(lvCreate, "--wipesignatures", "y", "-n", "thinpoolmeta", "docker", "--extents", fmt.Sprintf("%d%%VG", cfg.ThinpMetaPercent)).CombinedOutput()
230         if err != nil {
231                 return errors.Wrap(err, string(out))
232         }
233
234         out, err = exec.Command(lvConvert, "-y", "--zero", "n", "-c", "512K", "--thinpool", "docker/thinpool", "--poolmetadata", "docker/thinpoolmeta").CombinedOutput()
235         if err != nil {
236                 return errors.Wrap(err, string(out))
237         }
238
239         profile := fmt.Sprintf("activation{\nthin_pool_autoextend_threshold=%d\nthin_pool_autoextend_percent=%d\n}", cfg.AutoExtendThreshold, cfg.AutoExtendPercent)
240         err = ioutil.WriteFile("/etc/lvm/profile/docker-thinpool.profile", []byte(profile), 0600)
241         if err != nil {
242                 return errors.Wrap(err, "error writing docker thinp autoextend profile")
243         }
244
245         out, err = exec.Command(lvChange, "--metadataprofile", "docker-thinpool", "docker/thinpool").CombinedOutput()
246         return errors.Wrap(err, string(out))
247 }