diff --git a/fs/fs.go b/fs/fs.go index 0c665cc4..a6af45a6 100644 --- a/fs/fs.go +++ b/fs/fs.go @@ -19,7 +19,6 @@ package fs import ( "bufio" - "bytes" "encoding/json" "fmt" "os" @@ -337,6 +336,8 @@ func dockerStatusValue(status [][]string, target string) string { return "" } +// Devicemapper thin provisioning is detailed at +// https://www.kernel.org/doc/Documentation/device-mapper/thin-provisioning.txt func dockerDMDevice(driverStatus string) (string, uint, uint, uint, error) { var config [][]string err := json.Unmarshal([]byte(driverStatus), &config) @@ -348,46 +349,77 @@ func dockerDMDevice(driverStatus string) (string, uint, uint, uint, error) { return "", 0, 0, 0, fmt.Errorf("Could not get dm pool name") } - dmTable, err := exec.Command("dmsetup", "table", poolName).Output() + out, err := exec.Command("dmsetup", "table", poolName).Output() if err != nil { return "", 0, 0, 0, err } - var ( - major, minor, dataBlkSize, bkt uint - bkts string - ) - - _, err = fmt.Fscanf(bytes.NewReader(dmTable), - "%d %d %s %d:%d %d:%d %d %d %d %s", - &bkt, &bkt, &bkts, &bkt, &bkt, &major, &minor, &dataBlkSize, &bkt, &bkt, &bkts) + major, minor, dataBlkSize, err := parseDMTable(string(out)) if err != nil { return "", 0, 0, 0, err } + return poolName, major, minor, dataBlkSize, nil } +func parseDMTable(dmTable string) (uint, uint, uint, error) { + dmTable = strings.Replace(dmTable, ":", " ", -1) + dmFields := strings.Fields(dmTable) + + if len(dmFields) < 8 { + return 0, 0, 0, fmt.Errorf("Invalid dmsetup status output: %s", dmTable) + } + + major, err := strconv.ParseUint(dmFields[5], 10, 32) + if err != nil { + return 0, 0, 0, err + } + minor, err := strconv.ParseUint(dmFields[6], 10, 32) + if err != nil { + return 0, 0, 0, err + } + dataBlkSize, err := strconv.ParseUint(dmFields[7], 10, 32) + if err != nil { + return 0, 0, 0, err + } + + return uint(major), uint(minor), uint(dataBlkSize), nil +} + func getDMStats(poolName string, dataBlkSize uint) (uint64, uint64, uint64, error) { - dmStatus, err := exec.Command("dmsetup", "status", poolName).Output() + out, err := exec.Command("dmsetup", "status", poolName).Output() if err != nil { return 0, 0, 0, err } - var ( - total, used, bkt uint64 - bkts string - ) - - _, err = fmt.Fscanf(bytes.NewReader(dmStatus), - "%d %d %s %d %d/%d %d/%d %s %s %s %s", - &bkt, &bkt, &bkts, &bkt, &bkt, &bkt, &used, &total, &bkts, &bkts, &bkts, &bkts) + used, total, err := parseDMStatus(string(out)) if err != nil { return 0, 0, 0, err } - total *= 512 * uint64(dataBlkSize) used *= 512 * uint64(dataBlkSize) + total *= 512 * uint64(dataBlkSize) free := total - used return total, free, free, nil } + +func parseDMStatus(dmStatus string) (uint64, uint64, error) { + dmStatus = strings.Replace(dmStatus, "/", " ", -1) + dmFields := strings.Fields(dmStatus) + + if len(dmFields) < 8 { + return 0, 0, fmt.Errorf("Invalid dmsetup status output: %s", dmStatus) + } + + used, err := strconv.ParseUint(dmFields[6], 10, 64) + if err != nil { + return 0, 0, err + } + total, err := strconv.ParseUint(dmFields[7], 10, 64) + if err != nil { + return 0, 0, err + } + + return used, total, nil +} diff --git a/fs/fs_test.go b/fs/fs_test.go index 80b39787..f97ac908 100644 --- a/fs/fs_test.go +++ b/fs/fs_test.go @@ -100,3 +100,59 @@ func TestDirUsage(t *testing.T) { as.NoError(err) as.True(expectedSize <= size, "expected dir size to be at-least %d; got size: %d", expectedSize, size) } + +var dmStatusTests = []struct { + dmStatus string + used uint64 + total uint64 + errExpected bool +}{ + {`0 409534464 thin-pool 64085 3705/4161600 88106/3199488 - rw no_discard_passdown queue_if_no_space -`, 88106, 3199488, false}, + {`0 209715200 thin-pool 707 1215/524288 30282/1638400 - rw discard_passdown`, 30282, 1638400, false}, + {`Invalid status line`, 0, 0, false}, +} + +func TestParseDMStatus(t *testing.T) { + for _, tt := range dmStatusTests { + used, total, err := parseDMStatus(tt.dmStatus) + if tt.errExpected && err != nil { + t.Errorf("parseDMStatus(%q) expected error", tt.dmStatus) + } + if used != tt.used { + t.Errorf("parseDMStatus(%q) wrong used value => %q, want %q", tt.dmStatus, used, tt.used) + } + if total != tt.total { + t.Errorf("parseDMStatus(%q) wrong total value => %q, want %q", tt.dmStatus, total, tt.total) + } + } +} + +var dmTableTests = []struct { + dmTable string + major uint + minor uint + dataBlkSize uint + errExpected bool +}{ + {`0 409534464 thin-pool 253:6 253:7 128 32768 1 skip_block_zeroing`, 253, 7, 128, false}, + {`0 409534464 thin-pool 253:6 258:9 512 32768 1 skip_block_zeroing otherstuff`, 258, 9, 512, false}, + {`Invalid status line`, 0, 0, 0, false}, +} + +func TestParseDMTable(t *testing.T) { + for _, tt := range dmTableTests { + major, minor, dataBlkSize, err := parseDMTable(tt.dmTable) + if tt.errExpected && err != nil { + t.Errorf("parseDMTable(%q) expected error", tt.dmTable) + } + if major != tt.major { + t.Errorf("parseDMTable(%q) wrong major value => %q, want %q", tt.dmTable, major, tt.major) + } + if minor != tt.minor { + t.Errorf("parseDMTable(%q) wrong minor value => %q, want %q", tt.dmTable, minor, tt.minor) + } + if dataBlkSize != tt.dataBlkSize { + t.Errorf("parseDMTable(%q) wrong dataBlkSize value => %q, want %q", tt.dmTable, dataBlkSize, tt.dataBlkSize) + } + } +}