Initial commit

This commit is contained in:
Ria Bhatia
2017-12-04 13:32:57 -06:00
committed by Erik St. Martin
commit 0075e5b0f3
9056 changed files with 2523100 additions and 0 deletions

View File

@@ -0,0 +1,97 @@
## devicemapper - a storage backend based on Device Mapper
### Theory of operation
The device mapper graphdriver uses the device mapper thin provisioning
module (dm-thinp) to implement CoW snapshots. The preferred model is
to have a thin pool reserved outside of Docker and passed to the
daemon via the `--storage-opt dm.thinpooldev` option.
As a fallback if no thin pool is provided, loopback files will be
created. Loopback is very slow, but can be used without any
pre-configuration of storage. It is strongly recommended that you do
not use loopback in production. Ensure your Docker daemon has a
`--storage-opt dm.thinpooldev` argument provided.
In loopback, a thin pool is created at `/var/lib/docker/devicemapper`
(devicemapper graph location) based on two block devices, one for
data and one for metadata. By default these block devices are created
automatically by using loopback mounts of automatically created sparse
files.
The default loopback files used are
`/var/lib/docker/devicemapper/devicemapper/data` and
`/var/lib/docker/devicemapper/devicemapper/metadata`. Additional metadata
required to map from docker entities to the corresponding devicemapper
volumes is stored in the `/var/lib/docker/devicemapper/devicemapper/json`
file (encoded as Json).
In order to support multiple devicemapper graphs on a system, the thin
pool will be named something like: `docker-0:33-19478248-pool`, where
the `0:33` part is the minor/major device nr and `19478248` is the
inode number of the `/var/lib/docker/devicemapper` directory.
On the thin pool, docker automatically creates a base thin device,
called something like `docker-0:33-19478248-base` of a fixed
size. This is automatically formatted with an empty filesystem on
creation. This device is the base of all docker images and
containers. All base images are snapshots of this device and those
images are then in turn used as snapshots for other images and
eventually containers.
### Information on `docker info`
As of docker-1.4.1, `docker info` when using the `devicemapper` storage driver
will display something like:
$ sudo docker info
[...]
Storage Driver: devicemapper
Pool Name: docker-253:1-17538953-pool
Pool Blocksize: 65.54 kB
Base Device Size: 107.4 GB
Data file: /dev/loop4
Metadata file: /dev/loop4
Data Space Used: 2.536 GB
Data Space Total: 107.4 GB
Data Space Available: 104.8 GB
Metadata Space Used: 7.93 MB
Metadata Space Total: 2.147 GB
Metadata Space Available: 2.14 GB
Udev Sync Supported: true
Data loop file: /home/docker/devicemapper/devicemapper/data
Metadata loop file: /home/docker/devicemapper/devicemapper/metadata
Library Version: 1.02.82-git (2013-10-04)
[...]
#### status items
Each item in the indented section under `Storage Driver: devicemapper` are
status information about the driver.
* `Pool Name` name of the devicemapper pool for this driver.
* `Pool Blocksize` tells the blocksize the thin pool was initialized with. This only changes on creation.
* `Base Device Size` tells the maximum size of a container and image
* `Data file` blockdevice file used for the devicemapper data
* `Metadata file` blockdevice file used for the devicemapper metadata
* `Data Space Used` tells how much of `Data file` is currently used
* `Data Space Total` tells max size the `Data file`
* `Data Space Available` tells how much free space there is in the `Data file`. If you are using a loop device this will report the actual space available to the loop device on the underlying filesystem.
* `Metadata Space Used` tells how much of `Metadata file` is currently used
* `Metadata Space Total` tells max size the `Metadata file`
* `Metadata Space Available` tells how much free space there is in the `Metadata file`. If you are using a loop device this will report the actual space available to the loop device on the underlying filesystem.
* `Udev Sync Supported` tells whether devicemapper is able to sync with Udev. Should be `true`.
* `Data loop file` file attached to `Data file`, if loopback device is used
* `Metadata loop file` file attached to `Metadata file`, if loopback device is used
* `Library Version` from the libdevmapper used
### About the devicemapper options
The devicemapper backend supports some options that you can specify
when starting the docker daemon using the `--storage-opt` flags.
This uses the `dm` prefix and would be used something like `docker daemon --storage-opt dm.foo=bar`.
These options are currently documented both in [the man
page](../../../man/docker.1.md) and in [the online
documentation](https://docs.docker.com/reference/commandline/daemon/#docker-
execdriver-option). If you add an options, update both the `man` page and the
documentation.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,106 @@
package devmapper
// Definition of struct dm_task and sub structures (from lvm2)
//
// struct dm_ioctl {
// /*
// * The version number is made up of three parts:
// * major - no backward or forward compatibility,
// * minor - only backwards compatible,
// * patch - both backwards and forwards compatible.
// *
// * All clients of the ioctl interface should fill in the
// * version number of the interface that they were
// * compiled with.
// *
// * All recognized ioctl commands (ie. those that don't
// * return -ENOTTY) fill out this field, even if the
// * command failed.
// */
// uint32_t version[3]; /* in/out */
// uint32_t data_size; /* total size of data passed in
// * including this struct */
// uint32_t data_start; /* offset to start of data
// * relative to start of this struct */
// uint32_t target_count; /* in/out */
// int32_t open_count; /* out */
// uint32_t flags; /* in/out */
// /*
// * event_nr holds either the event number (input and output) or the
// * udev cookie value (input only).
// * The DM_DEV_WAIT ioctl takes an event number as input.
// * The DM_SUSPEND, DM_DEV_REMOVE and DM_DEV_RENAME ioctls
// * use the field as a cookie to return in the DM_COOKIE
// * variable with the uevents they issue.
// * For output, the ioctls return the event number, not the cookie.
// */
// uint32_t event_nr; /* in/out */
// uint32_t padding;
// uint64_t dev; /* in/out */
// char name[DM_NAME_LEN]; /* device name */
// char uuid[DM_UUID_LEN]; /* unique identifier for
// * the block device */
// char data[7]; /* padding or data */
// };
// struct target {
// uint64_t start;
// uint64_t length;
// char *type;
// char *params;
// struct target *next;
// };
// typedef enum {
// DM_ADD_NODE_ON_RESUME, /* add /dev/mapper node with dmsetup resume */
// DM_ADD_NODE_ON_CREATE /* add /dev/mapper node with dmsetup create */
// } dm_add_node_t;
// struct dm_task {
// int type;
// char *dev_name;
// char *mangled_dev_name;
// struct target *head, *tail;
// int read_only;
// uint32_t event_nr;
// int major;
// int minor;
// int allow_default_major_fallback;
// uid_t uid;
// gid_t gid;
// mode_t mode;
// uint32_t read_ahead;
// uint32_t read_ahead_flags;
// union {
// struct dm_ioctl *v4;
// } dmi;
// char *newname;
// char *message;
// char *geometry;
// uint64_t sector;
// int no_flush;
// int no_open_count;
// int skip_lockfs;
// int query_inactive_table;
// int suppress_identical_reload;
// dm_add_node_t add_node;
// uint64_t existing_table_size;
// int cookie_set;
// int new_uuid;
// int secure_data;
// int retry_remove;
// int enable_checks;
// int expected_errno;
// char *uuid;
// char *mangled_uuid;
// };
//

View File

@@ -0,0 +1,110 @@
// +build linux
package devmapper
import (
"fmt"
"testing"
"time"
"github.com/hyperhq/hypercli/daemon/graphdriver"
"github.com/hyperhq/hypercli/daemon/graphdriver/graphtest"
)
func init() {
// Reduce the size the the base fs and loopback for the tests
defaultDataLoopbackSize = 300 * 1024 * 1024
defaultMetaDataLoopbackSize = 200 * 1024 * 1024
defaultBaseFsSize = 300 * 1024 * 1024
defaultUdevSyncOverride = true
if err := graphtest.InitLoopbacks(); err != nil {
panic(err)
}
}
// This avoids creating a new driver for each test if all tests are run
// Make sure to put new tests between TestDevmapperSetup and TestDevmapperTeardown
func TestDevmapperSetup(t *testing.T) {
graphtest.GetDriver(t, "devicemapper")
}
func TestDevmapperCreateEmpty(t *testing.T) {
graphtest.DriverTestCreateEmpty(t, "devicemapper")
}
func TestDevmapperCreateBase(t *testing.T) {
graphtest.DriverTestCreateBase(t, "devicemapper")
}
func TestDevmapperCreateSnap(t *testing.T) {
graphtest.DriverTestCreateSnap(t, "devicemapper")
}
func TestDevmapperTeardown(t *testing.T) {
graphtest.PutDriver(t)
}
func TestDevmapperReduceLoopBackSize(t *testing.T) {
tenMB := int64(10 * 1024 * 1024)
testChangeLoopBackSize(t, -tenMB, defaultDataLoopbackSize, defaultMetaDataLoopbackSize)
}
func TestDevmapperIncreaseLoopBackSize(t *testing.T) {
tenMB := int64(10 * 1024 * 1024)
testChangeLoopBackSize(t, tenMB, defaultDataLoopbackSize+tenMB, defaultMetaDataLoopbackSize+tenMB)
}
func testChangeLoopBackSize(t *testing.T, delta, expectDataSize, expectMetaDataSize int64) {
driver := graphtest.GetDriver(t, "devicemapper").(*graphtest.Driver).Driver.(*graphdriver.NaiveDiffDriver).ProtoDriver.(*Driver)
defer graphtest.PutDriver(t)
// make sure data or metadata loopback size are the default size
if s := driver.DeviceSet.Status(); s.Data.Total != uint64(defaultDataLoopbackSize) || s.Metadata.Total != uint64(defaultMetaDataLoopbackSize) {
t.Fatalf("data or metadata loop back size is incorrect")
}
if err := driver.Cleanup(); err != nil {
t.Fatal(err)
}
//Reload
d, err := Init(driver.home, []string{
fmt.Sprintf("dm.loopdatasize=%d", defaultDataLoopbackSize+delta),
fmt.Sprintf("dm.loopmetadatasize=%d", defaultMetaDataLoopbackSize+delta),
}, nil, nil)
if err != nil {
t.Fatalf("error creating devicemapper driver: %v", err)
}
driver = d.(*graphdriver.NaiveDiffDriver).ProtoDriver.(*Driver)
if s := driver.DeviceSet.Status(); s.Data.Total != uint64(expectDataSize) || s.Metadata.Total != uint64(expectMetaDataSize) {
t.Fatalf("data or metadata loop back size is incorrect")
}
if err := driver.Cleanup(); err != nil {
t.Fatal(err)
}
}
// Make sure devices.Lock() has been release upon return from cleanupDeletedDevices() function
func TestDevmapperLockReleasedDeviceDeletion(t *testing.T) {
driver := graphtest.GetDriver(t, "devicemapper").(*graphtest.Driver).Driver.(*graphdriver.NaiveDiffDriver).ProtoDriver.(*Driver)
defer graphtest.PutDriver(t)
// Call cleanupDeletedDevices() and after the call take and release
// DeviceSet Lock. If lock has not been released, this will hang.
driver.DeviceSet.cleanupDeletedDevices()
doneChan := make(chan bool)
go func() {
driver.DeviceSet.Lock()
defer driver.DeviceSet.Unlock()
doneChan <- true
}()
select {
case <-time.After(time.Second * 5):
// Timer expired. That means lock was not released upon
// function return and we are deadlocked. Release lock
// here so that cleanup could succeed and fail the test.
driver.DeviceSet.Unlock()
t.Fatalf("Could not acquire devices lock after call to cleanupDeletedDevices()")
case <-doneChan:
}
}

View File

@@ -0,0 +1,204 @@
// +build linux
package devmapper
import (
"fmt"
"io/ioutil"
"os"
"path"
"strconv"
"github.com/Sirupsen/logrus"
"github.com/hyperhq/hypercli/daemon/graphdriver"
"github.com/hyperhq/hypercli/pkg/devicemapper"
"github.com/hyperhq/hypercli/pkg/idtools"
"github.com/hyperhq/hypercli/pkg/mount"
"github.com/docker/go-units"
)
func init() {
graphdriver.Register("devicemapper", Init)
}
// Driver contains the device set mounted and the home directory
type Driver struct {
*DeviceSet
home string
uidMaps []idtools.IDMap
gidMaps []idtools.IDMap
}
// Init creates a driver with the given home and the set of options.
func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
deviceSet, err := NewDeviceSet(home, true, options, uidMaps, gidMaps)
if err != nil {
return nil, err
}
if err := mount.MakePrivate(home); err != nil {
return nil, err
}
d := &Driver{
DeviceSet: deviceSet,
home: home,
uidMaps: uidMaps,
gidMaps: gidMaps,
}
return graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps), nil
}
func (d *Driver) String() string {
return "devicemapper"
}
// Status returns the status about the driver in a printable format.
// Information returned contains Pool Name, Data File, Metadata file, disk usage by
// the data and metadata, etc.
func (d *Driver) Status() [][2]string {
s := d.DeviceSet.Status()
status := [][2]string{
{"Pool Name", s.PoolName},
{"Pool Blocksize", fmt.Sprintf("%s", units.HumanSize(float64(s.SectorSize)))},
{"Base Device Size", fmt.Sprintf("%s", units.HumanSize(float64(s.BaseDeviceSize)))},
{"Backing Filesystem", s.BaseDeviceFS},
{"Data file", s.DataFile},
{"Metadata file", s.MetadataFile},
{"Data Space Used", fmt.Sprintf("%s", units.HumanSize(float64(s.Data.Used)))},
{"Data Space Total", fmt.Sprintf("%s", units.HumanSize(float64(s.Data.Total)))},
{"Data Space Available", fmt.Sprintf("%s", units.HumanSize(float64(s.Data.Available)))},
{"Metadata Space Used", fmt.Sprintf("%s", units.HumanSize(float64(s.Metadata.Used)))},
{"Metadata Space Total", fmt.Sprintf("%s", units.HumanSize(float64(s.Metadata.Total)))},
{"Metadata Space Available", fmt.Sprintf("%s", units.HumanSize(float64(s.Metadata.Available)))},
{"Udev Sync Supported", fmt.Sprintf("%v", s.UdevSyncSupported)},
{"Deferred Removal Enabled", fmt.Sprintf("%v", s.DeferredRemoveEnabled)},
{"Deferred Deletion Enabled", fmt.Sprintf("%v", s.DeferredDeleteEnabled)},
{"Deferred Deleted Device Count", fmt.Sprintf("%v", s.DeferredDeletedDeviceCount)},
}
if len(s.DataLoopback) > 0 {
status = append(status, [2]string{"Data loop file", s.DataLoopback})
}
if len(s.MetadataLoopback) > 0 {
status = append(status, [2]string{"Metadata loop file", s.MetadataLoopback})
}
if vStr, err := devicemapper.GetLibraryVersion(); err == nil {
status = append(status, [2]string{"Library Version", vStr})
}
return status
}
// GetMetadata returns a map of information about the device.
func (d *Driver) GetMetadata(id string) (map[string]string, error) {
m, err := d.DeviceSet.exportDeviceMetadata(id)
if err != nil {
return nil, err
}
metadata := make(map[string]string)
metadata["DeviceId"] = strconv.Itoa(m.deviceID)
metadata["DeviceSize"] = strconv.FormatUint(m.deviceSize, 10)
metadata["DeviceName"] = m.deviceName
return metadata, nil
}
// Cleanup unmounts a device.
func (d *Driver) Cleanup() error {
err := d.DeviceSet.Shutdown()
if err2 := mount.Unmount(d.home); err == nil {
err = err2
}
return err
}
// Create adds a device with a given id and the parent.
func (d *Driver) Create(id, parent, mountLabel string) error {
if err := d.DeviceSet.AddDevice(id, parent); err != nil {
return err
}
return nil
}
// Remove removes a device with a given id, unmounts the filesystem.
func (d *Driver) Remove(id string) error {
if !d.DeviceSet.HasDevice(id) {
// Consider removing a non-existing device a no-op
// This is useful to be able to progress on container removal
// if the underlying device has gone away due to earlier errors
return nil
}
// This assumes the device has been properly Get/Put:ed and thus is unmounted
if err := d.DeviceSet.DeleteDevice(id, false); err != nil {
return err
}
mp := path.Join(d.home, "mnt", id)
if err := os.RemoveAll(mp); err != nil && !os.IsNotExist(err) {
return err
}
return nil
}
// Get mounts a device with given id into the root filesystem
func (d *Driver) Get(id, mountLabel string) (string, error) {
mp := path.Join(d.home, "mnt", id)
uid, gid, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
if err != nil {
return "", err
}
// Create the target directories if they don't exist
if err := idtools.MkdirAllAs(path.Join(d.home, "mnt"), 0755, uid, gid); err != nil && !os.IsExist(err) {
return "", err
}
if err := idtools.MkdirAs(mp, 0755, uid, gid); err != nil && !os.IsExist(err) {
return "", err
}
// Mount the device
if err := d.DeviceSet.MountDevice(id, mp, mountLabel); err != nil {
return "", err
}
rootFs := path.Join(mp, "rootfs")
if err := idtools.MkdirAllAs(rootFs, 0755, uid, gid); err != nil && !os.IsExist(err) {
d.DeviceSet.UnmountDevice(id, mp)
return "", err
}
idFile := path.Join(mp, "id")
if _, err := os.Stat(idFile); err != nil && os.IsNotExist(err) {
// Create an "id" file with the container/image id in it to help reconstruct this in case
// of later problems
if err := ioutil.WriteFile(idFile, []byte(id), 0600); err != nil {
d.DeviceSet.UnmountDevice(id, mp)
return "", err
}
}
return rootFs, nil
}
// Put unmounts a device and removes it.
func (d *Driver) Put(id string) error {
mp := path.Join(d.home, "mnt", id)
err := d.DeviceSet.UnmountDevice(id, mp)
if err != nil {
logrus.Errorf("devmapper: Error unmounting device %s: %s", id, err)
}
return err
}
// Exists checks to see if the device exists.
func (d *Driver) Exists(id string) bool {
return d.DeviceSet.HasDevice(id)
}

View File

@@ -0,0 +1,89 @@
// +build linux
package devmapper
import (
"bytes"
"fmt"
"os"
"path/filepath"
"syscall"
)
// FIXME: this is copy-pasted from the aufs driver.
// It should be moved into the core.
// Mounted returns true if a mount point exists.
func Mounted(mountpoint string) (bool, error) {
mntpoint, err := os.Stat(mountpoint)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
parent, err := os.Stat(filepath.Join(mountpoint, ".."))
if err != nil {
return false, err
}
mntpointSt := mntpoint.Sys().(*syscall.Stat_t)
parentSt := parent.Sys().(*syscall.Stat_t)
return mntpointSt.Dev != parentSt.Dev, nil
}
type probeData struct {
fsName string
magic string
offset uint64
}
// ProbeFsType returns the filesystem name for the given device id.
func ProbeFsType(device string) (string, error) {
probes := []probeData{
{"btrfs", "_BHRfS_M", 0x10040},
{"ext4", "\123\357", 0x438},
{"xfs", "XFSB", 0},
}
maxLen := uint64(0)
for _, p := range probes {
l := p.offset + uint64(len(p.magic))
if l > maxLen {
maxLen = l
}
}
file, err := os.Open(device)
if err != nil {
return "", err
}
defer file.Close()
buffer := make([]byte, maxLen)
l, err := file.Read(buffer)
if err != nil {
return "", err
}
if uint64(l) != maxLen {
return "", fmt.Errorf("devmapper: unable to detect filesystem type of %s, short read", device)
}
for _, p := range probes {
if bytes.Equal([]byte(p.magic), buffer[p.offset:p.offset+uint64(len(p.magic))]) {
return p.fsName, nil
}
}
return "", fmt.Errorf("devmapper: Unknown filesystem type on %s", device)
}
func joinMountOptions(a, b string) string {
if a == "" {
return b
}
if b == "" {
return a
}
return a + "," + b
}