~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/worker/diskmanager/lsblk.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2014 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
// +build linux
 
5
 
 
6
package diskmanager
 
7
 
 
8
import (
 
9
        "bufio"
 
10
        "bytes"
 
11
        "fmt"
 
12
        "os"
 
13
        "os/exec"
 
14
        "regexp"
 
15
        "strconv"
 
16
        "strings"
 
17
        "syscall"
 
18
 
 
19
        "github.com/juju/errors"
 
20
 
 
21
        "github.com/juju/juju/storage"
 
22
)
 
23
 
 
24
var pairsRE = regexp.MustCompile(`([A-Z]+)=(?:"(.*?)")`)
 
25
 
 
26
const (
 
27
        // values for the TYPE column that we care about
 
28
 
 
29
        typeDisk = "disk"
 
30
        typeLoop = "loop"
 
31
)
 
32
 
 
33
func init() {
 
34
        DefaultListBlockDevices = listBlockDevices
 
35
}
 
36
 
 
37
func listBlockDevices() ([]storage.BlockDevice, error) {
 
38
        columns := []string{
 
39
                "KNAME",      // kernel name
 
40
                "SIZE",       // size
 
41
                "LABEL",      // filesystem label
 
42
                "UUID",       // filesystem UUID
 
43
                "FSTYPE",     // filesystem type
 
44
                "TYPE",       // device type
 
45
                "MOUNTPOINT", // moint point
 
46
        }
 
47
 
 
48
        logger.Tracef("executing lsblk")
 
49
        output, err := exec.Command(
 
50
                "lsblk",
 
51
                "-b", // output size in bytes
 
52
                "-P", // output fields as key=value pairs
 
53
                "-o", strings.Join(columns, ","),
 
54
        ).Output()
 
55
        if err != nil {
 
56
                return nil, errors.Annotate(
 
57
                        err, "cannot list block devices: lsblk failed",
 
58
                )
 
59
        }
 
60
 
 
61
        var devices []storage.BlockDevice
 
62
        s := bufio.NewScanner(bytes.NewReader(output))
 
63
        for s.Scan() {
 
64
                pairs := pairsRE.FindAllStringSubmatch(s.Text(), -1)
 
65
                var dev storage.BlockDevice
 
66
                var deviceType string
 
67
                for _, pair := range pairs {
 
68
                        switch pair[1] {
 
69
                        case "KNAME":
 
70
                                dev.DeviceName = pair[2]
 
71
                        case "SIZE":
 
72
                                size, err := strconv.ParseUint(pair[2], 10, 64)
 
73
                                if err != nil {
 
74
                                        logger.Errorf(
 
75
                                                "invalid size %q from lsblk: %v", pair[2], err,
 
76
                                        )
 
77
                                } else {
 
78
                                        dev.Size = size / bytesInMiB
 
79
                                }
 
80
                        case "LABEL":
 
81
                                dev.Label = pair[2]
 
82
                        case "UUID":
 
83
                                dev.UUID = pair[2]
 
84
                        case "FSTYPE":
 
85
                                dev.FilesystemType = pair[2]
 
86
                        case "TYPE":
 
87
                                deviceType = pair[2]
 
88
                        case "MOUNTPOINT":
 
89
                                dev.MountPoint = pair[2]
 
90
                        default:
 
91
                                logger.Debugf("unexpected field from lsblk: %q", pair[1])
 
92
                        }
 
93
                }
 
94
 
 
95
                // We may later want to expand this, e.g. to handle lvm,
 
96
                // dmraid, crypt, etc., but this is enough to cover bases
 
97
                // for now.
 
98
                switch deviceType {
 
99
                case typeDisk, typeLoop:
 
100
                default:
 
101
                        logger.Tracef("ignoring %q type device: %+v", deviceType, dev)
 
102
                        continue
 
103
                }
 
104
 
 
105
                // Check if the block device is in use. We need to know this so we can
 
106
                // issue an error if the user attempts to allocate an in-use disk to a
 
107
                // unit.
 
108
                dev.InUse, err = blockDeviceInUse(dev)
 
109
                if os.IsNotExist(err) {
 
110
                        // In LXC containers, lsblk will show the block devices of the
 
111
                        // host, but the devices will typically not be present.
 
112
                        continue
 
113
                } else if err != nil {
 
114
                        logger.Errorf(
 
115
                                "error checking if %q is in use: %v", dev.DeviceName, err,
 
116
                        )
 
117
                        // We cannot detect, so err on the side of caution and default to
 
118
                        // "in use" so the device cannot be used.
 
119
                        dev.InUse = true
 
120
                }
 
121
 
 
122
                // Add additional information from sysfs.
 
123
                if err := addHardwareInfo(&dev); err != nil {
 
124
                        logger.Errorf(
 
125
                                "error getting hardware info for %q from sysfs: %v",
 
126
                                dev.DeviceName, err,
 
127
                        )
 
128
                }
 
129
                devices = append(devices, dev)
 
130
        }
 
131
        if err := s.Err(); err != nil {
 
132
                return nil, errors.Annotate(err, "cannot parse lsblk output")
 
133
        }
 
134
        return devices, nil
 
135
}
 
136
 
 
137
// blockDeviceInUse checks if the specified block device
 
138
// is in use by attempting to open the device exclusively.
 
139
//
 
140
// If the error returned satisfies os.IsNotExists, then
 
141
// the device will be ignored altogether.
 
142
var blockDeviceInUse = func(dev storage.BlockDevice) (bool, error) {
 
143
        f, err := os.OpenFile("/dev/"+dev.DeviceName, os.O_EXCL, 0)
 
144
        if err == nil {
 
145
                f.Close()
 
146
                return false, nil
 
147
        }
 
148
        perr, ok := err.(*os.PathError)
 
149
        if !ok {
 
150
                return false, err
 
151
        }
 
152
        // open(2): "In general, the behavior of O_EXCL is undefined if
 
153
        // it is used without O_CREAT. There is one exception: on Linux
 
154
        // 2.6 and later, O_EXCL can be used without O_CREAT if pathname
 
155
        // refers to a block device. If the block device is in use by the
 
156
        // system  (e.g., mounted), open() fails with the error EBUSY."
 
157
        if errno, _ := perr.Err.(syscall.Errno); errno == syscall.EBUSY {
 
158
                return true, nil
 
159
        }
 
160
        return false, err
 
161
}
 
162
 
 
163
// addHardwareInfo adds additional information about the hardware, and how it is
 
164
// attached to the machine, to the given BlockDevice.
 
165
func addHardwareInfo(dev *storage.BlockDevice) error {
 
166
        logger.Tracef(`executing "udevadm info" for %s`, dev.DeviceName)
 
167
        output, err := exec.Command(
 
168
                "udevadm", "info",
 
169
                "-q", "property",
 
170
                "--path", "/block/"+dev.DeviceName,
 
171
        ).Output()
 
172
        if err != nil {
 
173
                msg := "udevadm failed"
 
174
                if output := bytes.TrimSpace(output); len(output) > 0 {
 
175
                        msg += fmt.Sprintf(" (%s)", output)
 
176
                }
 
177
                return errors.Annotate(err, msg)
 
178
        }
 
179
 
 
180
        var devpath, idBus, idSerial string
 
181
 
 
182
        s := bufio.NewScanner(bytes.NewReader(output))
 
183
        for s.Scan() {
 
184
                line := s.Text()
 
185
                sep := strings.IndexRune(line, '=')
 
186
                if sep == -1 {
 
187
                        logger.Debugf("unexpected udevadm output line: %q", line)
 
188
                        continue
 
189
                }
 
190
                key, value := line[:sep], line[sep+1:]
 
191
                switch key {
 
192
                case "DEVPATH":
 
193
                        devpath = value
 
194
                case "DEVLINKS":
 
195
                        dev.DeviceLinks = strings.Split(value, " ")
 
196
                case "ID_BUS":
 
197
                        idBus = value
 
198
                case "ID_SERIAL":
 
199
                        idSerial = value
 
200
                default:
 
201
                        logger.Tracef("ignoring line: %q", line)
 
202
                }
 
203
        }
 
204
        if err := s.Err(); err != nil {
 
205
                return errors.Annotate(err, "cannot parse udevadm output")
 
206
        }
 
207
 
 
208
        if idBus != "" && idSerial != "" {
 
209
                // ID_BUS will be something like "scsi" or "ata";
 
210
                // ID_SERIAL will be soemthing like ${MODEL}_${SERIALNO};
 
211
                // and together they make up the symlink in /dev/disk/by-id.
 
212
                dev.HardwareId = idBus + "-" + idSerial
 
213
        }
 
214
 
 
215
        // For devices on the SCSI bus, we include the address. This is to
 
216
        // support storage providers where the SCSI address may be specified,
 
217
        // but the device name can not (and may change, depending on timing).
 
218
        if idBus == "scsi" && devpath != "" {
 
219
                // DEVPATH will be "<uninteresting stuff>/<SCSI address>/block/<device>".
 
220
                re := regexp.MustCompile(fmt.Sprintf(
 
221
                        `^.*/(\d+):(\d+):(\d+):(\d+)/block/%s$`,
 
222
                        regexp.QuoteMeta(dev.DeviceName),
 
223
                ))
 
224
                submatch := re.FindStringSubmatch(devpath)
 
225
                if submatch != nil {
 
226
                        // We use the address scheme used by lshw: bus@address. We don't use
 
227
                        // lshw because it does things we don't need, and that slows it down.
 
228
                        //
 
229
                        // In DEVPATH, the address format is "H:C:T:L" ([H]ost, [C]hannel,
 
230
                        // [T]arget, [L]un); the lshw address format is "H:C.T.L"
 
231
                        dev.BusAddress = fmt.Sprintf(
 
232
                                "scsi@%s:%s.%s.%s",
 
233
                                submatch[1], submatch[2], submatch[3], submatch[4],
 
234
                        )
 
235
                } else {
 
236
                        logger.Debugf(
 
237
                                "non matching DEVPATH for %q: %q",
 
238
                                dev.DeviceName, devpath,
 
239
                        )
 
240
                }
 
241
        }
 
242
 
 
243
        return nil
 
244
}