89ec17d1 |
// +build linux |
24c03b2d |
|
0b12702c |
package devmapper
import ( |
07ff17fb |
"bufio" |
0b12702c |
"encoding/json"
"fmt"
"io"
"io/ioutil" |
39d244a5 |
"os"
"os/exec" |
0b12702c |
"path"
"path/filepath" |
5ef07d79 |
"reflect" |
1a1be5a8 |
"strconv" |
7f5ba068 |
"strings" |
c77697a4 |
"sync" |
a9fa1a13 |
"syscall" |
31b883b0 |
"time" |
46e05ed2 |
|
6f4d8470 |
"github.com/Sirupsen/logrus" |
ee7dd44c |
"github.com/docker/docker/daemon/graphdriver" |
b8f38747 |
"github.com/docker/docker/dockerversion" |
e2f8fbfb |
"github.com/docker/docker/pkg/devicemapper" |
442b4562 |
"github.com/docker/docker/pkg/idtools" |
af597527 |
"github.com/docker/docker/pkg/loopback" |
9a64f2bb |
"github.com/docker/docker/pkg/mount" |
43981084 |
"github.com/docker/docker/pkg/parsers" |
e9864cc0 |
units "github.com/docker/go-units" |
abbbf914 |
"github.com/opencontainers/selinux/go-selinux/label" |
069fdc8a |
"github.com/pkg/errors"
"golang.org/x/sys/unix" |
0b12702c |
)
|
c3f1bb32 |
var ( |
972a94b4 |
defaultDataLoopbackSize int64 = 100 * 1024 * 1024 * 1024
defaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024 |
e47112d3 |
defaultBaseFsSize uint64 = 10 * 1024 * 1024 * 1024 |
972a94b4 |
defaultThinpBlockSize uint32 = 128 // 64K = 128 512b sectors
defaultUdevSyncOverride = false
maxDeviceID = 0xffffff // 24 bit, pool limit
deviceIDMapSz = (maxDeviceID + 1) / 8 |
e07d3cd9 |
// We retry device removal so many a times that even error messages
// will fill up console during normal operation. So only log Fatal
// messages by default. |
2e222f69 |
logLevel = devicemapper.LogLevelFatal
driverDeferredRemovalSupport = false
enableDeferredRemoval = false
enableDeferredDeletion = false
userBaseSize = false
defaultMinFreeSpacePercent uint32 = 10 |
5ef07d79 |
lvmSetupConfigForce bool |
8b2f4aab |
) |
0b12702c |
|
15c74beb |
const deviceSetMetaFile string = "deviceset-metadata" |
c115c4aa |
const transactionMetaFile string = "transaction-metadata" |
15c74beb |
|
972a94b4 |
type transaction struct {
OpenTransactionID uint64 `json:"open_transaction_id"`
DeviceIDHash string `json:"device_hash"`
DeviceID int `json:"device_id"` |
f078bcd8 |
}
|
972a94b4 |
type devInfo struct { |
c7ff6bf6 |
Hash string `json:"-"` |
972a94b4 |
DeviceID int `json:"device_id"` |
c7ff6bf6 |
Size uint64 `json:"size"` |
972a94b4 |
TransactionID uint64 `json:"transaction_id"` |
c7ff6bf6 |
Initialized bool `json:"initialized"` |
d929589c |
Deleted bool `json:"deleted"` |
c7ff6bf6 |
devices *DeviceSet |
b95c560f |
|
3e8a02a9 |
// The global DeviceSet lock guarantees that we serialize all
// the calls to libdevmapper (which is not threadsafe), but we
// sometimes release that lock while sleeping. In that case
// this per-device lock is still held, protecting against
// other accesses to the device that we're doing the wait on. |
2ffef1b7 |
//
// WARNING: In order to avoid AB-BA deadlocks when releasing
// the global lock while holding the per-device locks all |
51462327 |
// device locks must be acquired *before* the device lock, and
// multiple device locks should be acquired parent before child. |
c7ff6bf6 |
lock sync.Mutex |
0b12702c |
}
|
972a94b4 |
type metaData struct { |
289145ec |
Devices map[string]*devInfo `json:"Devices"` |
0b12702c |
}
|
972a94b4 |
// DeviceSet holds information about list of devices |
4bd60218 |
type DeviceSet struct { |
972a94b4 |
metaData `json:"-"` |
289145ec |
sync.Mutex `json:"-"` // Protects all fields of DeviceSet and serializes calls into libdevmapper |
f078bcd8 |
root string
devicePrefix string |
972a94b4 |
TransactionID uint64 `json:"-"`
NextDeviceID int `json:"next_device_id"`
deviceIDMap []byte |
7f5ba068 |
// Options |
9af7afb9 |
dataLoopbackSize int64
metaDataLoopbackSize int64
baseFsSize uint64 |
fdc2641c |
filesystem string |
9af7afb9 |
mountOptions string
mkfsArgs []string
dataDevice string // block or loop dev
dataLoopFile string // loopback file, if used
metadataDevice string // block or loop dev
metadataLoopFile string // loopback file, if used
doBlkDiscard bool
thinpBlockSize uint32
thinPoolDevice string |
972a94b4 |
transaction `json:"-"` |
9af7afb9 |
overrideUdevSyncCheck bool
deferredRemove bool // use deferred removal |
51e059e7 |
deferredDelete bool // use deferred deletion |
f22ee02c |
BaseDeviceUUID string // save UUID of base device
BaseDeviceFilesystem string // save filesystem of base device
nrDeletedDevices uint // number of deleted devices |
87de0400 |
deletionWorkerTicker *time.Ticker |
442b4562 |
uidMaps []idtools.IDMap
gidMaps []idtools.IDMap |
2e222f69 |
minFreeSpacePercent uint32 //min free space percentage in thinpool |
4f0017b9 |
xfsNospaceRetries string // max retries when xfs receives ENOSPC |
5ef07d79 |
lvmSetupConfig directLVMConfig |
0b12702c |
}
|
972a94b4 |
// DiskUsage contains information about disk usage and is used when reporting Status of a device. |
d733cdce |
type DiskUsage struct { |
972a94b4 |
// Used bytes on the disk.
Used uint64
// Total bytes on the disk.
Total uint64
// Available bytes on the disk. |
726f89d7 |
Available uint64 |
d733cdce |
}
|
972a94b4 |
// Status returns the information about the device. |
d733cdce |
type Status struct { |
972a94b4 |
// PoolName is the name of the data pool.
PoolName string
// DataFile is the actual block device for data.
DataFile string
// DataLoopback loopback file, if used.
DataLoopback string
// MetadataFile is the actual block device for metadata.
MetadataFile string
// MetadataLoopback is the loopback file, if used.
MetadataLoopback string
// Data is the disk used for data.
Data DiskUsage
// Metadata is the disk used for meta data.
Metadata DiskUsage |
5c374c71 |
// BaseDeviceSize is base size of container and image
BaseDeviceSize uint64 |
fdc2641c |
// BaseDeviceFS is backing filesystem.
BaseDeviceFS string |
972a94b4 |
// SectorSize size of the vector.
SectorSize uint64
// UdevSyncSupported is true if sync is supported.
UdevSyncSupported bool
// DeferredRemoveEnabled is true then the device is not unmounted. |
66a53819 |
DeferredRemoveEnabled bool |
51e059e7 |
// True if deferred deletion is enabled. This is different from
// deferred removal. "removal" means that device mapper device is
// deactivated. Thin device is still in thin pool and can be activated
// again. But "deletion" means that thin device will be deleted from
// thin pool and it can't be activated again. |
d295dc66 |
DeferredDeleteEnabled bool
DeferredDeletedDeviceCount uint |
55a9b812 |
MinFreeSpace uint64 |
80aecc70 |
}
|
407a626b |
// Structure used to export image/container metadata in docker inspect. |
972a94b4 |
type deviceMetadata struct {
deviceID int |
407a626b |
deviceSize uint64 // size in bytes
deviceName string // Device name as used during activation
}
|
972a94b4 |
// DevStatus returns information about device mounted containing its id, size and sector information. |
80aecc70 |
type DevStatus struct { |
972a94b4 |
// DeviceID is the id of the device.
DeviceID int
// Size is the size of the filesystem.
Size uint64
// TransactionID is a unique integer per device set used to identify an operation on the file system, this number is incremental.
TransactionID uint64
// SizeInSectors indicates the size of the sectors allocated.
SizeInSectors uint64
// MappedSectors indicates number of mapped sectors.
MappedSectors uint64
// HighestMappedSector is the pointer to the highest mapped sector. |
80aecc70 |
HighestMappedSector uint64 |
d733cdce |
}
|
0b12702c |
func getDevName(name string) string {
return "/dev/mapper/" + name
}
|
972a94b4 |
func (info *devInfo) Name() string { |
0b12702c |
hash := info.Hash
if hash == "" {
hash = "base"
} |
8f343ea6 |
return fmt.Sprintf("%s-%s", info.devices.devicePrefix, hash) |
0b12702c |
}
|
972a94b4 |
func (info *devInfo) DevName() string { |
0b12702c |
return getDevName(info.Name())
}
|
4bd60218 |
func (devices *DeviceSet) loopbackDir() string { |
4431e9ed |
return path.Join(devices.root, "devicemapper") |
0b12702c |
}
|
6d631968 |
func (devices *DeviceSet) metadataDir() string {
return path.Join(devices.root, "metadata")
}
|
972a94b4 |
func (devices *DeviceSet) metadataFile(info *devInfo) string { |
6d631968 |
file := info.Hash
if file == "" {
file = "base"
}
return path.Join(devices.metadataDir(), file)
}
|
c115c4aa |
func (devices *DeviceSet) transactionMetaFile() string {
return path.Join(devices.metadataDir(), transactionMetaFile)
}
|
8c9e5e5e |
func (devices *DeviceSet) deviceSetMetaFile() string { |
15c74beb |
return path.Join(devices.metadataDir(), deviceSetMetaFile) |
8c9e5e5e |
}
|
6d631968 |
func (devices *DeviceSet) oldMetadataFile() string { |
0b12702c |
return path.Join(devices.loopbackDir(), "json")
}
|
4bd60218 |
func (devices *DeviceSet) getPoolName() string { |
2b10749c |
if devices.thinPoolDevice == "" {
return devices.devicePrefix + "-pool"
} |
553b50bd |
return devices.thinPoolDevice |
0b12702c |
}
|
4bd60218 |
func (devices *DeviceSet) getPoolDevName() string { |
0b12702c |
return getDevName(devices.getPoolName())
}
|
4bd60218 |
func (devices *DeviceSet) hasImage(name string) bool { |
0b12702c |
dirname := devices.loopbackDir()
filename := path.Join(dirname, name)
|
39d244a5 |
_, err := os.Stat(filename) |
0b12702c |
return err == nil
}
|
ad968ef3 |
// ensureImage creates a sparse file of <size> bytes at the path
// <root>/devicemapper/<name>. |
2458452a |
// If the file already exists and new size is larger than its current size, it grows to the new size. |
ad968ef3 |
// Either way it returns the full path. |
4bd60218 |
func (devices *DeviceSet) ensureImage(name string, size int64) (string, error) { |
0b12702c |
dirname := devices.loopbackDir()
filename := path.Join(dirname, name)
|
442b4562 |
uid, gid, err := idtools.GetRootUIDGID(devices.uidMaps, devices.gidMaps)
if err != nil {
return "", err
}
if err := idtools.MkdirAllAs(dirname, 0700, uid, gid); err != nil && !os.IsExist(err) { |
0b12702c |
return "", err
}
|
2458452a |
if fi, err := os.Stat(filename); err != nil { |
39d244a5 |
if !os.IsNotExist(err) { |
0b12702c |
return "", err
} |
0aa6ace6 |
logrus.Debugf("devmapper: Creating loopback file %s for device-manage use", filename) |
39d244a5 |
file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600) |
0b12702c |
if err != nil {
return "", err
} |
bbc9fc79 |
defer file.Close() |
8b2f4aab |
|
84453814 |
if err := file.Truncate(size); err != nil { |
0b12702c |
return "", err
} |
2458452a |
} else {
if fi.Size() < size {
file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600)
if err != nil {
return "", err
}
defer file.Close()
if err := file.Truncate(size); err != nil { |
0aa6ace6 |
return "", fmt.Errorf("devmapper: Unable to grow loopback file %s: %v", filename, err) |
2458452a |
}
} else if fi.Size() > size { |
0aa6ace6 |
logrus.Warnf("devmapper: Can't shrink loopback file %s", filename) |
2458452a |
} |
0b12702c |
}
return filename, nil
}
|
972a94b4 |
func (devices *DeviceSet) allocateTransactionID() uint64 {
devices.OpenTransactionID = devices.TransactionID + 1
return devices.OpenTransactionID |
0b12702c |
}
|
972a94b4 |
func (devices *DeviceSet) updatePoolTransactionID() error { |
6990b76a |
if err := devicemapper.SetTransactionID(devices.getPoolDevName(), devices.TransactionID, devices.OpenTransactionID); err != nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Error setting devmapper transaction ID: %s", err) |
0db6cc85 |
} |
972a94b4 |
devices.TransactionID = devices.OpenTransactionID |
0db6cc85 |
return nil
}
|
972a94b4 |
func (devices *DeviceSet) removeMetadata(info *devInfo) error { |
39d244a5 |
if err := os.RemoveAll(devices.metadataFile(info)); err != nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Error removing metadata file %s: %s", devices.metadataFile(info), err) |
6d631968 |
}
return nil
}
|
67fbd34d |
// Given json data and file path, write it to disk
func (devices *DeviceSet) writeMetaFile(jsonData []byte, filePath string) error { |
6d631968 |
tmpFile, err := ioutil.TempFile(devices.metadataDir(), ".tmp") |
0b12702c |
if err != nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Error creating metadata file: %s", err) |
0b12702c |
}
n, err := tmpFile.Write(jsonData)
if err != nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Error writing metadata to %s: %s", tmpFile.Name(), err) |
0b12702c |
}
if n < len(jsonData) { |
8b2f4aab |
return io.ErrShortWrite |
0b12702c |
} |
8b2f4aab |
if err := tmpFile.Sync(); err != nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Error syncing metadata file %s: %s", tmpFile.Name(), err) |
0b12702c |
} |
8b2f4aab |
if err := tmpFile.Close(); err != nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Error closing metadata file %s: %s", tmpFile.Name(), err) |
0b12702c |
} |
67fbd34d |
if err := os.Rename(tmpFile.Name(), filePath); err != nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Error committing metadata file %s: %s", tmpFile.Name(), err) |
0b12702c |
}
|
67fbd34d |
return nil
}
|
972a94b4 |
func (devices *DeviceSet) saveMetadata(info *devInfo) error { |
67fbd34d |
jsonData, err := json.Marshal(info)
if err != nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Error encoding metadata to json: %s", err) |
67fbd34d |
} |
15c74beb |
if err := devices.writeMetaFile(jsonData, devices.metadataFile(info)); err != nil { |
67fbd34d |
return err
} |
0b12702c |
return nil
}
|
972a94b4 |
func (devices *DeviceSet) markDeviceIDUsed(deviceID int) { |
4d39e056 |
var mask byte |
972a94b4 |
i := deviceID % 8 |
4d39e056 |
mask = 1 << uint(i) |
972a94b4 |
devices.deviceIDMap[deviceID/8] = devices.deviceIDMap[deviceID/8] | mask |
4d39e056 |
}
|
972a94b4 |
func (devices *DeviceSet) markDeviceIDFree(deviceID int) { |
4d39e056 |
var mask byte |
972a94b4 |
i := deviceID % 8 |
4d39e056 |
mask = ^(1 << uint(i)) |
972a94b4 |
devices.deviceIDMap[deviceID/8] = devices.deviceIDMap[deviceID/8] & mask |
4d39e056 |
}
|
972a94b4 |
func (devices *DeviceSet) isDeviceIDFree(deviceID int) bool { |
4d39e056 |
var mask byte |
972a94b4 |
i := deviceID % 8 |
4d39e056 |
mask = (1 << uint(i)) |
3a425180 |
return (devices.deviceIDMap[deviceID/8] & mask) == 0 |
4d39e056 |
}
|
289145ec |
// Should be called with devices.Lock() held. |
972a94b4 |
func (devices *DeviceSet) lookupDevice(hash string) (*devInfo, error) { |
e01b71ce |
info := devices.Devices[hash]
if info == nil { |
6d631968 |
info = devices.loadMetadata(hash)
if info == nil { |
0aa6ace6 |
return nil, fmt.Errorf("devmapper: Unknown device %s", hash) |
6d631968 |
}
devices.Devices[hash] = info |
e01b71ce |
}
return info, nil
}
|
289145ec |
func (devices *DeviceSet) lookupDeviceWithLock(hash string) (*devInfo, error) {
devices.Lock()
defer devices.Unlock()
info, err := devices.lookupDevice(hash)
return info, err
}
|
6b8b4fea |
// This function relies on that device hash map has been loaded in advance.
// Should be called with devices.Lock() held.
func (devices *DeviceSet) constructDeviceIDMap() { |
fa710e50 |
logrus.Debug("devmapper: constructDeviceIDMap()")
defer logrus.Debug("devmapper: constructDeviceIDMap() END") |
6b8b4fea |
for _, info := range devices.Devices {
devices.markDeviceIDUsed(info.DeviceID) |
0aa6ace6 |
logrus.Debugf("devmapper: Added deviceId=%d to DeviceIdMap", info.DeviceID) |
6b8b4fea |
}
}
|
39dc7829 |
func (devices *DeviceSet) deviceFileWalkFunction(path string, finfo os.FileInfo) error {
// Skip some of the meta files which are not device files.
if strings.HasSuffix(finfo.Name(), ".migrated") { |
0aa6ace6 |
logrus.Debugf("devmapper: Skipping file %s", path) |
39dc7829 |
return nil
}
|
080a6f1e |
if strings.HasPrefix(finfo.Name(), ".") { |
0aa6ace6 |
logrus.Debugf("devmapper: Skipping file %s", path) |
080a6f1e |
return nil
}
|
39dc7829 |
if finfo.Name() == deviceSetMetaFile { |
0aa6ace6 |
logrus.Debugf("devmapper: Skipping file %s", path) |
39dc7829 |
return nil
}
|
ba02bf31 |
if finfo.Name() == transactionMetaFile { |
0aa6ace6 |
logrus.Debugf("devmapper: Skipping file %s", path) |
ba02bf31 |
return nil
}
|
0aa6ace6 |
logrus.Debugf("devmapper: Loading data for file %s", path) |
39dc7829 |
hash := finfo.Name()
if hash == "base" {
hash = ""
}
|
d929589c |
// Include deleted devices also as cleanup delete device logic
// will go through it and see if there are any deleted devices. |
6b8b4fea |
if _, err := devices.lookupDevice(hash); err != nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Error looking up device %s:%v", hash, err) |
39dc7829 |
}
return nil
}
|
6b8b4fea |
func (devices *DeviceSet) loadDeviceFilesOnStart() error { |
fa710e50 |
logrus.Debug("devmapper: loadDeviceFilesOnStart()")
defer logrus.Debug("devmapper: loadDeviceFilesOnStart() END") |
39dc7829 |
var scan = func(path string, info os.FileInfo, err error) error {
if err != nil { |
0aa6ace6 |
logrus.Debugf("devmapper: Can't walk the file %s", path) |
39dc7829 |
return nil
}
// Skip any directories
if info.IsDir() {
return nil
}
return devices.deviceFileWalkFunction(path, info)
}
return filepath.Walk(devices.metadataDir(), scan)
}
|
289145ec |
// Should be called with devices.Lock() held. |
288f933e |
func (devices *DeviceSet) unregisterDevice(hash string) error {
logrus.Debugf("devmapper: unregisterDevice(%v)", hash) |
972a94b4 |
info := &devInfo{ |
288f933e |
Hash: hash, |
44224792 |
}
delete(devices.Devices, hash)
if err := devices.removeMetadata(info); err != nil { |
0aa6ace6 |
logrus.Debugf("devmapper: Error removing metadata: %s", err) |
44224792 |
return err
}
return nil
}
|
289145ec |
// Should be called with devices.Lock() held. |
972a94b4 |
func (devices *DeviceSet) registerDevice(id int, hash string, size uint64, transactionID uint64) (*devInfo, error) { |
0aa6ace6 |
logrus.Debugf("devmapper: registerDevice(%v, %v)", id, hash) |
972a94b4 |
info := &devInfo{ |
0b12702c |
Hash: hash, |
972a94b4 |
DeviceID: id, |
0b12702c |
Size: size, |
972a94b4 |
TransactionID: transactionID, |
0b12702c |
Initialized: false, |
8f343ea6 |
devices: devices, |
0b12702c |
}
devices.Devices[hash] = info |
70826e8b |
|
6d631968 |
if err := devices.saveMetadata(info); err != nil { |
0b12702c |
// Try to remove unused device |
8b2f4aab |
delete(devices.Devices, hash) |
0b12702c |
return nil, err
}
return info, nil
}
|
d929589c |
func (devices *DeviceSet) activateDeviceIfNeeded(info *devInfo, ignoreDeleted bool) error { |
0aa6ace6 |
logrus.Debugf("devmapper: activateDeviceIfNeeded(%v)", info.Hash) |
0b12702c |
|
d929589c |
if info.Deleted && !ignoreDeleted {
return fmt.Errorf("devmapper: Can't activate device %v as it is marked for deletion", info.Hash)
}
|
ddc8aceb |
// Make sure deferred removal on device is canceled, if one was
// scheduled. |
0e633ee1 |
if err := devices.cancelDeferredRemovalIfNeeded(info); err != nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Device Deferred Removal Cancellation Failed: %s", err) |
ddc8aceb |
}
|
e2f8fbfb |
if devinfo, _ := devicemapper.GetInfo(info.Name()); devinfo != nil && devinfo.Exists != 0 { |
0b12702c |
return nil
}
|
972a94b4 |
return devicemapper.ActivateDevice(devices.getPoolDevName(), info.Name(), info.DeviceID, info.Size) |
0b12702c |
}
|
07ff17fb |
// Return true only if kernel supports xfs and mkfs.xfs is available
func xfsSupported() bool {
// Make sure mkfs.xfs is available
if _, err := exec.LookPath("mkfs.xfs"); err != nil {
return false
}
// Check if kernel supports xfs filesystem or not.
exec.Command("modprobe", "xfs").Run()
f, err := os.Open("/proc/filesystems")
if err != nil { |
0aa6ace6 |
logrus.Warnf("devmapper: Could not check if xfs is supported: %v", err) |
07ff17fb |
return false
}
defer f.Close()
s := bufio.NewScanner(f)
for s.Scan() {
if strings.HasSuffix(s.Text(), "\txfs") {
return true
}
}
if err := s.Err(); err != nil { |
0aa6ace6 |
logrus.Warnf("devmapper: Could not check if xfs is supported: %v", err) |
07ff17fb |
}
return false
}
|
83a34e00 |
func determineDefaultFS() string { |
07ff17fb |
if xfsSupported() {
return "xfs"
}
|
2eee6133 |
logrus.Warn("devmapper: XFS is not supported in your system. Either the kernel doesn't support it or mkfs.xfs is not in your PATH. Defaulting to ext4 filesystem") |
83a34e00 |
return "ext4"
}
|
a489e685 |
func (devices *DeviceSet) createFilesystem(info *devInfo) (err error) { |
0b12702c |
devname := info.DevName()
|
807bc2cd |
args := []string{} |
64238fef |
args = append(args, devices.mkfsArgs...) |
807bc2cd |
args = append(args, devname)
|
fdc2641c |
if devices.filesystem == "" {
devices.filesystem = determineDefaultFS() |
83a34e00 |
} |
f22ee02c |
if err := devices.saveBaseDeviceFilesystem(devices.filesystem); err != nil {
return err
} |
83a34e00 |
|
a489e685 |
logrus.Infof("devmapper: Creating filesystem %s on device %s", devices.filesystem, info.Name())
defer func() {
if err != nil {
logrus.Infof("devmapper: Error while creating filesystem %s on device %s: %v", devices.filesystem, info.Name(), err)
} else {
logrus.Infof("devmapper: Successfully created filesystem %s on device %s", devices.filesystem, info.Name())
}
}()
|
fdc2641c |
switch devices.filesystem { |
807bc2cd |
case "xfs":
err = exec.Command("mkfs.xfs", args...).Run()
case "ext4":
err = exec.Command("mkfs.ext4", append([]string{"-E", "nodiscard,lazy_itable_init=0,lazy_journal_init=0"}, args...)...).Run()
if err != nil {
err = exec.Command("mkfs.ext4", append([]string{"-E", "nodiscard,lazy_itable_init=0"}, args...)...).Run()
} |
d2298215 |
if err != nil {
return err
}
err = exec.Command("tune2fs", append([]string{"-c", "-1", "-i", "0"}, devname)...).Run() |
807bc2cd |
default: |
0aa6ace6 |
err = fmt.Errorf("devmapper: Unsupported filesystem type %s", devices.filesystem) |
0b12702c |
} |
a489e685 |
return |
0b12702c |
}
|
824a87f7 |
func (devices *DeviceSet) migrateOldMetaData() error { |
f4ac86de |
// Migrate old metadata file |
6d631968 |
jsonData, err := ioutil.ReadFile(devices.oldMetadataFile()) |
39d244a5 |
if err != nil && !os.IsNotExist(err) { |
0b12702c |
return err
}
if jsonData != nil { |
972a94b4 |
m := metaData{Devices: make(map[string]*devInfo)} |
6d631968 |
if err := json.Unmarshal(jsonData, &m); err != nil { |
0b12702c |
return err
}
|
6d631968 |
for hash, info := range m.Devices {
info.Hash = hash |
bb00453e |
devices.saveMetadata(info) |
0b12702c |
} |
39d244a5 |
if err := os.Rename(devices.oldMetadataFile(), devices.oldMetadataFile()+".migrated"); err != nil { |
6d631968 |
return err |
0b12702c |
} |
6d631968 |
|
0b12702c |
} |
6d631968 |
|
0b12702c |
return nil
}
|
d929589c |
// Cleanup deleted devices. It assumes that all the devices have been |
87de0400 |
// loaded in the hash table. |
d929589c |
func (devices *DeviceSet) cleanupDeletedDevices() error { |
87de0400 |
devices.Lock()
|
d295dc66 |
// If there are no deleted devices, there is nothing to do.
if devices.nrDeletedDevices == 0 { |
2f16895e |
devices.Unlock() |
d295dc66 |
return nil
}
|
d929589c |
var deletedDevices []*devInfo
for _, info := range devices.Devices {
if !info.Deleted {
continue
}
logrus.Debugf("devmapper: Found deleted device %s.", info.Hash)
deletedDevices = append(deletedDevices, info)
}
// Delete the deleted devices. DeleteDevice() first takes the info lock
// and then devices.Lock(). So drop it to avoid deadlock.
devices.Unlock()
for _, info := range deletedDevices {
// This will again try deferred deletion.
if err := devices.DeleteDevice(info.Hash, false); err != nil {
logrus.Warnf("devmapper: Deletion of device %s, device_id=%v failed:%v", info.Hash, info.DeviceID, err)
}
}
return nil
}
|
d295dc66 |
func (devices *DeviceSet) countDeletedDevices() {
for _, info := range devices.Devices {
if !info.Deleted {
continue
}
devices.nrDeletedDevices++
}
}
|
87de0400 |
func (devices *DeviceSet) startDeviceDeletionWorker() {
// Deferred deletion is not enabled. Don't do anything.
if !devices.deferredDelete {
return
}
|
a72b45db |
logrus.Debug("devmapper: Worker to cleanup deleted devices started") |
87de0400 |
for range devices.deletionWorkerTicker.C {
devices.cleanupDeletedDevices()
}
}
|
824a87f7 |
func (devices *DeviceSet) initMetaData() error { |
289145ec |
devices.Lock()
defer devices.Unlock()
|
824a87f7 |
if err := devices.migrateOldMetaData(); err != nil {
return err
}
|
972a94b4 |
_, transactionID, _, _, _, _, err := devices.poolStatus() |
824a87f7 |
if err != nil {
return err
}
|
972a94b4 |
devices.TransactionID = transactionID |
39dc7829 |
|
6b8b4fea |
if err := devices.loadDeviceFilesOnStart(); err != nil {
return fmt.Errorf("devmapper: Failed to load device files:%v", err) |
39dc7829 |
} |
c115c4aa |
|
6b8b4fea |
devices.constructDeviceIDMap() |
d295dc66 |
devices.countDeletedDevices() |
6b8b4fea |
|
c115c4aa |
if err := devices.processPendingTransaction(); err != nil {
return err
} |
d929589c |
|
87de0400 |
// Start a goroutine to cleanup Deleted Devices
go devices.startDeviceDeletionWorker() |
824a87f7 |
return nil
}
|
972a94b4 |
func (devices *DeviceSet) incNextDeviceID() {
// IDs are 24bit, so wrap around
devices.NextDeviceID = (devices.NextDeviceID + 1) & maxDeviceID |
7b2b15d3 |
}
|
972a94b4 |
func (devices *DeviceSet) getNextFreeDeviceID() (int, error) {
devices.incNextDeviceID()
for i := 0; i <= maxDeviceID; i++ {
if devices.isDeviceIDFree(devices.NextDeviceID) {
devices.markDeviceIDUsed(devices.NextDeviceID)
return devices.NextDeviceID, nil |
e28a419e |
} |
972a94b4 |
devices.incNextDeviceID() |
e28a419e |
}
|
0aa6ace6 |
return 0, fmt.Errorf("devmapper: Unable to find a free device ID") |
a44c23fe |
}
|
2e222f69 |
func (devices *DeviceSet) poolHasFreeSpace() error {
if devices.minFreeSpacePercent == 0 {
return nil
}
_, _, dataUsed, dataTotal, metadataUsed, metadataTotal, err := devices.poolStatus()
if err != nil {
return err
}
minFreeData := (dataTotal * uint64(devices.minFreeSpacePercent)) / 100
if minFreeData < 1 {
minFreeData = 1
}
dataFree := dataTotal - dataUsed
if dataFree < minFreeData {
return fmt.Errorf("devmapper: Thin Pool has %v free data blocks which is less than minimum required %v free data blocks. Create more free space in thin pool or use dm.min_free_space option to change behavior", (dataTotal - dataUsed), minFreeData)
}
minFreeMetadata := (metadataTotal * uint64(devices.minFreeSpacePercent)) / 100
if minFreeMetadata < 1 { |
4141a009 |
minFreeMetadata = 1 |
2e222f69 |
}
metadataFree := metadataTotal - metadataUsed
if metadataFree < minFreeMetadata {
return fmt.Errorf("devmapper: Thin Pool has %v free metadata blocks which is less than minimum required %v free metadata blocks. Create more free metadata space in thin pool or use dm.min_free_space option to change behavior", (metadataTotal - metadataUsed), minFreeMetadata)
}
return nil
}
|
972a94b4 |
func (devices *DeviceSet) createRegisterDevice(hash string) (*devInfo, error) { |
289145ec |
devices.Lock()
defer devices.Unlock()
|
972a94b4 |
deviceID, err := devices.getNextFreeDeviceID() |
e28a419e |
if err != nil {
return nil, err
}
|
972a94b4 |
if err := devices.openTransaction(hash, deviceID); err != nil { |
0aa6ace6 |
logrus.Debugf("devmapper: Error opening transaction hash = %s deviceID = %d", hash, deviceID) |
972a94b4 |
devices.markDeviceIDFree(deviceID) |
c115c4aa |
return nil, err
}
|
7b2b15d3 |
for { |
972a94b4 |
if err := devicemapper.CreateDevice(devices.getPoolDevName(), deviceID); err != nil { |
6990b76a |
if devicemapper.DeviceIDExists(err) { |
972a94b4 |
// Device ID already exists. This should not |
927b334e |
// happen. Now we have a mechanism to find |
972a94b4 |
// a free device ID. So something is not right. |
e28a419e |
// Give a warning and continue. |
0aa6ace6 |
logrus.Errorf("devmapper: Device ID %d exists in pool but it is supposed to be unused", deviceID) |
972a94b4 |
deviceID, err = devices.getNextFreeDeviceID() |
e28a419e |
if err != nil {
return nil, err
} |
c115c4aa |
// Save new device id into transaction |
972a94b4 |
devices.refreshTransaction(deviceID) |
7b2b15d3 |
continue
} |
0aa6ace6 |
logrus.Debugf("devmapper: Error creating device: %s", err) |
972a94b4 |
devices.markDeviceIDFree(deviceID) |
14d0dd85 |
return nil, err |
7b2b15d3 |
}
break
} |
ad9118c6 |
|
0aa6ace6 |
logrus.Debugf("devmapper: Registering device (id %v) with FS size %v", deviceID, devices.baseFsSize) |
972a94b4 |
info, err := devices.registerDevice(deviceID, hash, devices.baseFsSize, devices.OpenTransactionID) |
ad9118c6 |
if err != nil { |
972a94b4 |
_ = devicemapper.DeleteDevice(devices.getPoolDevName(), deviceID)
devices.markDeviceIDFree(deviceID) |
ad9118c6 |
return nil, err
}
|
c115c4aa |
if err := devices.closeTransaction(); err != nil { |
288f933e |
devices.unregisterDevice(hash) |
972a94b4 |
devicemapper.DeleteDevice(devices.getPoolDevName(), deviceID)
devices.markDeviceIDFree(deviceID) |
ad9118c6 |
return nil, err
}
return info, nil
}
|
0e633ee1 |
func (devices *DeviceSet) takeSnapshot(hash string, baseInfo *devInfo, size uint64) error {
var (
devinfo *devicemapper.Info
err error
)
if err = devices.poolHasFreeSpace(); err != nil {
return err
}
if devices.deferredRemove {
devinfo, err = devicemapper.GetInfoWithDeferred(baseInfo.Name())
if err != nil {
return err
}
if devinfo != nil && devinfo.DeferredRemove != 0 {
err = devices.cancelDeferredRemoval(baseInfo)
if err != nil {
// If Error is ErrEnxio. Device is probably already gone. Continue.
if err != devicemapper.ErrEnxio {
return err
} |
6e25bb2e |
devinfo = nil |
0e633ee1 |
} else {
defer devices.deactivateDevice(baseInfo)
}
}
} else {
devinfo, err = devicemapper.GetInfo(baseInfo.Name())
if err != nil {
return err
}
}
doSuspend := devinfo != nil && devinfo.Exists != 0
if doSuspend {
if err = devicemapper.SuspendDevice(baseInfo.Name()); err != nil {
return err
}
defer devicemapper.ResumeDevice(baseInfo.Name())
}
if err = devices.createRegisterSnapDevice(hash, baseInfo, size); err != nil { |
2e222f69 |
return err
}
|
0e633ee1 |
return nil
}
func (devices *DeviceSet) createRegisterSnapDevice(hash string, baseInfo *devInfo, size uint64) error { |
972a94b4 |
deviceID, err := devices.getNextFreeDeviceID() |
e28a419e |
if err != nil {
return err
}
|
972a94b4 |
if err := devices.openTransaction(hash, deviceID); err != nil { |
0aa6ace6 |
logrus.Debugf("devmapper: Error opening transaction hash = %s deviceID = %d", hash, deviceID) |
972a94b4 |
devices.markDeviceIDFree(deviceID) |
c115c4aa |
return err
}
|
7b2b15d3 |
for { |
0e633ee1 |
if err := devicemapper.CreateSnapDeviceRaw(devices.getPoolDevName(), deviceID, baseInfo.DeviceID); err != nil { |
6990b76a |
if devicemapper.DeviceIDExists(err) { |
972a94b4 |
// Device ID already exists. This should not |
927b334e |
// happen. Now we have a mechanism to find |
972a94b4 |
// a free device ID. So something is not right. |
e28a419e |
// Give a warning and continue. |
0aa6ace6 |
logrus.Errorf("devmapper: Device ID %d exists in pool but it is supposed to be unused", deviceID) |
972a94b4 |
deviceID, err = devices.getNextFreeDeviceID() |
e28a419e |
if err != nil {
return err
} |
c115c4aa |
// Save new device id into transaction |
972a94b4 |
devices.refreshTransaction(deviceID) |
7b2b15d3 |
continue
} |
0aa6ace6 |
logrus.Debugf("devmapper: Error creating snap device: %s", err) |
972a94b4 |
devices.markDeviceIDFree(deviceID) |
7b2b15d3 |
return err
}
break
} |
ad9118c6 |
|
b16decfc |
if _, err := devices.registerDevice(deviceID, hash, size, devices.OpenTransactionID); err != nil { |
972a94b4 |
devicemapper.DeleteDevice(devices.getPoolDevName(), deviceID)
devices.markDeviceIDFree(deviceID) |
0aa6ace6 |
logrus.Debugf("devmapper: Error registering device: %s", err) |
ad9118c6 |
return err
}
|
c115c4aa |
if err := devices.closeTransaction(); err != nil { |
288f933e |
devices.unregisterDevice(hash) |
972a94b4 |
devicemapper.DeleteDevice(devices.getPoolDevName(), deviceID)
devices.markDeviceIDFree(deviceID) |
ad9118c6 |
return err
}
return nil
}
|
972a94b4 |
func (devices *DeviceSet) loadMetadata(hash string) *devInfo {
info := &devInfo{Hash: hash, devices: devices} |
6d631968 |
jsonData, err := ioutil.ReadFile(devices.metadataFile(info))
if err != nil { |
d4a2bcc9 |
logrus.Debugf("devmapper: Failed to read %s with err: %v", devices.metadataFile(info), err) |
6d631968 |
return nil
}
if err := json.Unmarshal(jsonData, &info); err != nil { |
d4a2bcc9 |
logrus.Debugf("devmapper: Failed to unmarshal devInfo from %s with err: %v", devices.metadataFile(info), err) |
6d631968 |
return nil
}
|
94caae24 |
if info.DeviceID > maxDeviceID { |
0aa6ace6 |
logrus.Errorf("devmapper: Ignoring Invalid DeviceId=%d", info.DeviceID) |
94caae24 |
return nil
}
|
6d631968 |
return info
}
|
c06b05b1 |
func getDeviceUUID(device string) (string, error) {
out, err := exec.Command("blkid", "-s", "UUID", "-o", "value", device).Output()
if err != nil { |
0aa6ace6 |
return "", fmt.Errorf("devmapper: Failed to find uuid for device %s:%v", device, err) |
c06b05b1 |
}
uuid := strings.TrimSuffix(string(out), "\n")
uuid = strings.TrimSpace(uuid) |
0aa6ace6 |
logrus.Debugf("devmapper: UUID for device: %s is:%s", device, uuid) |
c06b05b1 |
return uuid, nil
}
|
5c374c71 |
func (devices *DeviceSet) getBaseDeviceSize() uint64 {
info, _ := devices.lookupDevice("")
if info == nil {
return 0
}
return info.Size
}
|
fdc2641c |
func (devices *DeviceSet) getBaseDeviceFS() string { |
f22ee02c |
return devices.BaseDeviceFilesystem |
fdc2641c |
}
|
83a34e00 |
func (devices *DeviceSet) verifyBaseDeviceUUIDFS(baseInfo *devInfo) error { |
f0898990 |
devices.Lock()
defer devices.Unlock()
|
d929589c |
if err := devices.activateDeviceIfNeeded(baseInfo, false); err != nil { |
c06b05b1 |
return err
}
defer devices.deactivateDevice(baseInfo)
uuid, err := getDeviceUUID(baseInfo.DevName())
if err != nil {
return err
}
if devices.BaseDeviceUUID != uuid { |
0aa6ace6 |
return fmt.Errorf("devmapper: Current Base Device UUID:%s does not match with stored UUID:%s. Possibly using a different thin pool than last invocation", uuid, devices.BaseDeviceUUID) |
c06b05b1 |
}
|
f22ee02c |
if devices.BaseDeviceFilesystem == "" {
fsType, err := ProbeFsType(baseInfo.DevName()) |
83a34e00 |
if err != nil {
return err
} |
f22ee02c |
if err := devices.saveBaseDeviceFilesystem(fsType); err != nil {
return err |
83a34e00 |
}
} |
f22ee02c |
// If user specified a filesystem using dm.fs option and current
// file system of base image is not same, warn user that dm.fs
// will be ignored.
if devices.BaseDeviceFilesystem != devices.filesystem {
logrus.Warnf("devmapper: Base device already exists and has filesystem %s on it. User specified filesystem %s will be ignored.", devices.BaseDeviceFilesystem, devices.filesystem)
devices.filesystem = devices.BaseDeviceFilesystem
} |
c06b05b1 |
return nil
}
|
f22ee02c |
func (devices *DeviceSet) saveBaseDeviceFilesystem(fs string) error {
devices.BaseDeviceFilesystem = fs
return devices.saveDeviceSetMetaData()
}
|
972a94b4 |
func (devices *DeviceSet) saveBaseDeviceUUID(baseInfo *devInfo) error { |
f0898990 |
devices.Lock()
defer devices.Unlock()
|
d929589c |
if err := devices.activateDeviceIfNeeded(baseInfo, false); err != nil { |
c06b05b1 |
return err
}
defer devices.deactivateDevice(baseInfo)
uuid, err := getDeviceUUID(baseInfo.DevName())
if err != nil {
return err
}
devices.BaseDeviceUUID = uuid |
f22ee02c |
return devices.saveDeviceSetMetaData() |
c06b05b1 |
}
|
efc1ddd7 |
func (devices *DeviceSet) createBaseImage() error { |
a72b45db |
logrus.Debug("devmapper: Initializing base device-mapper thin volume") |
efc1ddd7 |
// Create initial device
info, err := devices.createRegisterDevice("")
if err != nil {
return err
}
|
a72b45db |
logrus.Debug("devmapper: Creating filesystem on base device-mapper thin volume") |
efc1ddd7 |
|
d929589c |
if err := devices.activateDeviceIfNeeded(info, false); err != nil { |
efc1ddd7 |
return err
}
if err := devices.createFilesystem(info); err != nil {
return err
}
info.Initialized = true
if err := devices.saveMetadata(info); err != nil {
info.Initialized = false
return err
}
if err := devices.saveBaseDeviceUUID(info); err != nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Could not query and save base device UUID:%v", err) |
efc1ddd7 |
}
return nil
}
|
6d2d0a74 |
// Returns if thin pool device exists or not. If device exists, also makes
// sure it is a thin pool device and not some other type of device.
func (devices *DeviceSet) thinPoolExists(thinPoolDevice string) (bool, error) {
logrus.Debugf("devmapper: Checking for existence of the pool %s", thinPoolDevice)
info, err := devicemapper.GetInfo(thinPoolDevice)
if err != nil {
return false, fmt.Errorf("devmapper: GetInfo() on device %s failed: %v", thinPoolDevice, err)
}
// Device does not exist.
if info.Exists == 0 {
return false, nil
}
_, _, deviceType, _, err := devicemapper.GetStatus(thinPoolDevice)
if err != nil {
return false, fmt.Errorf("devmapper: GetStatus() on device %s failed: %v", thinPoolDevice, err)
}
if deviceType != "thin-pool" {
return false, fmt.Errorf("devmapper: Device %s is not a thin pool", thinPoolDevice)
}
return true, nil
}
|
69051ec0 |
func (devices *DeviceSet) checkThinPool() error {
_, transactionID, dataUsed, _, _, _, err := devices.poolStatus()
if err != nil {
return err
}
if dataUsed != 0 { |
0aa6ace6 |
return fmt.Errorf("devmapper: Unable to take ownership of thin-pool (%s) that already has used data blocks", |
69051ec0 |
devices.thinPoolDevice)
}
if transactionID != 0 { |
0aa6ace6 |
return fmt.Errorf("devmapper: Unable to take ownership of thin-pool (%s) with non-zero transaction ID", |
69051ec0 |
devices.thinPoolDevice)
}
return nil
}
|
0fcd4856 |
// Base image is initialized properly. Either save UUID for first time (for
// upgrade case or verify UUID. |
83a34e00 |
func (devices *DeviceSet) setupVerifyBaseImageUUIDFS(baseInfo *devInfo) error { |
0fcd4856 |
// If BaseDeviceUUID is nil (upgrade case), save it and return success.
if devices.BaseDeviceUUID == "" {
if err := devices.saveBaseDeviceUUID(baseInfo); err != nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Could not query and save base device UUID:%v", err) |
0fcd4856 |
}
return nil
}
|
83a34e00 |
if err := devices.verifyBaseDeviceUUIDFS(baseInfo); err != nil { |
b1806212 |
return fmt.Errorf("devmapper: Base Device UUID and Filesystem verification failed: %v", err) |
0fcd4856 |
}
return nil
}
|
e47112d3 |
func (devices *DeviceSet) checkGrowBaseDeviceFS(info *devInfo) error {
if !userBaseSize {
return nil
}
if devices.baseFsSize < devices.getBaseDeviceSize() {
return fmt.Errorf("devmapper: Base device size cannot be smaller than %s", units.HumanSize(float64(devices.getBaseDeviceSize())))
}
if devices.baseFsSize == devices.getBaseDeviceSize() {
return nil
}
info.lock.Lock()
defer info.lock.Unlock()
devices.Lock()
defer devices.Unlock()
info.Size = devices.baseFsSize
if err := devices.saveMetadata(info); err != nil {
// Try to remove unused device
delete(devices.Devices, info.Hash)
return err
}
return devices.growFS(info)
}
func (devices *DeviceSet) growFS(info *devInfo) error {
if err := devices.activateDeviceIfNeeded(info, false); err != nil {
return fmt.Errorf("Error activating devmapper device: %s", err)
}
defer devices.deactivateDevice(info)
fsMountPoint := "/run/docker/mnt"
if _, err := os.Stat(fsMountPoint); os.IsNotExist(err) {
if err := os.MkdirAll(fsMountPoint, 0700); err != nil {
return err
}
defer os.RemoveAll(fsMountPoint)
}
options := ""
if devices.BaseDeviceFilesystem == "xfs" {
// XFS needs nouuid or it can't mount filesystems with the same fs
options = joinMountOptions(options, "nouuid")
}
options = joinMountOptions(options, devices.mountOptions)
if err := mount.Mount(info.DevName(), fsMountPoint, devices.BaseDeviceFilesystem, options); err != nil {
return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), fsMountPoint, err)
}
|
069fdc8a |
defer unix.Unmount(fsMountPoint, unix.MNT_DETACH) |
e47112d3 |
switch devices.BaseDeviceFilesystem {
case "ext4":
if out, err := exec.Command("resize2fs", info.DevName()).CombinedOutput(); err != nil {
return fmt.Errorf("Failed to grow rootfs:%v:%s", err, string(out))
}
case "xfs":
if out, err := exec.Command("xfs_growfs", info.DevName()).CombinedOutput(); err != nil {
return fmt.Errorf("Failed to grow rootfs:%v:%s", err, string(out))
}
default:
return fmt.Errorf("Unsupported filesystem type %s", devices.BaseDeviceFilesystem)
}
return nil
}
|
4bd60218 |
func (devices *DeviceSet) setupBaseImage() error { |
289145ec |
oldInfo, _ := devices.lookupDeviceWithLock("") |
0fcd4856 |
// base image already exists. If it is initialized properly, do UUID
// verification and return. Otherwise remove image and set it up
// fresh.
if oldInfo != nil { |
d929589c |
if oldInfo.Initialized && !oldInfo.Deleted { |
83a34e00 |
if err := devices.setupVerifyBaseImageUUIDFS(oldInfo); err != nil { |
0fcd4856 |
return err |
c06b05b1 |
} |
f22ee02c |
|
e47112d3 |
if err := devices.checkGrowBaseDeviceFS(oldInfo); err != nil {
return err |
e035d272 |
} |
f22ee02c |
|
0fcd4856 |
return nil |
c06b05b1 |
} |
0b12702c |
|
a72b45db |
logrus.Debug("devmapper: Removing uninitialized base image") |
d929589c |
// If previous base device is in deferred delete state,
// that needs to be cleaned up first. So don't try
// deferred deletion.
if err := devices.DeleteDevice("", true); err != nil { |
0b12702c |
return err
}
}
|
0fcd4856 |
// If we are setting up base image for the first time, make sure
// thin pool is empty. |
2b10749c |
if devices.thinPoolDevice != "" && oldInfo == nil { |
69051ec0 |
if err := devices.checkThinPool(); err != nil { |
2b10749c |
return err |
b9f1b0a7 |
} |
2b10749c |
}
|
efc1ddd7 |
// Create new base image device
if err := devices.createBaseImage(); err != nil { |
0b12702c |
return err
}
return nil
}
|
1a1be5a8 |
func setCloseOnExec(name string) { |
a39bd656 |
if fileInfos, _ := ioutil.ReadDir("/proc/self/fd"); fileInfos != nil { |
1a1be5a8 |
for _, i := range fileInfos { |
39d244a5 |
link, _ := os.Readlink(filepath.Join("/proc/self/fd", i.Name())) |
c3f1bb32 |
if link == name { |
1a1be5a8 |
fd, err := strconv.Atoi(i.Name())
if err == nil { |
069fdc8a |
unix.CloseOnExec(fd) |
1a1be5a8 |
}
}
}
}
}
|
972a94b4 |
// DMLog implements logging using DevMapperLogger interface. |
e2f8fbfb |
func (devices *DeviceSet) DMLog(level int, file string, line int, dmError int, message string) { |
e07d3cd9 |
// By default libdm sends us all the messages including debug ones.
// We need to filter out messages here and figure out which one
// should be printed. |
972a94b4 |
if level > logLevel { |
e07d3cd9 |
return |
b440ec01 |
}
|
e2f8fbfb |
// FIXME(vbatts) push this back into ./pkg/devicemapper/ |
e07d3cd9 |
if level <= devicemapper.LogLevelErr {
logrus.Errorf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message)
} else if level <= devicemapper.LogLevelInfo {
logrus.Infof("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message)
} else {
// FIXME(vbatts) push this back into ./pkg/devicemapper/
logrus.Debugf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message)
} |
b440ec01 |
}
|
f1127b93 |
func major(device uint64) uint64 {
return (device >> 8) & 0xfff |
572b1fd9 |
}
|
f1127b93 |
func minor(device uint64) uint64 { |
572b1fd9 |
return (device & 0xff) | ((device >> 12) & 0xfff00)
}
|
972a94b4 |
// ResizePool increases the size of the pool. |
a0224e61 |
func (devices *DeviceSet) ResizePool(size int64) error {
dirname := devices.loopbackDir()
datafilename := path.Join(dirname, "data") |
636e8561 |
if len(devices.dataDevice) > 0 {
datafilename = devices.dataDevice
} |
a0224e61 |
metadatafilename := path.Join(dirname, "metadata") |
636e8561 |
if len(devices.metadataDevice) > 0 {
metadatafilename = devices.metadataDevice
} |
a0224e61 |
|
39d244a5 |
datafile, err := os.OpenFile(datafilename, os.O_RDWR, 0) |
a0224e61 |
if datafile == nil {
return err
}
defer datafile.Close()
fi, err := datafile.Stat()
if fi == nil {
return err
}
if fi.Size() > size { |
0aa6ace6 |
return fmt.Errorf("devmapper: Can't shrink file") |
a0224e61 |
}
|
af597527 |
dataloopback := loopback.FindLoopDeviceFor(datafile) |
a0224e61 |
if dataloopback == nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Unable to find loopback mount for: %s", datafilename) |
a0224e61 |
}
defer dataloopback.Close()
|
39d244a5 |
metadatafile, err := os.OpenFile(metadatafilename, os.O_RDWR, 0) |
a0224e61 |
if metadatafile == nil {
return err
}
defer metadatafile.Close()
|
af597527 |
metadataloopback := loopback.FindLoopDeviceFor(metadatafile) |
a0224e61 |
if metadataloopback == nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Unable to find loopback mount for: %s", metadatafilename) |
a0224e61 |
}
defer metadataloopback.Close()
// Grow loopback file
if err := datafile.Truncate(size); err != nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Unable to grow loopback file: %s", err) |
a0224e61 |
}
// Reload size for loopback device |
af597527 |
if err := loopback.SetCapacity(dataloopback); err != nil {
return fmt.Errorf("Unable to update loopback capacity: %s", err) |
a0224e61 |
}
// Suspend the pool |
e2f8fbfb |
if err := devicemapper.SuspendDevice(devices.getPoolName()); err != nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Unable to suspend pool: %s", err) |
a0224e61 |
}
// Reload with the new block sizes |
e2f8fbfb |
if err := devicemapper.ReloadPool(devices.getPoolName(), dataloopback, metadataloopback, devices.thinpBlockSize); err != nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Unable to reload pool: %s", err) |
a0224e61 |
}
// Resume the pool |
e2f8fbfb |
if err := devicemapper.ResumeDevice(devices.getPoolName()); err != nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Unable to resume pool: %s", err) |
a0224e61 |
}
return nil
}
|
c115c4aa |
func (devices *DeviceSet) loadTransactionMetaData() error {
jsonData, err := ioutil.ReadFile(devices.transactionMetaFile())
if err != nil {
// There is no active transaction. This will be the case
// during upgrade.
if os.IsNotExist(err) { |
972a94b4 |
devices.OpenTransactionID = devices.TransactionID |
c115c4aa |
return nil
}
return err
}
|
972a94b4 |
json.Unmarshal(jsonData, &devices.transaction) |
c115c4aa |
return nil
}
func (devices *DeviceSet) saveTransactionMetaData() error { |
972a94b4 |
jsonData, err := json.Marshal(&devices.transaction) |
c115c4aa |
if err != nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Error encoding metadata to json: %s", err) |
c115c4aa |
}
return devices.writeMetaFile(jsonData, devices.transactionMetaFile())
}
func (devices *DeviceSet) removeTransactionMetaData() error { |
3a425180 |
return os.RemoveAll(devices.transactionMetaFile()) |
c115c4aa |
}
func (devices *DeviceSet) rollbackTransaction() error { |
0aa6ace6 |
logrus.Debugf("devmapper: Rolling back open transaction: TransactionID=%d hash=%s device_id=%d", devices.OpenTransactionID, devices.DeviceIDHash, devices.DeviceID) |
c115c4aa |
// A device id might have already been deleted before transaction
// closed. In that case this call will fail. Just leave a message
// in case of failure. |
972a94b4 |
if err := devicemapper.DeleteDevice(devices.getPoolDevName(), devices.DeviceID); err != nil { |
0aa6ace6 |
logrus.Errorf("devmapper: Unable to delete device: %s", err) |
c115c4aa |
}
|
972a94b4 |
dinfo := &devInfo{Hash: devices.DeviceIDHash} |
c115c4aa |
if err := devices.removeMetadata(dinfo); err != nil { |
0aa6ace6 |
logrus.Errorf("devmapper: Unable to remove metadata: %s", err) |
c115c4aa |
} else { |
972a94b4 |
devices.markDeviceIDFree(devices.DeviceID) |
c115c4aa |
}
if err := devices.removeTransactionMetaData(); err != nil { |
0aa6ace6 |
logrus.Errorf("devmapper: Unable to remove transaction meta file %s: %s", devices.transactionMetaFile(), err) |
c115c4aa |
}
return nil
}
func (devices *DeviceSet) processPendingTransaction() error {
if err := devices.loadTransactionMetaData(); err != nil {
return err
}
|
972a94b4 |
// If there was open transaction but pool transaction ID is same
// as open transaction ID, nothing to roll back.
if devices.TransactionID == devices.OpenTransactionID { |
c115c4aa |
return nil
}
|
972a94b4 |
// If open transaction ID is less than pool transaction ID, something |
c115c4aa |
// is wrong. Bail out. |
972a94b4 |
if devices.OpenTransactionID < devices.TransactionID { |
0aa6ace6 |
logrus.Errorf("devmapper: Open Transaction id %d is less than pool transaction id %d", devices.OpenTransactionID, devices.TransactionID) |
c115c4aa |
return nil
}
|
972a94b4 |
// Pool transaction ID is not same as open transaction. There is |
c115c4aa |
// a transaction which was not completed.
if err := devices.rollbackTransaction(); err != nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Rolling back open transaction failed: %s", err) |
c115c4aa |
}
|
972a94b4 |
devices.OpenTransactionID = devices.TransactionID |
c115c4aa |
return nil
}
|
8c9e5e5e |
func (devices *DeviceSet) loadDeviceSetMetaData() error {
jsonData, err := ioutil.ReadFile(devices.deviceSetMetaFile())
if err != nil { |
15c74beb |
// For backward compatibility return success if file does
// not exist.
if os.IsNotExist(err) {
return nil
}
return err |
8c9e5e5e |
}
|
15c74beb |
return json.Unmarshal(jsonData, devices) |
8c9e5e5e |
}
func (devices *DeviceSet) saveDeviceSetMetaData() error {
jsonData, err := json.Marshal(devices)
if err != nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Error encoding metadata to json: %s", err) |
8c9e5e5e |
}
|
15c74beb |
return devices.writeMetaFile(jsonData, devices.deviceSetMetaFile()) |
8c9e5e5e |
}
|
972a94b4 |
func (devices *DeviceSet) openTransaction(hash string, DeviceID int) error {
devices.allocateTransactionID()
devices.DeviceIDHash = hash
devices.DeviceID = DeviceID |
c115c4aa |
if err := devices.saveTransactionMetaData(); err != nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Error saving transaction metadata: %s", err) |
c115c4aa |
}
return nil
}
|
972a94b4 |
func (devices *DeviceSet) refreshTransaction(DeviceID int) error {
devices.DeviceID = DeviceID |
c115c4aa |
if err := devices.saveTransactionMetaData(); err != nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Error saving transaction metadata: %s", err) |
c115c4aa |
}
return nil
}
func (devices *DeviceSet) closeTransaction() error { |
972a94b4 |
if err := devices.updatePoolTransactionID(); err != nil { |
a72b45db |
logrus.Debug("devmapper: Failed to close Transaction") |
c115c4aa |
return err
}
return nil
}
|
15c158b2 |
func determineDriverCapabilities(version string) error {
/*
* Driver version 4.27.0 and greater support deferred activation
* feature.
*/
logrus.Debugf("devicemapper: driver version is %s", version)
versionSplit := strings.Split(version, ".")
major, err := strconv.Atoi(versionSplit[0])
if err != nil {
return graphdriver.ErrNotSupported
}
if major > 4 { |
972a94b4 |
driverDeferredRemovalSupport = true |
15c158b2 |
return nil
}
if major < 4 {
return nil
}
minor, err := strconv.Atoi(versionSplit[1])
if err != nil {
return graphdriver.ErrNotSupported
}
/*
* If major is 4 and minor is 27, then there is no need to
* check for patch level as it can not be less than 0.
*/
if minor >= 27 { |
972a94b4 |
driverDeferredRemovalSupport = true |
15c158b2 |
return nil
}
return nil
}
|
bebf5344 |
// Determine the major and minor number of loopback device
func getDeviceMajorMinor(file *os.File) (uint64, uint64, error) {
stat, err := file.Stat()
if err != nil {
return 0, 0, err
}
dev := stat.Sys().(*syscall.Stat_t).Rdev
majorNum := major(dev)
minorNum := minor(dev)
|
0aa6ace6 |
logrus.Debugf("devmapper: Major:Minor for device: %s is:%v:%v", file.Name(), majorNum, minorNum) |
bebf5344 |
return majorNum, minorNum, nil
}
// Given a file which is backing file of a loop back device, find the
// loopback device name and its major/minor number.
func getLoopFileDeviceMajMin(filename string) (string, uint64, uint64, error) {
file, err := os.Open(filename)
if err != nil { |
0aa6ace6 |
logrus.Debugf("devmapper: Failed to open file %s", filename) |
bebf5344 |
return "", 0, 0, err
}
defer file.Close() |
af597527 |
loopbackDevice := loopback.FindLoopDeviceFor(file) |
bebf5344 |
if loopbackDevice == nil { |
0aa6ace6 |
return "", 0, 0, fmt.Errorf("devmapper: Unable to find loopback mount for: %s", filename) |
bebf5344 |
}
defer loopbackDevice.Close()
Major, Minor, err := getDeviceMajorMinor(loopbackDevice)
if err != nil {
return "", 0, 0, err
}
return loopbackDevice.Name(), Major, Minor, nil
}
// Get the major/minor numbers of thin pool data and metadata devices
func (devices *DeviceSet) getThinPoolDataMetaMajMin() (uint64, uint64, uint64, uint64, error) {
var params, poolDataMajMin, poolMetadataMajMin string
_, _, _, params, err := devicemapper.GetTable(devices.getPoolName())
if err != nil {
return 0, 0, 0, 0, err
}
if _, err = fmt.Sscanf(params, "%s %s", &poolMetadataMajMin, &poolDataMajMin); err != nil {
return 0, 0, 0, 0, err
}
|
0aa6ace6 |
logrus.Debugf("devmapper: poolDataMajMin=%s poolMetaMajMin=%s\n", poolDataMajMin, poolMetadataMajMin) |
bebf5344 |
poolDataMajMinorSplit := strings.Split(poolDataMajMin, ":")
poolDataMajor, err := strconv.ParseUint(poolDataMajMinorSplit[0], 10, 32)
if err != nil {
return 0, 0, 0, 0, err
}
poolDataMinor, err := strconv.ParseUint(poolDataMajMinorSplit[1], 10, 32)
if err != nil {
return 0, 0, 0, 0, err
}
poolMetadataMajMinorSplit := strings.Split(poolMetadataMajMin, ":")
poolMetadataMajor, err := strconv.ParseUint(poolMetadataMajMinorSplit[0], 10, 32)
if err != nil {
return 0, 0, 0, 0, err
}
poolMetadataMinor, err := strconv.ParseUint(poolMetadataMajMinorSplit[1], 10, 32)
if err != nil {
return 0, 0, 0, 0, err
}
return poolDataMajor, poolDataMinor, poolMetadataMajor, poolMetadataMinor, nil
}
func (devices *DeviceSet) loadThinPoolLoopBackInfo() error {
poolDataMajor, poolDataMinor, poolMetadataMajor, poolMetadataMinor, err := devices.getThinPoolDataMetaMajMin()
if err != nil {
return err
}
dirname := devices.loopbackDir()
// data device has not been passed in. So there should be a data file
// which is being mounted as loop device.
if devices.dataDevice == "" {
datafilename := path.Join(dirname, "data")
dataLoopDevice, dataMajor, dataMinor, err := getLoopFileDeviceMajMin(datafilename)
if err != nil {
return err
}
// Compare the two
if poolDataMajor == dataMajor && poolDataMinor == dataMinor {
devices.dataDevice = dataLoopDevice
devices.dataLoopFile = datafilename
}
}
// metadata device has not been passed in. So there should be a
// metadata file which is being mounted as loop device.
if devices.metadataDevice == "" {
metadatafilename := path.Join(dirname, "metadata")
metadataLoopDevice, metadataMajor, metadataMinor, err := getLoopFileDeviceMajMin(metadatafilename)
if err != nil {
return err
}
if poolMetadataMajor == metadataMajor && poolMetadataMinor == metadataMinor {
devices.metadataDevice = metadataLoopDevice
devices.metadataLoopFile = metadatafilename
}
}
return nil
}
|
cac6658d |
func (devices *DeviceSet) enableDeferredRemovalDeletion() error { |
15c158b2 |
|
51e059e7 |
// If user asked for deferred removal then check both libdm library
// and kernel driver support deferred removal otherwise error out.
if enableDeferredRemoval {
if !driverDeferredRemovalSupport {
return fmt.Errorf("devmapper: Deferred removal can not be enabled as kernel does not support it")
}
if !devicemapper.LibraryDeferredRemovalSupport {
return fmt.Errorf("devmapper: Deferred removal can not be enabled as libdm does not support it")
} |
a72b45db |
logrus.Debug("devmapper: Deferred removal support enabled.") |
15c158b2 |
devices.deferredRemove = true
}
|
51e059e7 |
if enableDeferredDeletion {
if !devices.deferredRemove {
return fmt.Errorf("devmapper: Deferred deletion can not be enabled as deferred removal is not enabled. Enable deferred removal using --storage-opt dm.use_deferred_removal=true parameter")
} |
a72b45db |
logrus.Debug("devmapper: Deferred deletion support enabled.") |
51e059e7 |
devices.deferredDelete = true
} |
cac6658d |
return nil
}
|
ea22d7ab |
func (devices *DeviceSet) initDevmapper(doInit bool) (retErr error) { |
cac6658d |
// give ourselves to libdm as a log handler
devicemapper.LogInit(devices)
if err := devices.enableDeferredRemovalDeletion(); err != nil {
return err
} |
51e059e7 |
|
022e1232 |
// https://github.com/docker/docker/issues/4036
if supported := devicemapper.UdevSetSyncSupport(true); !supported { |
b8f38747 |
if dockerversion.IAmStatic == "true" { |
e9864cc0 |
logrus.Error("devmapper: Udev sync is not supported. This will lead to data loss and unexpected behavior. Install a dynamic binary to use devicemapper or select a different storage driver. For more information, see https://docs.docker.com/engine/reference/commandline/dockerd/#storage-driver-options") |
b8f38747 |
} else { |
e9864cc0 |
logrus.Error("devmapper: Udev sync is not supported. This will lead to data loss and unexpected behavior. Install a more recent version of libdevmapper or select a different storage driver. For more information, see https://docs.docker.com/engine/reference/commandline/dockerd/#storage-driver-options") |
b8f38747 |
}
|
de641715 |
if !devices.overrideUdevSyncCheck {
return graphdriver.ErrNotSupported
} |
022e1232 |
}
|
442b4562 |
//create the root dir of the devmapper driver ownership to match this
//daemon's remapped root uid/gid so containers can start properly
uid, gid, err := idtools.GetRootUIDGID(devices.uidMaps, devices.gidMaps)
if err != nil {
return err
}
if err := idtools.MkdirAs(devices.root, 0700, uid, gid); err != nil && !os.IsExist(err) {
return err
}
if err := os.MkdirAll(devices.metadataDir(), 0700); err != nil && !os.IsExist(err) { |
0b12702c |
return err
}
|
5ef07d79 |
prevSetupConfig, err := readLVMConfig(devices.root)
if err != nil {
return err
}
if !reflect.DeepEqual(devices.lvmSetupConfig, directLVMConfig{}) {
if devices.thinPoolDevice != "" {
return errors.New("cannot setup direct-lvm when `dm.thinpooldev` is also specified")
}
if !reflect.DeepEqual(prevSetupConfig, devices.lvmSetupConfig) {
if !reflect.DeepEqual(prevSetupConfig, directLVMConfig{}) {
return errors.New("changing direct-lvm config is not supported")
}
logrus.WithField("storage-driver", "devicemapper").WithField("direct-lvm-config", devices.lvmSetupConfig).Debugf("Setting up direct lvm mode")
if err := verifyBlockDevice(devices.lvmSetupConfig.Device, lvmSetupConfigForce); err != nil {
return err
}
if err := setupDirectLVM(devices.lvmSetupConfig); err != nil {
return err
}
if err := writeLVMConfig(devices.root, devices.lvmSetupConfig); err != nil {
return err
}
}
devices.thinPoolDevice = "docker-thinpool"
logrus.WithField("storage-driver", "devicemapper").Debugf("Setting dm.thinpooldev to %q", devices.thinPoolDevice)
} |
f1d07e2d |
|
5ef07d79 |
// Set the device prefix from the device id and inode of the docker root dir |
39d244a5 |
st, err := os.Stat(devices.root) |
0b12702c |
if err != nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Error looking up dir %s: %s", devices.root, err) |
0b12702c |
} |
39d244a5 |
sysSt := st.Sys().(*syscall.Stat_t) |
f1d07e2d |
// "reg-" stands for "regular file".
// In the future we might use "dev-" for "device file", etc. |
4756ad24 |
// docker-maj,min[-inode] stands for:
// - Managed by docker
// - The target of this device is at major <maj> and minor <min>
// - If <inode> is defined, use that file inside the device as a loopback image. Otherwise use the device itself. |
2812baf3 |
devices.devicePrefix = fmt.Sprintf("docker-%d:%d-%d", major(sysSt.Dev), minor(sysSt.Dev), sysSt.Ino) |
0aa6ace6 |
logrus.Debugf("devmapper: Generated prefix: %s", devices.devicePrefix) |
f1d07e2d |
|
2b10749c |
// Check for the existence of the thin-pool device |
6d2d0a74 |
poolExists, err := devices.thinPoolExists(devices.getPoolName())
if err != nil { |
0b12702c |
return err
}
|
f1d07e2d |
// It seems libdevmapper opens this without O_CLOEXEC, and go exec will not close files |
3b5fac46 |
// that are not Close-on-exec, |
f1d07e2d |
// so we add this badhack to make sure it closes itself
setCloseOnExec("/dev/mapper/control")
|
6d631968 |
// Make sure the sparse images exist in <root>/devicemapper/data and
// <root>/devicemapper/metadata
createdLoopback := false
|
f1d07e2d |
// If the pool doesn't exist, create it |
6d2d0a74 |
if !poolExists && devices.thinPoolDevice == "" { |
a72b45db |
logrus.Debug("devmapper: Pool doesn't exist. Creating it.") |
31b883b0 |
|
a226168a |
var (
dataFile *os.File
metadataFile *os.File
) |
6d631968 |
|
a226168a |
if devices.dataDevice == "" {
// Make sure the sparse images exist in <root>/devicemapper/data |
6d631968 |
|
a226168a |
hasData := devices.hasImage("data") |
6d631968 |
|
a226168a |
if !doInit && !hasData { |
110ab746 |
return errors.New("loopback data file not found") |
a226168a |
} |
6d631968 |
|
a226168a |
if !hasData {
createdLoopback = true
}
data, err := devices.ensureImage("data", devices.dataLoopbackSize)
if err != nil { |
0aa6ace6 |
logrus.Debugf("devmapper: Error device ensureImage (data): %s", err) |
a226168a |
return err
}
|
af597527 |
dataFile, err = loopback.AttachLoopDevice(data) |
a226168a |
if err != nil {
return err
} |
09c033ff |
devices.dataLoopFile = data
devices.dataDevice = dataFile.Name() |
a226168a |
} else {
dataFile, err = os.OpenFile(devices.dataDevice, os.O_RDWR, 0600)
if err != nil {
return err
} |
f1d07e2d |
} |
b1ac791d |
defer dataFile.Close() |
f1d07e2d |
|
a226168a |
if devices.metadataDevice == "" {
// Make sure the sparse images exist in <root>/devicemapper/metadata
hasMetadata := devices.hasImage("metadata")
if !doInit && !hasMetadata { |
110ab746 |
return errors.New("loopback metadata file not found") |
a226168a |
}
if !hasMetadata {
createdLoopback = true
}
metadata, err := devices.ensureImage("metadata", devices.metaDataLoopbackSize)
if err != nil { |
0aa6ace6 |
logrus.Debugf("devmapper: Error device ensureImage (metadata): %s", err) |
a226168a |
return err
}
|
af597527 |
metadataFile, err = loopback.AttachLoopDevice(metadata) |
a226168a |
if err != nil {
return err
} |
09c033ff |
devices.metadataLoopFile = metadata
devices.metadataDevice = metadataFile.Name() |
a226168a |
} else {
metadataFile, err = os.OpenFile(devices.metadataDevice, os.O_RDWR, 0600)
if err != nil {
return err
} |
f1d07e2d |
} |
b1ac791d |
defer metadataFile.Close() |
f1d07e2d |
|
e2f8fbfb |
if err := devicemapper.CreatePool(devices.getPoolName(), dataFile, metadataFile, devices.thinpBlockSize); err != nil { |
f1d07e2d |
return err
} |
ea22d7ab |
defer func() {
if retErr != nil {
err = devices.deactivatePool()
if err != nil {
logrus.Warnf("devmapper: Failed to deactivatePool: %v", err)
}
}
}() |
0b12702c |
}
|
bebf5344 |
// Pool already exists and caller did not pass us a pool. That means
// we probably created pool earlier and could not remove it as some
// containers were still using it. Detect some of the properties of
// pool, like is it using loop devices. |
6d2d0a74 |
if poolExists && devices.thinPoolDevice == "" { |
bebf5344 |
if err := devices.loadThinPoolLoopBackInfo(); err != nil { |
0aa6ace6 |
logrus.Debugf("devmapper: Failed to load thin pool loopback device information:%v", err) |
bebf5344 |
return err
}
}
|
f1d07e2d |
// If we didn't just create the data or metadata image, we need to |
6d631968 |
// load the transaction id and migrate old metadata |
0b12702c |
if !createdLoopback { |
84453814 |
if err := devices.initMetaData(); err != nil { |
0b12702c |
return err
}
}
|
4870fb36 |
if devices.thinPoolDevice == "" {
if devices.metadataLoopFile != "" || devices.dataLoopFile != "" { |
44ccbb31 |
logrus.Warn("devmapper: Usage of loopback devices is strongly discouraged for production use. Please use `--storage-opt dm.thinpooldev` or use `man docker` to refer to dm.thinpooldev section.") |
4870fb36 |
}
}
|
972a94b4 |
// Right now this loads only NextDeviceID. If there is more metadata |
8c9e5e5e |
// down the line, we might have to move it earlier. |
84453814 |
if err := devices.loadDeviceSetMetaData(); err != nil { |
15c74beb |
return err
} |
8c9e5e5e |
|
f1d07e2d |
// Setup the base image |
80aecc70 |
if doInit {
if err := devices.setupBaseImage(); err != nil { |
0aa6ace6 |
logrus.Debugf("devmapper: Error device setupBaseImage: %s", err) |
80aecc70 |
return err
} |
0b12702c |
}
return nil
}
|
972a94b4 |
// AddDevice adds a device and registers in the hash. |
b16decfc |
func (devices *DeviceSet) AddDevice(hash, baseHash string, storageOpt map[string]string) error { |
664ad194 |
logrus.Debugf("devmapper: AddDevice START(hash=%s basehash=%s)", hash, baseHash)
defer logrus.Debugf("devmapper: AddDevice END(hash=%s basehash=%s)", hash, baseHash) |
7b2b15d3 |
|
d929589c |
// If a deleted device exists, return error. |
289145ec |
baseInfo, err := devices.lookupDeviceWithLock(baseHash) |
e01b71ce |
if err != nil {
return err |
0b12702c |
}
|
d929589c |
if baseInfo.Deleted {
return fmt.Errorf("devmapper: Base device %v has been marked for deferred deletion", baseInfo.Hash)
}
|
3e8a02a9 |
baseInfo.lock.Lock()
defer baseInfo.lock.Unlock()
|
2ffef1b7 |
devices.Lock()
defer devices.Unlock()
|
d929589c |
// Also include deleted devices in case hash of new device is
// same as one of the deleted devices. |
2ffef1b7 |
if info, _ := devices.lookupDevice(hash); info != nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: device %s already exists. Deleted=%v", hash, info.Deleted) |
2ffef1b7 |
}
|
45dc5b46 |
size, err := devices.parseStorageOpt(storageOpt)
if err != nil { |
0b12702c |
return err
} |
44224792 |
|
45dc5b46 |
if size == 0 {
size = baseInfo.Size |
b16decfc |
}
|
45dc5b46 |
if size < baseInfo.Size { |
b16decfc |
return fmt.Errorf("devmapper: Container size cannot be smaller than %s", units.HumanSize(float64(baseInfo.Size)))
}
|
0e633ee1 |
if err := devices.takeSnapshot(hash, baseInfo, size); err != nil { |
b16decfc |
return err
}
// Grow the container rootfs. |
34a66a14 |
if size > baseInfo.Size { |
b16decfc |
info, err := devices.lookupDevice(hash)
if err != nil {
return err
}
if err := devices.growFS(info); err != nil {
return err
}
}
return nil
}
|
45dc5b46 |
func (devices *DeviceSet) parseStorageOpt(storageOpt map[string]string) (uint64, error) { |
b16decfc |
// Read size to change the block device size per container.
for key, val := range storageOpt {
key := strings.ToLower(key)
switch key {
case "size":
size, err := units.RAMInBytes(val)
if err != nil { |
45dc5b46 |
return 0, err |
b16decfc |
} |
45dc5b46 |
return uint64(size), nil |
b16decfc |
default: |
45dc5b46 |
return 0, fmt.Errorf("Unknown option %s", key) |
b16decfc |
}
}
|
45dc5b46 |
return 0, nil |
0b12702c |
}
|
d929589c |
func (devices *DeviceSet) markForDeferredDeletion(info *devInfo) error {
// If device is already in deleted state, there is nothing to be done.
if info.Deleted {
return nil
}
logrus.Debugf("devmapper: Marking device %s for deferred deletion.", info.Hash)
info.Deleted = true
|
927b334e |
// save device metadata to reflect deleted state. |
d929589c |
if err := devices.saveMetadata(info); err != nil {
info.Deleted = false
return err
} |
d295dc66 |
devices.nrDeletedDevices++ |
d929589c |
return nil
}
|
927b334e |
// Should be called with devices.Lock() held. |
d929589c |
func (devices *DeviceSet) deleteTransaction(info *devInfo, syncDelete bool) error { |
482eca30 |
if err := devices.openTransaction(info.Hash, info.DeviceID); err != nil { |
0aa6ace6 |
logrus.Debugf("devmapper: Error opening transaction hash = %s deviceId = %d", "", info.DeviceID) |
482eca30 |
return err |
93e120e7 |
}
|
482eca30 |
defer devices.closeTransaction()
|
d929589c |
err := devicemapper.DeleteDevice(devices.getPoolDevName(), info.DeviceID)
if err != nil {
// If syncDelete is true, we want to return error. If deferred
// deletion is not enabled, we return an error. If error is
// something other then EBUSY, return an error.
if syncDelete || !devices.deferredDelete || err != devicemapper.ErrBusy { |
0aa6ace6 |
logrus.Debugf("devmapper: Error deleting device: %s", err) |
d929589c |
return err
} |
0b12702c |
}
|
d929589c |
if err == nil { |
288f933e |
if err := devices.unregisterDevice(info.Hash); err != nil { |
d929589c |
return err
} |
d295dc66 |
// If device was already in deferred delete state that means
// deletion was being tried again later. Reduce the deleted
// device count.
if info.Deleted {
devices.nrDeletedDevices--
} |
2dccb562 |
devices.markDeviceIDFree(info.DeviceID) |
d929589c |
} else {
if err := devices.markForDeferredDeletion(info); err != nil {
return err
} |
17b75a21 |
}
|
482eca30 |
return nil
}
// Issue discard only if device open count is zero.
func (devices *DeviceSet) issueDiscard(info *devInfo) error { |
664ad194 |
logrus.Debugf("devmapper: issueDiscard START(device: %s).", info.Hash)
defer logrus.Debugf("devmapper: issueDiscard END(device: %s).", info.Hash) |
482eca30 |
// This is a workaround for the kernel not discarding block so
// on the thin pool when we remove a thinp device, so we do it |
d929589c |
// manually. |
927b334e |
// Even if device is deferred deleted, activate it and issue |
d929589c |
// discards.
if err := devices.activateDeviceIfNeeded(info, true); err != nil { |
0b12702c |
return err
}
|
482eca30 |
devinfo, err := devicemapper.GetInfo(info.Name())
if err != nil { |
0b12702c |
return err
}
|
482eca30 |
if devinfo.OpenCount != 0 {
logrus.Debugf("devmapper: Device: %s is in use. OpenCount=%d. Not issuing discards.", info.Hash, devinfo.OpenCount)
return nil
}
if err := devicemapper.BlockDeviceDiscard(info.DevName()); err != nil { |
0aa6ace6 |
logrus.Debugf("devmapper: Error discarding block on device: %s (ignoring)", err) |
482eca30 |
}
return nil
}
// Should be called with devices.Lock() held. |
d929589c |
func (devices *DeviceSet) deleteDevice(info *devInfo, syncDelete bool) error { |
482eca30 |
if devices.doBlkDiscard {
devices.issueDiscard(info)
}
// Try to deactivate device in case it is active.
if err := devices.deactivateDevice(info); err != nil { |
0aa6ace6 |
logrus.Debugf("devmapper: Error deactivating device: %s", err) |
482eca30 |
return err
}
|
d929589c |
if err := devices.deleteTransaction(info, syncDelete); err != nil { |
17b75a21 |
return err
}
|
0b12702c |
return nil
}
|
d929589c |
// DeleteDevice will return success if device has been marked for deferred
// removal. If one wants to override that and want DeleteDevice() to fail if
// device was busy and could not be deleted, set syncDelete=true.
func (devices *DeviceSet) DeleteDevice(hash string, syncDelete bool) error { |
664ad194 |
logrus.Debugf("devmapper: DeleteDevice START(hash=%v syncDelete=%v)", hash, syncDelete)
defer logrus.Debugf("devmapper: DeleteDevice END(hash=%v syncDelete=%v)", hash, syncDelete) |
289145ec |
info, err := devices.lookupDeviceWithLock(hash) |
e01b71ce |
if err != nil {
return err |
3e8a02a9 |
}
info.lock.Lock()
defer info.lock.Unlock()
|
2ffef1b7 |
devices.Lock()
defer devices.Unlock()
|
d929589c |
return devices.deleteDevice(info, syncDelete) |
c77697a4 |
}
|
eab27039 |
func (devices *DeviceSet) deactivatePool() error { |
664ad194 |
logrus.Debug("devmapper: deactivatePool() START")
defer logrus.Debug("devmapper: deactivatePool() END") |
eab27039 |
devname := devices.getPoolDevName() |
8e7aa44f |
|
e2f8fbfb |
devinfo, err := devicemapper.GetInfo(devname) |
eab27039 |
if err != nil {
return err
} |
f83d05c3 |
if devinfo.Exists == 0 {
return nil |
8e7aa44f |
} |
f83d05c3 |
if err := devicemapper.RemoveDevice(devname); err != nil {
return err
}
if d, err := devicemapper.GetDeps(devname); err == nil { |
0aa6ace6 |
logrus.Warnf("devmapper: device %s still has %d active dependents", devname, d.Count) |
eab27039 |
}
return nil
}
|
972a94b4 |
func (devices *DeviceSet) deactivateDevice(info *devInfo) error { |
664ad194 |
logrus.Debugf("devmapper: deactivateDevice START(%s)", info.Hash) |
0aa6ace6 |
defer logrus.Debugf("devmapper: deactivateDevice END(%s)", info.Hash) |
6128dcea |
|
e2f8fbfb |
devinfo, err := devicemapper.GetInfo(info.Name()) |
0b12702c |
if err != nil {
return err
} |
e37c7203 |
if devinfo.Exists == 0 {
return nil
}
if devices.deferredRemove {
if err := devicemapper.RemoveDeviceDeferred(info.Name()); err != nil {
return err
}
} else { |
dbf04ec4 |
if err := devices.removeDevice(info.Name()); err != nil { |
0b12702c |
return err
}
}
return nil
}
|
dbf04ec4 |
// Issues the underlying dm remove operation.
func (devices *DeviceSet) removeDevice(devname string) error { |
2c82fd93 |
var err error
|
0aa6ace6 |
logrus.Debugf("devmapper: removeDevice START(%s)", devname)
defer logrus.Debugf("devmapper: removeDevice END(%s)", devname) |
cb7c8932 |
|
c737800b |
for i := 0; i < 200; i++ { |
e2f8fbfb |
err = devicemapper.RemoveDevice(devname) |
2c82fd93 |
if err == nil {
break
} |
e2f8fbfb |
if err != devicemapper.ErrBusy { |
2c82fd93 |
return err
}
// If we see EBUSY it may be a transient error,
// sleep a bit a retry a few times. |
81f148be |
devices.Unlock() |
c737800b |
time.Sleep(100 * time.Millisecond) |
81f148be |
devices.Lock() |
2c82fd93 |
}
|
dbf04ec4 |
return err |
ea04f3de |
}
|
0e633ee1 |
func (devices *DeviceSet) cancelDeferredRemovalIfNeeded(info *devInfo) error { |
ddc8aceb |
if !devices.deferredRemove {
return nil
}
|
0e633ee1 |
logrus.Debugf("devmapper: cancelDeferredRemovalIfNeeded START(%s)", info.Name())
defer logrus.Debugf("devmapper: cancelDeferredRemovalIfNeeded END(%s)", info.Name()) |
ddc8aceb |
devinfo, err := devicemapper.GetInfoWithDeferred(info.Name()) |
0e633ee1 |
if err != nil {
return err
} |
ddc8aceb |
if devinfo != nil && devinfo.DeferredRemove == 0 {
return nil
}
// Cancel deferred remove |
0e633ee1 |
if err := devices.cancelDeferredRemoval(info); err != nil {
// If Error is ErrEnxio. Device is probably already gone. Continue.
if err != devicemapper.ErrEnxio {
return err |
ddc8aceb |
} |
0e633ee1 |
}
return nil
} |
ddc8aceb |
|
0e633ee1 |
func (devices *DeviceSet) cancelDeferredRemoval(info *devInfo) error {
logrus.Debugf("devmapper: cancelDeferredRemoval START(%s)", info.Name())
defer logrus.Debugf("devmapper: cancelDeferredRemoval END(%s)", info.Name()) |
ddc8aceb |
|
0e633ee1 |
var err error |
ddc8aceb |
|
0e633ee1 |
// Cancel deferred remove
for i := 0; i < 100; i++ {
err = devicemapper.CancelDeferredRemove(info.Name())
if err != nil {
if err == devicemapper.ErrBusy {
// If we see EBUSY it may be a transient error,
// sleep a bit a retry a few times.
devices.Unlock()
time.Sleep(100 * time.Millisecond)
devices.Lock()
continue
}
}
break |
ddc8aceb |
}
return err
}
|
972a94b4 |
// Shutdown shuts down the device by unmounting the root. |
65d79e3e |
func (devices *DeviceSet) Shutdown(home string) error { |
0aa6ace6 |
logrus.Debugf("devmapper: [deviceset %s] Shutdown()", devices.devicePrefix)
logrus.Debugf("devmapper: Shutting down DeviceSet: %s", devices.root)
defer logrus.Debugf("devmapper: [deviceset %s] Shutdown() END", devices.devicePrefix) |
56901397 |
|
87de0400 |
// Stop deletion worker. This should start delivering new events to
// ticker channel. That means no new instance of cleanupDeletedDevice()
// will run after this call. If one instance is already running at
// the time of the call, it must be holding devices.Lock() and
// we will block on this lock till cleanup function exits.
devices.deletionWorkerTicker.Stop()
|
289145ec |
devices.Lock() |
482eca30 |
// Save DeviceSet Metadata first. Docker kills all threads if they
// don't finish in certain time. It is possible that Shutdown()
// routine does not finish in time as we loop trying to deactivate
// some devices while these are busy. In that case shutdown() routine
// will be killed and we will not get a chance to save deviceset
// metadata. Hence save this early before trying to deactivate devices.
devices.saveDeviceSetMetaData()
|
65d79e3e |
// ignore the error since it's just a best effort to not try to unmount something that's mounted
mounts, _ := mount.GetMounts()
mounted := make(map[string]bool, len(mounts))
for _, mnt := range mounts {
mounted[mnt.Mountpoint] = true |
70826e8b |
}
|
65d79e3e |
if err := filepath.Walk(path.Join(home, "mnt"), func(p string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
return nil
}
if mounted[p] { |
a9fa1a13 |
// We use MNT_DETACH here in case it is still busy in some running
// container. This means it'll go away from the global scope directly,
// and the device will be released when that container dies. |
069fdc8a |
if err := unix.Unmount(p, unix.MNT_DETACH); err != nil { |
65d79e3e |
logrus.Debugf("devmapper: Shutdown unmounting %s, error: %s", p, err) |
c6e8813c |
} |
65d79e3e |
} |
3e8a02a9 |
|
65d79e3e |
if devInfo, err := devices.lookupDevice(path.Base(p)); err != nil {
logrus.Debugf("devmapper: Shutdown lookup device %s, error: %s", path.Base(p), err)
} else {
if err := devices.deactivateDevice(devInfo); err != nil {
logrus.Debugf("devmapper: Shutdown deactivate %s , error: %s", devInfo.Hash, err) |
a9fa1a13 |
} |
c6e8813c |
} |
65d79e3e |
return nil
}); err != nil && !os.IsNotExist(err) {
devices.Unlock()
return err |
c6e8813c |
}
|
65d79e3e |
devices.Unlock()
|
289145ec |
info, _ := devices.lookupDeviceWithLock("") |
59558467 |
if info != nil { |
2ffef1b7 |
info.lock.Lock()
devices.Lock() |
59558467 |
if err := devices.deactivateDevice(info); err != nil { |
0aa6ace6 |
logrus.Debugf("devmapper: Shutdown deactivate base , error: %s", err) |
59558467 |
} |
2ffef1b7 |
devices.Unlock()
info.lock.Unlock() |
66c5e19f |
}
|
2ffef1b7 |
devices.Lock() |
2b10749c |
if devices.thinPoolDevice == "" {
if err := devices.deactivatePool(); err != nil { |
0aa6ace6 |
logrus.Debugf("devmapper: Shutdown deactivate pool , error: %s", err) |
2b10749c |
} |
c6e8813c |
} |
2ffef1b7 |
devices.Unlock() |
c6e8813c |
return nil
}
|
4f0017b9 |
// Recent XFS changes allow changing behavior of filesystem in case of errors.
// When thin pool gets full and XFS gets ENOSPC error, currently it tries
// IO infinitely and sometimes it can block the container process
// and process can't be killWith 0 value, XFS will not retry upon error
// and instead will shutdown filesystem.
func (devices *DeviceSet) xfsSetNospaceRetries(info *devInfo) error {
dmDevicePath, err := os.Readlink(info.DevName())
if err != nil {
return fmt.Errorf("devmapper: readlink failed for device %v:%v", info.DevName(), err)
}
dmDeviceName := path.Base(dmDevicePath)
filePath := "/sys/fs/xfs/" + dmDeviceName + "/error/metadata/ENOSPC/max_retries"
maxRetriesFile, err := os.OpenFile(filePath, os.O_WRONLY, 0)
if err != nil { |
6cc55dd6 |
return fmt.Errorf("devmapper: user specified daemon option dm.xfs_nospace_max_retries but it does not seem to be supported on this system :%v", err) |
4f0017b9 |
}
defer maxRetriesFile.Close()
// Set max retries to 0
_, err = maxRetriesFile.WriteString(devices.xfsNospaceRetries)
if err != nil {
return fmt.Errorf("devmapper: Failed to write string %v to file %v:%v", devices.xfsNospaceRetries, filePath, err)
}
return nil
}
|
972a94b4 |
// MountDevice mounts the device if not already mounted. |
f0e6e135 |
func (devices *DeviceSet) MountDevice(hash, path, mountLabel string) error { |
289145ec |
info, err := devices.lookupDeviceWithLock(hash) |
e01b71ce |
if err != nil {
return err |
693d3f8c |
} |
b95c560f |
|
d929589c |
if info.Deleted {
return fmt.Errorf("devmapper: Can't mount device %v as it has been marked for deferred deletion", info.Hash)
}
|
3e8a02a9 |
info.lock.Lock()
defer info.lock.Unlock()
|
2ffef1b7 |
devices.Lock()
defer devices.Unlock()
|
d929589c |
if err := devices.activateDeviceIfNeeded(info, false); err != nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Error activating devmapper device for '%s': %s", hash, err) |
0b12702c |
}
|
10083f41 |
fstype, err := ProbeFsType(info.DevName())
if err != nil {
return err
}
|
807bc2cd |
options := ""
if fstype == "xfs" {
// XFS needs nouuid or it can't mount filesystems with the same fs
options = joinMountOptions(options, "nouuid")
}
options = joinMountOptions(options, devices.mountOptions)
options = joinMountOptions(options, label.FormatMountLabel("", mountLabel))
|
9a64f2bb |
if err := mount.Mount(info.DevName(), path, fstype, options); err != nil { |
0aa6ace6 |
return fmt.Errorf("devmapper: Error mounting '%s' on '%s': %s", info.DevName(), path, err) |
0b12702c |
} |
c6e8813c |
|
4f0017b9 |
if fstype == "xfs" && devices.xfsNospaceRetries != "" {
if err := devices.xfsSetNospaceRetries(info); err != nil { |
069fdc8a |
unix.Unmount(path, unix.MNT_DETACH) |
6cc55dd6 |
devices.deactivateDevice(info) |
4f0017b9 |
return err
}
}
|
6d631968 |
return nil |
0b12702c |
}
|
972a94b4 |
// UnmountDevice unmounts the device and removes it from hash. |
f7bdb973 |
func (devices *DeviceSet) UnmountDevice(hash, mountPath string) error { |
664ad194 |
logrus.Debugf("devmapper: UnmountDevice START(hash=%s)", hash)
defer logrus.Debugf("devmapper: UnmountDevice END(hash=%s)", hash) |
c77697a4 |
|
289145ec |
info, err := devices.lookupDeviceWithLock(hash) |
e01b71ce |
if err != nil {
return err |
304e33a2 |
} |
b95c560f |
|
3e8a02a9 |
info.lock.Lock()
defer info.lock.Unlock()
|
2ffef1b7 |
devices.Lock()
defer devices.Unlock()
|
0aa6ace6 |
logrus.Debugf("devmapper: Unmount(%s)", mountPath) |
069fdc8a |
if err := unix.Unmount(mountPath, unix.MNT_DETACH); err != nil { |
251a7ed4 |
return err
} |
a72b45db |
logrus.Debug("devmapper: Unmount done") |
a9fa1a13 |
|
3a425180 |
return devices.deactivateDevice(info) |
251a7ed4 |
}
|
f5c0eb9f |
// HasDevice returns true if the device metadata exists. |
4bd60218 |
func (devices *DeviceSet) HasDevice(hash string) bool { |
289145ec |
info, _ := devices.lookupDeviceWithLock(hash) |
e01b71ce |
return info != nil |
0b12702c |
}
|
972a94b4 |
// List returns a list of device ids. |
80aecc70 |
func (devices *DeviceSet) List() []string {
devices.Lock()
defer devices.Unlock()
ids := make([]string, len(devices.Devices))
i := 0
for k := range devices.Devices {
ids[i] = k
i++
}
return ids
}
func (devices *DeviceSet) deviceStatus(devName string) (sizeInSectors, mappedSectors, highestMappedSector uint64, err error) {
var params string |
e2f8fbfb |
_, sizeInSectors, _, params, err = devicemapper.GetStatus(devName) |
80aecc70 |
if err != nil {
return
}
if _, err = fmt.Sscanf(params, "%d %d", &mappedSectors, &highestMappedSector); err == nil {
return
}
return
}
|
972a94b4 |
// GetDeviceStatus provides size, mapped sectors |
80aecc70 |
func (devices *DeviceSet) GetDeviceStatus(hash string) (*DevStatus, error) { |
289145ec |
info, err := devices.lookupDeviceWithLock(hash) |
e01b71ce |
if err != nil {
return nil, err |
80aecc70 |
}
|
3e8a02a9 |
info.lock.Lock()
defer info.lock.Unlock()
|
2ffef1b7 |
devices.Lock()
defer devices.Unlock()
|
80aecc70 |
status := &DevStatus{ |
972a94b4 |
DeviceID: info.DeviceID, |
80aecc70 |
Size: info.Size, |
972a94b4 |
TransactionID: info.TransactionID, |
80aecc70 |
}
|
d929589c |
if err := devices.activateDeviceIfNeeded(info, false); err != nil { |
0aa6ace6 |
return nil, fmt.Errorf("devmapper: Error activating devmapper device for '%s': %s", hash, err) |
80aecc70 |
}
|
e479e1c9 |
sizeInSectors, mappedSectors, highestMappedSector, err := devices.deviceStatus(info.DevName())
if err != nil { |
80aecc70 |
return nil, err
}
|
e479e1c9 |
status.SizeInSectors = sizeInSectors
status.MappedSectors = mappedSectors
status.HighestMappedSector = highestMappedSector
|
80aecc70 |
return status, nil
}
|
972a94b4 |
func (devices *DeviceSet) poolStatus() (totalSizeInSectors, transactionID, dataUsed, dataTotal, metadataUsed, metadataTotal uint64, err error) { |
80aecc70 |
var params string |
e2f8fbfb |
if _, totalSizeInSectors, _, params, err = devicemapper.GetStatus(devices.getPoolName()); err == nil { |
972a94b4 |
_, err = fmt.Sscanf(params, "%d %d/%d %d/%d", &transactionID, &metadataUsed, &metadataTotal, &dataUsed, &dataTotal) |
80aecc70 |
}
return
}
|
f4ac86de |
// DataDevicePath returns the path to the data storage for this deviceset, |
09c033ff |
// regardless of loopback or block device |
bb96e53b |
func (devices *DeviceSet) DataDevicePath() string { |
09c033ff |
return devices.dataDevice
}
// MetadataDevicePath returns the path to the metadata storage for this deviceset,
// regardless of loopback or block device |
bb96e53b |
func (devices *DeviceSet) MetadataDevicePath() string { |
09c033ff |
return devices.metadataDevice
}
|
726f89d7 |
func (devices *DeviceSet) getUnderlyingAvailableSpace(loopFile string) (uint64, error) { |
069fdc8a |
buf := new(unix.Statfs_t)
if err := unix.Statfs(loopFile, buf); err != nil { |
0aa6ace6 |
logrus.Warnf("devmapper: Couldn't stat loopfile filesystem %v: %v", loopFile, err) |
726f89d7 |
return 0, err
}
return buf.Bfree * uint64(buf.Bsize), nil
}
func (devices *DeviceSet) isRealFile(loopFile string) (bool, error) {
if loopFile != "" {
fi, err := os.Stat(loopFile)
if err != nil { |
0aa6ace6 |
logrus.Warnf("devmapper: Couldn't stat loopfile %v: %v", loopFile, err) |
726f89d7 |
return false, err
}
return fi.Mode().IsRegular(), nil
}
return false, nil
}
|
09c033ff |
// Status returns the current status of this deviceset |
73545199 |
func (devices *DeviceSet) Status() *Status { |
d733cdce |
devices.Lock()
defer devices.Unlock()
|
f1127b93 |
status := &Status{} |
d733cdce |
status.PoolName = devices.getPoolName() |
09c033ff |
status.DataFile = devices.DataDevicePath()
status.DataLoopback = devices.dataLoopFile
status.MetadataFile = devices.MetadataDevicePath()
status.MetadataLoopback = devices.metadataLoopFile |
d2593546 |
status.UdevSyncSupported = devicemapper.UdevSyncSupported() |
66a53819 |
status.DeferredRemoveEnabled = devices.deferredRemove |
51e059e7 |
status.DeferredDeleteEnabled = devices.deferredDelete |
d295dc66 |
status.DeferredDeletedDeviceCount = devices.nrDeletedDevices |
5c374c71 |
status.BaseDeviceSize = devices.getBaseDeviceSize() |
fdc2641c |
status.BaseDeviceFS = devices.getBaseDeviceFS() |
d733cdce |
|
80aecc70 |
totalSizeInSectors, _, dataUsed, dataTotal, metadataUsed, metadataTotal, err := devices.poolStatus() |
d733cdce |
if err == nil { |
80aecc70 |
// Convert from blocks to bytes
blockSizeInSectors := totalSizeInSectors / dataTotal |
d733cdce |
|
80aecc70 |
status.Data.Used = dataUsed * blockSizeInSectors * 512
status.Data.Total = dataTotal * blockSizeInSectors * 512 |
726f89d7 |
status.Data.Available = status.Data.Total - status.Data.Used |
d733cdce |
|
80aecc70 |
// metadata blocks are always 4k
status.Metadata.Used = metadataUsed * 4096
status.Metadata.Total = metadataTotal * 4096 |
726f89d7 |
status.Metadata.Available = status.Metadata.Total - status.Metadata.Used |
80aecc70 |
status.SectorSize = blockSizeInSectors * 512 |
726f89d7 |
if check, _ := devices.isRealFile(devices.dataLoopFile); check {
actualSpace, err := devices.getUnderlyingAvailableSpace(devices.dataLoopFile)
if err == nil && actualSpace < status.Data.Available {
status.Data.Available = actualSpace
}
}
if check, _ := devices.isRealFile(devices.metadataLoopFile); check {
actualSpace, err := devices.getUnderlyingAvailableSpace(devices.metadataLoopFile)
if err == nil && actualSpace < status.Metadata.Available {
status.Metadata.Available = actualSpace
}
} |
55a9b812 |
minFreeData := (dataTotal * uint64(devices.minFreeSpacePercent)) / 100
status.MinFreeSpace = minFreeData * blockSizeInSectors * 512 |
d733cdce |
}
return status
}
|
407a626b |
// Status returns the current status of this deviceset |
972a94b4 |
func (devices *DeviceSet) exportDeviceMetadata(hash string) (*deviceMetadata, error) { |
289145ec |
info, err := devices.lookupDeviceWithLock(hash) |
407a626b |
if err != nil {
return nil, err
}
info.lock.Lock()
defer info.lock.Unlock()
|
972a94b4 |
metadata := &deviceMetadata{info.DeviceID, info.Size, info.Name()} |
407a626b |
return metadata, nil
}
|
972a94b4 |
// NewDeviceSet creates the device set based on the options provided. |
442b4562 |
func NewDeviceSet(root string, doInit bool, options []string, uidMaps, gidMaps []idtools.IDMap) (*DeviceSet, error) { |
e2f8fbfb |
devicemapper.SetDevDir("/dev") |
8f343ea6 |
|
b5795749 |
devices := &DeviceSet{ |
9af7afb9 |
root: root, |
972a94b4 |
metaData: metaData{Devices: make(map[string]*devInfo)},
dataLoopbackSize: defaultDataLoopbackSize,
metaDataLoopbackSize: defaultMetaDataLoopbackSize,
baseFsSize: defaultBaseFsSize,
overrideUdevSyncCheck: defaultUdevSyncOverride, |
9af7afb9 |
doBlkDiscard: true, |
972a94b4 |
thinpBlockSize: defaultThinpBlockSize,
deviceIDMap: make([]byte, deviceIDMapSz), |
87de0400 |
deletionWorkerTicker: time.NewTicker(time.Second * 30), |
442b4562 |
uidMaps: uidMaps,
gidMaps: gidMaps, |
2e222f69 |
minFreeSpacePercent: defaultMinFreeSpacePercent, |
7f5ba068 |
}
|
0dc1a805 |
version, err := devicemapper.GetDriverVersion()
if err != nil {
// Can't even get driver version, assume not supported
return nil, graphdriver.ErrNotSupported
}
if err := determineDriverCapabilities(version); err != nil {
return nil, graphdriver.ErrNotSupported
}
if driverDeferredRemovalSupport && devicemapper.LibraryDeferredRemovalSupport {
// enable deferred stuff by default
enableDeferredDeletion = true
enableDeferredRemoval = true
}
|
0434a2ce |
foundBlkDiscard := false |
5ef07d79 |
var lvmSetupConfig directLVMConfig |
7f5ba068 |
for _, option := range options { |
43981084 |
key, val, err := parsers.ParseKeyValueOpt(option) |
7f5ba068 |
if err != nil {
return nil, err
}
key = strings.ToLower(key)
switch key {
case "dm.basesize": |
2470a5ed |
size, err := units.RAMInBytes(val) |
7f5ba068 |
if err != nil {
return nil, err
} |
e47112d3 |
userBaseSize = true |
7f5ba068 |
devices.baseFsSize = uint64(size)
case "dm.loopdatasize": |
2470a5ed |
size, err := units.RAMInBytes(val) |
7f5ba068 |
if err != nil {
return nil, err
}
devices.dataLoopbackSize = size
case "dm.loopmetadatasize": |
2470a5ed |
size, err := units.RAMInBytes(val) |
7f5ba068 |
if err != nil {
return nil, err
}
devices.metaDataLoopbackSize = size |
807bc2cd |
case "dm.fs":
if val != "ext4" && val != "xfs" { |
0aa6ace6 |
return nil, fmt.Errorf("devmapper: Unsupported filesystem %s\n", val) |
807bc2cd |
} |
fdc2641c |
devices.filesystem = val |
807bc2cd |
case "dm.mkfsarg":
devices.mkfsArgs = append(devices.mkfsArgs, val)
case "dm.mountopt":
devices.mountOptions = joinMountOptions(devices.mountOptions, val) |
a226168a |
case "dm.metadatadev":
devices.metadataDevice = val
case "dm.datadev":
devices.dataDevice = val |
2b10749c |
case "dm.thinpooldev":
devices.thinPoolDevice = strings.TrimPrefix(val, "/dev/mapper/") |
0434a2ce |
case "dm.blkdiscard":
foundBlkDiscard = true
devices.doBlkDiscard, err = strconv.ParseBool(val)
if err != nil {
return nil, err
} |
09ee269d |
case "dm.blocksize":
size, err := units.RAMInBytes(val)
if err != nil {
return nil, err
}
// convert to 512b sectors
devices.thinpBlockSize = uint32(size) >> 9 |
9af7afb9 |
case "dm.override_udev_sync_check":
devices.overrideUdevSyncCheck, err = strconv.ParseBool(val)
if err != nil {
return nil, err
}
|
15c158b2 |
case "dm.use_deferred_removal": |
972a94b4 |
enableDeferredRemoval, err = strconv.ParseBool(val) |
15c158b2 |
if err != nil {
return nil, err
}
|
51e059e7 |
case "dm.use_deferred_deletion":
enableDeferredDeletion, err = strconv.ParseBool(val)
if err != nil {
return nil, err
}
|
2e222f69 |
case "dm.min_free_space":
if !strings.HasSuffix(val, "%") {
return nil, fmt.Errorf("devmapper: Option dm.min_free_space requires %% suffix")
}
valstring := strings.TrimSuffix(val, "%")
minFreeSpacePercent, err := strconv.ParseUint(valstring, 10, 32)
if err != nil {
return nil, err
}
if minFreeSpacePercent >= 100 {
return nil, fmt.Errorf("devmapper: Invalid value %v for option dm.min_free_space", val)
}
devices.minFreeSpacePercent = uint32(minFreeSpacePercent) |
4f0017b9 |
case "dm.xfs_nospace_max_retries":
_, err := strconv.ParseUint(val, 10, 64)
if err != nil {
return nil, err
}
devices.xfsNospaceRetries = val |
5ef07d79 |
case "dm.directlvm_device":
lvmSetupConfig.Device = val
case "dm.directlvm_device_force":
lvmSetupConfigForce, err = strconv.ParseBool(val)
if err != nil {
return nil, err
}
case "dm.thinp_percent":
per, err := strconv.ParseUint(strings.TrimSuffix(val, "%"), 10, 32)
if err != nil {
return nil, errors.Wrapf(err, "could not parse `dm.thinp_percent=%s`", val)
}
if per >= 100 {
return nil, errors.New("dm.thinp_percent must be greater than 0 and less than 100")
}
lvmSetupConfig.ThinpPercent = per
case "dm.thinp_metapercent":
per, err := strconv.ParseUint(strings.TrimSuffix(val, "%"), 10, 32)
if err != nil {
return nil, errors.Wrapf(err, "could not parse `dm.thinp_metapercent=%s`", val)
}
if per >= 100 {
return nil, errors.New("dm.thinp_metapercent must be greater than 0 and less than 100")
}
lvmSetupConfig.ThinpMetaPercent = per
case "dm.thinp_autoextend_percent":
per, err := strconv.ParseUint(strings.TrimSuffix(val, "%"), 10, 32)
if err != nil {
return nil, errors.Wrapf(err, "could not parse `dm.thinp_autoextend_percent=%s`", val)
}
if per > 100 {
return nil, errors.New("dm.thinp_autoextend_percent must be greater than 0 and less than 100")
}
lvmSetupConfig.AutoExtendPercent = per
case "dm.thinp_autoextend_threshold":
per, err := strconv.ParseUint(strings.TrimSuffix(val, "%"), 10, 32)
if err != nil {
return nil, errors.Wrapf(err, "could not parse `dm.thinp_autoextend_threshold=%s`", val)
}
if per > 100 {
return nil, errors.New("dm.thinp_autoextend_threshold must be greater than 0 and less than 100")
}
lvmSetupConfig.AutoExtendThreshold = per |
7f5ba068 |
default: |
0aa6ace6 |
return nil, fmt.Errorf("devmapper: Unknown option %s\n", key) |
7f5ba068 |
} |
0b12702c |
} |
b5795749 |
|
5ef07d79 |
if err := validateLVMConfig(lvmSetupConfig); err != nil {
return nil, err
}
devices.lvmSetupConfig = lvmSetupConfig
|
0434a2ce |
// By default, don't do blk discard hack on raw devices, its rarely useful and is expensive |
e49567ba |
if !foundBlkDiscard && (devices.dataDevice != "" || devices.thinPoolDevice != "") { |
0434a2ce |
devices.doBlkDiscard = false
}
|
80aecc70 |
if err := devices.initDevmapper(doInit); err != nil { |
b5795749 |
return nil, err
}
return devices, nil |
0b12702c |
} |