435 lines
12 KiB
Go
435 lines
12 KiB
Go
// Copyright 2014 Google Inc. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package fs
|
|
|
|
import (
|
|
"errors"
|
|
"io/ioutil"
|
|
"os"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/docker/docker/pkg/mount"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestGetDiskStatsMap(t *testing.T) {
|
|
diskStatsMap, err := getDiskStatsMap("test_resources/diskstats")
|
|
if err != nil {
|
|
t.Errorf("Error calling getDiskStatMap %s", err)
|
|
}
|
|
if len(diskStatsMap) != 30 {
|
|
t.Errorf("diskStatsMap %+v not valid", diskStatsMap)
|
|
}
|
|
keySet := map[string]string{
|
|
"/dev/sda": "/dev/sda",
|
|
"/dev/sdb": "/dev/sdb",
|
|
"/dev/sdc": "/dev/sdc",
|
|
"/dev/sdd": "/dev/sdd",
|
|
"/dev/sde": "/dev/sde",
|
|
"/dev/sdf": "/dev/sdf",
|
|
"/dev/sdg": "/dev/sdg",
|
|
"/dev/sdh": "/dev/sdh",
|
|
"/dev/sdb1": "/dev/sdb1",
|
|
"/dev/sdb2": "/dev/sdb2",
|
|
"/dev/sda1": "/dev/sda1",
|
|
"/dev/sda2": "/dev/sda2",
|
|
"/dev/sdc1": "/dev/sdc1",
|
|
"/dev/sdc2": "/dev/sdc2",
|
|
"/dev/sdc3": "/dev/sdc3",
|
|
"/dev/sdc4": "/dev/sdc4",
|
|
"/dev/sdd1": "/dev/sdd1",
|
|
"/dev/sdd2": "/dev/sdd2",
|
|
"/dev/sdd3": "/dev/sdd3",
|
|
"/dev/sdd4": "/dev/sdd4",
|
|
"/dev/sde1": "/dev/sde1",
|
|
"/dev/sde2": "/dev/sde2",
|
|
"/dev/sdf1": "/dev/sdf1",
|
|
"/dev/sdf2": "/dev/sdf2",
|
|
"/dev/sdg1": "/dev/sdg1",
|
|
"/dev/sdg2": "/dev/sdg2",
|
|
"/dev/sdh1": "/dev/sdh1",
|
|
"/dev/sdh2": "/dev/sdh2",
|
|
"/dev/dm-0": "/dev/dm-0",
|
|
"/dev/dm-1": "/dev/dm-1",
|
|
}
|
|
|
|
for device := range diskStatsMap {
|
|
if _, ok := keySet[device]; !ok {
|
|
t.Errorf("Cannot find device %s", device)
|
|
}
|
|
delete(keySet, device)
|
|
}
|
|
if len(keySet) != 0 {
|
|
t.Errorf("diskStatsMap %+v contains illegal keys %+v", diskStatsMap, keySet)
|
|
}
|
|
}
|
|
|
|
func TestFileNotExist(t *testing.T) {
|
|
_, err := getDiskStatsMap("/file_does_not_exist")
|
|
if err != nil {
|
|
t.Fatalf("getDiskStatsMap must not error for absent file: %s", err)
|
|
}
|
|
}
|
|
|
|
func TestDirUsage(t *testing.T) {
|
|
as := assert.New(t)
|
|
fsInfo, err := NewFsInfo(Context{})
|
|
as.NoError(err)
|
|
dir, err := ioutil.TempDir(os.TempDir(), "")
|
|
as.NoError(err)
|
|
defer os.RemoveAll(dir)
|
|
dataSize := 1024 * 100 //100 KB
|
|
b := make([]byte, dataSize)
|
|
f, err := ioutil.TempFile(dir, "")
|
|
as.NoError(err)
|
|
as.NoError(ioutil.WriteFile(f.Name(), b, 0700))
|
|
fi, err := f.Stat()
|
|
as.NoError(err)
|
|
expectedSize := uint64(fi.Size())
|
|
size, err := fsInfo.GetDirUsage(dir, time.Minute)
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAddSystemRootLabel(t *testing.T) {
|
|
tests := []struct {
|
|
mounts []*mount.Info
|
|
expected string
|
|
}{
|
|
{
|
|
mounts: []*mount.Info{
|
|
{Source: "/dev/sda1", Mountpoint: "/foo"},
|
|
{Source: "/dev/sdb1", Mountpoint: "/"},
|
|
},
|
|
expected: "/dev/sdb1",
|
|
},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
fsInfo := &RealFsInfo{
|
|
labels: map[string]string{},
|
|
partitions: map[string]partition{},
|
|
}
|
|
fsInfo.addSystemRootLabel(tt.mounts)
|
|
|
|
if source, ok := fsInfo.labels[LabelSystemRoot]; !ok || source != tt.expected {
|
|
t.Errorf("case %d: expected mount source '%s', got '%s'", i, tt.expected, source)
|
|
}
|
|
}
|
|
}
|
|
|
|
type testDmsetup struct {
|
|
data []byte
|
|
err error
|
|
}
|
|
|
|
func (*testDmsetup) Message(deviceName string, sector int, message string) ([]byte, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (*testDmsetup) Status(deviceName string) ([]byte, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (t *testDmsetup) Table(poolName string) ([]byte, error) {
|
|
return t.data, t.err
|
|
}
|
|
|
|
func TestGetDockerDeviceMapperInfo(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
driver string
|
|
driverStatus map[string]string
|
|
dmsetupTable string
|
|
dmsetupTableError error
|
|
expectedDevice string
|
|
expectedPartition *partition
|
|
expectedError bool
|
|
}{
|
|
{
|
|
name: "not devicemapper",
|
|
driver: "btrfs",
|
|
expectedDevice: "",
|
|
expectedPartition: nil,
|
|
expectedError: false,
|
|
},
|
|
{
|
|
name: "nil driver status",
|
|
driver: "devicemapper",
|
|
driverStatus: nil,
|
|
expectedDevice: "",
|
|
expectedPartition: nil,
|
|
expectedError: true,
|
|
},
|
|
{
|
|
name: "loopback",
|
|
driver: "devicemapper",
|
|
driverStatus: map[string]string{"Data loop file": "/var/lib/docker/devicemapper/devicemapper/data"},
|
|
expectedDevice: "",
|
|
expectedPartition: nil,
|
|
expectedError: false,
|
|
},
|
|
{
|
|
name: "missing pool name",
|
|
driver: "devicemapper",
|
|
driverStatus: map[string]string{},
|
|
expectedDevice: "",
|
|
expectedPartition: nil,
|
|
expectedError: true,
|
|
},
|
|
{
|
|
name: "error invoking dmsetup",
|
|
driver: "devicemapper",
|
|
driverStatus: map[string]string{"Pool Name": "vg_vagrant-docker--pool"},
|
|
dmsetupTableError: errors.New("foo"),
|
|
expectedDevice: "",
|
|
expectedPartition: nil,
|
|
expectedError: true,
|
|
},
|
|
{
|
|
name: "unable to parse dmsetup table",
|
|
driver: "devicemapper",
|
|
driverStatus: map[string]string{"Pool Name": "vg_vagrant-docker--pool"},
|
|
dmsetupTable: "no data here!",
|
|
expectedDevice: "",
|
|
expectedPartition: nil,
|
|
expectedError: true,
|
|
},
|
|
{
|
|
name: "happy path",
|
|
driver: "devicemapper",
|
|
driverStatus: map[string]string{"Pool Name": "vg_vagrant-docker--pool"},
|
|
dmsetupTable: "0 53870592 thin-pool 253:2 253:3 1024 0 1 skip_block_zeroing",
|
|
expectedDevice: "vg_vagrant-docker--pool",
|
|
expectedPartition: &partition{
|
|
fsType: "devicemapper",
|
|
major: 253,
|
|
minor: 3,
|
|
blockSize: 1024,
|
|
},
|
|
expectedError: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
fsInfo := &RealFsInfo{
|
|
dmsetup: &testDmsetup{
|
|
data: []byte(tt.dmsetupTable),
|
|
},
|
|
}
|
|
|
|
dockerCtx := DockerContext{
|
|
Driver: tt.driver,
|
|
DriverStatus: tt.driverStatus,
|
|
}
|
|
|
|
device, partition, err := fsInfo.getDockerDeviceMapperInfo(dockerCtx)
|
|
|
|
if tt.expectedError && err == nil {
|
|
t.Errorf("%s: expected error but got nil", tt.name)
|
|
continue
|
|
}
|
|
if !tt.expectedError && err != nil {
|
|
t.Errorf("%s: unexpected error: %v", tt.name, err)
|
|
continue
|
|
}
|
|
|
|
if e, a := tt.expectedDevice, device; e != a {
|
|
t.Errorf("%s: device: expected %q, got %q", tt.name, e, a)
|
|
}
|
|
|
|
if e, a := tt.expectedPartition, partition; !reflect.DeepEqual(e, a) {
|
|
t.Errorf("%s: partition: expected %#v, got %#v", tt.name, e, a)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAddDockerImagesLabel(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
driver string
|
|
driverStatus map[string]string
|
|
dmsetupTable string
|
|
getDockerDeviceMapperInfoError error
|
|
mounts []*mount.Info
|
|
expectedDockerDevice string
|
|
expectedPartition *partition
|
|
}{
|
|
{
|
|
name: "devicemapper, not loopback",
|
|
driver: "devicemapper",
|
|
driverStatus: map[string]string{"Pool Name": "vg_vagrant-docker--pool"},
|
|
dmsetupTable: "0 53870592 thin-pool 253:2 253:3 1024 0 1 skip_block_zeroing",
|
|
mounts: []*mount.Info{
|
|
{
|
|
Source: "/dev/mapper/vg_vagrant-lv_root",
|
|
Mountpoint: "/",
|
|
Fstype: "devicemapper",
|
|
},
|
|
},
|
|
expectedDockerDevice: "vg_vagrant-docker--pool",
|
|
expectedPartition: &partition{
|
|
fsType: "devicemapper",
|
|
major: 253,
|
|
minor: 3,
|
|
blockSize: 1024,
|
|
},
|
|
},
|
|
{
|
|
name: "devicemapper, loopback on non-root partition",
|
|
driver: "devicemapper",
|
|
driverStatus: map[string]string{"Data loop file": "/var/lib/docker/devicemapper/devicemapper/data"},
|
|
mounts: []*mount.Info{
|
|
{
|
|
Source: "/dev/mapper/vg_vagrant-lv_root",
|
|
Mountpoint: "/",
|
|
Fstype: "devicemapper",
|
|
},
|
|
{
|
|
Source: "/dev/sdb1",
|
|
Mountpoint: "/var/lib/docker/devicemapper",
|
|
},
|
|
},
|
|
expectedDockerDevice: "/dev/sdb1",
|
|
},
|
|
{
|
|
name: "multiple mounts - innermost check",
|
|
mounts: []*mount.Info{
|
|
{
|
|
Source: "/dev/sda1",
|
|
Mountpoint: "/",
|
|
Fstype: "ext4",
|
|
},
|
|
{
|
|
Source: "/dev/sdb1",
|
|
Mountpoint: "/var/lib/docker",
|
|
Fstype: "ext4",
|
|
},
|
|
{
|
|
Source: "/dev/sdb2",
|
|
Mountpoint: "/var/lib/docker/btrfs",
|
|
Fstype: "btrfs",
|
|
},
|
|
},
|
|
expectedDockerDevice: "/dev/sdb2",
|
|
},
|
|
{
|
|
name: "root fs inside container, docker-images bindmount",
|
|
mounts: []*mount.Info{
|
|
{
|
|
Source: "overlay",
|
|
Mountpoint: "/",
|
|
Fstype: "overlay",
|
|
},
|
|
{
|
|
Source: "/dev/sda1",
|
|
Mountpoint: "/var/lib/docker",
|
|
Fstype: "ext4",
|
|
},
|
|
},
|
|
expectedDockerDevice: "/dev/sda1",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
fsInfo := &RealFsInfo{
|
|
labels: map[string]string{},
|
|
partitions: map[string]partition{},
|
|
dmsetup: &testDmsetup{
|
|
data: []byte(tt.dmsetupTable),
|
|
},
|
|
}
|
|
|
|
context := Context{
|
|
Docker: DockerContext{
|
|
Root: "/var/lib/docker",
|
|
Driver: tt.driver,
|
|
DriverStatus: tt.driverStatus,
|
|
},
|
|
}
|
|
|
|
fsInfo.addDockerImagesLabel(context, tt.mounts)
|
|
|
|
if e, a := tt.expectedDockerDevice, fsInfo.labels[LabelDockerImages]; e != a {
|
|
t.Errorf("%s: docker device: expected %q, got %q", tt.name, e, a)
|
|
}
|
|
|
|
if tt.expectedPartition == nil {
|
|
continue
|
|
}
|
|
if e, a := *tt.expectedPartition, fsInfo.partitions[tt.expectedDockerDevice]; !reflect.DeepEqual(e, a) {
|
|
t.Errorf("%s: docker partition: expected %#v, got %#v", tt.name, e, a)
|
|
}
|
|
}
|
|
}
|