Merge component 'engine' from git@github.com:moby/moby master

This commit is contained in:
GordonTheTurtle
2018-04-20 17:07:42 +00:00
322 changed files with 7124 additions and 3902 deletions
+5 -4
View File
@@ -37,6 +37,7 @@ import (
"github.com/docker/docker/restartmanager"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/volume"
volumemounts "github.com/docker/docker/volume/mounts"
"github.com/docker/go-connections/nat"
units "github.com/docker/go-units"
"github.com/docker/libnetwork"
@@ -94,7 +95,7 @@ type Container struct {
RestartCount int
HasBeenStartedBefore bool
HasBeenManuallyStopped bool // used for unless-stopped restart policy
MountPoints map[string]*volume.MountPoint
MountPoints map[string]*volumemounts.MountPoint
HostConfig *containertypes.HostConfig `json:"-"` // do not serialize the host config in the json, otherwise we'll make the container unportable
ExecCommands *exec.Store `json:"-"`
DependencyStore agentexec.DependencyGetter `json:"-"`
@@ -128,7 +129,7 @@ func NewBaseContainer(id, root string) *Container {
State: NewState(),
ExecCommands: exec.NewStore(),
Root: root,
MountPoints: make(map[string]*volume.MountPoint),
MountPoints: make(map[string]*volumemounts.MountPoint),
StreamConfig: stream.NewConfig(),
attachContext: &attachContext{},
}
@@ -450,8 +451,8 @@ func (container *Container) AddMountPointWithVolume(destination string, vol volu
if operatingSystem == "" {
operatingSystem = runtime.GOOS
}
volumeParser := volume.NewParser(operatingSystem)
container.MountPoints[destination] = &volume.MountPoint{
volumeParser := volumemounts.NewParser(operatingSystem)
container.MountPoints[destination] = &volumemounts.MountPoint{
Type: mounttypes.TypeVolume,
Name: vol.Name(),
Driver: vol.DriverName(),
@@ -15,6 +15,7 @@ import (
"github.com/docker/docker/pkg/mount"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/volume"
volumemounts "github.com/docker/docker/volume/mounts"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -61,7 +62,7 @@ func (container *Container) BuildHostnameFile() error {
func (container *Container) NetworkMounts() []Mount {
var mounts []Mount
shared := container.HostConfig.NetworkMode.IsContainer()
parser := volume.NewParser(container.OS)
parser := volumemounts.NewParser(container.OS)
if container.ResolvConfPath != "" {
if _, err := os.Stat(container.ResolvConfPath); err != nil {
logrus.Warnf("ResolvConfPath set to %q, but can't stat this filename (err = %v); skipping", container.ResolvConfPath, err)
@@ -198,7 +199,7 @@ func (container *Container) UnmountIpcMount(unmount func(pth string) error) erro
// IpcMounts returns the list of IPC mounts
func (container *Container) IpcMounts() []Mount {
var mounts []Mount
parser := volume.NewParser(container.OS)
parser := volumemounts.NewParser(container.OS)
if container.HasMountFor("/dev/shm") {
return mounts
@@ -402,7 +403,7 @@ func copyExistingContents(source, destination string) error {
// TmpfsMounts returns the list of tmpfs mounts
func (container *Container) TmpfsMounts() ([]Mount, error) {
parser := volume.NewParser(container.OS)
parser := volumemounts.NewParser(container.OS)
var mounts []Mount
for dest, data := range container.HostConfig.Tmpfs {
mounts = append(mounts, Mount{
+2 -2
View File
@@ -4,7 +4,7 @@ package daemon // import "github.com/docker/docker/daemon"
import (
"github.com/docker/docker/container"
"github.com/docker/docker/volume"
volumemounts "github.com/docker/docker/volume/mounts"
)
// checkIfPathIsInAVolume checks if the path is in a volume. If it is, it
@@ -12,7 +12,7 @@ import (
// cannot be configured with a read-only rootfs.
func checkIfPathIsInAVolume(container *container.Container, absPath string) (bool, error) {
var toVolume bool
parser := volume.NewParser(container.OS)
parser := volumemounts.NewParser(container.OS)
for _, mnt := range container.MountPoints {
if toVolume = parser.HasResource(mnt, absPath); toVolume {
if mnt.RW {
+2 -2
View File
@@ -20,7 +20,7 @@ import (
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/pkg/truncindex"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/volume"
volumemounts "github.com/docker/docker/volume/mounts"
"github.com/docker/go-connections/nat"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
@@ -296,7 +296,7 @@ func (daemon *Daemon) verifyContainerSettings(platform string, hostConfig *conta
}
// Validate mounts; check if host directories still exist
parser := volume.NewParser(platform)
parser := volumemounts.NewParser(platform)
for _, cfg := range hostConfig.Mounts {
if err := parser.ValidateMountConfig(&cfg); err != nil {
return nil, err
+2 -2
View File
@@ -7,7 +7,7 @@ import (
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/container"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/volume"
volumemounts "github.com/docker/docker/volume/mounts"
)
// createContainerOSSpecificSettings performs host-OS specific container create functionality
@@ -26,7 +26,7 @@ func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Con
}
hostConfig.Isolation = "hyperv"
}
parser := volume.NewParser(container.OS)
parser := volumemounts.NewParser(container.OS)
for spec := range config.Volumes {
mp, err := parser.ParseMountRaw(spec, hostConfig.VolumeDriver)
+4 -11
View File
@@ -179,11 +179,6 @@ func (daemon *Daemon) restore() error {
delete(containers, id)
continue
}
// verify that all volumes valid and have been migrated from the pre-1.7 layout
if err := daemon.verifyVolumesInfo(c); err != nil {
// don't skip the container due to error
logrus.Errorf("Failed to verify volumes for container '%s': %v", c.ID, err)
}
if err := daemon.Register(c); err != nil {
logrus.Errorf("Failed to register container %s: %s", c.ID, err)
delete(containers, id)
@@ -1150,17 +1145,15 @@ func setDefaultMtu(conf *config.Config) {
}
func (daemon *Daemon) configureVolumes(rootIDs idtools.IDPair) (*store.VolumeStore, error) {
volumesDriver, err := local.New(daemon.configStore.Root, rootIDs)
volumeDriver, err := local.New(daemon.configStore.Root, rootIDs)
if err != nil {
return nil, err
}
volumedrivers.RegisterPluginGetter(daemon.PluginStore)
if !volumedrivers.Register(volumesDriver, volumesDriver.Name()) {
drivers := volumedrivers.NewStore(daemon.PluginStore)
if !drivers.Register(volumeDriver, volumeDriver.Name()) {
return nil, errors.New("local volume driver could not be registered")
}
return store.New(daemon.configStore.Root)
return store.New(daemon.configStore.Root, drivers)
}
// IsShuttingDown tells whether the daemon is shutting down or not
+3 -4
View File
@@ -13,7 +13,6 @@ import (
_ "github.com/docker/docker/pkg/discovery/memory"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/truncindex"
"github.com/docker/docker/volume"
volumedrivers "github.com/docker/docker/volume/drivers"
"github.com/docker/docker/volume/local"
"github.com/docker/docker/volume/store"
@@ -121,7 +120,8 @@ func initDaemonWithVolumeStore(tmp string) (*Daemon, error) {
repository: tmp,
root: tmp,
}
daemon.volumes, err = store.New(tmp)
drivers := volumedrivers.NewStore(nil)
daemon.volumes, err = store.New(tmp, drivers)
if err != nil {
return nil, err
}
@@ -130,7 +130,7 @@ func initDaemonWithVolumeStore(tmp string) (*Daemon, error) {
if err != nil {
return nil, err
}
volumedrivers.Register(volumesDriver, volumesDriver.Name())
drivers.Register(volumesDriver, volumesDriver.Name())
return daemon, nil
}
@@ -208,7 +208,6 @@ func TestContainerInitDNS(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer volumedrivers.Unregister(volume.DefaultDriverName)
c, err := daemon.load(containerID)
if err != nil {
+2 -2
View File
@@ -33,7 +33,7 @@ import (
"github.com/docker/docker/pkg/parsers/kernel"
"github.com/docker/docker/pkg/sysinfo"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/volume"
volumemounts "github.com/docker/docker/volume/mounts"
"github.com/docker/libnetwork"
nwconfig "github.com/docker/libnetwork/config"
"github.com/docker/libnetwork/drivers/bridge"
@@ -626,7 +626,7 @@ func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes.
return warnings, fmt.Errorf("Unknown runtime specified %s", hostConfig.Runtime)
}
parser := volume.NewParser(runtime.GOOS)
parser := volumemounts.NewParser(runtime.GOOS)
for dest := range hostConfig.Tmpfs {
if err := parser.ValidateTmpfsMountDestination(dest); err != nil {
return warnings, err
@@ -6,18 +6,11 @@ import (
"errors"
"io/ioutil"
"os"
"path/filepath"
"testing"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/container"
"github.com/docker/docker/daemon/config"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/volume"
"github.com/docker/docker/volume/drivers"
"github.com/docker/docker/volume/local"
"github.com/docker/docker/volume/store"
"github.com/gotestyourself/gotestyourself/assert"
)
type fakeContainerGetter struct {
@@ -273,85 +266,3 @@ func TestNetworkOptions(t *testing.T) {
t.Fatal("Expected networkOptions error, got nil")
}
}
func TestMigratePre17Volumes(t *testing.T) {
rootDir, err := ioutil.TempDir("", "test-daemon-volumes")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(rootDir)
volumeRoot := filepath.Join(rootDir, "volumes")
err = os.MkdirAll(volumeRoot, 0755)
if err != nil {
t.Fatal(err)
}
containerRoot := filepath.Join(rootDir, "containers")
cid := "1234"
err = os.MkdirAll(filepath.Join(containerRoot, cid), 0755)
assert.NilError(t, err)
vid := "5678"
vfsPath := filepath.Join(rootDir, "vfs", "dir", vid)
err = os.MkdirAll(vfsPath, 0755)
assert.NilError(t, err)
config := []byte(`
{
"ID": "` + cid + `",
"Volumes": {
"/foo": "` + vfsPath + `",
"/bar": "/foo",
"/quux": "/quux"
},
"VolumesRW": {
"/foo": true,
"/bar": true,
"/quux": false
}
}
`)
volStore, err := store.New(volumeRoot)
if err != nil {
t.Fatal(err)
}
drv, err := local.New(volumeRoot, idtools.IDPair{UID: 0, GID: 0})
if err != nil {
t.Fatal(err)
}
volumedrivers.Register(drv, volume.DefaultDriverName)
daemon := &Daemon{
root: rootDir,
repository: containerRoot,
volumes: volStore,
}
err = ioutil.WriteFile(filepath.Join(containerRoot, cid, "config.v2.json"), config, 600)
if err != nil {
t.Fatal(err)
}
c, err := daemon.load(cid)
if err != nil {
t.Fatal(err)
}
if err := daemon.verifyVolumesInfo(c); err != nil {
t.Fatal(err)
}
expected := map[string]volume.MountPoint{
"/foo": {Destination: "/foo", RW: true, Name: vid},
"/bar": {Source: "/foo", Destination: "/bar", RW: true},
"/quux": {Source: "/quux", Destination: "/quux", RW: false},
}
for id, mp := range c.MountPoints {
x, exists := expected[id]
if !exists {
t.Fatal("volume not migrated")
}
if mp.Source != x.Source || mp.Destination != x.Destination || mp.RW != x.RW || mp.Name != x.Name {
t.Fatalf("got unexpected mountpoint, expected: %+v, got: %+v", x, mp)
}
}
}
@@ -632,13 +632,6 @@ func setupDaemonProcess(config *config.Config) error {
return nil
}
// verifyVolumesInfo is a no-op on windows.
// This is called during daemon initialization to migrate volumes from pre-1.7.
// volumes were not supported on windows pre-1.7
func (daemon *Daemon) verifyVolumesInfo(container *container.Container) error {
return nil
}
func (daemon *Daemon) setupSeccompProfile() error {
return nil
}
+29 -31
View File
@@ -35,41 +35,39 @@ func (daemon *Daemon) SystemDiskUsage(ctx context.Context) (*types.DiskUsage, er
return nil, fmt.Errorf("failed to retrieve image list: %v", err)
}
// Get all local volumes
allVolumes := []*types.Volume{}
getLocalVols := func(v volume.Volume) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
if d, ok := v.(volume.DetailedVolume); ok {
// skip local volumes with mount options since these could have external
// mounted filesystems that will be slow to enumerate.
if len(d.Options()) > 0 {
return nil
}
}
name := v.Name()
refs := daemon.volumes.Refs(v)
tv := volumeToAPIType(v)
sz, err := directory.Size(ctx, v.Path())
if err != nil {
logrus.Warnf("failed to determine size of volume %v", name)
sz = -1
}
tv.UsageData = &types.VolumeUsageData{Size: sz, RefCount: int64(len(refs))}
allVolumes = append(allVolumes, tv)
}
return nil
}
err = daemon.traverseLocalVolumes(getLocalVols)
volumes, err := daemon.volumes.FilterByDriver(volume.DefaultDriverName)
if err != nil {
return nil, err
}
var allVolumes []*types.Volume
for _, v := range volumes {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
if d, ok := v.(volume.DetailedVolume); ok {
if len(d.Options()) > 0 {
// skip local volumes with mount options since these could have external
// mounted filesystems that will be slow to enumerate.
continue
}
}
name := v.Name()
refs := daemon.volumes.Refs(v)
tv := volumeToAPIType(v)
sz, err := directory.Size(ctx, v.Path())
if err != nil {
logrus.Warnf("failed to determine size of volume %v", name)
sz = -1
}
tv.UsageData = &types.VolumeUsageData{Size: sz, RefCount: int64(len(refs))}
allVolumes = append(allVolumes, tv)
}
allLayersSize, err := daemon.imageService.LayerDiskUsage(ctx)
if err != nil {
return nil, err
@@ -109,7 +109,7 @@ func Init(root string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
switch fsMagic {
case graphdriver.FsMagicAufs, graphdriver.FsMagicBtrfs, graphdriver.FsMagicEcryptfs:
logrus.Errorf("AUFS is not supported over %s", backingFs)
logrus.WithField("storage-driver", "aufs").Errorf("AUFS is not supported over %s", backingFs)
return nil, graphdriver.ErrIncompatibleFS
}
@@ -143,10 +143,7 @@ func Init(root string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
return nil, err
}
}
logger := logrus.WithFields(logrus.Fields{
"module": "graphdriver",
"driver": "aufs",
})
logger := logrus.WithField("storage-driver", "aufs")
for _, path := range []string{"mnt", "diff"} {
p := filepath.Join(root, path)
@@ -310,9 +307,8 @@ func (a *Driver) Remove(id string) error {
}
logger := logrus.WithFields(logrus.Fields{
"module": "graphdriver",
"driver": "aufs",
"layer": id,
"storage-driver": "aufs",
"layer": id,
})
var retries int
@@ -443,7 +439,7 @@ func (a *Driver) Put(id string) error {
err := a.unmount(m)
if err != nil {
logrus.Debugf("Failed to unmount %s aufs: %v", id, err)
logrus.WithField("storage-driver", "aufs").Debugf("Failed to unmount %s aufs: %v", id, err)
}
return err
}
@@ -601,7 +597,7 @@ func (a *Driver) Cleanup() error {
for _, m := range dirs {
if err := a.unmount(m); err != nil {
logrus.Debugf("aufs error unmounting %s: %s", m, err)
logrus.WithField("storage-driver", "aufs").Debugf("error unmounting %s: %s", m, err)
}
}
return mountpk.RecursiveUnmount(a.root)
@@ -656,17 +652,18 @@ func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err erro
// useDirperm checks dirperm1 mount option can be used with the current
// version of aufs.
func useDirperm() bool {
logger := logrus.WithField("storage-driver", "aufs")
enableDirpermLock.Do(func() {
base, err := ioutil.TempDir("", "docker-aufs-base")
if err != nil {
logrus.Errorf("error checking dirperm1: %v", err)
logger.Errorf("error checking dirperm1: %v", err)
return
}
defer os.RemoveAll(base)
union, err := ioutil.TempDir("", "docker-aufs-union")
if err != nil {
logrus.Errorf("error checking dirperm1: %v", err)
logger.Errorf("error checking dirperm1: %v", err)
return
}
defer os.RemoveAll(union)
@@ -677,7 +674,7 @@ func useDirperm() bool {
}
enableDirperm = true
if err := Unmount(union); err != nil {
logrus.Errorf("error checking dirperm1: failed to unmount %v", err)
logger.Errorf("error checking dirperm1: failed to unmount %v", err)
}
})
return enableDirperm
@@ -12,7 +12,7 @@ import (
// Unmount the target specified.
func Unmount(target string) error {
if err := exec.Command("auplink", target, "flush").Run(); err != nil {
logrus.Warnf("Couldn't run auplink before unmount %s: %s", target, err)
logrus.WithField("storage-driver", "aufs").Warnf("Couldn't run auplink before unmount %s: %s", target, err)
}
return unix.Unmount(target, 0)
}
@@ -291,10 +291,10 @@ func subvolDelete(dirpath, name string, quotaEnabled bool) error {
_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QGROUP_CREATE,
uintptr(unsafe.Pointer(&args)))
if errno != 0 {
logrus.Errorf("Failed to delete btrfs qgroup %v for %s: %v", qgroupid, fullPath, errno.Error())
logrus.WithField("storage-driver", "btrfs").Errorf("Failed to delete btrfs qgroup %v for %s: %v", qgroupid, fullPath, errno.Error())
}
} else {
logrus.Errorf("Failed to lookup btrfs qgroup for %s: %v", fullPath, err.Error())
logrus.WithField("storage-driver", "btrfs").Errorf("Failed to lookup btrfs qgroup for %s: %v", fullPath, err.Error())
}
}
@@ -276,7 +276,7 @@ func (devices *DeviceSet) ensureImage(name string, size int64) (string, error) {
if !os.IsNotExist(err) {
return "", err
}
logrus.Debugf("devmapper: Creating loopback file %s for device-manage use", filename)
logrus.WithField("storage-driver", "devicemapper").Debugf("Creating loopback file %s for device-manage use", filename)
file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600)
if err != nil {
return "", err
@@ -297,7 +297,7 @@ func (devices *DeviceSet) ensureImage(name string, size int64) (string, error) {
return "", fmt.Errorf("devmapper: Unable to grow loopback file %s: %v", filename, err)
}
} else if fi.Size() > size {
logrus.Warnf("devmapper: Can't shrink loopback file %s", filename)
logrus.WithField("storage-driver", "devicemapper").Warnf("Can't shrink loopback file %s", filename)
}
}
return filename, nil
@@ -403,39 +403,40 @@ func (devices *DeviceSet) lookupDeviceWithLock(hash string) (*devInfo, error) {
// This function relies on that device hash map has been loaded in advance.
// Should be called with devices.Lock() held.
func (devices *DeviceSet) constructDeviceIDMap() {
logrus.Debug("devmapper: constructDeviceIDMap()")
defer logrus.Debug("devmapper: constructDeviceIDMap() END")
logrus.WithField("storage-driver", "devicemapper").Debug("constructDeviceIDMap()")
defer logrus.WithField("storage-driver", "devicemapper").Debug("constructDeviceIDMap() END")
for _, info := range devices.Devices {
devices.markDeviceIDUsed(info.DeviceID)
logrus.Debugf("devmapper: Added deviceId=%d to DeviceIdMap", info.DeviceID)
logrus.WithField("storage-driver", "devicemapper").Debugf("Added deviceId=%d to DeviceIdMap", info.DeviceID)
}
}
func (devices *DeviceSet) deviceFileWalkFunction(path string, finfo os.FileInfo) error {
logger := logrus.WithField("storage-driver", "devicemapper")
// Skip some of the meta files which are not device files.
if strings.HasSuffix(finfo.Name(), ".migrated") {
logrus.Debugf("devmapper: Skipping file %s", path)
logger.Debugf("Skipping file %s", path)
return nil
}
if strings.HasPrefix(finfo.Name(), ".") {
logrus.Debugf("devmapper: Skipping file %s", path)
logger.Debugf("Skipping file %s", path)
return nil
}
if finfo.Name() == deviceSetMetaFile {
logrus.Debugf("devmapper: Skipping file %s", path)
logger.Debugf("Skipping file %s", path)
return nil
}
if finfo.Name() == transactionMetaFile {
logrus.Debugf("devmapper: Skipping file %s", path)
logger.Debugf("Skipping file %s", path)
return nil
}
logrus.Debugf("devmapper: Loading data for file %s", path)
logger.Debugf("Loading data for file %s", path)
hash := finfo.Name()
if hash == "base" {
@@ -452,12 +453,12 @@ func (devices *DeviceSet) deviceFileWalkFunction(path string, finfo os.FileInfo)
}
func (devices *DeviceSet) loadDeviceFilesOnStart() error {
logrus.Debug("devmapper: loadDeviceFilesOnStart()")
defer logrus.Debug("devmapper: loadDeviceFilesOnStart() END")
logrus.WithField("storage-driver", "devicemapper").Debug("loadDeviceFilesOnStart()")
defer logrus.WithField("storage-driver", "devicemapper").Debug("loadDeviceFilesOnStart() END")
var scan = func(path string, info os.FileInfo, err error) error {
if err != nil {
logrus.Debugf("devmapper: Can't walk the file %s", path)
logrus.WithField("storage-driver", "devicemapper").Debugf("Can't walk the file %s", path)
return nil
}
@@ -474,7 +475,7 @@ func (devices *DeviceSet) loadDeviceFilesOnStart() error {
// Should be called with devices.Lock() held.
func (devices *DeviceSet) unregisterDevice(hash string) error {
logrus.Debugf("devmapper: unregisterDevice(%v)", hash)
logrus.WithField("storage-driver", "devicemapper").Debugf("unregisterDevice(%v)", hash)
info := &devInfo{
Hash: hash,
}
@@ -482,7 +483,7 @@ func (devices *DeviceSet) unregisterDevice(hash string) error {
delete(devices.Devices, hash)
if err := devices.removeMetadata(info); err != nil {
logrus.Debugf("devmapper: Error removing metadata: %s", err)
logrus.WithField("storage-driver", "devicemapper").Debugf("Error removing metadata: %s", err)
return err
}
@@ -491,7 +492,7 @@ func (devices *DeviceSet) unregisterDevice(hash string) error {
// Should be called with devices.Lock() held.
func (devices *DeviceSet) registerDevice(id int, hash string, size uint64, transactionID uint64) (*devInfo, error) {
logrus.Debugf("devmapper: registerDevice(%v, %v)", id, hash)
logrus.WithField("storage-driver", "devicemapper").Debugf("registerDevice(%v, %v)", id, hash)
info := &devInfo{
Hash: hash,
DeviceID: id,
@@ -513,7 +514,7 @@ func (devices *DeviceSet) registerDevice(id int, hash string, size uint64, trans
}
func (devices *DeviceSet) activateDeviceIfNeeded(info *devInfo, ignoreDeleted bool) error {
logrus.Debugf("devmapper: activateDeviceIfNeeded(%v)", info.Hash)
logrus.WithField("storage-driver", "devicemapper").Debugf("activateDeviceIfNeeded(%v)", info.Hash)
if info.Deleted && !ignoreDeleted {
return fmt.Errorf("devmapper: Can't activate device %v as it is marked for deletion", info.Hash)
@@ -568,7 +569,7 @@ func determineDefaultFS() string {
return "xfs"
}
logrus.Warnf("devmapper: XFS is not supported in your system (%v). Defaulting to ext4 filesystem", err)
logrus.WithField("storage-driver", "devicemapper").Warnf("XFS is not supported in your system (%v). Defaulting to ext4 filesystem", err)
return "ext4"
}
@@ -597,12 +598,12 @@ func (devices *DeviceSet) createFilesystem(info *devInfo) (err error) {
args = append(args, devices.mkfsArgs...)
args = append(args, devname)
logrus.Infof("devmapper: Creating filesystem %s on device %s, mkfs args: %v", devices.filesystem, info.Name(), args)
logrus.WithField("storage-driver", "devicemapper").Infof("Creating filesystem %s on device %s, mkfs args: %v", devices.filesystem, info.Name(), args)
defer func() {
if err != nil {
logrus.Infof("devmapper: Error while creating filesystem %s on device %s: %v", devices.filesystem, info.Name(), err)
logrus.WithField("storage-driver", "devicemapper").Infof("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())
logrus.WithField("storage-driver", "devicemapper").Infof("Successfully created filesystem %s on device %s", devices.filesystem, info.Name())
}
}()
@@ -668,7 +669,7 @@ func (devices *DeviceSet) cleanupDeletedDevices() error {
if !info.Deleted {
continue
}
logrus.Debugf("devmapper: Found deleted device %s.", info.Hash)
logrus.WithField("storage-driver", "devicemapper").Debugf("Found deleted device %s.", info.Hash)
deletedDevices = append(deletedDevices, info)
}
@@ -679,7 +680,7 @@ func (devices *DeviceSet) cleanupDeletedDevices() error {
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)
logrus.WithField("storage-driver", "devicemapper").Warnf("Deletion of device %s, device_id=%v failed:%v", info.Hash, info.DeviceID, err)
}
}
@@ -701,7 +702,7 @@ func (devices *DeviceSet) startDeviceDeletionWorker() {
return
}
logrus.Debug("devmapper: Worker to cleanup deleted devices started")
logrus.WithField("storage-driver", "devicemapper").Debug("Worker to cleanup deleted devices started")
for range devices.deletionWorkerTicker.C {
devices.cleanupDeletedDevices()
}
@@ -797,8 +798,10 @@ func (devices *DeviceSet) createRegisterDevice(hash string) (*devInfo, error) {
return nil, err
}
logger := logrus.WithField("storage-driver", "devicemapper")
if err := devices.openTransaction(hash, deviceID); err != nil {
logrus.Debugf("devmapper: Error opening transaction hash = %s deviceID = %d", hash, deviceID)
logger.Debugf("Error opening transaction hash = %s deviceID = %d", hash, deviceID)
devices.markDeviceIDFree(deviceID)
return nil, err
}
@@ -810,7 +813,7 @@ func (devices *DeviceSet) createRegisterDevice(hash string) (*devInfo, error) {
// happen. Now we have a mechanism to find
// a free device ID. So something is not right.
// Give a warning and continue.
logrus.Errorf("devmapper: Device ID %d exists in pool but it is supposed to be unused", deviceID)
logger.Errorf("Device ID %d exists in pool but it is supposed to be unused", deviceID)
deviceID, err = devices.getNextFreeDeviceID()
if err != nil {
return nil, err
@@ -819,14 +822,14 @@ func (devices *DeviceSet) createRegisterDevice(hash string) (*devInfo, error) {
devices.refreshTransaction(deviceID)
continue
}
logrus.Debugf("devmapper: Error creating device: %s", err)
logger.Debugf("Error creating device: %s", err)
devices.markDeviceIDFree(deviceID)
return nil, err
}
break
}
logrus.Debugf("devmapper: Registering device (id %v) with FS size %v", deviceID, devices.baseFsSize)
logger.Debugf("Registering device (id %v) with FS size %v", deviceID, devices.baseFsSize)
info, err := devices.registerDevice(deviceID, hash, devices.baseFsSize, devices.OpenTransactionID)
if err != nil {
_ = devicemapper.DeleteDevice(devices.getPoolDevName(), deviceID)
@@ -895,8 +898,10 @@ func (devices *DeviceSet) createRegisterSnapDevice(hash string, baseInfo *devInf
return err
}
logger := logrus.WithField("storage-driver", "devicemapper")
if err := devices.openTransaction(hash, deviceID); err != nil {
logrus.Debugf("devmapper: Error opening transaction hash = %s deviceID = %d", hash, deviceID)
logger.Debugf("Error opening transaction hash = %s deviceID = %d", hash, deviceID)
devices.markDeviceIDFree(deviceID)
return err
}
@@ -908,7 +913,7 @@ func (devices *DeviceSet) createRegisterSnapDevice(hash string, baseInfo *devInf
// happen. Now we have a mechanism to find
// a free device ID. So something is not right.
// Give a warning and continue.
logrus.Errorf("devmapper: Device ID %d exists in pool but it is supposed to be unused", deviceID)
logger.Errorf("Device ID %d exists in pool but it is supposed to be unused", deviceID)
deviceID, err = devices.getNextFreeDeviceID()
if err != nil {
return err
@@ -917,7 +922,7 @@ func (devices *DeviceSet) createRegisterSnapDevice(hash string, baseInfo *devInf
devices.refreshTransaction(deviceID)
continue
}
logrus.Debugf("devmapper: Error creating snap device: %s", err)
logger.Debugf("Error creating snap device: %s", err)
devices.markDeviceIDFree(deviceID)
return err
}
@@ -927,7 +932,7 @@ func (devices *DeviceSet) createRegisterSnapDevice(hash string, baseInfo *devInf
if _, err := devices.registerDevice(deviceID, hash, size, devices.OpenTransactionID); err != nil {
devicemapper.DeleteDevice(devices.getPoolDevName(), deviceID)
devices.markDeviceIDFree(deviceID)
logrus.Debugf("devmapper: Error registering device: %s", err)
logger.Debugf("Error registering device: %s", err)
return err
}
@@ -942,20 +947,21 @@ func (devices *DeviceSet) createRegisterSnapDevice(hash string, baseInfo *devInf
func (devices *DeviceSet) loadMetadata(hash string) *devInfo {
info := &devInfo{Hash: hash, devices: devices}
logger := logrus.WithField("storage-driver", "devicemapper")
jsonData, err := ioutil.ReadFile(devices.metadataFile(info))
if err != nil {
logrus.Debugf("devmapper: Failed to read %s with err: %v", devices.metadataFile(info), err)
logger.Debugf("Failed to read %s with err: %v", devices.metadataFile(info), err)
return nil
}
if err := json.Unmarshal(jsonData, &info); err != nil {
logrus.Debugf("devmapper: Failed to unmarshal devInfo from %s with err: %v", devices.metadataFile(info), err)
logger.Debugf("Failed to unmarshal devInfo from %s with err: %v", devices.metadataFile(info), err)
return nil
}
if info.DeviceID > maxDeviceID {
logrus.Errorf("devmapper: Ignoring Invalid DeviceId=%d", info.DeviceID)
logger.Errorf("Ignoring Invalid DeviceId=%d", info.DeviceID)
return nil
}
@@ -970,7 +976,7 @@ func getDeviceUUID(device string) (string, error) {
uuid := strings.TrimSuffix(string(out), "\n")
uuid = strings.TrimSpace(uuid)
logrus.Debugf("devmapper: UUID for device: %s is:%s", device, uuid)
logrus.WithField("storage-driver", "devicemapper").Debugf("UUID for device: %s is:%s", device, uuid)
return uuid, nil
}
@@ -1018,7 +1024,7 @@ func (devices *DeviceSet) verifyBaseDeviceUUIDFS(baseInfo *devInfo) error {
// 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)
logrus.WithField("storage-driver", "devicemapper").Warnf("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
}
return nil
@@ -1048,7 +1054,7 @@ func (devices *DeviceSet) saveBaseDeviceUUID(baseInfo *devInfo) error {
}
func (devices *DeviceSet) createBaseImage() error {
logrus.Debug("devmapper: Initializing base device-mapper thin volume")
logrus.WithField("storage-driver", "devicemapper").Debug("Initializing base device-mapper thin volume")
// Create initial device
info, err := devices.createRegisterDevice("")
@@ -1056,7 +1062,7 @@ func (devices *DeviceSet) createBaseImage() error {
return err
}
logrus.Debug("devmapper: Creating filesystem on base device-mapper thin volume")
logrus.WithField("storage-driver", "devicemapper").Debug("Creating filesystem on base device-mapper thin volume")
if err := devices.activateDeviceIfNeeded(info, false); err != nil {
return err
@@ -1082,7 +1088,7 @@ func (devices *DeviceSet) createBaseImage() error {
// 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)
logrus.WithField("storage-driver", "devicemapper").Debugf("Checking for existence of the pool %s", thinPoolDevice)
info, err := devicemapper.GetInfo(thinPoolDevice)
if err != nil {
@@ -1229,7 +1235,7 @@ func (devices *DeviceSet) setupBaseImage() error {
return devices.checkGrowBaseDeviceFS(oldInfo)
}
logrus.Debug("devmapper: Removing uninitialized base image")
logrus.WithField("storage-driver", "devicemapper").Debug("Removing uninitialized base image")
// If previous base device is in deferred delete state,
// that needs to be cleaned up first. So don't try
// deferred deletion.
@@ -1374,24 +1380,26 @@ func (devices *DeviceSet) removeTransactionMetaData() error {
}
func (devices *DeviceSet) rollbackTransaction() error {
logrus.Debugf("devmapper: Rolling back open transaction: TransactionID=%d hash=%s device_id=%d", devices.OpenTransactionID, devices.DeviceIDHash, devices.DeviceID)
logger := logrus.WithField("storage-driver", "devicemapper")
logger.Debugf("Rolling back open transaction: TransactionID=%d hash=%s device_id=%d", devices.OpenTransactionID, devices.DeviceIDHash, devices.DeviceID)
// 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.
if err := devicemapper.DeleteDevice(devices.getPoolDevName(), devices.DeviceID); err != nil {
logrus.Errorf("devmapper: Unable to delete device: %s", err)
logger.Errorf("Unable to delete device: %s", err)
}
dinfo := &devInfo{Hash: devices.DeviceIDHash}
if err := devices.removeMetadata(dinfo); err != nil {
logrus.Errorf("devmapper: Unable to remove metadata: %s", err)
logger.Errorf("Unable to remove metadata: %s", err)
} else {
devices.markDeviceIDFree(devices.DeviceID)
}
if err := devices.removeTransactionMetaData(); err != nil {
logrus.Errorf("devmapper: Unable to remove transaction meta file %s: %s", devices.transactionMetaFile(), err)
logger.Errorf("Unable to remove transaction meta file %s: %s", devices.transactionMetaFile(), err)
}
return nil
@@ -1411,7 +1419,7 @@ func (devices *DeviceSet) processPendingTransaction() error {
// If open transaction ID is less than pool transaction ID, something
// is wrong. Bail out.
if devices.OpenTransactionID < devices.TransactionID {
logrus.Errorf("devmapper: Open Transaction id %d is less than pool transaction id %d", devices.OpenTransactionID, devices.TransactionID)
logrus.WithField("storage-driver", "devicemapper").Errorf("Open Transaction id %d is less than pool transaction id %d", devices.OpenTransactionID, devices.TransactionID)
return nil
}
@@ -1468,7 +1476,7 @@ func (devices *DeviceSet) refreshTransaction(DeviceID int) error {
func (devices *DeviceSet) closeTransaction() error {
if err := devices.updatePoolTransactionID(); err != nil {
logrus.Debug("devmapper: Failed to close Transaction")
logrus.WithField("storage-driver", "devicemapper").Debug("Failed to close Transaction")
return err
}
return nil
@@ -1477,7 +1485,7 @@ func (devices *DeviceSet) closeTransaction() error {
func determineDriverCapabilities(version string) error {
// Kernel driver version >= 4.27.0 support deferred removal
logrus.Debugf("devicemapper: kernel dm driver version is %s", version)
logrus.WithField("storage-driver", "devicemapper").Debugf("kernel dm driver version is %s", version)
versionSplit := strings.Split(version, ".")
major, err := strconv.Atoi(versionSplit[0])
@@ -1523,7 +1531,7 @@ func getDeviceMajorMinor(file *os.File) (uint64, uint64, error) {
majorNum := major(dev)
minorNum := minor(dev)
logrus.Debugf("devmapper: Major:Minor for device: %s is:%v:%v", file.Name(), majorNum, minorNum)
logrus.WithField("storage-driver", "devicemapper").Debugf("Major:Minor for device: %s is:%v:%v", file.Name(), majorNum, minorNum)
return majorNum, minorNum, nil
}
@@ -1532,7 +1540,7 @@ func getDeviceMajorMinor(file *os.File) (uint64, uint64, error) {
func getLoopFileDeviceMajMin(filename string) (string, uint64, uint64, error) {
file, err := os.Open(filename)
if err != nil {
logrus.Debugf("devmapper: Failed to open file %s", filename)
logrus.WithField("storage-driver", "devicemapper").Debugf("Failed to open file %s", filename)
return "", 0, 0, err
}
@@ -1563,7 +1571,7 @@ func (devices *DeviceSet) getThinPoolDataMetaMajMin() (uint64, uint64, uint64, u
return 0, 0, 0, 0, err
}
logrus.Debugf("devmapper: poolDataMajMin=%s poolMetaMajMin=%s\n", poolDataMajMin, poolMetadataMajMin)
logrus.WithField("storage-driver", "devicemapper").Debugf("poolDataMajMin=%s poolMetaMajMin=%s\n", poolDataMajMin, poolMetadataMajMin)
poolDataMajMinorSplit := strings.Split(poolDataMajMin, ":")
poolDataMajor, err := strconv.ParseUint(poolDataMajMinorSplit[0], 10, 32)
@@ -1643,7 +1651,7 @@ func (devices *DeviceSet) enableDeferredRemovalDeletion() error {
if !devicemapper.LibraryDeferredRemovalSupport {
return fmt.Errorf("devmapper: Deferred removal can not be enabled as libdm does not support it")
}
logrus.Debug("devmapper: Deferred removal support enabled.")
logrus.WithField("storage-driver", "devicemapper").Debug("Deferred removal support enabled.")
devices.deferredRemove = true
}
@@ -1651,7 +1659,7 @@ func (devices *DeviceSet) enableDeferredRemovalDeletion() error {
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")
}
logrus.Debug("devmapper: Deferred deletion support enabled.")
logrus.WithField("storage-driver", "devicemapper").Debug("Deferred deletion support enabled.")
devices.deferredDelete = true
}
return nil
@@ -1662,12 +1670,14 @@ func (devices *DeviceSet) initDevmapper(doInit bool) (retErr error) {
return err
}
logger := logrus.WithField("storage-driver", "devicemapper")
// https://github.com/docker/docker/issues/4036
if supported := devicemapper.UdevSetSyncSupport(true); !supported {
if dockerversion.IAmStatic == "true" {
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")
logger.Error("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")
} else {
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")
logger.Error("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")
}
if !devices.overrideUdevSyncCheck {
@@ -1702,7 +1712,7 @@ func (devices *DeviceSet) initDevmapper(doInit bool) (retErr error) {
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")
logger.WithField("direct-lvm-config", devices.lvmSetupConfig).Debugf("Setting up direct lvm mode")
if err := verifyBlockDevice(devices.lvmSetupConfig.Device, lvmSetupConfigForce); err != nil {
return err
}
@@ -1714,7 +1724,7 @@ func (devices *DeviceSet) initDevmapper(doInit bool) (retErr error) {
}
}
devices.thinPoolDevice = "docker-thinpool"
logrus.WithField("storage-driver", "devicemapper").Debugf("Setting dm.thinpooldev to %q", devices.thinPoolDevice)
logger.Debugf("Setting dm.thinpooldev to %q", devices.thinPoolDevice)
}
// Set the device prefix from the device id and inode of the docker root dir
@@ -1729,7 +1739,7 @@ func (devices *DeviceSet) initDevmapper(doInit bool) (retErr error) {
// - 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.
devices.devicePrefix = fmt.Sprintf("docker-%d:%d-%d", major(st.Dev), minor(st.Dev), st.Ino)
logrus.Debugf("devmapper: Generated prefix: %s", devices.devicePrefix)
logger.Debugf("Generated prefix: %s", devices.devicePrefix)
// Check for the existence of the thin-pool device
poolExists, err := devices.thinPoolExists(devices.getPoolName())
@@ -1749,7 +1759,7 @@ func (devices *DeviceSet) initDevmapper(doInit bool) (retErr error) {
// If the pool doesn't exist, create it
if !poolExists && devices.thinPoolDevice == "" {
logrus.Debug("devmapper: Pool doesn't exist. Creating it.")
logger.Debug("Pool doesn't exist. Creating it.")
var (
dataFile *os.File
@@ -1771,7 +1781,7 @@ func (devices *DeviceSet) initDevmapper(doInit bool) (retErr error) {
data, err := devices.ensureImage("data", devices.dataLoopbackSize)
if err != nil {
logrus.Debugf("devmapper: Error device ensureImage (data): %s", err)
logger.Debugf("Error device ensureImage (data): %s", err)
return err
}
@@ -1804,7 +1814,7 @@ func (devices *DeviceSet) initDevmapper(doInit bool) (retErr error) {
metadata, err := devices.ensureImage("metadata", devices.metaDataLoopbackSize)
if err != nil {
logrus.Debugf("devmapper: Error device ensureImage (metadata): %s", err)
logger.Debugf("Error device ensureImage (metadata): %s", err)
return err
}
@@ -1829,7 +1839,7 @@ func (devices *DeviceSet) initDevmapper(doInit bool) (retErr error) {
if retErr != nil {
err = devices.deactivatePool()
if err != nil {
logrus.Warnf("devmapper: Failed to deactivatePool: %v", err)
logger.Warnf("Failed to deactivatePool: %v", err)
}
}
}()
@@ -1841,7 +1851,7 @@ func (devices *DeviceSet) initDevmapper(doInit bool) (retErr error) {
// pool, like is it using loop devices.
if poolExists && devices.thinPoolDevice == "" {
if err := devices.loadThinPoolLoopBackInfo(); err != nil {
logrus.Debugf("devmapper: Failed to load thin pool loopback device information:%v", err)
logger.Debugf("Failed to load thin pool loopback device information:%v", err)
return err
}
}
@@ -1856,7 +1866,7 @@ func (devices *DeviceSet) initDevmapper(doInit bool) (retErr error) {
if devices.thinPoolDevice == "" {
if devices.metadataLoopFile != "" || devices.dataLoopFile != "" {
logrus.Warn("devmapper: Usage of loopback devices is strongly discouraged for production use. Please use `--storage-opt dm.thinpooldev` or use `man dockerd` to refer to dm.thinpooldev section.")
logger.Warn("Usage of loopback devices is strongly discouraged for production use. Please use `--storage-opt dm.thinpooldev` or use `man dockerd` to refer to dm.thinpooldev section.")
}
}
@@ -1869,7 +1879,7 @@ func (devices *DeviceSet) initDevmapper(doInit bool) (retErr error) {
// Setup the base image
if doInit {
if err := devices.setupBaseImage(); err != nil {
logrus.Debugf("devmapper: Error device setupBaseImage: %s", err)
logger.Debugf("Error device setupBaseImage: %s", err)
return err
}
}
@@ -1879,8 +1889,8 @@ func (devices *DeviceSet) initDevmapper(doInit bool) (retErr error) {
// AddDevice adds a device and registers in the hash.
func (devices *DeviceSet) AddDevice(hash, baseHash string, storageOpt map[string]string) error {
logrus.Debugf("devmapper: AddDevice START(hash=%s basehash=%s)", hash, baseHash)
defer logrus.Debugf("devmapper: AddDevice END(hash=%s basehash=%s)", hash, baseHash)
logrus.WithField("storage-driver", "devicemapper").Debugf("AddDevice START(hash=%s basehash=%s)", hash, baseHash)
defer logrus.WithField("storage-driver", "devicemapper").Debugf("AddDevice END(hash=%s basehash=%s)", hash, baseHash)
// If a deleted device exists, return error.
baseInfo, err := devices.lookupDeviceWithLock(baseHash)
@@ -1962,7 +1972,7 @@ func (devices *DeviceSet) markForDeferredDeletion(info *devInfo) error {
return nil
}
logrus.Debugf("devmapper: Marking device %s for deferred deletion.", info.Hash)
logrus.WithField("storage-driver", "devicemapper").Debugf("Marking device %s for deferred deletion.", info.Hash)
info.Deleted = true
@@ -1979,7 +1989,7 @@ func (devices *DeviceSet) markForDeferredDeletion(info *devInfo) error {
// Should be called with devices.Lock() held.
func (devices *DeviceSet) deleteTransaction(info *devInfo, syncDelete bool) error {
if err := devices.openTransaction(info.Hash, info.DeviceID); err != nil {
logrus.Debugf("devmapper: Error opening transaction hash = %s deviceId = %d", "", info.DeviceID)
logrus.WithField("storage-driver", "devicemapper").Debugf("Error opening transaction hash = %s deviceId = %d", "", info.DeviceID)
return err
}
@@ -1991,7 +2001,7 @@ func (devices *DeviceSet) deleteTransaction(info *devInfo, syncDelete bool) erro
// 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 {
logrus.Debugf("devmapper: Error deleting device: %s", err)
logrus.WithField("storage-driver", "devicemapper").Debugf("Error deleting device: %s", err)
return err
}
}
@@ -2018,8 +2028,9 @@ func (devices *DeviceSet) deleteTransaction(info *devInfo, syncDelete bool) erro
// Issue discard only if device open count is zero.
func (devices *DeviceSet) issueDiscard(info *devInfo) error {
logrus.Debugf("devmapper: issueDiscard START(device: %s).", info.Hash)
defer logrus.Debugf("devmapper: issueDiscard END(device: %s).", info.Hash)
logger := logrus.WithField("storage-driver", "devicemapper")
logger.Debugf("issueDiscard START(device: %s).", info.Hash)
defer logger.Debugf("issueDiscard END(device: %s).", info.Hash)
// 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
// manually.
@@ -2035,12 +2046,12 @@ func (devices *DeviceSet) issueDiscard(info *devInfo) error {
}
if devinfo.OpenCount != 0 {
logrus.Debugf("devmapper: Device: %s is in use. OpenCount=%d. Not issuing discards.", info.Hash, devinfo.OpenCount)
logger.Debugf("Device: %s is in use. OpenCount=%d. Not issuing discards.", info.Hash, devinfo.OpenCount)
return nil
}
if err := devicemapper.BlockDeviceDiscard(info.DevName()); err != nil {
logrus.Debugf("devmapper: Error discarding block on device: %s (ignoring)", err)
logger.Debugf("Error discarding block on device: %s (ignoring)", err)
}
return nil
}
@@ -2062,7 +2073,7 @@ func (devices *DeviceSet) deleteDevice(info *devInfo, syncDelete bool) error {
}
if err := devices.deactivateDeviceMode(info, deferredRemove); err != nil {
logrus.Debugf("devmapper: Error deactivating device: %s", err)
logrus.WithField("storage-driver", "devicemapper").Debugf("Error deactivating device: %s", err)
return err
}
@@ -2073,8 +2084,8 @@ func (devices *DeviceSet) deleteDevice(info *devInfo, syncDelete bool) error {
// 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 {
logrus.Debugf("devmapper: DeleteDevice START(hash=%v syncDelete=%v)", hash, syncDelete)
defer logrus.Debugf("devmapper: DeleteDevice END(hash=%v syncDelete=%v)", hash, syncDelete)
logrus.WithField("storage-driver", "devicemapper").Debugf("DeleteDevice START(hash=%v syncDelete=%v)", hash, syncDelete)
defer logrus.WithField("storage-driver", "devicemapper").Debugf("DeleteDevice END(hash=%v syncDelete=%v)", hash, syncDelete)
info, err := devices.lookupDeviceWithLock(hash)
if err != nil {
return err
@@ -2090,8 +2101,8 @@ func (devices *DeviceSet) DeleteDevice(hash string, syncDelete bool) error {
}
func (devices *DeviceSet) deactivatePool() error {
logrus.Debug("devmapper: deactivatePool() START")
defer logrus.Debug("devmapper: deactivatePool() END")
logrus.WithField("storage-driver", "devicemapper").Debug("deactivatePool() START")
defer logrus.WithField("storage-driver", "devicemapper").Debug("deactivatePool() END")
devname := devices.getPoolDevName()
devinfo, err := devicemapper.GetInfo(devname)
@@ -2107,7 +2118,7 @@ func (devices *DeviceSet) deactivatePool() error {
}
if d, err := devicemapper.GetDeps(devname); err == nil {
logrus.Warnf("devmapper: device %s still has %d active dependents", devname, d.Count)
logrus.WithField("storage-driver", "devicemapper").Warnf("device %s still has %d active dependents", devname, d.Count)
}
return nil
@@ -2119,8 +2130,8 @@ func (devices *DeviceSet) deactivateDevice(info *devInfo) error {
func (devices *DeviceSet) deactivateDeviceMode(info *devInfo, deferredRemove bool) error {
var err error
logrus.Debugf("devmapper: deactivateDevice START(%s)", info.Hash)
defer logrus.Debugf("devmapper: deactivateDevice END(%s)", info.Hash)
logrus.WithField("storage-driver", "devicemapper").Debugf("deactivateDevice START(%s)", info.Hash)
defer logrus.WithField("storage-driver", "devicemapper").Debugf("deactivateDevice END(%s)", info.Hash)
devinfo, err := devicemapper.GetInfo(info.Name())
if err != nil {
@@ -2150,8 +2161,8 @@ func (devices *DeviceSet) deactivateDeviceMode(info *devInfo, deferredRemove boo
func (devices *DeviceSet) removeDevice(devname string) error {
var err error
logrus.Debugf("devmapper: removeDevice START(%s)", devname)
defer logrus.Debugf("devmapper: removeDevice END(%s)", devname)
logrus.WithField("storage-driver", "devicemapper").Debugf("removeDevice START(%s)", devname)
defer logrus.WithField("storage-driver", "devicemapper").Debugf("removeDevice END(%s)", devname)
for i := 0; i < 200; i++ {
err = devicemapper.RemoveDevice(devname)
@@ -2177,8 +2188,8 @@ func (devices *DeviceSet) cancelDeferredRemovalIfNeeded(info *devInfo) error {
return nil
}
logrus.Debugf("devmapper: cancelDeferredRemovalIfNeeded START(%s)", info.Name())
defer logrus.Debugf("devmapper: cancelDeferredRemovalIfNeeded END(%s)", info.Name())
logrus.WithField("storage-driver", "devicemapper").Debugf("cancelDeferredRemovalIfNeeded START(%s)", info.Name())
defer logrus.WithField("storage-driver", "devicemapper").Debugf("cancelDeferredRemovalIfNeeded END(%s)", info.Name())
devinfo, err := devicemapper.GetInfoWithDeferred(info.Name())
if err != nil {
@@ -2200,8 +2211,8 @@ func (devices *DeviceSet) cancelDeferredRemovalIfNeeded(info *devInfo) error {
}
func (devices *DeviceSet) cancelDeferredRemoval(info *devInfo) error {
logrus.Debugf("devmapper: cancelDeferredRemoval START(%s)", info.Name())
defer logrus.Debugf("devmapper: cancelDeferredRemoval END(%s)", info.Name())
logrus.WithField("storage-driver", "devicemapper").Debugf("cancelDeferredRemoval START(%s)", info.Name())
defer logrus.WithField("storage-driver", "devicemapper").Debugf("cancelDeferredRemoval END(%s)", info.Name())
var err error
@@ -2224,9 +2235,11 @@ func (devices *DeviceSet) cancelDeferredRemoval(info *devInfo) error {
}
func (devices *DeviceSet) unmountAndDeactivateAll(dir string) {
logger := logrus.WithField("storage-driver", "devicemapper")
files, err := ioutil.ReadDir(dir)
if err != nil {
logrus.Warnf("devmapper: unmountAndDeactivate: %s", err)
logger.Warnf("unmountAndDeactivate: %s", err)
return
}
@@ -2242,14 +2255,14 @@ func (devices *DeviceSet) unmountAndDeactivateAll(dir string) {
// container. This means it'll go away from the global scope directly,
// and the device will be released when that container dies.
if err := unix.Unmount(fullname, unix.MNT_DETACH); err != nil && err != unix.EINVAL {
logrus.Warnf("devmapper: Shutdown unmounting %s, error: %s", fullname, err)
logger.Warnf("Shutdown unmounting %s, error: %s", fullname, err)
}
if devInfo, err := devices.lookupDevice(name); err != nil {
logrus.Debugf("devmapper: Shutdown lookup device %s, error: %s", name, err)
logger.Debugf("Shutdown lookup device %s, error: %s", name, err)
} else {
if err := devices.deactivateDevice(devInfo); err != nil {
logrus.Debugf("devmapper: Shutdown deactivate %s, error: %s", devInfo.Hash, err)
logger.Debugf("Shutdown deactivate %s, error: %s", devInfo.Hash, err)
}
}
}
@@ -2257,9 +2270,11 @@ func (devices *DeviceSet) unmountAndDeactivateAll(dir string) {
// Shutdown shuts down the device by unmounting the root.
func (devices *DeviceSet) Shutdown(home string) error {
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)
logger := logrus.WithField("storage-driver", "devicemapper")
logger.Debugf("[deviceset %s] Shutdown()", devices.devicePrefix)
logger.Debugf("Shutting down DeviceSet: %s", devices.root)
defer logger.Debugf("[deviceset %s] Shutdown() END", devices.devicePrefix)
// Stop deletion worker. This should start delivering new events to
// ticker channel. That means no new instance of cleanupDeletedDevice()
@@ -2284,7 +2299,7 @@ func (devices *DeviceSet) Shutdown(home string) error {
info.lock.Lock()
devices.Lock()
if err := devices.deactivateDevice(info); err != nil {
logrus.Debugf("devmapper: Shutdown deactivate base , error: %s", err)
logger.Debugf("Shutdown deactivate base , error: %s", err)
}
devices.Unlock()
info.lock.Unlock()
@@ -2293,7 +2308,7 @@ func (devices *DeviceSet) Shutdown(home string) error {
devices.Lock()
if devices.thinPoolDevice == "" {
if err := devices.deactivatePool(); err != nil {
logrus.Debugf("devmapper: Shutdown deactivate pool , error: %s", err)
logger.Debugf("Shutdown deactivate pool , error: %s", err)
}
}
devices.Unlock()
@@ -2382,8 +2397,10 @@ func (devices *DeviceSet) MountDevice(hash, path, mountLabel string) error {
// UnmountDevice unmounts the device and removes it from hash.
func (devices *DeviceSet) UnmountDevice(hash, mountPath string) error {
logrus.Debugf("devmapper: UnmountDevice START(hash=%s)", hash)
defer logrus.Debugf("devmapper: UnmountDevice END(hash=%s)", hash)
logger := logrus.WithField("storage-driver", "devicemapper")
logger.Debugf("UnmountDevice START(hash=%s)", hash)
defer logger.Debugf("UnmountDevice END(hash=%s)", hash)
info, err := devices.lookupDeviceWithLock(hash)
if err != nil {
@@ -2396,11 +2413,11 @@ func (devices *DeviceSet) UnmountDevice(hash, mountPath string) error {
devices.Lock()
defer devices.Unlock()
logrus.Debugf("devmapper: Unmount(%s)", mountPath)
logger.Debugf("Unmount(%s)", mountPath)
if err := unix.Unmount(mountPath, unix.MNT_DETACH); err != nil {
return err
}
logrus.Debug("devmapper: Unmount done")
logger.Debug("Unmount done")
// Remove the mountpoint here. Removing the mountpoint (in newer kernels)
// will cause all other instances of this mount in other mount namespaces
@@ -2411,7 +2428,7 @@ func (devices *DeviceSet) UnmountDevice(hash, mountPath string) error {
// older kernels which don't have
// torvalds/linux@8ed936b5671bfb33d89bc60bdcc7cf0470ba52fe applied.
if err := os.Remove(mountPath); err != nil {
logrus.Debugf("devmapper: error doing a remove on unmounted device %s: %v", mountPath, err)
logger.Debugf("error doing a remove on unmounted device %s: %v", mountPath, err)
}
return devices.deactivateDevice(info)
@@ -2508,7 +2525,7 @@ func (devices *DeviceSet) MetadataDevicePath() string {
func (devices *DeviceSet) getUnderlyingAvailableSpace(loopFile string) (uint64, error) {
buf := new(unix.Statfs_t)
if err := unix.Statfs(loopFile, buf); err != nil {
logrus.Warnf("devmapper: Couldn't stat loopfile filesystem %v: %v", loopFile, err)
logrus.WithField("storage-driver", "devicemapper").Warnf("Couldn't stat loopfile filesystem %v: %v", loopFile, err)
return 0, err
}
return buf.Bfree * uint64(buf.Bsize), nil
@@ -2518,7 +2535,7 @@ func (devices *DeviceSet) isRealFile(loopFile string) (bool, error) {
if loopFile != "" {
fi, err := os.Stat(loopFile)
if err != nil {
logrus.Warnf("devmapper: Couldn't stat loopfile %v: %v", loopFile, err)
logrus.WithField("storage-driver", "devicemapper").Warnf("Couldn't stat loopfile %v: %v", loopFile, err)
return false, err
}
return fi.Mode().IsRegular(), nil
@@ -246,7 +246,7 @@ func (d *Driver) Put(id string) error {
err := d.DeviceSet.UnmountDevice(id, mp)
if err != nil {
logrus.Errorf("devmapper: Error unmounting device %s: %v", id, err)
logrus.WithField("storage-driver", "devicemapper").Errorf("Error unmounting device %s: %v", id, err)
}
return err
@@ -139,7 +139,7 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
switch fsMagic {
case graphdriver.FsMagicAufs, graphdriver.FsMagicBtrfs, graphdriver.FsMagicEcryptfs, graphdriver.FsMagicNfsFs, graphdriver.FsMagicOverlay, graphdriver.FsMagicZfs:
logrus.Errorf("'overlay' is not supported over %s", backingFs)
logrus.WithField("storage-driver", "overlay").Errorf("'overlay' is not supported over %s", backingFs)
return nil, graphdriver.ErrIncompatibleFS
}
@@ -152,7 +152,7 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
return nil, overlayutils.ErrDTypeNotSupported("overlay", backingFs)
}
// allow running without d_type only for existing setups (#27443)
logrus.Warn(overlayutils.ErrDTypeNotSupported("overlay", backingFs))
logrus.WithField("storage-driver", "overlay").Warn(overlayutils.ErrDTypeNotSupported("overlay", backingFs))
}
rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
@@ -193,7 +193,7 @@ func supportsOverlay() error {
return nil
}
}
logrus.Error("'overlay' not found as a supported filesystem on this host. Please ensure kernel is new enough and has overlay support loaded.")
logrus.WithField("storage-driver", "overlay").Error("'overlay' not found as a supported filesystem on this host. Please ensure kernel is new enough and has overlay support loaded.")
return graphdriver.ErrNotSupported
}
@@ -369,11 +369,11 @@ func (d *Driver) Get(id, mountLabel string) (_ containerfs.ContainerFS, err erro
if err != nil {
if c := d.ctr.Decrement(mergedDir); c <= 0 {
if mntErr := unix.Unmount(mergedDir, 0); mntErr != nil {
logrus.Debugf("Failed to unmount %s: %v: %v", id, mntErr, err)
logrus.WithField("storage-driver", "overlay").Debugf("Failed to unmount %s: %v: %v", id, mntErr, err)
}
// Cleanup the created merged directory; see the comment in Put's rmdir
if rmErr := unix.Rmdir(mergedDir); rmErr != nil && !os.IsNotExist(rmErr) {
logrus.Warnf("Failed to remove %s: %v: %v", id, rmErr, err)
logrus.WithField("storage-driver", "overlay").Warnf("Failed to remove %s: %v: %v", id, rmErr, err)
}
}
}
@@ -417,11 +417,12 @@ func (d *Driver) Put(id string) error {
return nil
}
mountpoint := path.Join(d.dir(id), "merged")
logger := logrus.WithField("storage-driver", "overlay")
if count := d.ctr.Decrement(mountpoint); count > 0 {
return nil
}
if err := unix.Unmount(mountpoint, unix.MNT_DETACH); err != nil {
logrus.Debugf("Failed to unmount %s overlay: %v", id, err)
logger.Debugf("Failed to unmount %s overlay: %v", id, err)
}
// Remove the mountpoint here. Removing the mountpoint (in newer kernels)
@@ -432,7 +433,7 @@ func (d *Driver) Put(id string) error {
// fail on older kernels which don't have
// torvalds/linux@8ed936b5671bfb33d89bc60bdcc7cf0470ba52fe applied.
if err := unix.Rmdir(mountpoint); err != nil {
logrus.Debugf("Failed to remove %s overlay: %v", id, err)
logger.Debugf("Failed to remove %s overlay: %v", id, err)
}
return nil
}
@@ -27,7 +27,7 @@ func doesSupportNativeDiff(d string) error {
}
defer func() {
if err := os.RemoveAll(td); err != nil {
logrus.Warnf("Failed to remove check directory %v: %v", td, err)
logrus.WithField("storage-driver", "overlay2").Warnf("Failed to remove check directory %v: %v", td, err)
}
}()
@@ -62,7 +62,7 @@ func doesSupportNativeDiff(d string) error {
}
defer func() {
if err := unix.Unmount(filepath.Join(td, "merged"), 0); err != nil {
logrus.Warnf("Failed to unmount check directory %v: %v", filepath.Join(td, "merged"), err)
logrus.WithField("storage-driver", "overlay2").Warnf("Failed to unmount check directory %v: %v", filepath.Join(td, "merged"), err)
}
}()
@@ -113,7 +113,7 @@ func supportsMultipleLowerDir(d string) error {
}
defer func() {
if err := os.RemoveAll(td); err != nil {
logrus.Warnf("Failed to remove check directory %v: %v", td, err)
logrus.WithField("storage-driver", "overlay2").Warnf("Failed to remove check directory %v: %v", td, err)
}
}()
@@ -128,7 +128,7 @@ func supportsMultipleLowerDir(d string) error {
return errors.Wrap(err, "failed to mount overlay")
}
if err := unix.Unmount(filepath.Join(td, "merged"), 0); err != nil {
logrus.Warnf("Failed to unmount check directory %v: %v", filepath.Join(td, "merged"), err)
logrus.WithField("storage-driver", "overlay2").Warnf("Failed to unmount check directory %v: %v", filepath.Join(td, "merged"), err)
}
return nil
}
@@ -155,28 +155,30 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
backingFs = fsName
}
logger := logrus.WithField("storage-driver", "overlay2")
switch fsMagic {
case graphdriver.FsMagicAufs, graphdriver.FsMagicEcryptfs, graphdriver.FsMagicNfsFs, graphdriver.FsMagicOverlay, graphdriver.FsMagicZfs:
logrus.Errorf("'overlay2' is not supported over %s", backingFs)
logger.Errorf("'overlay2' is not supported over %s", backingFs)
return nil, graphdriver.ErrIncompatibleFS
case graphdriver.FsMagicBtrfs:
// Support for OverlayFS on BTRFS was added in kernel 4.7
// See https://btrfs.wiki.kernel.org/index.php/Changelog
if kernel.CompareKernelVersion(*v, kernel.VersionInfo{Kernel: 4, Major: 7, Minor: 0}) < 0 {
if !opts.overrideKernelCheck {
logrus.Errorf("'overlay2' requires kernel 4.7 to use on %s", backingFs)
logger.Errorf("'overlay2' requires kernel 4.7 to use on %s", backingFs)
return nil, graphdriver.ErrIncompatibleFS
}
logrus.Warn("Using pre-4.7.0 kernel for overlay2 on btrfs, may require kernel update")
logger.Warn("Using pre-4.7.0 kernel for overlay2 on btrfs, may require kernel update")
}
}
if kernel.CompareKernelVersion(*v, kernel.VersionInfo{Kernel: 4, Major: 0, Minor: 0}) < 0 {
if opts.overrideKernelCheck {
logrus.Warn("Using pre-4.0.0 kernel for overlay2, mount failures may require kernel update")
logger.Warn("Using pre-4.0.0 kernel for overlay2, mount failures may require kernel update")
} else {
if err := supportsMultipleLowerDir(testdir); err != nil {
logrus.Debugf("Multiple lower dirs not supported: %v", err)
logger.Debugf("Multiple lower dirs not supported: %v", err)
return nil, graphdriver.ErrNotSupported
}
}
@@ -190,7 +192,7 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
return nil, overlayutils.ErrDTypeNotSupported("overlay2", backingFs)
}
// allow running without d_type only for existing setups (#27443)
logrus.Warn(overlayutils.ErrDTypeNotSupported("overlay2", backingFs))
logger.Warn(overlayutils.ErrDTypeNotSupported("overlay2", backingFs))
}
rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
@@ -226,7 +228,7 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
return nil, fmt.Errorf("Storage Option overlay2.size only supported for backingFS XFS. Found %v", backingFs)
}
logrus.Debugf("backingFs=%s, projectQuotaSupported=%v", backingFs, projectQuotaSupported)
logger.Debugf("backingFs=%s, projectQuotaSupported=%v", backingFs, projectQuotaSupported)
return d, nil
}
@@ -275,14 +277,14 @@ func supportsOverlay() error {
return nil
}
}
logrus.Error("'overlay' not found as a supported filesystem on this host. Please ensure kernel is new enough and has overlay support loaded.")
logrus.WithField("storage-driver", "overlay2").Error("'overlay' not found as a supported filesystem on this host. Please ensure kernel is new enough and has overlay support loaded.")
return graphdriver.ErrNotSupported
}
func useNaiveDiff(home string) bool {
useNaiveDiffLock.Do(func() {
if err := doesSupportNativeDiff(home); err != nil {
logrus.Warnf("Not using native diff for overlay2, this may cause degraded performance for building images: %v", err)
logrus.WithField("storage-driver", "overlay2").Warnf("Not using native diff for overlay2, this may cause degraded performance for building images: %v", err)
useNaiveDiffOnly = true
}
})
@@ -517,7 +519,7 @@ func (d *Driver) Remove(id string) error {
lid, err := ioutil.ReadFile(path.Join(dir, "link"))
if err == nil {
if err := os.RemoveAll(path.Join(d.home, linkDir, string(lid))); err != nil {
logrus.Debugf("Failed to remove link: %v", err)
logrus.WithField("storage-driver", "overlay2").Debugf("Failed to remove link: %v", err)
}
}
@@ -554,11 +556,11 @@ func (d *Driver) Get(id, mountLabel string) (_ containerfs.ContainerFS, retErr e
if retErr != nil {
if c := d.ctr.Decrement(mergedDir); c <= 0 {
if mntErr := unix.Unmount(mergedDir, 0); mntErr != nil {
logrus.Errorf("error unmounting %v: %v", mergedDir, mntErr)
logrus.WithField("storage-driver", "overlay2").Errorf("error unmounting %v: %v", mergedDir, mntErr)
}
// Cleanup the created merged directory; see the comment in Put's rmdir
if rmErr := unix.Rmdir(mergedDir); rmErr != nil && !os.IsNotExist(rmErr) {
logrus.Debugf("Failed to remove %s: %v: %v", id, rmErr, err)
logrus.WithField("storage-driver", "overlay2").Debugf("Failed to remove %s: %v: %v", id, rmErr, err)
}
}
}
@@ -641,11 +643,12 @@ func (d *Driver) Put(id string) error {
}
mountpoint := path.Join(dir, "merged")
logger := logrus.WithField("storage-driver", "overlay2")
if count := d.ctr.Decrement(mountpoint); count > 0 {
return nil
}
if err := unix.Unmount(mountpoint, unix.MNT_DETACH); err != nil {
logrus.Debugf("Failed to unmount %s overlay: %s - %v", id, mountpoint, err)
logger.Debugf("Failed to unmount %s overlay: %s - %v", id, mountpoint, err)
}
// Remove the mountpoint here. Removing the mountpoint (in newer kernels)
// will cause all other instances of this mount in other mount namespaces
@@ -655,7 +658,7 @@ func (d *Driver) Put(id string) error {
// fail on older kernels which don't have
// torvalds/linux@8ed936b5671bfb33d89bc60bdcc7cf0470ba52fe applied.
if err := unix.Rmdir(mountpoint); err != nil && !os.IsNotExist(err) {
logrus.Debugf("Failed to remove %s overlay: %v", id, err)
logger.Debugf("Failed to remove %s overlay: %v", id, err)
}
return nil
}
@@ -696,7 +699,7 @@ func (d *Driver) ApplyDiff(id string, parent string, diff io.Reader) (size int64
applyDir := d.getDiffPath(id)
logrus.Debugf("Applying tar in %s", applyDir)
logrus.WithField("storage-driver", "overlay2").Debugf("Applying tar in %s", applyDir)
// Overlay doesn't need the parent id to apply the diff
if err := untar(diff, applyDir, &archive.TarOptions{
UIDMaps: d.uidMaps,
@@ -734,7 +737,7 @@ func (d *Driver) Diff(id, parent string) (io.ReadCloser, error) {
}
diffPath := d.getDiffPath(id)
logrus.Debugf("Tar with options on %s", diffPath)
logrus.WithField("storage-driver", "overlay2").Debugf("Tar with options on %s", diffPath)
return archive.TarWithOptions(diffPath, &archive.TarOptions{
Compression: archive.Uncompressed,
UIDMaps: d.uidMaps,
+14 -10
View File
@@ -37,7 +37,7 @@ type Logger struct{}
// Log wraps log message from ZFS driver with a prefix '[zfs]'.
func (*Logger) Log(cmd []string) {
logrus.Debugf("[zfs] %s", strings.Join(cmd, " "))
logrus.WithField("storage-driver", "zfs").Debugf("[zfs] %s", strings.Join(cmd, " "))
}
// Init returns a new ZFS driver.
@@ -46,14 +46,16 @@ func (*Logger) Log(cmd []string) {
func Init(base string, opt []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
var err error
logger := logrus.WithField("storage-driver", "zfs")
if _, err := exec.LookPath("zfs"); err != nil {
logrus.Debugf("[zfs] zfs command is not available: %v", err)
logger.Debugf("zfs command is not available: %v", err)
return nil, graphdriver.ErrPrerequisites
}
file, err := os.OpenFile("/dev/zfs", os.O_RDWR, 600)
if err != nil {
logrus.Debugf("[zfs] cannot open /dev/zfs: %v", err)
logger.Debugf("cannot open /dev/zfs: %v", err)
return nil, graphdriver.ErrPrerequisites
}
defer file.Close()
@@ -151,7 +153,7 @@ func lookupZfsDataset(rootdir string) (string, error) {
}
for _, m := range mounts {
if err := unix.Stat(m.Mountpoint, &stat); err != nil {
logrus.Debugf("[zfs] failed to stat '%s' while scanning for zfs mount: %v", m.Mountpoint, err)
logrus.WithField("storage-driver", "zfs").Debugf("failed to stat '%s' while scanning for zfs mount: %v", m.Mountpoint, err)
continue // may fail on fuse file systems
}
@@ -364,10 +366,10 @@ func (d *Driver) Get(id, mountLabel string) (_ containerfs.ContainerFS, retErr e
if retErr != nil {
if c := d.ctr.Decrement(mountpoint); c <= 0 {
if mntErr := unix.Unmount(mountpoint, 0); mntErr != nil {
logrus.Errorf("Error unmounting %v: %v", mountpoint, mntErr)
logrus.WithField("storage-driver", "zfs").Errorf("Error unmounting %v: %v", mountpoint, mntErr)
}
if rmErr := unix.Rmdir(mountpoint); rmErr != nil && !os.IsNotExist(rmErr) {
logrus.Debugf("Failed to remove %s: %v", id, rmErr)
logrus.WithField("storage-driver", "zfs").Debugf("Failed to remove %s: %v", id, rmErr)
}
}
@@ -376,7 +378,7 @@ func (d *Driver) Get(id, mountLabel string) (_ containerfs.ContainerFS, retErr e
filesystem := d.zfsPath(id)
options := label.FormatMountLabel("", mountLabel)
logrus.Debugf(`[zfs] mount("%s", "%s", "%s")`, filesystem, mountpoint, options)
logrus.WithField("storage-driver", "zfs").Debugf(`mount("%s", "%s", "%s")`, filesystem, mountpoint, options)
rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
if err != nil {
@@ -407,13 +409,15 @@ func (d *Driver) Put(id string) error {
return nil
}
logrus.Debugf(`[zfs] unmount("%s")`, mountpoint)
logger := logrus.WithField("storage-driver", "zfs")
logger.Debugf(`unmount("%s")`, mountpoint)
if err := unix.Unmount(mountpoint, unix.MNT_DETACH); err != nil {
logrus.Warnf("Failed to unmount %s mount %s: %v", id, mountpoint, err)
logger.Warnf("Failed to unmount %s mount %s: %v", id, mountpoint, err)
}
if err := unix.Rmdir(mountpoint); err != nil && !os.IsNotExist(err) {
logrus.Debugf("Failed to remove %s mount point %s: %v", id, mountpoint, err)
logger.Debugf("Failed to remove %s mount point %s: %v", id, mountpoint, err)
}
return nil
@@ -17,7 +17,7 @@ func checkRootdirFs(rootdir string) error {
// on FreeBSD buf.Fstypename contains ['z', 'f', 's', 0 ... ]
if (buf.Fstypename[0] != 122) || (buf.Fstypename[1] != 102) || (buf.Fstypename[2] != 115) || (buf.Fstypename[3] != 0) {
logrus.Debugf("[zfs] no zfs dataset found for rootdir '%s'", rootdir)
logrus.WithField("storage-driver", "zfs").Debugf("no zfs dataset found for rootdir '%s'", rootdir)
return graphdriver.ErrPrerequisites
}
@@ -16,7 +16,7 @@ func checkRootdirFs(rootDir string) error {
}
if fsMagic != graphdriver.FsMagicZfs {
logrus.WithField("root", rootDir).WithField("backingFS", backingFS).WithField("driver", "zfs").Error("No zfs dataset found for root")
logrus.WithField("root", rootDir).WithField("backingFS", backingFS).WithField("storage-driver", "zfs").Error("No zfs dataset found for root")
return graphdriver.ErrPrerequisites
}
+1 -2
View File
@@ -19,7 +19,6 @@ import (
"github.com/docker/docker/pkg/sysinfo"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/registry"
"github.com/docker/docker/volume/drivers"
"github.com/docker/go-connections/sockets"
"github.com/sirupsen/logrus"
)
@@ -196,7 +195,7 @@ func (daemon *Daemon) SystemVersion() types.Version {
func (daemon *Daemon) showPluginsInfo() types.PluginsInfo {
var pluginsInfo types.PluginsInfo
pluginsInfo.Volume = volumedrivers.GetDriverList()
pluginsInfo.Volume = daemon.volumes.GetDriverList()
pluginsInfo.Network = daemon.GetNetworkDriverList()
// The authorization plugins are returned in the order they are
// used as they constitute a request/response modification chain.
+2 -2
View File
@@ -18,7 +18,7 @@ import (
"github.com/docker/docker/oci"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/mount"
"github.com/docker/docker/volume"
volumemounts "github.com/docker/docker/volume/mounts"
"github.com/opencontainers/runc/libcontainer/apparmor"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/devices"
@@ -580,7 +580,7 @@ func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []c
if m.Source == "tmpfs" {
data := m.Data
parser := volume.NewParser("linux")
parser := volumemounts.NewParser("linux")
options := []string{"noexec", "nosuid", "nodev", string(parser.DefaultPropagationMode())}
if data != "" {
options = append(options, strings.Split(data, ",")...)
+15 -11
View File
@@ -107,11 +107,20 @@ func (daemon *Daemon) VolumesPrune(ctx context.Context, pruneFilters filters.Arg
rep := &types.VolumesPruneReport{}
pruneVols := func(v volume.Volume) error {
volumes, err := daemon.volumes.FilterByDriver(volume.DefaultDriverName)
if err != nil {
return nil, err
}
for _, v := range volumes {
select {
case <-ctx.Done():
logrus.Debugf("VolumesPrune operation cancelled: %#v", *rep)
return ctx.Err()
err := ctx.Err()
if err == context.Canceled {
return rep, nil
}
return rep, err
default:
}
@@ -122,9 +131,10 @@ func (daemon *Daemon) VolumesPrune(ctx context.Context, pruneFilters filters.Arg
detailedVolume, ok := v.(volume.DetailedVolume)
if ok {
if !matchLabels(pruneFilters, detailedVolume.Labels()) {
return nil
continue
}
}
vSize, err := directory.Size(ctx, v.Path())
if err != nil {
logrus.Warnf("could not determine size of volume %s: %v", name, err)
@@ -132,21 +142,15 @@ func (daemon *Daemon) VolumesPrune(ctx context.Context, pruneFilters filters.Arg
err = daemon.volumeRm(v)
if err != nil {
logrus.Warnf("could not remove volume %s: %v", name, err)
return nil
continue
}
rep.SpaceReclaimed += uint64(vSize)
rep.VolumesDeleted = append(rep.VolumesDeleted, name)
}
return nil
}
err = daemon.traverseLocalVolumes(pruneVols)
if err == context.Canceled {
return rep, nil
}
return rep, err
return rep, nil
}
// localNetworksPrune removes unused local networks
+10 -39
View File
@@ -1,7 +1,6 @@
package daemon // import "github.com/docker/docker/daemon"
import (
"fmt"
"os"
"path/filepath"
"reflect"
@@ -15,7 +14,7 @@ import (
"github.com/docker/docker/container"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/volume"
"github.com/docker/docker/volume/drivers"
volumemounts "github.com/docker/docker/volume/mounts"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -76,8 +75,9 @@ func (m mounts) parts(i int) int {
// 4. Cleanup old volumes that are about to be reassigned.
func (daemon *Daemon) registerMountPoints(container *container.Container, hostConfig *containertypes.HostConfig) (retErr error) {
binds := map[string]bool{}
mountPoints := map[string]*volume.MountPoint{}
parser := volume.NewParser(container.OS)
mountPoints := map[string]*volumemounts.MountPoint{}
parser := volumemounts.NewParser(container.OS)
defer func() {
// clean up the container mountpoints once return with error
if retErr != nil {
@@ -117,7 +117,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
}
for _, m := range c.MountPoints {
cp := &volume.MountPoint{
cp := &volumemounts.MountPoint{
Type: m.Type,
Name: m.Name,
Source: m.Source,
@@ -252,7 +252,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
// lazyInitializeVolume initializes a mountpoint's volume if needed.
// This happens after a daemon restart.
func (daemon *Daemon) lazyInitializeVolume(containerID string, m *volume.MountPoint) error {
func (daemon *Daemon) lazyInitializeVolume(containerID string, m *volumemounts.MountPoint) error {
if len(m.Driver) > 0 && m.Volume == nil {
v, err := daemon.volumes.GetWithRef(m.Name, m.Driver, containerID)
if err != nil {
@@ -272,7 +272,7 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) {
container.Lock()
defer container.Unlock()
parser := volume.NewParser(container.OS)
parser := volumemounts.NewParser(container.OS)
maybeUpdate := make(map[string]bool)
for _, mp := range container.MountPoints {
@@ -290,7 +290,7 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) {
mountSpecs[m.Target] = true
}
binds := make(map[string]*volume.MountPoint, len(container.HostConfig.Binds))
binds := make(map[string]*volumemounts.MountPoint, len(container.HostConfig.Binds))
for _, rawSpec := range container.HostConfig.Binds {
mp, err := parser.ParseMountRaw(rawSpec, container.HostConfig.VolumeDriver)
if err != nil {
@@ -300,7 +300,7 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) {
binds[mp.Destination] = mp
}
volumesFrom := make(map[string]volume.MountPoint)
volumesFrom := make(map[string]volumemounts.MountPoint)
for _, fromSpec := range container.HostConfig.VolumesFrom {
from, _, err := parser.ParseVolumesFrom(fromSpec)
if err != nil {
@@ -323,7 +323,7 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) {
fromC.Unlock()
}
needsUpdate := func(containerMount, other *volume.MountPoint) bool {
needsUpdate := func(containerMount, other *volumemounts.MountPoint) bool {
if containerMount.Type != other.Type || !reflect.DeepEqual(containerMount.Spec, other.Spec) {
return true
}
@@ -385,32 +385,3 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) {
cm.Spec.ReadOnly = !cm.RW
}
}
func (daemon *Daemon) traverseLocalVolumes(fn func(volume.Volume) error) error {
localVolumeDriver, err := volumedrivers.GetDriver(volume.DefaultDriverName)
if err != nil {
return fmt.Errorf("can't retrieve local volume driver: %v", err)
}
vols, err := localVolumeDriver.List()
if err != nil {
return fmt.Errorf("can't retrieve local volumes: %v", err)
}
for _, v := range vols {
name := v.Name()
vol, err := daemon.volumes.Get(name)
if err != nil {
logrus.Warnf("failed to retrieve volume %s from store: %v", name, err)
} else {
// daemon.volumes.Get will return DetailedVolume
v = vol
}
err = fn(v)
if err != nil {
return err
}
}
return nil
}
@@ -4,7 +4,7 @@ import (
"runtime"
"testing"
"github.com/docker/docker/volume"
volumemounts "github.com/docker/docker/volume/mounts"
)
func TestParseVolumesFrom(t *testing.T) {
@@ -21,7 +21,7 @@ func TestParseVolumesFrom(t *testing.T) {
{"foobar:baz", "", "", true},
}
parser := volume.NewParser(runtime.GOOS)
parser := volumemounts.NewParser(runtime.GOOS)
for _, c := range cases {
id, mode, err := parser.ParseVolumesFrom(c.spec)
+3 -82
View File
@@ -3,10 +3,8 @@
package daemon // import "github.com/docker/docker/daemon"
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
@@ -14,10 +12,7 @@ import (
"github.com/docker/docker/container"
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/pkg/mount"
"github.com/docker/docker/volume"
"github.com/docker/docker/volume/drivers"
"github.com/docker/docker/volume/local"
"github.com/pkg/errors"
volumemounts "github.com/docker/docker/volume/mounts"
)
// setupMounts iterates through each of the mount points for a container and
@@ -45,7 +40,7 @@ func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, er
// mount the socket the daemon is listening on. During daemon shutdown, the socket
// (/var/run/docker.sock by default) doesn't exist anymore causing the call to m.Setup to
// create at directory instead. This in turn will prevent the daemon to restart.
checkfunc := func(m *volume.MountPoint) error {
checkfunc := func(m *volumemounts.MountPoint) error {
if _, exist := daemon.hosts[m.Source]; exist && daemon.IsShuttingDown() {
return fmt.Errorf("Could not mount %q to container while the daemon is shutting down", m.Source)
}
@@ -107,86 +102,12 @@ func sortMounts(m []container.Mount) []container.Mount {
// setBindModeIfNull is platform specific processing to ensure the
// shared mode is set to 'z' if it is null. This is called in the case
// of processing a named volume and not a typical bind.
func setBindModeIfNull(bind *volume.MountPoint) {
func setBindModeIfNull(bind *volumemounts.MountPoint) {
if bind.Mode == "" {
bind.Mode = "z"
}
}
// migrateVolume links the contents of a volume created pre Docker 1.7
// into the location expected by the local driver.
// It creates a symlink from DOCKER_ROOT/vfs/dir/VOLUME_ID to DOCKER_ROOT/volumes/VOLUME_ID/_container_data.
// It preserves the volume json configuration generated pre Docker 1.7 to be able to
// downgrade from Docker 1.7 to Docker 1.6 without losing volume compatibility.
func migrateVolume(id, vfs string) error {
l, err := volumedrivers.GetDriver(volume.DefaultDriverName)
if err != nil {
return err
}
newDataPath := l.(*local.Root).DataPath(id)
fi, err := os.Stat(newDataPath)
if err != nil && !os.IsNotExist(err) {
return err
}
if fi != nil && fi.IsDir() {
return nil
}
return os.Symlink(vfs, newDataPath)
}
// verifyVolumesInfo ports volumes configured for the containers pre docker 1.7.
// It reads the container configuration and creates valid mount points for the old volumes.
func (daemon *Daemon) verifyVolumesInfo(container *container.Container) error {
container.Lock()
defer container.Unlock()
// Inspect old structures only when we're upgrading from old versions
// to versions >= 1.7 and the MountPoints has not been populated with volumes data.
type volumes struct {
Volumes map[string]string
VolumesRW map[string]bool
}
cfgPath, err := container.ConfigPath()
if err != nil {
return err
}
f, err := os.Open(cfgPath)
if err != nil {
return errors.Wrap(err, "could not open container config")
}
defer f.Close()
var cv volumes
if err := json.NewDecoder(f).Decode(&cv); err != nil {
return errors.Wrap(err, "could not decode container config")
}
if len(container.MountPoints) == 0 && len(cv.Volumes) > 0 {
for destination, hostPath := range cv.Volumes {
vfsPath := filepath.Join(daemon.root, "vfs", "dir")
rw := cv.VolumesRW != nil && cv.VolumesRW[destination]
if strings.HasPrefix(hostPath, vfsPath) {
id := filepath.Base(hostPath)
v, err := daemon.volumes.CreateWithRef(id, volume.DefaultDriverName, container.ID, nil, nil)
if err != nil {
return err
}
if err := migrateVolume(id, hostPath); err != nil {
return err
}
container.AddMountPointWithVolume(destination, v, true)
} else { // Bind mount
m := volume.MountPoint{Source: hostPath, Destination: destination, RW: rw}
container.MountPoints[destination] = &m
}
}
}
return nil
}
func (daemon *Daemon) mountVolumes(container *container.Container) error {
mounts, err := daemon.setupMounts(container)
if err != nil {
+14 -14
View File
@@ -11,7 +11,7 @@ import (
containertypes "github.com/docker/docker/api/types/container"
mounttypes "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/container"
"github.com/docker/docker/volume"
volumemounts "github.com/docker/docker/volume/mounts"
)
func TestBackportMountSpec(t *testing.T) {
@@ -19,7 +19,7 @@ func TestBackportMountSpec(t *testing.T) {
c := &container.Container{
State: &container.State{},
MountPoints: map[string]*volume.MountPoint{
MountPoints: map[string]*volumemounts.MountPoint{
"/apple": {Destination: "/apple", Source: "/var/lib/docker/volumes/12345678", Name: "12345678", RW: true, CopyData: true}, // anonymous volume
"/banana": {Destination: "/banana", Source: "/var/lib/docker/volumes/data", Name: "data", RW: true, CopyData: true}, // named volume
"/cherry": {Destination: "/cherry", Source: "/var/lib/docker/volumes/data", Name: "data", CopyData: true}, // RO named volume
@@ -73,7 +73,7 @@ func TestBackportMountSpec(t *testing.T) {
d.containers.Add("1", &container.Container{
State: &container.State{},
ID: "1",
MountPoints: map[string]*volume.MountPoint{
MountPoints: map[string]*volumemounts.MountPoint{
"/kumquat": {Destination: "/kumquat", Name: "data", RW: false, CopyData: true},
},
HostConfig: &containertypes.HostConfig{
@@ -84,11 +84,11 @@ func TestBackportMountSpec(t *testing.T) {
})
type expected struct {
mp *volume.MountPoint
mp *volumemounts.MountPoint
comment string
}
pretty := func(mp *volume.MountPoint) string {
pretty := func(mp *volumemounts.MountPoint) string {
b, err := json.MarshalIndent(mp, "\t", " ")
if err != nil {
return fmt.Sprintf("%#v", mp)
@@ -98,7 +98,7 @@ func TestBackportMountSpec(t *testing.T) {
for _, x := range []expected{
{
mp: &volume.MountPoint{
mp: &volumemounts.MountPoint{
Type: mounttypes.TypeVolume,
Destination: "/apple",
RW: true,
@@ -114,7 +114,7 @@ func TestBackportMountSpec(t *testing.T) {
comment: "anonymous volume",
},
{
mp: &volume.MountPoint{
mp: &volumemounts.MountPoint{
Type: mounttypes.TypeVolume,
Destination: "/banana",
RW: true,
@@ -130,7 +130,7 @@ func TestBackportMountSpec(t *testing.T) {
comment: "named volume",
},
{
mp: &volume.MountPoint{
mp: &volumemounts.MountPoint{
Type: mounttypes.TypeVolume,
Destination: "/cherry",
Name: "data",
@@ -146,7 +146,7 @@ func TestBackportMountSpec(t *testing.T) {
comment: "read-only named volume",
},
{
mp: &volume.MountPoint{
mp: &volumemounts.MountPoint{
Type: mounttypes.TypeVolume,
Destination: "/dates",
Name: "data",
@@ -162,7 +162,7 @@ func TestBackportMountSpec(t *testing.T) {
comment: "named volume with nocopy",
},
{
mp: &volume.MountPoint{
mp: &volumemounts.MountPoint{
Type: mounttypes.TypeVolume,
Destination: "/elderberry",
Name: "data",
@@ -178,7 +178,7 @@ func TestBackportMountSpec(t *testing.T) {
comment: "masks an anonymous volume",
},
{
mp: &volume.MountPoint{
mp: &volumemounts.MountPoint{
Type: mounttypes.TypeBind,
Destination: "/fig",
Source: "/data",
@@ -192,7 +192,7 @@ func TestBackportMountSpec(t *testing.T) {
comment: "bind mount with read/write",
},
{
mp: &volume.MountPoint{
mp: &volumemounts.MountPoint{
Type: mounttypes.TypeBind,
Destination: "/guava",
Source: "/data",
@@ -209,7 +209,7 @@ func TestBackportMountSpec(t *testing.T) {
comment: "bind mount with read/write + shared propagation",
},
{
mp: &volume.MountPoint{
mp: &volumemounts.MountPoint{
Type: mounttypes.TypeVolume,
Destination: "/honeydew",
Source: "/var/lib/docker/volumes/data",
@@ -229,7 +229,7 @@ func TestBackportMountSpec(t *testing.T) {
comment: "volume defined in mounts API",
},
{
mp: &volume.MountPoint{
mp: &volumemounts.MountPoint{
Type: mounttypes.TypeVolume,
Destination: "/kumquat",
Source: "/var/lib/docker/volumes/data",
+3 -3
View File
@@ -6,7 +6,7 @@ import (
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/container"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/volume"
volumemounts "github.com/docker/docker/volume/mounts"
)
// setupMounts configures the mount points for a container by appending each
@@ -20,7 +20,7 @@ import (
func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, error) {
var mnts []container.Mount
for _, mount := range c.MountPoints { // type is volume.MountPoint
for _, mount := range c.MountPoints { // type is volumemounts.MountPoint
if err := daemon.lazyInitializeVolume(c.ID, mount); err != nil {
return nil, err
}
@@ -42,7 +42,7 @@ func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, er
// setBindModeIfNull is platform specific processing which is a no-op on
// Windows.
func setBindModeIfNull(bind *volume.MountPoint) {
func setBindModeIfNull(bind *volumemounts.MountPoint) {
return
}
@@ -350,14 +350,7 @@ func (s *DockerSwarmSuite) TearDownTest(c *check.C) {
for _, d := range s.daemons {
if d != nil {
d.Stop(c)
// FIXME(vdemeester) should be handled by SwarmDaemon ?
// raft state file is quite big (64MB) so remove it after every test
walDir := filepath.Join(d.Root, "swarm/raft/wal")
if err := os.RemoveAll(walDir); err != nil {
c.Logf("error removing %v: %v", walDir, err)
}
d.CleanupExecRoot(c)
d.Cleanup(c)
}
}
s.daemons = nil
@@ -2,7 +2,6 @@ package daemon // import "github.com/docker/docker/integration-cli/daemon"
import (
"fmt"
"os/exec"
"strings"
"time"
@@ -88,19 +87,6 @@ func (d *Daemon) inspectFieldWithError(name, field string) (string, error) {
return d.inspectFilter(name, fmt.Sprintf(".%s", field))
}
// BuildImageWithOut builds an image with the specified dockerfile and options and returns the output
func (d *Daemon) BuildImageWithOut(name, dockerfile string, useCache bool, buildFlags ...string) (string, int, error) {
buildCmd := BuildImageCmdWithHost(d.dockerBinary, name, dockerfile, d.Sock(), useCache, buildFlags...)
result := icmd.RunCmd(icmd.Cmd{
Command: buildCmd.Args,
Env: buildCmd.Env,
Dir: buildCmd.Dir,
Stdin: buildCmd.Stdin,
Stdout: buildCmd.Stdout,
})
return result.Combined(), result.ExitCode, result.Error
}
// CheckActiveContainerCount returns the number of active containers
// FIXME(vdemeester) should re-use ActivateContainers in some way
func (d *Daemon) CheckActiveContainerCount(c *check.C) (interface{}, check.CommentInterface) {
@@ -155,22 +141,3 @@ func WaitInspectWithArgs(dockerBinary, name, expr, expected string, timeout time
}
return nil
}
// BuildImageCmdWithHost create a build command with the specified arguments.
// Deprecated
// FIXME(vdemeester) move this away
func BuildImageCmdWithHost(dockerBinary, name, dockerfile, host string, useCache bool, buildFlags ...string) *exec.Cmd {
args := []string{}
if host != "" {
args = append(args, "--host", host)
}
args = append(args, "build", "-t", name)
if !useCache {
args = append(args, "--no-cache")
}
args = append(args, buildFlags...)
args = append(args, "-")
buildCmd := exec.Command(dockerBinary, args...)
buildCmd.Stdin = strings.NewReader(dockerfile)
return buildCmd
}
@@ -4,20 +4,19 @@ package main
import (
"fmt"
"path"
"strconv"
"strings"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/swarm/runtime"
"github.com/docker/docker/integration-cli/checker"
"github.com/docker/docker/integration-cli/cli"
"github.com/docker/docker/integration-cli/cli/build"
"github.com/docker/docker/integration-cli/daemon"
testdaemon "github.com/docker/docker/internal/test/daemon"
"github.com/docker/docker/internal/test/fixtures/plugin"
"github.com/docker/docker/internal/test/registry"
"github.com/go-check/check"
"github.com/gotestyourself/gotestyourself/icmd"
"golang.org/x/net/context"
"golang.org/x/sys/unix"
)
@@ -209,12 +208,12 @@ func (s *DockerSwarmSuite) TestAPISwarmServicesUpdateStartFirst(c *check.C) {
image2 := "testhealth:latest"
// service started from this image won't pass health check
_, _, err := d.BuildImageWithOut(image2,
`FROM busybox
result := cli.BuildCmd(c, image2, cli.Daemon(d),
build.WithDockerfile(`FROM busybox
HEALTHCHECK --interval=1s --timeout=30s --retries=1024 \
CMD cat /status`,
true)
c.Check(err, check.IsNil)
CMD cat /status`),
)
result.Assert(c, icmd.Success)
// create service
instances := 5
@@ -611,78 +610,3 @@ func (s *DockerSwarmSuite) TestAPISwarmServicesStateReporting(c *check.C) {
}
}
}
// Test plugins deployed via swarm services
func (s *DockerSwarmSuite) TestAPISwarmServicesPlugin(c *check.C) {
testRequires(c, ExperimentalDaemon, DaemonIsLinux, IsAmd64)
reg := registry.NewV2(c)
defer reg.Close()
repo := path.Join(privateRegistryURL, "swarm", "test:v1")
repo2 := path.Join(privateRegistryURL, "swarm", "test:v2")
name := "test"
err := plugin.CreateInRegistry(context.Background(), repo, nil)
c.Assert(err, checker.IsNil, check.Commentf("failed to create plugin"))
err = plugin.CreateInRegistry(context.Background(), repo2, nil)
c.Assert(err, checker.IsNil, check.Commentf("failed to create plugin"))
d1 := s.AddDaemon(c, true, true)
d2 := s.AddDaemon(c, true, true)
d3 := s.AddDaemon(c, true, false)
makePlugin := func(repo, name string, constraints []string) func(*swarm.Service) {
return func(s *swarm.Service) {
s.Spec.TaskTemplate.Runtime = "plugin"
s.Spec.TaskTemplate.PluginSpec = &runtime.PluginSpec{
Name: name,
Remote: repo,
}
if constraints != nil {
s.Spec.TaskTemplate.Placement = &swarm.Placement{
Constraints: constraints,
}
}
}
}
id := d1.CreateService(c, makePlugin(repo, name, nil))
waitAndAssert(c, defaultReconciliationTimeout, d1.CheckPluginRunning(name), checker.True)
waitAndAssert(c, defaultReconciliationTimeout, d2.CheckPluginRunning(name), checker.True)
waitAndAssert(c, defaultReconciliationTimeout, d3.CheckPluginRunning(name), checker.True)
service := d1.GetService(c, id)
d1.UpdateService(c, service, makePlugin(repo2, name, nil))
waitAndAssert(c, defaultReconciliationTimeout, d1.CheckPluginImage(name), checker.Equals, repo2)
waitAndAssert(c, defaultReconciliationTimeout, d2.CheckPluginImage(name), checker.Equals, repo2)
waitAndAssert(c, defaultReconciliationTimeout, d3.CheckPluginImage(name), checker.Equals, repo2)
waitAndAssert(c, defaultReconciliationTimeout, d1.CheckPluginRunning(name), checker.True)
waitAndAssert(c, defaultReconciliationTimeout, d2.CheckPluginRunning(name), checker.True)
waitAndAssert(c, defaultReconciliationTimeout, d3.CheckPluginRunning(name), checker.True)
d1.RemoveService(c, id)
waitAndAssert(c, defaultReconciliationTimeout, d1.CheckPluginRunning(name), checker.False)
waitAndAssert(c, defaultReconciliationTimeout, d2.CheckPluginRunning(name), checker.False)
waitAndAssert(c, defaultReconciliationTimeout, d3.CheckPluginRunning(name), checker.False)
// constrain to managers only
id = d1.CreateService(c, makePlugin(repo, name, []string{"node.role==manager"}))
waitAndAssert(c, defaultReconciliationTimeout, d1.CheckPluginRunning(name), checker.True)
waitAndAssert(c, defaultReconciliationTimeout, d2.CheckPluginRunning(name), checker.True)
waitAndAssert(c, defaultReconciliationTimeout, d3.CheckPluginRunning(name), checker.False) // Not a manager, not running it
d1.RemoveService(c, id)
waitAndAssert(c, defaultReconciliationTimeout, d1.CheckPluginRunning(name), checker.False)
waitAndAssert(c, defaultReconciliationTimeout, d2.CheckPluginRunning(name), checker.False)
waitAndAssert(c, defaultReconciliationTimeout, d3.CheckPluginRunning(name), checker.False)
// with no name
id = d1.CreateService(c, makePlugin(repo, "", nil))
waitAndAssert(c, defaultReconciliationTimeout, d1.CheckPluginRunning(repo), checker.True)
waitAndAssert(c, defaultReconciliationTimeout, d2.CheckPluginRunning(repo), checker.True)
waitAndAssert(c, defaultReconciliationTimeout, d3.CheckPluginRunning(repo), checker.True)
d1.RemoveService(c, id)
waitAndAssert(c, defaultReconciliationTimeout, d1.CheckPluginRunning(repo), checker.False)
waitAndAssert(c, defaultReconciliationTimeout, d2.CheckPluginRunning(repo), checker.False)
waitAndAssert(c, defaultReconciliationTimeout, d3.CheckPluginRunning(repo), checker.False)
}
@@ -7,7 +7,6 @@ import (
"io/ioutil"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
@@ -810,10 +809,6 @@ func (s *DockerSwarmSuite) TestAPISwarmRestartCluster(c *check.C) {
if err := daemon.StopWithError(); err != nil {
errs <- err
}
// FIXME(vdemeester) This is duplicated…
if root := os.Getenv("DOCKER_REMAP_ROOT"); root != "" {
daemon.Root = filepath.Dir(daemon.Root)
}
}(d)
}
wg.Wait()
@@ -231,24 +231,6 @@ func (s *DockerDaemonSuite) TestVolumePlugin(c *check.C) {
c.Assert(err, checker.IsNil, check.Commentf(out))
}
func (s *DockerDaemonSuite) TestGraphdriverPlugin(c *check.C) {
testRequires(c, Network, IsAmd64, DaemonIsLinux, overlay2Supported, ExperimentalDaemon)
s.d.Start(c)
// install the plugin
plugin := "cpuguy83/docker-overlay2-graphdriver-plugin"
out, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", plugin)
c.Assert(err, checker.IsNil, check.Commentf(out))
// restart the daemon with the plugin set as the storage driver
s.d.Restart(c, "-s", plugin, "--storage-opt", "overlay2.override_kernel_check=1")
// run a container
out, err = s.d.Cmd("run", "--rm", "busybox", "true") // this will pull busybox using the plugin
c.Assert(err, checker.IsNil, check.Commentf(out))
}
func (s *DockerDaemonSuite) TestPluginVolumeRemoveOnRestart(c *check.C) {
testRequires(c, DaemonIsLinux, Network, IsAmd64)
@@ -31,11 +31,11 @@ import (
moby_daemon "github.com/docker/docker/daemon"
"github.com/docker/docker/integration-cli/checker"
"github.com/docker/docker/integration-cli/cli"
"github.com/docker/docker/integration-cli/cli/build"
"github.com/docker/docker/integration-cli/daemon"
testdaemon "github.com/docker/docker/internal/test/daemon"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/mount"
"github.com/docker/docker/pkg/stringid"
units "github.com/docker/go-units"
"github.com/docker/libnetwork/iptables"
"github.com/docker/libtrust"
@@ -1156,14 +1156,16 @@ func (s *DockerDaemonSuite) TestDaemonLoggingDriverNoneLogsError(c *check.C) {
func (s *DockerDaemonSuite) TestDaemonLoggingDriverShouldBeIgnoredForBuild(c *check.C) {
s.d.StartWithBusybox(c, "--log-driver=splunk")
out, err := s.d.Cmd("build")
out, code, err := s.d.BuildImageWithOut("busyboxs", `
result := cli.BuildCmd(c, "busyboxs", cli.Daemon(s.d),
build.WithDockerfile(`
FROM busybox
RUN echo foo`, false)
comment := check.Commentf("Failed to build image. output %s, exitCode %d, err %v", out, code, err)
c.Assert(err, check.IsNil, comment)
c.Assert(code, check.Equals, 0, comment)
c.Assert(out, checker.Contains, "foo", comment)
RUN echo foo`),
build.WithoutCache,
)
comment := check.Commentf("Failed to build image. output %s, exitCode %d, err %v", result.Combined(), result.ExitCode, result.Error)
c.Assert(result.Error, check.IsNil, comment)
c.Assert(result.ExitCode, check.Equals, 0, comment)
c.Assert(result.Combined(), checker.Contains, "foo", comment)
}
func (s *DockerDaemonSuite) TestDaemonUnixSockCleanedUp(c *check.C) {
@@ -1805,23 +1807,18 @@ func (s *DockerDaemonSuite) TestDaemonNoSpaceLeftOnDeviceError(c *check.C) {
c.Assert(mount.MakeRShared(testDir), checker.IsNil)
defer mount.Unmount(testDir)
// create a 2MiB image and mount it as graph root
// create a 3MiB image (with a 2MiB ext4 fs) and mount it as graph root
// Why in a container? Because `mount` sometimes behaves weirdly and often fails outright on this test in debian:jessie (which is what the test suite runs under if run from the Makefile)
dockerCmd(c, "run", "--rm", "-v", testDir+":/test", "busybox", "sh", "-c", "dd of=/test/testfs.img bs=1M seek=3 count=0")
icmd.RunCommand("mkfs.ext4", "-F", filepath.Join(testDir, "testfs.img")).Assert(c, icmd.Success)
result := icmd.RunCommand("losetup", "-f", "--show", filepath.Join(testDir, "testfs.img"))
result.Assert(c, icmd.Success)
loopname := strings.TrimSpace(string(result.Combined()))
defer exec.Command("losetup", "-d", loopname).Run()
dockerCmd(c, "run", "--privileged", "--rm", "-v", testDir+":/test:shared", "busybox", "sh", "-c", fmt.Sprintf("mkdir -p /test/test-mount && mount -t ext4 -no loop,rw %v /test/test-mount", loopname))
dockerCmd(c, "run", "--privileged", "--rm", "-v", testDir+":/test:shared", "busybox", "sh", "-c", "mkdir -p /test/test-mount && mount -n /test/testfs.img /test/test-mount")
defer mount.Unmount(filepath.Join(testDir, "test-mount"))
s.d.Start(c, "--data-root", filepath.Join(testDir, "test-mount"))
defer s.d.Stop(c)
// pull a repository large enough to fill the mount point
// pull a repository large enough to overfill the mounted filesystem
pullOut, err := s.d.Cmd("pull", "debian:stretch")
c.Assert(err, checker.NotNil, check.Commentf(pullOut))
c.Assert(pullOut, checker.Contains, "no space left on device")
@@ -2404,12 +2401,16 @@ func (s *DockerDaemonSuite) TestDaemonMaxConcurrencyWithConfigFileReload(c *chec
func (s *DockerDaemonSuite) TestBuildOnDisabledBridgeNetworkDaemon(c *check.C) {
s.d.StartWithBusybox(c, "-b=none", "--iptables=false")
out, code, err := s.d.BuildImageWithOut("busyboxs",
`FROM busybox
RUN cat /etc/hosts`, false)
comment := check.Commentf("Failed to build image. output %s, exitCode %d, err %v", out, code, err)
c.Assert(err, check.IsNil, comment)
c.Assert(code, check.Equals, 0, comment)
result := cli.BuildCmd(c, "busyboxs", cli.Daemon(s.d),
build.WithDockerfile(`
FROM busybox
RUN cat /etc/hosts`),
build.WithoutCache,
)
comment := check.Commentf("Failed to build image. output %s, exitCode %d, err %v", result.Combined(), result.ExitCode, result.Error)
c.Assert(result.Error, check.IsNil, comment)
c.Assert(result.ExitCode, check.Equals, 0, comment)
}
// Test case for #21976
@@ -2668,84 +2669,6 @@ func (s *DockerDaemonSuite) TestDaemonRestartSaveContainerExitCode(c *check.C) {
c.Assert(out, checker.Equals, errMsg1)
}
func (s *DockerDaemonSuite) TestDaemonBackcompatPre17Volumes(c *check.C) {
testRequires(c, SameHostDaemon)
d := s.d
d.StartWithBusybox(c)
// hack to be able to side-load a container config
out, err := d.Cmd("create", "busybox:latest")
c.Assert(err, checker.IsNil, check.Commentf(out))
id := strings.TrimSpace(out)
out, err = d.Cmd("inspect", "--type=image", "--format={{.ID}}", "busybox:latest")
c.Assert(err, checker.IsNil, check.Commentf(out))
d.Stop(c)
<-d.Wait
imageID := strings.TrimSpace(out)
volumeID := stringid.GenerateNonCryptoID()
vfsPath := filepath.Join(d.Root, "vfs", "dir", volumeID)
c.Assert(os.MkdirAll(vfsPath, 0755), checker.IsNil)
config := []byte(`
{
"ID": "` + id + `",
"Name": "hello",
"Driver": "` + d.StorageDriver() + `",
"Image": "` + imageID + `",
"Config": {"Image": "busybox:latest"},
"NetworkSettings": {},
"Volumes": {
"/bar":"/foo",
"/foo": "` + vfsPath + `",
"/quux":"/quux"
},
"VolumesRW": {
"/bar": true,
"/foo": true,
"/quux": false
}
}
`)
configPath := filepath.Join(d.Root, "containers", id, "config.v2.json")
c.Assert(ioutil.WriteFile(configPath, config, 600), checker.IsNil)
d.Start(c)
out, err = d.Cmd("inspect", "--type=container", "--format={{ json .Mounts }}", id)
c.Assert(err, checker.IsNil, check.Commentf(out))
type mount struct {
Name string
Source string
Destination string
Driver string
RW bool
}
ls := []mount{}
err = json.NewDecoder(strings.NewReader(out)).Decode(&ls)
c.Assert(err, checker.IsNil)
expected := []mount{
{Source: "/foo", Destination: "/bar", RW: true},
{Name: volumeID, Destination: "/foo", RW: true},
{Source: "/quux", Destination: "/quux", RW: false},
}
c.Assert(ls, checker.HasLen, len(expected))
for _, m := range ls {
var matched bool
for _, x := range expected {
if m.Source == x.Source && m.Destination == x.Destination && m.RW == x.RW || m.Name != x.Name {
matched = true
break
}
}
c.Assert(matched, checker.True, check.Commentf("did find match for %+v", m))
}
}
func (s *DockerDaemonSuite) TestDaemonWithUserlandProxyPath(c *check.C) {
testRequires(c, SameHostDaemon, DaemonIsLinux)
@@ -12,8 +12,10 @@ import (
"github.com/docker/docker/integration-cli/checker"
"github.com/docker/docker/integration-cli/cli"
"github.com/docker/docker/integration-cli/cli/build"
"github.com/docker/docker/integration-cli/daemon"
"github.com/go-check/check"
"github.com/gotestyourself/gotestyourself/icmd"
)
func pruneNetworkAndVerify(c *check.C, d *daemon.Daemon, kept, pruned []string) {
@@ -79,13 +81,15 @@ func (s *DockerSwarmSuite) TestPruneNetwork(c *check.C) {
func (s *DockerDaemonSuite) TestPruneImageDangling(c *check.C) {
s.d.StartWithBusybox(c)
out, _, err := s.d.BuildImageWithOut("test",
`FROM busybox
LABEL foo=bar`, true, "-q")
c.Assert(err, checker.IsNil)
id := strings.TrimSpace(out)
result := cli.BuildCmd(c, "test", cli.Daemon(s.d),
build.WithDockerfile(`FROM busybox
LABEL foo=bar`),
cli.WithFlags("-q"),
)
result.Assert(c, icmd.Success)
id := strings.TrimSpace(result.Combined())
out, err = s.d.Cmd("images", "-q", "--no-trunc")
out, err := s.d.Cmd("images", "-q", "--no-trunc")
c.Assert(err, checker.IsNil)
c.Assert(strings.TrimSpace(out), checker.Contains, id)
@@ -266,20 +270,24 @@ func (s *DockerSuite) TestPruneNetworkLabel(c *check.C) {
func (s *DockerDaemonSuite) TestPruneImageLabel(c *check.C) {
s.d.StartWithBusybox(c)
out, _, err := s.d.BuildImageWithOut("test1",
`FROM busybox
LABEL foo=bar`, true, "-q")
c.Assert(err, checker.IsNil)
id1 := strings.TrimSpace(out)
out, err = s.d.Cmd("images", "-q", "--no-trunc")
result := cli.BuildCmd(c, "test1", cli.Daemon(s.d),
build.WithDockerfile(`FROM busybox
LABEL foo=bar`),
cli.WithFlags("-q"),
)
result.Assert(c, icmd.Success)
id1 := strings.TrimSpace(result.Combined())
out, err := s.d.Cmd("images", "-q", "--no-trunc")
c.Assert(err, checker.IsNil)
c.Assert(strings.TrimSpace(out), checker.Contains, id1)
out, _, err = s.d.BuildImageWithOut("test2",
`FROM busybox
LABEL bar=foo`, true, "-q")
c.Assert(err, checker.IsNil)
id2 := strings.TrimSpace(out)
result = cli.BuildCmd(c, "test2", cli.Daemon(s.d),
build.WithDockerfile(`FROM busybox
LABEL bar=foo`),
cli.WithFlags("-q"),
)
result.Assert(c, icmd.Success)
id2 := strings.TrimSpace(result.Combined())
out, err = s.d.Cmd("images", "-q", "--no-trunc")
c.Assert(err, checker.IsNil)
c.Assert(strings.TrimSpace(out), checker.Contains, id2)
@@ -9,7 +9,10 @@ import (
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/daemon/cluster/executor/container"
"github.com/docker/docker/integration-cli/checker"
"github.com/docker/docker/integration-cli/cli"
"github.com/docker/docker/integration-cli/cli/build"
"github.com/go-check/check"
"github.com/gotestyourself/gotestyourself/icmd"
)
// start a service, and then make its task unhealthy during running
@@ -20,15 +23,14 @@ func (s *DockerSwarmSuite) TestServiceHealthRun(c *check.C) {
d := s.AddDaemon(c, true, true)
// build image with health-check
// note: use `daemon.buildImageWithOut` to build, do not use `buildImage` to build
imageName := "testhealth"
_, _, err := d.BuildImageWithOut(imageName,
`FROM busybox
result := cli.BuildCmd(c, imageName, cli.Daemon(d),
build.WithDockerfile(`FROM busybox
RUN touch /status
HEALTHCHECK --interval=1s --timeout=1s --retries=1\
CMD cat /status`,
true)
c.Check(err, check.IsNil)
CMD cat /status`),
)
result.Assert(c, icmd.Success)
serviceName := "healthServiceRun"
out, err := d.Cmd("service", "create", "--no-resolve-image", "--detach=true", "--name", serviceName, imageName, "top")
@@ -84,12 +86,12 @@ func (s *DockerSwarmSuite) TestServiceHealthStart(c *check.C) {
// service started from this image won't pass health check
imageName := "testhealth"
_, _, err := d.BuildImageWithOut(imageName,
`FROM busybox
result := cli.BuildCmd(c, imageName, cli.Daemon(d),
build.WithDockerfile(`FROM busybox
HEALTHCHECK --interval=1s --timeout=1s --retries=1024\
CMD cat /status`,
true)
c.Check(err, check.IsNil)
CMD cat /status`),
)
result.Assert(c, icmd.Success)
serviceName := "healthServiceStart"
out, err := d.Cmd("service", "create", "--no-resolve-image", "--detach=true", "--name", serviceName, imageName, "top")
@@ -5,11 +5,15 @@ import (
"strings"
"testing"
"time"
"github.com/docker/docker/pkg/parsers/kernel"
"github.com/gotestyourself/gotestyourself/icmd"
)
// HasHubConnectivity checks to see if https://hub.docker.com is
// accessible from the present environment
func HasHubConnectivity(t *testing.T) bool {
t.Helper()
// Set a timeout on the GET at 15s
var timeout = 15 * time.Second
var url = "https://hub.docker.com"
@@ -24,3 +28,26 @@ func HasHubConnectivity(t *testing.T) bool {
}
return err == nil
}
func overlayFSSupported() bool {
result := icmd.RunCommand("/bin/sh", "-c", "cat /proc/filesystems")
if result.Error != nil {
return false
}
return strings.Contains(result.Combined(), "overlay\n")
}
// Overlay2Supported returns true if the current system supports overlay2 as graphdriver
func Overlay2Supported(kernelVersion string) bool {
if !overlayFSSupported() {
return false
}
daemonV, err := kernel.ParseRelease(kernelVersion)
if err != nil {
return false
}
requiredV := kernel.VersionInfo{Kernel: 4}
return kernel.CompareKernelVersion(*daemonV, requiredV) > -1
}
@@ -19,10 +19,10 @@ import (
// ServicePoll tweaks the pollSettings for `service`
func ServicePoll(config *poll.Settings) {
// Override the default pollSettings for `service` resource here ...
config.Timeout = 30 * time.Second
config.Delay = 100 * time.Millisecond
if runtime.GOARCH == "arm64" || runtime.GOARCH == "arm" {
config.Timeout = 1 * time.Minute
config.Delay = 100 * time.Millisecond
}
}
@@ -49,6 +49,7 @@ func ContainerPoll(config *poll.Settings) {
// NewSwarm creates a swarm daemon for testing
func NewSwarm(t *testing.T, testEnv *environment.Execution, ops ...func(*daemon.Daemon)) *daemon.Daemon {
t.Helper()
skip.IfCondition(t, testEnv.IsRemoteDaemon())
if testEnv.DaemonInfo.ExperimentalBuild {
ops = append(ops, daemon.WithExperimental)
@@ -63,6 +64,7 @@ type ServiceSpecOpt func(*swarmtypes.ServiceSpec)
// CreateService creates a service on the passed in swarm daemon.
func CreateService(t *testing.T, d *daemon.Daemon, opts ...ServiceSpecOpt) string {
t.Helper()
spec := defaultServiceSpec()
for _, o := range opts {
o(&spec)
@@ -136,6 +138,7 @@ func ServiceWithName(name string) ServiceSpecOpt {
// GetRunningTasks gets the list of running tasks for a service
func GetRunningTasks(t *testing.T, d *daemon.Daemon, serviceID string) []swarmtypes.Task {
t.Helper()
client := d.NewClientT(t)
defer client.Close()
@@ -153,6 +156,7 @@ func GetRunningTasks(t *testing.T, d *daemon.Daemon, serviceID string) []swarmty
// ExecTask runs the passed in exec config on the given task
func ExecTask(t *testing.T, d *daemon.Daemon, task swarmtypes.Task, config types.ExecConfig) types.HijackedResponse {
t.Helper()
client := d.NewClientT(t)
defer client.Close()
@@ -1,8 +1,7 @@
// +build !windows
package main
package graphdriver
import (
"context"
"encoding/json"
"fmt"
"io"
@@ -10,31 +9,24 @@ import (
"net/http"
"net/http/httptest"
"os"
"strings"
"runtime"
"testing"
"github.com/docker/docker/api/types"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/docker/docker/daemon/graphdriver"
"github.com/docker/docker/daemon/graphdriver/vfs"
"github.com/docker/docker/integration-cli/daemon"
testdaemon "github.com/docker/docker/internal/test/daemon"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/integration/internal/requirement"
"github.com/docker/docker/internal/test/daemon"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/plugins"
"github.com/go-check/check"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
"github.com/gotestyourself/gotestyourself/skip"
)
func init() {
check.Suite(&DockerExternalGraphdriverSuite{
ds: &DockerSuite{},
})
}
type DockerExternalGraphdriverSuite struct {
server *httptest.Server
jserver *httptest.Server
ds *DockerSuite
d *daemon.Daemon
ec map[string]*graphEventsCounter
}
type graphEventsCounter struct {
activations int
creations int
@@ -52,46 +44,69 @@ type graphEventsCounter struct {
diffsize int
}
func (s *DockerExternalGraphdriverSuite) SetUpTest(c *check.C) {
s.d = daemon.New(c, dockerBinary, dockerdBinary, testdaemon.WithEnvironment(testEnv.Execution))
}
func TestExternalGraphDriver(t *testing.T) {
skip.If(t, runtime.GOOS == "windows")
skip.If(t, testEnv.IsRemoteDaemon(), "cannot run daemon when remote daemon")
skip.If(t, !requirement.HasHubConnectivity(t))
func (s *DockerExternalGraphdriverSuite) OnTimeout(c *check.C) {
s.d.DumpStackAndQuit()
}
// Setup plugin(s)
ec := make(map[string]*graphEventsCounter)
sserver := setupPluginViaSpecFile(t, ec)
jserver := setupPluginViaJSONFile(t, ec)
// Create daemon
d := daemon.New(t, daemon.WithExperimental)
c := d.NewClientT(t)
func (s *DockerExternalGraphdriverSuite) TearDownTest(c *check.C) {
if s.d != nil {
s.d.Stop(c)
s.ds.TearDownTest(c)
for _, tc := range []struct {
name string
test func(client.APIClient, *daemon.Daemon) func(*testing.T)
}{
{
name: "json",
test: testExternalGraphDriver("json", ec),
},
{
name: "spec",
test: testExternalGraphDriver("spec", ec),
},
{
name: "pull",
test: testGraphDriverPull,
},
} {
t.Run(tc.name, tc.test(c, d))
}
sserver.Close()
jserver.Close()
err := os.RemoveAll("/etc/docker/plugins")
assert.NilError(t, err)
}
func (s *DockerExternalGraphdriverSuite) SetUpSuite(c *check.C) {
s.ec = make(map[string]*graphEventsCounter)
s.setUpPluginViaSpecFile(c)
s.setUpPluginViaJSONFile(c)
}
func (s *DockerExternalGraphdriverSuite) setUpPluginViaSpecFile(c *check.C) {
func setupPluginViaSpecFile(t *testing.T, ec map[string]*graphEventsCounter) *httptest.Server {
mux := http.NewServeMux()
s.server = httptest.NewServer(mux)
server := httptest.NewServer(mux)
s.setUpPlugin(c, "test-external-graph-driver", "spec", mux, []byte(s.server.URL))
setupPlugin(t, ec, "spec", mux, []byte(server.URL))
return server
}
func (s *DockerExternalGraphdriverSuite) setUpPluginViaJSONFile(c *check.C) {
func setupPluginViaJSONFile(t *testing.T, ec map[string]*graphEventsCounter) *httptest.Server {
mux := http.NewServeMux()
s.jserver = httptest.NewServer(mux)
server := httptest.NewServer(mux)
p := plugins.NewLocalPlugin("json-external-graph-driver", s.jserver.URL)
p := plugins.NewLocalPlugin("json-external-graph-driver", server.URL)
b, err := json.Marshal(p)
c.Assert(err, check.IsNil)
assert.NilError(t, err)
s.setUpPlugin(c, "json-external-graph-driver", "json", mux, b)
setupPlugin(t, ec, "json", mux, b)
return server
}
func (s *DockerExternalGraphdriverSuite) setUpPlugin(c *check.C, name string, ext string, mux *http.ServeMux, b []byte) {
func setupPlugin(t *testing.T, ec map[string]*graphEventsCounter, ext string, mux *http.ServeMux, b []byte) {
name := fmt.Sprintf("%s-external-graph-driver", ext)
type graphDriverRequest struct {
ID string `json:",omitempty"`
Parent string `json:",omitempty"`
@@ -130,24 +145,24 @@ func (s *DockerExternalGraphdriverSuite) setUpPlugin(c *check.C, name string, ex
}
base, err := ioutil.TempDir("", name)
c.Assert(err, check.IsNil)
assert.NilError(t, err)
vfsProto, err := vfs.Init(base, []string{}, nil, nil)
c.Assert(err, check.IsNil, check.Commentf("error initializing graph driver"))
assert.NilError(t, err, "error initializing graph driver")
driver := graphdriver.NewNaiveDiffDriver(vfsProto, nil, nil)
s.ec[ext] = &graphEventsCounter{}
ec[ext] = &graphEventsCounter{}
mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
s.ec[ext].activations++
ec[ext].activations++
respond(w, `{"Implements": ["GraphDriver"]}`)
})
mux.HandleFunc("/GraphDriver.Init", func(w http.ResponseWriter, r *http.Request) {
s.ec[ext].init++
ec[ext].init++
respond(w, "{}")
})
mux.HandleFunc("/GraphDriver.CreateReadWrite", func(w http.ResponseWriter, r *http.Request) {
s.ec[ext].creations++
ec[ext].creations++
var req graphDriverRequest
if err := decReq(r.Body, &req, w); err != nil {
@@ -161,7 +176,7 @@ func (s *DockerExternalGraphdriverSuite) setUpPlugin(c *check.C, name string, ex
})
mux.HandleFunc("/GraphDriver.Create", func(w http.ResponseWriter, r *http.Request) {
s.ec[ext].creations++
ec[ext].creations++
var req graphDriverRequest
if err := decReq(r.Body, &req, w); err != nil {
@@ -175,7 +190,7 @@ func (s *DockerExternalGraphdriverSuite) setUpPlugin(c *check.C, name string, ex
})
mux.HandleFunc("/GraphDriver.Remove", func(w http.ResponseWriter, r *http.Request) {
s.ec[ext].removals++
ec[ext].removals++
var req graphDriverRequest
if err := decReq(r.Body, &req, w); err != nil {
@@ -190,7 +205,7 @@ func (s *DockerExternalGraphdriverSuite) setUpPlugin(c *check.C, name string, ex
})
mux.HandleFunc("/GraphDriver.Get", func(w http.ResponseWriter, r *http.Request) {
s.ec[ext].gets++
ec[ext].gets++
var req graphDriverRequest
if err := decReq(r.Body, &req, w); err != nil {
@@ -207,7 +222,7 @@ func (s *DockerExternalGraphdriverSuite) setUpPlugin(c *check.C, name string, ex
})
mux.HandleFunc("/GraphDriver.Put", func(w http.ResponseWriter, r *http.Request) {
s.ec[ext].puts++
ec[ext].puts++
var req graphDriverRequest
if err := decReq(r.Body, &req, w); err != nil {
@@ -222,7 +237,7 @@ func (s *DockerExternalGraphdriverSuite) setUpPlugin(c *check.C, name string, ex
})
mux.HandleFunc("/GraphDriver.Exists", func(w http.ResponseWriter, r *http.Request) {
s.ec[ext].exists++
ec[ext].exists++
var req graphDriverRequest
if err := decReq(r.Body, &req, w); err != nil {
@@ -232,12 +247,12 @@ func (s *DockerExternalGraphdriverSuite) setUpPlugin(c *check.C, name string, ex
})
mux.HandleFunc("/GraphDriver.Status", func(w http.ResponseWriter, r *http.Request) {
s.ec[ext].stats++
ec[ext].stats++
respond(w, &graphDriverResponse{Status: driver.Status()})
})
mux.HandleFunc("/GraphDriver.Cleanup", func(w http.ResponseWriter, r *http.Request) {
s.ec[ext].cleanups++
ec[ext].cleanups++
err := driver.Cleanup()
if err != nil {
respond(w, err)
@@ -247,7 +262,7 @@ func (s *DockerExternalGraphdriverSuite) setUpPlugin(c *check.C, name string, ex
})
mux.HandleFunc("/GraphDriver.GetMetadata", func(w http.ResponseWriter, r *http.Request) {
s.ec[ext].metadata++
ec[ext].metadata++
var req graphDriverRequest
if err := decReq(r.Body, &req, w); err != nil {
@@ -263,7 +278,7 @@ func (s *DockerExternalGraphdriverSuite) setUpPlugin(c *check.C, name string, ex
})
mux.HandleFunc("/GraphDriver.Diff", func(w http.ResponseWriter, r *http.Request) {
s.ec[ext].diff++
ec[ext].diff++
var req graphDriverRequest
if err := decReq(r.Body, &req, w); err != nil {
@@ -279,7 +294,7 @@ func (s *DockerExternalGraphdriverSuite) setUpPlugin(c *check.C, name string, ex
})
mux.HandleFunc("/GraphDriver.Changes", func(w http.ResponseWriter, r *http.Request) {
s.ec[ext].changes++
ec[ext].changes++
var req graphDriverRequest
if err := decReq(r.Body, &req, w); err != nil {
return
@@ -294,7 +309,7 @@ func (s *DockerExternalGraphdriverSuite) setUpPlugin(c *check.C, name string, ex
})
mux.HandleFunc("/GraphDriver.ApplyDiff", func(w http.ResponseWriter, r *http.Request) {
s.ec[ext].applydiff++
ec[ext].applydiff++
diff := r.Body
defer r.Body.Close()
@@ -314,7 +329,7 @@ func (s *DockerExternalGraphdriverSuite) setUpPlugin(c *check.C, name string, ex
})
mux.HandleFunc("/GraphDriver.DiffSize", func(w http.ResponseWriter, r *http.Request) {
s.ec[ext].diffsize++
ec[ext].diffsize++
var req graphDriverRequest
if err := decReq(r.Body, &req, w); err != nil {
@@ -330,77 +345,118 @@ func (s *DockerExternalGraphdriverSuite) setUpPlugin(c *check.C, name string, ex
})
err = os.MkdirAll("/etc/docker/plugins", 0755)
c.Assert(err, check.IsNil, check.Commentf("error creating /etc/docker/plugins"))
assert.NilError(t, err)
specFile := "/etc/docker/plugins/" + name + "." + ext
err = ioutil.WriteFile(specFile, b, 0644)
c.Assert(err, check.IsNil, check.Commentf("error writing to %s", specFile))
assert.NilError(t, err)
}
func (s *DockerExternalGraphdriverSuite) TearDownSuite(c *check.C) {
s.server.Close()
s.jserver.Close()
func testExternalGraphDriver(ext string, ec map[string]*graphEventsCounter) func(client.APIClient, *daemon.Daemon) func(*testing.T) {
return func(c client.APIClient, d *daemon.Daemon) func(*testing.T) {
return func(t *testing.T) {
driverName := fmt.Sprintf("%s-external-graph-driver", ext)
d.StartWithBusybox(t, "-s", driverName)
err := os.RemoveAll("/etc/docker/plugins")
c.Assert(err, check.IsNil, check.Commentf("error removing /etc/docker/plugins"))
ctx := context.Background()
testGraphDriver(t, c, ctx, driverName, func(t *testing.T) {
d.Restart(t, "-s", driverName)
})
_, err := c.Info(ctx)
assert.NilError(t, err)
d.Stop(t)
// Don't check ec.exists, because the daemon no longer calls the
// Exists function.
assert.Check(t, is.Equal(ec[ext].activations, 2))
assert.Check(t, is.Equal(ec[ext].init, 2))
assert.Check(t, ec[ext].creations >= 1)
assert.Check(t, ec[ext].removals >= 1)
assert.Check(t, ec[ext].gets >= 1)
assert.Check(t, ec[ext].puts >= 1)
assert.Check(t, is.Equal(ec[ext].stats, 5))
assert.Check(t, is.Equal(ec[ext].cleanups, 2))
assert.Check(t, ec[ext].applydiff >= 1)
assert.Check(t, is.Equal(ec[ext].changes, 1))
assert.Check(t, is.Equal(ec[ext].diffsize, 0))
assert.Check(t, is.Equal(ec[ext].diff, 0))
assert.Check(t, is.Equal(ec[ext].metadata, 1))
}
}
}
func (s *DockerExternalGraphdriverSuite) TestExternalGraphDriver(c *check.C) {
testRequires(c, ExperimentalDaemon, SameHostDaemon)
func testGraphDriverPull(c client.APIClient, d *daemon.Daemon) func(*testing.T) {
return func(t *testing.T) {
d.Start(t)
defer d.Stop(t)
ctx := context.Background()
s.testExternalGraphDriver("test-external-graph-driver", "spec", c)
s.testExternalGraphDriver("json-external-graph-driver", "json", c)
r, err := c.ImagePull(ctx, "busybox:latest", types.ImagePullOptions{})
assert.NilError(t, err)
_, err = io.Copy(ioutil.Discard, r)
assert.NilError(t, err)
container.Run(t, ctx, c, container.WithImage("busybox:latest"))
}
}
func (s *DockerExternalGraphdriverSuite) testExternalGraphDriver(name string, ext string, c *check.C) {
s.d.StartWithBusybox(c, "-s", name)
func TestGraphdriverPluginV2(t *testing.T) {
skip.If(t, runtime.GOOS == "windows")
skip.If(t, testEnv.IsRemoteDaemon(), "cannot run daemon when remote daemon")
skip.If(t, !requirement.HasHubConnectivity(t))
skip.If(t, os.Getenv("DOCKER_ENGINE_GOARCH") != "amd64")
skip.If(t, !requirement.Overlay2Supported(testEnv.DaemonInfo.KernelVersion))
out, err := s.d.Cmd("run", "--name=graphtest", "busybox", "sh", "-c", "echo hello > /hello")
c.Assert(err, check.IsNil, check.Commentf(out))
d := daemon.New(t, daemon.WithExperimental)
d.Start(t)
defer d.Stop(t)
s.d.Restart(c, "-s", name)
client := d.NewClientT(t)
defer client.Close()
ctx := context.Background()
out, err = s.d.Cmd("inspect", "--format={{.GraphDriver.Name}}", "graphtest")
c.Assert(err, check.IsNil, check.Commentf(out))
c.Assert(strings.TrimSpace(out), check.Equals, name)
// install the plugin
plugin := "cpuguy83/docker-overlay2-graphdriver-plugin"
responseReader, err := client.PluginInstall(ctx, plugin, types.PluginInstallOptions{
RemoteRef: plugin,
AcceptAllPermissions: true,
})
defer responseReader.Close()
assert.NilError(t, err)
// ensure it's done by waiting for EOF on the response
_, err = io.Copy(ioutil.Discard, responseReader)
assert.NilError(t, err)
out, err = s.d.Cmd("diff", "graphtest")
c.Assert(err, check.IsNil, check.Commentf(out))
c.Assert(strings.Contains(out, "A /hello"), check.Equals, true, check.Commentf("diff output: %s", out))
// restart the daemon with the plugin set as the storage driver
d.Stop(t)
d.StartWithBusybox(t, "-s", plugin, "--storage-opt", "overlay2.override_kernel_check=1")
out, err = s.d.Cmd("rm", "-f", "graphtest")
c.Assert(err, check.IsNil, check.Commentf(out))
out, err = s.d.Cmd("info")
c.Assert(err, check.IsNil, check.Commentf(out))
s.d.Stop(c)
// Don't check s.ec.exists, because the daemon no longer calls the
// Exists function.
c.Assert(s.ec[ext].activations, check.Equals, 2)
c.Assert(s.ec[ext].init, check.Equals, 2)
c.Assert(s.ec[ext].creations >= 1, check.Equals, true)
c.Assert(s.ec[ext].removals >= 1, check.Equals, true)
c.Assert(s.ec[ext].gets >= 1, check.Equals, true)
c.Assert(s.ec[ext].puts >= 1, check.Equals, true)
c.Assert(s.ec[ext].stats, check.Equals, 5)
c.Assert(s.ec[ext].cleanups, check.Equals, 2)
c.Assert(s.ec[ext].applydiff >= 1, check.Equals, true)
c.Assert(s.ec[ext].changes, check.Equals, 1)
c.Assert(s.ec[ext].diffsize, check.Equals, 0)
c.Assert(s.ec[ext].diff, check.Equals, 0)
c.Assert(s.ec[ext].metadata, check.Equals, 1)
testGraphDriver(t, client, ctx, plugin, nil)
}
func (s *DockerExternalGraphdriverSuite) TestExternalGraphDriverPull(c *check.C) {
testRequires(c, Network, ExperimentalDaemon, SameHostDaemon)
func testGraphDriver(t *testing.T, c client.APIClient, ctx context.Context, driverName string, afterContainerRunFn func(*testing.T)) { //nolint: golint
id := container.Run(t, ctx, c, container.WithCmd("sh", "-c", "echo hello > /hello"))
s.d.Start(c)
if afterContainerRunFn != nil {
afterContainerRunFn(t)
}
out, err := s.d.Cmd("pull", "busybox:latest")
c.Assert(err, check.IsNil, check.Commentf(out))
i, err := c.ContainerInspect(ctx, id)
assert.NilError(t, err)
assert.Check(t, is.Equal(i.GraphDriver.Name, driverName))
out, err = s.d.Cmd("run", "-d", "busybox", "top")
c.Assert(err, check.IsNil, check.Commentf(out))
diffs, err := c.ContainerDiff(ctx, id)
assert.NilError(t, err)
assert.Check(t, is.Contains(diffs, containertypes.ContainerChangeResponseItem{
Kind: archive.ChangeAdd,
Path: "/hello",
}), "diffs: %v", diffs)
err = c.ContainerRemove(ctx, id, types.ContainerRemoveOptions{
Force: true,
})
assert.NilError(t, err)
}
@@ -0,0 +1,36 @@
package graphdriver // import "github.com/docker/docker/integration/plugin/graphdriver"
import (
"fmt"
"os"
"testing"
"github.com/docker/docker/internal/test/environment"
"github.com/docker/docker/pkg/reexec"
)
var (
testEnv *environment.Execution
)
func init() {
reexec.Init() // This is required for external graphdriver tests
}
const dockerdBinary = "dockerd"
func TestMain(m *testing.M) {
var err error
testEnv, err = environment.New()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = environment.EnsureFrozenImagesLinux(testEnv)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
testEnv.Print()
os.Exit(m.Run())
}
@@ -0,0 +1,120 @@
package service
import (
"context"
"io"
"io/ioutil"
"os"
"path"
"testing"
"github.com/docker/docker/api/types"
swarmtypes "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/swarm/runtime"
"github.com/docker/docker/integration/internal/swarm"
"github.com/docker/docker/internal/test/daemon"
"github.com/docker/docker/internal/test/fixtures/plugin"
"github.com/docker/docker/internal/test/registry"
"github.com/gotestyourself/gotestyourself/assert"
"github.com/gotestyourself/gotestyourself/poll"
"github.com/gotestyourself/gotestyourself/skip"
)
func TestServicePlugin(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType == "windows")
skip.If(t, os.Getenv("DOCKER_ENGINE_GOARCH") != "amd64")
defer setupTest(t)()
reg := registry.NewV2(t)
defer reg.Close()
repo := path.Join(registry.DefaultURL, "swarm", "test:v1")
repo2 := path.Join(registry.DefaultURL, "swarm", "test:v2")
name := "test"
d := daemon.New(t)
d.StartWithBusybox(t)
apiclient := d.NewClientT(t)
err := plugin.Create(context.Background(), apiclient, repo)
assert.NilError(t, err)
r, err := apiclient.PluginPush(context.Background(), repo, "")
assert.NilError(t, err)
_, err = io.Copy(ioutil.Discard, r)
assert.NilError(t, err)
err = apiclient.PluginRemove(context.Background(), repo, types.PluginRemoveOptions{})
assert.NilError(t, err)
err = plugin.Create(context.Background(), apiclient, repo2)
assert.NilError(t, err)
r, err = apiclient.PluginPush(context.Background(), repo2, "")
assert.NilError(t, err)
_, err = io.Copy(ioutil.Discard, r)
assert.NilError(t, err)
err = apiclient.PluginRemove(context.Background(), repo2, types.PluginRemoveOptions{})
assert.NilError(t, err)
d.Stop(t)
d1 := swarm.NewSwarm(t, testEnv, daemon.WithExperimental)
defer d1.Stop(t)
d2 := daemon.New(t, daemon.WithExperimental, daemon.WithSwarmPort(daemon.DefaultSwarmPort+1))
d2.StartAndSwarmJoin(t, d1, true)
defer d2.Stop(t)
d3 := daemon.New(t, daemon.WithExperimental, daemon.WithSwarmPort(daemon.DefaultSwarmPort+2))
d3.StartAndSwarmJoin(t, d1, false)
defer d3.Stop(t)
id := d1.CreateService(t, makePlugin(repo, name, nil))
poll.WaitOn(t, d1.PluginIsRunning(name), swarm.ServicePoll)
poll.WaitOn(t, d2.PluginIsRunning(name), swarm.ServicePoll)
poll.WaitOn(t, d3.PluginIsRunning(name), swarm.ServicePoll)
service := d1.GetService(t, id)
d1.UpdateService(t, service, makePlugin(repo2, name, nil))
poll.WaitOn(t, d1.PluginReferenceIs(name, repo2), swarm.ServicePoll)
poll.WaitOn(t, d2.PluginReferenceIs(name, repo2), swarm.ServicePoll)
poll.WaitOn(t, d3.PluginReferenceIs(name, repo2), swarm.ServicePoll)
poll.WaitOn(t, d1.PluginIsRunning(name), swarm.ServicePoll)
poll.WaitOn(t, d2.PluginIsRunning(name), swarm.ServicePoll)
poll.WaitOn(t, d3.PluginIsRunning(name), swarm.ServicePoll)
d1.RemoveService(t, id)
poll.WaitOn(t, d1.PluginIsNotPresent(name), swarm.ServicePoll)
poll.WaitOn(t, d2.PluginIsNotPresent(name), swarm.ServicePoll)
poll.WaitOn(t, d3.PluginIsNotPresent(name), swarm.ServicePoll)
// constrain to managers only
id = d1.CreateService(t, makePlugin(repo, name, []string{"node.role==manager"}))
poll.WaitOn(t, d1.PluginIsRunning(name), swarm.ServicePoll)
poll.WaitOn(t, d2.PluginIsRunning(name), swarm.ServicePoll)
poll.WaitOn(t, d3.PluginIsNotPresent(name), swarm.ServicePoll)
d1.RemoveService(t, id)
poll.WaitOn(t, d1.PluginIsNotPresent(name), swarm.ServicePoll)
poll.WaitOn(t, d2.PluginIsNotPresent(name), swarm.ServicePoll)
poll.WaitOn(t, d3.PluginIsNotPresent(name), swarm.ServicePoll)
// with no name
id = d1.CreateService(t, makePlugin(repo, "", nil))
poll.WaitOn(t, d1.PluginIsRunning(repo), swarm.ServicePoll)
poll.WaitOn(t, d2.PluginIsRunning(repo), swarm.ServicePoll)
poll.WaitOn(t, d3.PluginIsRunning(repo), swarm.ServicePoll)
d1.RemoveService(t, id)
poll.WaitOn(t, d1.PluginIsNotPresent(repo), swarm.ServicePoll)
poll.WaitOn(t, d2.PluginIsNotPresent(repo), swarm.ServicePoll)
poll.WaitOn(t, d3.PluginIsNotPresent(repo), swarm.ServicePoll)
}
func makePlugin(repo, name string, constraints []string) func(*swarmtypes.Service) {
return func(s *swarmtypes.Service) {
s.Spec.TaskTemplate.Runtime = "plugin"
s.Spec.TaskTemplate.PluginSpec = &runtime.PluginSpec{
Name: name,
Remote: repo,
}
if constraints != nil {
s.Spec.TaskTemplate.Placement = &swarmtypes.Placement{
Constraints: constraints,
}
}
}
}
@@ -5,6 +5,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/internal/test"
"github.com/gotestyourself/gotestyourself/assert"
)
@@ -13,6 +14,9 @@ type ConfigConstructor func(*swarm.Config)
// CreateConfig creates a config given the specified spec
func (d *Daemon) CreateConfig(t assert.TestingT, configSpec swarm.ConfigSpec) string {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -23,6 +27,9 @@ func (d *Daemon) CreateConfig(t assert.TestingT, configSpec swarm.ConfigSpec) st
// ListConfigs returns the list of the current swarm configs
func (d *Daemon) ListConfigs(t assert.TestingT) []swarm.Config {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -33,6 +40,9 @@ func (d *Daemon) ListConfigs(t assert.TestingT) []swarm.Config {
// GetConfig returns a swarm config identified by the specified id
func (d *Daemon) GetConfig(t assert.TestingT, id string) *swarm.Config {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -43,6 +53,9 @@ func (d *Daemon) GetConfig(t assert.TestingT, id string) *swarm.Config {
// DeleteConfig removes the swarm config identified by the specified id
func (d *Daemon) DeleteConfig(t assert.TestingT, id string) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -53,6 +66,9 @@ func (d *Daemon) DeleteConfig(t assert.TestingT, id string) {
// UpdateConfig updates the swarm config identified by the specified id
// Currently, only label update is supported.
func (d *Daemon) UpdateConfig(t assert.TestingT, id string, f ...ConfigConstructor) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -4,11 +4,15 @@ import (
"context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/internal/test"
"github.com/gotestyourself/gotestyourself/assert"
)
// ActiveContainers returns the list of ids of the currently running containers
func (d *Daemon) ActiveContainers(t assert.TestingT) []string {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -24,6 +28,9 @@ func (d *Daemon) ActiveContainers(t assert.TestingT) []string {
// FindContainerIP returns the ip of the specified container
func (d *Daemon) FindContainerIP(t assert.TestingT, id string) string {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -16,6 +16,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/client"
"github.com/docker/docker/internal/test"
"github.com/docker/docker/internal/test/request"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/ioutils"
@@ -80,6 +81,9 @@ type Daemon struct {
// This will create a directory such as d123456789 in the folder specified by $DOCKER_INTEGRATION_DAEMON_DEST or $DEST.
// The daemon will not automatically start.
func New(t testingT, ops ...func(*Daemon)) *Daemon {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
dest := os.Getenv("DOCKER_INTEGRATION_DAEMON_DEST")
if dest == "" {
dest = os.Getenv("DEST")
@@ -113,7 +117,7 @@ func New(t testingT, ops ...func(*Daemon)) *Daemon {
execRoot: filepath.Join(os.TempDir(), "docker-execroot", id),
dockerdBinary: defaultDockerdBinary,
swarmListenAddr: defaultSwarmListenAddr,
SwarmPort: defaultSwarmPort,
SwarmPort: DefaultSwarmPort,
log: t,
}
@@ -169,6 +173,9 @@ func (d *Daemon) NewClient() (*client.Client, error) {
// NewClientT creates new client based on daemon's socket path
// FIXME(vdemeester): replace NewClient with NewClientT
func (d *Daemon) NewClientT(t assert.TestingT) *client.Client {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
c, err := client.NewClientWithOpts(
client.FromEnv,
client.WithHost(d.Sock()))
@@ -176,13 +183,21 @@ func (d *Daemon) NewClientT(t assert.TestingT) *client.Client {
return c
}
// CleanupExecRoot cleans the daemon exec root (network namespaces, ...)
func (d *Daemon) CleanupExecRoot(t testingT) {
cleanupExecRoot(t, d.execRoot)
// Cleanup cleans the daemon files : exec root (network namespaces, ...), swarmkit files
func (d *Daemon) Cleanup(t testingT) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
// Cleanup swarmkit wal files if present
cleanupRaftDir(t, d.Root)
cleanupNetworkNamespace(t, d.execRoot)
}
// Start starts the daemon and return once it is ready to receive requests.
func (d *Daemon) Start(t testingT, args ...string) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
if err := d.StartWithError(args...); err != nil {
t.Fatalf("Error starting daemon with arguments: %v", args)
}
@@ -201,6 +216,7 @@ func (d *Daemon) StartWithError(args ...string) error {
// StartWithLogFile will start the daemon and attach its streams to a given file.
func (d *Daemon) StartWithLogFile(out *os.File, providedArgs ...string) error {
d.handleUserns()
dockerdBinary, err := exec.LookPath(d.dockerdBinary)
if err != nil {
return errors.Wrapf(err, "[%s] could not find docker binary in $PATH", d.id)
@@ -313,6 +329,9 @@ func (d *Daemon) StartWithLogFile(out *os.File, providedArgs ...string) error {
// StartWithBusybox will first start the daemon with Daemon.Start()
// then save the busybox image from the main daemon and load it into this Daemon instance.
func (d *Daemon) StartWithBusybox(t testingT, arg ...string) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
d.Start(t, arg...)
d.LoadBusybox(t)
}
@@ -369,6 +388,9 @@ func (d *Daemon) DumpStackAndQuit() {
// instantiate a new one with NewDaemon.
// If an error occurs while starting the daemon, the test will fail.
func (d *Daemon) Stop(t testingT) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
err := d.StopWithError()
if err != nil {
if err != errDaemonNotStarted {
@@ -445,8 +467,10 @@ out2:
// Restart will restart the daemon by first stopping it and the starting it.
// If an error occurs while starting the daemon, the test will fail.
func (d *Daemon) Restart(t testingT, args ...string) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
d.Stop(t)
d.handleUserns()
d.Start(t, args...)
}
@@ -455,7 +479,6 @@ func (d *Daemon) RestartWithError(arg ...string) error {
if err := d.StopWithError(); err != nil {
return err
}
d.handleUserns()
return d.StartWithError(arg...)
}
@@ -520,6 +543,9 @@ func (d *Daemon) ReloadConfig() error {
// LoadBusybox image into the daemon
func (d *Daemon) LoadBusybox(t assert.TestingT) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
clientHost, err := client.NewEnvClient()
assert.NilError(t, err, "failed to create client")
defer clientHost.Close()
@@ -630,9 +656,22 @@ func (d *Daemon) queryRootDir() (string, error) {
// Info returns the info struct for this daemon
func (d *Daemon) Info(t assert.TestingT) types.Info {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
apiclient, err := d.NewClient()
assert.NilError(t, err)
info, err := apiclient.Info(context.Background())
assert.NilError(t, err)
return info
}
func cleanupRaftDir(t testingT, rootPath string) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
walDir := filepath.Join(rootPath, "swarm/raft/wal")
if err := os.RemoveAll(walDir); err != nil {
t.Logf("error removing %v: %v", walDir, err)
}
}
@@ -6,10 +6,14 @@ import (
"os"
"path/filepath"
"github.com/docker/docker/internal/test"
"golang.org/x/sys/unix"
)
func cleanupExecRoot(t testingT, execRoot string) {
func cleanupNetworkNamespace(t testingT, execRoot string) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
// Cleanup network namespaces in the exec root of this
// daemon because this exec root is specific to this
// daemon instance and has no chance of getting
@@ -21,5 +21,5 @@ func signalDaemonReload(pid int) error {
return fmt.Errorf("daemon reload not supported")
}
func cleanupExecRoot(t testingT, execRoot string) {
func cleanupNetworkNamespace(t testingT, execRoot string) {
}
@@ -7,6 +7,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/internal/test"
"github.com/gotestyourself/gotestyourself/assert"
)
@@ -15,6 +16,9 @@ type NodeConstructor func(*swarm.Node)
// GetNode returns a swarm node identified by the specified id
func (d *Daemon) GetNode(t assert.TestingT, id string) *swarm.Node {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -26,6 +30,9 @@ func (d *Daemon) GetNode(t assert.TestingT, id string) *swarm.Node {
// RemoveNode removes the specified node
func (d *Daemon) RemoveNode(t assert.TestingT, id string, force bool) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -38,6 +45,9 @@ func (d *Daemon) RemoveNode(t assert.TestingT, id string, force bool) {
// UpdateNode updates a swarm node with the specified node constructor
func (d *Daemon) UpdateNode(t assert.TestingT, id string, f ...NodeConstructor) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -59,6 +69,9 @@ func (d *Daemon) UpdateNode(t assert.TestingT, id string, f ...NodeConstructor)
// ListNodes returns the list of the current swarm nodes
func (d *Daemon) ListNodes(t assert.TestingT) []swarm.Node {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -0,0 +1,77 @@
package daemon
import (
"context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/gotestyourself/gotestyourself/poll"
)
// PluginIsRunning provides a poller to check if the specified plugin is running
func (d *Daemon) PluginIsRunning(name string) func(poll.LogT) poll.Result {
return withClient(d, withPluginInspect(name, func(plugin *types.Plugin, t poll.LogT) poll.Result {
if plugin.Enabled {
return poll.Success()
}
return poll.Continue("plugin %q is not enabled", name)
}))
}
// PluginIsNotRunning provides a poller to check if the specified plugin is not running
func (d *Daemon) PluginIsNotRunning(name string) func(poll.LogT) poll.Result {
return withClient(d, withPluginInspect(name, func(plugin *types.Plugin, t poll.LogT) poll.Result {
if !plugin.Enabled {
return poll.Success()
}
return poll.Continue("plugin %q is enabled", name)
}))
}
// PluginIsNotPresent provides a poller to check if the specified plugin is not present
func (d *Daemon) PluginIsNotPresent(name string) func(poll.LogT) poll.Result {
return withClient(d, func(c client.APIClient, t poll.LogT) poll.Result {
_, _, err := c.PluginInspectWithRaw(context.Background(), name)
if client.IsErrNotFound(err) {
return poll.Success()
}
if err != nil {
return poll.Error(err)
}
return poll.Continue("plugin %q exists")
})
}
// PluginReferenceIs provides a poller to check if the specified plugin has the specified reference
func (d *Daemon) PluginReferenceIs(name, expectedRef string) func(poll.LogT) poll.Result {
return withClient(d, withPluginInspect(name, func(plugin *types.Plugin, t poll.LogT) poll.Result {
if plugin.PluginReference == expectedRef {
return poll.Success()
}
return poll.Continue("plugin %q reference is not %q", name, expectedRef)
}))
}
func withPluginInspect(name string, f func(*types.Plugin, poll.LogT) poll.Result) func(client.APIClient, poll.LogT) poll.Result {
return func(c client.APIClient, t poll.LogT) poll.Result {
plugin, _, err := c.PluginInspectWithRaw(context.Background(), name)
if client.IsErrNotFound(err) {
return poll.Continue("plugin %q not found", name)
}
if err != nil {
return poll.Error(err)
}
return f(plugin, t)
}
}
func withClient(d *Daemon, f func(client.APIClient, poll.LogT) poll.Result) func(poll.LogT) poll.Result {
return func(t poll.LogT) poll.Result {
c, err := d.NewClient()
if err != nil {
poll.Error(err)
}
return f(c, t)
}
}
@@ -5,6 +5,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/internal/test"
"github.com/gotestyourself/gotestyourself/assert"
)
@@ -13,6 +14,9 @@ type SecretConstructor func(*swarm.Secret)
// CreateSecret creates a secret given the specified spec
func (d *Daemon) CreateSecret(t assert.TestingT, secretSpec swarm.SecretSpec) string {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -24,6 +28,9 @@ func (d *Daemon) CreateSecret(t assert.TestingT, secretSpec swarm.SecretSpec) st
// ListSecrets returns the list of the current swarm secrets
func (d *Daemon) ListSecrets(t assert.TestingT) []swarm.Secret {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -34,6 +41,9 @@ func (d *Daemon) ListSecrets(t assert.TestingT) []swarm.Secret {
// GetSecret returns a swarm secret identified by the specified id
func (d *Daemon) GetSecret(t assert.TestingT, id string) *swarm.Secret {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -44,6 +54,9 @@ func (d *Daemon) GetSecret(t assert.TestingT, id string) *swarm.Secret {
// DeleteSecret removes the swarm secret identified by the specified id
func (d *Daemon) DeleteSecret(t assert.TestingT, id string) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -54,6 +67,9 @@ func (d *Daemon) DeleteSecret(t assert.TestingT, id string) {
// UpdateSecret updates the swarm secret identified by the specified id
// Currently, only label update is supported.
func (d *Daemon) UpdateSecret(t assert.TestingT, id string, f ...SecretConstructor) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -7,6 +7,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/internal/test"
"github.com/gotestyourself/gotestyourself/assert"
)
@@ -14,6 +15,9 @@ import (
type ServiceConstructor func(*swarm.Service)
func (d *Daemon) createServiceWithOptions(t assert.TestingT, opts types.ServiceCreateOptions, f ...ServiceConstructor) string {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
var service swarm.Service
for _, fn := range f {
fn(&service)
@@ -32,11 +36,17 @@ func (d *Daemon) createServiceWithOptions(t assert.TestingT, opts types.ServiceC
// CreateService creates a swarm service given the specified service constructor
func (d *Daemon) CreateService(t assert.TestingT, f ...ServiceConstructor) string {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
return d.createServiceWithOptions(t, types.ServiceCreateOptions{}, f...)
}
// GetService returns the swarm service corresponding to the specified id
func (d *Daemon) GetService(t assert.TestingT, id string) *swarm.Service {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -47,6 +57,9 @@ func (d *Daemon) GetService(t assert.TestingT, id string) *swarm.Service {
// GetServiceTasks returns the swarm tasks for the specified service
func (d *Daemon) GetServiceTasks(t assert.TestingT, service string) []swarm.Task {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -65,6 +78,9 @@ func (d *Daemon) GetServiceTasks(t assert.TestingT, service string) []swarm.Task
// UpdateService updates a swarm service with the specified service constructor
func (d *Daemon) UpdateService(t assert.TestingT, service *swarm.Service, f ...ServiceConstructor) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -78,6 +94,9 @@ func (d *Daemon) UpdateService(t assert.TestingT, service *swarm.Service, f ...S
// RemoveService removes the specified service
func (d *Daemon) RemoveService(t assert.TestingT, id string) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -87,6 +106,9 @@ func (d *Daemon) RemoveService(t assert.TestingT, id string) {
// ListServices returns the list of the current swarm services
func (d *Daemon) ListServices(t assert.TestingT) []swarm.Service {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -97,6 +119,9 @@ func (d *Daemon) ListServices(t assert.TestingT) []swarm.Service {
// GetTask returns the swarm task identified by the specified id
func (d *Daemon) GetTask(t assert.TestingT, id string) swarm.Task {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -5,17 +5,22 @@ import (
"fmt"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/internal/test"
"github.com/gotestyourself/gotestyourself/assert"
"github.com/pkg/errors"
)
const (
defaultSwarmPort = 2477
// DefaultSwarmPort is the default port use for swarm in the tests
DefaultSwarmPort = 2477
defaultSwarmListenAddr = "0.0.0.0"
)
// StartAndSwarmInit starts the daemon (with busybox) and init the swarm
func (d *Daemon) StartAndSwarmInit(t testingT) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
// avoid networking conflicts
args := []string{"--iptables=false", "--swarm-default-advertise-addr=lo"}
d.StartWithBusybox(t, args...)
@@ -25,6 +30,9 @@ func (d *Daemon) StartAndSwarmInit(t testingT) {
// StartAndSwarmJoin starts the daemon (with busybox) and join the specified swarm as worker or manager
func (d *Daemon) StartAndSwarmJoin(t testingT, leader *Daemon, manager bool) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
// avoid networking conflicts
args := []string{"--iptables=false", "--swarm-default-advertise-addr=lo"}
d.StartWithBusybox(t, args...)
@@ -55,6 +63,9 @@ func (d *Daemon) NodeID() string {
// SwarmInit initializes a new swarm cluster.
func (d *Daemon) SwarmInit(t assert.TestingT, req swarm.InitRequest) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
if req.ListenAddr == "" {
req.ListenAddr = fmt.Sprintf("%s:%d", d.swarmListenAddr, d.SwarmPort)
}
@@ -67,6 +78,9 @@ func (d *Daemon) SwarmInit(t assert.TestingT, req swarm.InitRequest) {
// SwarmJoin joins a daemon to an existing cluster.
func (d *Daemon) SwarmJoin(t assert.TestingT, req swarm.JoinRequest) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
if req.ListenAddr == "" {
req.ListenAddr = fmt.Sprintf("%s:%d", d.swarmListenAddr, d.SwarmPort)
}
@@ -93,6 +107,9 @@ func (d *Daemon) SwarmLeave(force bool) error {
// SwarmInfo returns the swarm information of the daemon
func (d *Daemon) SwarmInfo(t assert.TestingT) swarm.Info {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
info, err := cli.Info(context.Background())
assert.NilError(t, err, "get swarm info")
@@ -115,6 +132,9 @@ func (d *Daemon) SwarmUnlock(req swarm.UnlockRequest) error {
// GetSwarm returns the current swarm object
func (d *Daemon) GetSwarm(t assert.TestingT) swarm.Swarm {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -125,6 +145,9 @@ func (d *Daemon) GetSwarm(t assert.TestingT) swarm.Swarm {
// UpdateSwarm updates the current swarm object with the specified spec constructors
func (d *Daemon) UpdateSwarm(t assert.TestingT, f ...SpecConstructor) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -139,6 +162,9 @@ func (d *Daemon) UpdateSwarm(t assert.TestingT, f ...SpecConstructor) {
// RotateTokens update the swarm to rotate tokens
func (d *Daemon) RotateTokens(t assert.TestingT) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -156,6 +182,9 @@ func (d *Daemon) RotateTokens(t assert.TestingT) {
// JoinTokens returns the current swarm join tokens
func (d *Daemon) JoinTokens(t assert.TestingT) swarm.JoinTokens {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
cli := d.NewClientT(t)
defer cli.Close()
@@ -7,6 +7,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"github.com/docker/docker/internal/test"
"github.com/gotestyourself/gotestyourself/assert"
"golang.org/x/net/context"
)
@@ -25,6 +26,9 @@ type logT interface {
// and removing everything else. It's meant to run after any tests so that they don't
// depend on each others.
func (e *Execution) Clean(t testingT) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
client := e.APIClient()
platform := e.OSType
@@ -41,6 +45,9 @@ func (e *Execution) Clean(t testingT) {
}
func unpauseAllContainers(t assert.TestingT, client client.ContainerAPIClient) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
ctx := context.Background()
containers := getPausedContainers(ctx, t, client)
if len(containers) > 0 {
@@ -52,6 +59,9 @@ func unpauseAllContainers(t assert.TestingT, client client.ContainerAPIClient) {
}
func getPausedContainers(ctx context.Context, t assert.TestingT, client client.ContainerAPIClient) []types.Container {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
filter := filters.NewArgs()
filter.Add("status", "paused")
containers, err := client.ContainerList(ctx, types.ContainerListOptions{
@@ -66,6 +76,9 @@ func getPausedContainers(ctx context.Context, t assert.TestingT, client client.C
var alreadyExists = regexp.MustCompile(`Error response from daemon: removal of container (\w+) is already in progress`)
func deleteAllContainers(t assert.TestingT, apiclient client.ContainerAPIClient, protectedContainers map[string]struct{}) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
ctx := context.Background()
containers := getAllContainers(ctx, t, apiclient)
if len(containers) == 0 {
@@ -88,6 +101,9 @@ func deleteAllContainers(t assert.TestingT, apiclient client.ContainerAPIClient,
}
func getAllContainers(ctx context.Context, t assert.TestingT, client client.ContainerAPIClient) []types.Container {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
containers, err := client.ContainerList(ctx, types.ContainerListOptions{
Quiet: true,
All: true,
@@ -97,6 +113,9 @@ func getAllContainers(ctx context.Context, t assert.TestingT, client client.Cont
}
func deleteAllImages(t testingT, apiclient client.ImageAPIClient, protectedImages map[string]struct{}) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
images, err := apiclient.ImageList(context.Background(), types.ImageListOptions{})
assert.Check(t, err, "failed to list images")
@@ -119,6 +138,9 @@ func deleteAllImages(t testingT, apiclient client.ImageAPIClient, protectedImage
}
func removeImage(ctx context.Context, t assert.TestingT, apiclient client.ImageAPIClient, ref string) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
_, err := apiclient.ImageRemove(ctx, ref, types.ImageRemoveOptions{
Force: true,
})
@@ -129,6 +151,9 @@ func removeImage(ctx context.Context, t assert.TestingT, apiclient client.ImageA
}
func deleteAllVolumes(t assert.TestingT, c client.VolumeAPIClient, protectedVolumes map[string]struct{}) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
volumes, err := c.VolumeList(context.Background(), filters.Args{})
assert.Check(t, err, "failed to list volumes")
@@ -146,6 +171,9 @@ func deleteAllVolumes(t assert.TestingT, c client.VolumeAPIClient, protectedVolu
}
func deleteAllNetworks(t assert.TestingT, c client.NetworkAPIClient, daemonPlatform string, protectedNetworks map[string]struct{}) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
networks, err := c.NetworkList(context.Background(), types.NetworkListOptions{})
assert.Check(t, err, "failed to list networks")
@@ -166,6 +194,9 @@ func deleteAllNetworks(t assert.TestingT, c client.NetworkAPIClient, daemonPlatf
}
func deleteAllPlugins(t assert.TestingT, c client.PluginAPIClient, protectedPlugins map[string]struct{}) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
plugins, err := c.PluginList(context.Background(), filters.Args{})
// Docker EE does not allow cluster-wide plugin management.
if client.IsErrNotImplemented(err) {
@@ -6,6 +6,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
dclient "github.com/docker/docker/client"
"github.com/docker/docker/internal/test"
"github.com/gotestyourself/gotestyourself/assert"
)
@@ -33,6 +34,9 @@ func newProtectedElements() protectedElements {
// volumes, and, on Linux, plugins) from being cleaned up at the end of test
// runs
func ProtectAll(t testingT, testEnv *Execution) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
ProtectContainers(t, testEnv)
ProtectImages(t, testEnv)
ProtectNetworks(t, testEnv)
@@ -45,6 +49,9 @@ func ProtectAll(t testingT, testEnv *Execution) {
// ProtectContainer adds the specified container(s) to be protected in case of
// clean
func (e *Execution) ProtectContainer(t testingT, containers ...string) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
for _, container := range containers {
e.protectedElements.containers[container] = struct{}{}
}
@@ -53,11 +60,17 @@ func (e *Execution) ProtectContainer(t testingT, containers ...string) {
// ProtectContainers protects existing containers from being cleaned up at the
// end of test runs
func ProtectContainers(t testingT, testEnv *Execution) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
containers := getExistingContainers(t, testEnv)
testEnv.ProtectContainer(t, containers...)
}
func getExistingContainers(t assert.TestingT, testEnv *Execution) []string {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
client := testEnv.APIClient()
containerList, err := client.ContainerList(context.Background(), types.ContainerListOptions{
All: true,
@@ -73,6 +86,9 @@ func getExistingContainers(t assert.TestingT, testEnv *Execution) []string {
// ProtectImage adds the specified image(s) to be protected in case of clean
func (e *Execution) ProtectImage(t testingT, images ...string) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
for _, image := range images {
e.protectedElements.images[image] = struct{}{}
}
@@ -81,6 +97,9 @@ func (e *Execution) ProtectImage(t testingT, images ...string) {
// ProtectImages protects existing images and on linux frozen images from being
// cleaned up at the end of test runs
func ProtectImages(t testingT, testEnv *Execution) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
images := getExistingImages(t, testEnv)
if testEnv.OSType == "linux" {
@@ -90,6 +109,9 @@ func ProtectImages(t testingT, testEnv *Execution) {
}
func getExistingImages(t assert.TestingT, testEnv *Execution) []string {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
client := testEnv.APIClient()
filter := filters.NewArgs()
filter.Add("dangling", "false")
@@ -124,6 +146,9 @@ func tagsFromImageSummary(image types.ImageSummary) []string {
// ProtectNetwork adds the specified network(s) to be protected in case of
// clean
func (e *Execution) ProtectNetwork(t testingT, networks ...string) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
for _, network := range networks {
e.protectedElements.networks[network] = struct{}{}
}
@@ -132,11 +157,17 @@ func (e *Execution) ProtectNetwork(t testingT, networks ...string) {
// ProtectNetworks protects existing networks from being cleaned up at the end
// of test runs
func ProtectNetworks(t testingT, testEnv *Execution) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
networks := getExistingNetworks(t, testEnv)
testEnv.ProtectNetwork(t, networks...)
}
func getExistingNetworks(t assert.TestingT, testEnv *Execution) []string {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
client := testEnv.APIClient()
networkList, err := client.NetworkList(context.Background(), types.NetworkListOptions{})
assert.NilError(t, err, "failed to list networks")
@@ -150,6 +181,9 @@ func getExistingNetworks(t assert.TestingT, testEnv *Execution) []string {
// ProtectPlugin adds the specified plugin(s) to be protected in case of clean
func (e *Execution) ProtectPlugin(t testingT, plugins ...string) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
for _, plugin := range plugins {
e.protectedElements.plugins[plugin] = struct{}{}
}
@@ -158,11 +192,17 @@ func (e *Execution) ProtectPlugin(t testingT, plugins ...string) {
// ProtectPlugins protects existing plugins from being cleaned up at the end of
// test runs
func ProtectPlugins(t testingT, testEnv *Execution) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
plugins := getExistingPlugins(t, testEnv)
testEnv.ProtectPlugin(t, plugins...)
}
func getExistingPlugins(t assert.TestingT, testEnv *Execution) []string {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
client := testEnv.APIClient()
pluginList, err := client.PluginList(context.Background(), filters.Args{})
// Docker EE does not allow cluster-wide plugin management.
@@ -180,6 +220,9 @@ func getExistingPlugins(t assert.TestingT, testEnv *Execution) []string {
// ProtectVolume adds the specified volume(s) to be protected in case of clean
func (e *Execution) ProtectVolume(t testingT, volumes ...string) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
for _, volume := range volumes {
e.protectedElements.volumes[volume] = struct{}{}
}
@@ -188,11 +231,17 @@ func (e *Execution) ProtectVolume(t testingT, volumes ...string) {
// ProtectVolumes protects existing volumes from being cleaned up at the end of
// test runs
func ProtectVolumes(t testingT, testEnv *Execution) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
volumes := getExistingVolumes(t, testEnv)
testEnv.ProtectVolume(t, volumes...)
}
func getExistingVolumes(t assert.TestingT, testEnv *Execution) []string {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
client := testEnv.APIClient()
volumeList, err := client.VolumeList(context.Background(), filters.Args{})
assert.NilError(t, err, "failed to list volumes")
@@ -7,6 +7,7 @@ import (
"os"
"path/filepath"
"github.com/docker/docker/internal/test"
"github.com/docker/docker/pkg/archive"
)
@@ -17,6 +18,9 @@ type testingT interface {
// New creates a fake build context
func New(t testingT, dir string, modifiers ...func(*Fake) error) *Fake {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
fakeContext := &Fake{Dir: dir}
if dir == "" {
if err := newDir(fakeContext); err != nil {
@@ -116,6 +120,9 @@ func (f *Fake) Close() error {
// AsTarReader returns a ReadCloser with the contents of Dir as a tar archive.
func (f *Fake) AsTarReader(t testingT) io.ReadCloser {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
reader, err := archive.TarWithOptions(f.Dir, &archive.TarOptions{})
if err != nil {
t.Fatalf("Failed to create tar from %s: %s", f.Dir, err)
@@ -9,6 +9,7 @@ import (
"os/exec"
"path/filepath"
"github.com/docker/docker/internal/test"
"github.com/docker/docker/internal/test/fakecontext"
"github.com/docker/docker/internal/test/fakestorage"
"github.com/gotestyourself/gotestyourself/assert"
@@ -63,6 +64,9 @@ func (g *FakeGit) Close() {
// New create a fake git server that can be used for git related tests
func New(c testingT, name string, files map[string]string, enforceLocalServer bool) *FakeGit {
if ht, ok := c.(test.HelperT); ok {
ht.Helper()
}
ctx := fakecontext.New(c, "", fakecontext.WithFiles(files))
defer ctx.Close()
curdir, err := os.Getwd()
@@ -10,6 +10,7 @@ import (
"sync"
"github.com/docker/docker/api/types"
"github.com/docker/docker/internal/test"
"github.com/docker/docker/pkg/archive"
"github.com/gotestyourself/gotestyourself/assert"
)
@@ -17,6 +18,9 @@ import (
var ensureHTTPServerOnce sync.Once
func ensureHTTPServerImage(t testingT) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
var doIt bool
ensureHTTPServerOnce.Do(func() {
doIt = true
@@ -14,6 +14,7 @@ import (
"github.com/docker/docker/api/types"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/docker/docker/internal/test"
"github.com/docker/docker/internal/test/environment"
"github.com/docker/docker/internal/test/fakecontext"
"github.com/docker/docker/internal/test/request"
@@ -56,6 +57,9 @@ func SetTestEnvironment(env *environment.Execution) {
// New returns a static file server that will be use as build context.
func New(t testingT, dir string, modifiers ...func(*fakecontext.Fake) error) Fake {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
if testEnv == nil {
t.Fatal("fakstorage package requires SetTestEnvironment() to be called before use.")
}
@@ -0,0 +1,6 @@
package test
// HelperT is a subset of testing.T that implements the Helper function
type HelperT interface {
Helper()
}
@@ -9,6 +9,7 @@ import (
"path/filepath"
"time"
"github.com/docker/docker/internal/test"
"github.com/gotestyourself/gotestyourself/assert"
"github.com/opencontainers/go-digest"
)
@@ -54,6 +55,9 @@ type Config struct {
// NewV2 creates a v2 registry server
func NewV2(t testingT, ops ...func(*Config)) *V2 {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
c := &Config{
registryURL: DefaultURL,
}
@@ -135,6 +139,9 @@ http:
// WaitReady waits for the registry to be ready to serve requests (or fail after a while)
func (r *V2) WaitReady(t testingT) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
var err error
for i := 0; i != 50; i++ {
if err = r.Ping(); err == nil {
@@ -183,6 +190,9 @@ func (r *V2) getBlobFilename(blobDigest digest.Digest) string {
// ReadBlobContents read the file corresponding to the specified digest
func (r *V2) ReadBlobContents(t assert.TestingT, blobDigest digest.Digest) []byte {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
// Load the target manifest blob.
manifestBlob, err := ioutil.ReadFile(r.getBlobFilename(blobDigest))
assert.NilError(t, err, "unable to read blob")
@@ -191,6 +201,9 @@ func (r *V2) ReadBlobContents(t assert.TestingT, blobDigest digest.Digest) []byt
// WriteBlobContents write the file corresponding to the specified digest with the given content
func (r *V2) WriteBlobContents(t assert.TestingT, blobDigest digest.Digest, data []byte) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
err := ioutil.WriteFile(r.getBlobFilename(blobDigest), data, os.FileMode(0644))
assert.NilError(t, err, "unable to write malicious data blob")
}
@@ -198,6 +211,9 @@ func (r *V2) WriteBlobContents(t assert.TestingT, blobDigest digest.Digest, data
// TempMoveBlobData moves the existing data file aside, so that we can replace it with a
// malicious blob of data for example.
func (r *V2) TempMoveBlobData(t testingT, blobDigest digest.Digest) (undo func()) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
tempFile, err := ioutil.TempFile("", "registry-temp-blob-")
assert.NilError(t, err, "unable to get temporary blob file")
tempFile.Close()
@@ -6,6 +6,8 @@ import (
"regexp"
"strings"
"sync"
"github.com/docker/docker/internal/test"
)
type handlerFunc func(w http.ResponseWriter, r *http.Request)
@@ -27,6 +29,9 @@ func (tr *Mock) RegisterHandler(path string, h handlerFunc) {
// NewMock creates a registry mock
func NewMock(t testingT) (*Mock, error) {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
testReg := &Mock{handlers: make(map[string]handlerFunc)}
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -14,6 +14,7 @@ import (
"time"
"github.com/docker/docker/client"
"github.com/docker/docker/internal/test"
"github.com/docker/docker/internal/test/environment"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/ioutils"
@@ -25,6 +26,9 @@ import (
// NewAPIClient returns a docker API client configured from environment variables
func NewAPIClient(t assert.TestingT, ops ...func(*client.Client) error) client.APIClient {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
ops = append([]func(*client.Client) error{client.FromEnv}, ops...)
clt, err := client.NewClientWithOpts(ops...)
assert.NilError(t, err)
@@ -33,6 +37,9 @@ func NewAPIClient(t assert.TestingT, ops ...func(*client.Client) error) client.A
// DaemonTime provides the current time on the daemon host
func DaemonTime(ctx context.Context, t assert.TestingT, client client.APIClient, testEnv *environment.Execution) time.Time {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
if testEnv.IsLocalDaemon() {
return time.Now()
}
@@ -48,6 +55,9 @@ func DaemonTime(ctx context.Context, t assert.TestingT, client client.APIClient,
// DaemonUnixTime returns the current time on the daemon host with nanoseconds precision.
// It return the time formatted how the client sends timestamps to the server.
func DaemonUnixTime(ctx context.Context, t assert.TestingT, client client.APIClient, testEnv *environment.Execution) string {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
dt := DaemonTime(ctx, t, client, testEnv)
return fmt.Sprintf("%d.%09d", dt.Unix(), int64(dt.Nanosecond()))
}
@@ -34,7 +34,7 @@ import (
"github.com/docker/docker/errdefs"
"github.com/docker/docker/pkg/ioutils"
"github.com/opencontainers/image-spec/specs-go/v1"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -114,6 +114,13 @@ type client struct {
containers map[string]*container
}
func (c *client) reconnect() error {
c.Lock()
err := c.remote.Reconnect()
c.Unlock()
return err
}
func (c *client) setRemote(remote *containerd.Client) {
c.Lock()
c.remote = remote
@@ -131,9 +138,30 @@ func (c *client) Version(ctx context.Context) (containerd.Version, error) {
return c.getRemote().Version(ctx)
}
// Restore loads the containerd container.
// It should not be called concurrently with any other operation for the given ID.
func (c *client) Restore(ctx context.Context, id string, attachStdio StdioCallback) (alive bool, pid int, err error) {
c.Lock()
defer c.Unlock()
_, ok := c.containers[id]
if ok {
c.Unlock()
return false, 0, errors.WithStack(newConflictError("id already in use"))
}
cntr := &container{}
c.containers[id] = cntr
cntr.mu.Lock()
defer cntr.mu.Unlock()
c.Unlock()
defer func() {
if err != nil {
c.Lock()
delete(c.containers, id)
c.Unlock()
}
}()
var dio *cio.DirectIO
defer func() {
@@ -144,9 +172,9 @@ func (c *client) Restore(ctx context.Context, id string, attachStdio StdioCallba
err = wrapError(err)
}()
ctr, err := c.remote.LoadContainer(ctx, id)
ctr, err := c.getRemote().LoadContainer(ctx, id)
if err != nil {
return false, -1, errors.WithStack(err)
return false, -1, errors.WithStack(wrapError(err))
}
attachIO := func(fifos *cio.FIFOSet) (cio.IO, error) {
@@ -160,24 +188,23 @@ func (c *client) Restore(ctx context.Context, id string, attachStdio StdioCallba
}
t, err := ctr.Task(ctx, attachIO)
if err != nil && !containerderrors.IsNotFound(err) {
return false, -1, err
return false, -1, errors.Wrap(wrapError(err), "error getting containerd task for container")
}
if t != nil {
s, err := t.Status(ctx)
if err != nil {
return false, -1, err
return false, -1, errors.Wrap(wrapError(err), "error getting task status")
}
alive = s.Status != containerd.Stopped
pid = int(t.Pid())
}
c.containers[id] = &container{
bundleDir: filepath.Join(c.stateDir, id),
ctr: ctr,
task: t,
// TODO(mlaventure): load execs
}
cntr.bundleDir = filepath.Join(c.stateDir, id)
cntr.ctr = ctr
cntr.task = t
// TODO(mlaventure): load execs
c.logger.WithFields(logrus.Fields{
"container": id,
@@ -311,20 +311,17 @@ func (r *remote) monitorConnection(monitor *containerd.Client) {
<-r.daemonWaitCh
}
monitor.Close()
os.Remove(r.GRPC.Address)
if err := r.startContainerd(); err != nil {
r.logger.WithError(err).Error("failed restarting containerd")
continue
}
newMonitor, err := containerd.New(r.GRPC.Address)
if err != nil {
if err := monitor.Reconnect(); err != nil {
r.logger.WithError(err).Error("failed connect to containerd")
continue
}
monitor = newMonitor
var wg sync.WaitGroup
for _, c := range r.clients {
@@ -333,18 +330,12 @@ func (r *remote) monitorConnection(monitor *containerd.Client) {
go func(c *client) {
defer wg.Done()
c.logger.WithField("namespace", c.namespace).Debug("creating new containerd remote client")
c.remote.Close()
remote, err := containerd.New(r.GRPC.Address, containerd.WithDefaultNamespace(c.namespace))
if err != nil {
if err := c.reconnect(); err != nil {
r.logger.WithError(err).Error("failed to connect to containerd")
// TODO: Better way to handle this?
// This *shouldn't* happen, but this could wind up where the daemon
// is not able to communicate with an eventually up containerd
return
}
c.setRemote(remote)
}(c)
wg.Wait()
@@ -16,19 +16,3 @@ func (o oomScore) Apply(r Remote) error {
}
return fmt.Errorf("WithOOMScore option not supported for this remote")
}
// WithSubreaper sets whether containerd should register itself as a
// subreaper
func WithSubreaper(reap bool) RemoteOption {
return subreaper(reap)
}
type subreaper bool
func (s subreaper) Apply(r Remote) error {
if remote, ok := r.(*remote); ok {
remote.NoSubreaper = !bool(s)
return nil
}
return fmt.Errorf("WithSubreaper option not supported for this remote")
}
+1 -1
View File
@@ -1,4 +1,4 @@
// +build darwin freebsd openbsd
// +build darwin freebsd openbsd netbsd
package term // import "github.com/docker/docker/pkg/term"
+2 -2
View File
@@ -13,7 +13,7 @@ github.com/mattn/go-shellwords v1.0.3
github.com/sirupsen/logrus v1.0.3
github.com/tchap/go-patricia v2.2.6
github.com/vdemeester/shakers 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
golang.org/x/net 7dcfb8076726a3fdd9353b6b8a1f1b6be6811bd6
golang.org/x/net 5561cd9b4330353950f399814f427425c0a26fd2
golang.org/x/sys 37707fdb30a5b38865cfb95e5aab41707daec7fd
github.com/docker/go-units 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1
github.com/docker/go-connections 7beb39f0b969b075d1325fecb092faf27fd357b6
@@ -108,7 +108,7 @@ github.com/googleapis/gax-go da06d194a00e19ce00d9011a13931c3f6f6887c7
google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
# containerd
github.com/containerd/containerd 3fa104f843ec92328912e042b767d26825f202aa
github.com/containerd/containerd 4ac4fd0b6a268fe6f38b2b2e32e40daa7e424fac
github.com/containerd/fifo fbfb6a11ec671efbe94ad1c12c2e98773f19e1e6
github.com/containerd/continuity d8fb8589b0e8e85b8c8bbaa8840226d0dfeb7371
github.com/containerd/cgroups fe281dd265766145e943a034aa41086474ea6130
+2 -1
View File
@@ -4,6 +4,7 @@
[![Build Status](https://travis-ci.org/containerd/containerd.svg?branch=master)](https://travis-ci.org/containerd/containerd)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fcontainerd%2Fcontainerd.svg?type=shield)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fcontainerd%2Fcontainerd?ref=badge_shield)
[![Go Report Card](https://goreportcard.com/badge/github.com/containerd/containerd)](https://goreportcard.com/report/github.com/containerd/containerd)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/1271/badge)](https://bestpractices.coreinfrastructure.org/projects/1271)
containerd is an industry-standard container runtime with an emphasis on simplicity, robustness and portability. It is available as a daemon for Linux and Windows, which can manage the complete container lifecycle of its host system: image transfer and storage, container execution and supervision, low-level storage and network attachments, etc.
@@ -13,7 +14,7 @@ containerd is designed to be embedded into a larger system, rather than being us
## Getting Started
See our documentation on [containerd.io](containerd.io):
See our documentation on [containerd.io](https://containerd.io):
* [for ops and admins](docs/ops.md)
* [namespaces](docs/namespaces.md)
* [client options](docs/client-opts.md)
@@ -158,9 +158,7 @@ func (m *ContainerCreate_Runtime) Field(fieldpath []string) (string, bool) {
return "", false
}
adaptor, ok := decoded.(interface {
Field([]string) (string, bool)
})
adaptor, ok := decoded.(interface{ Field([]string) (string, bool) })
if !ok {
return "", false
}
@@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package events has protobuf types for various events that are used in
// containerd.
package events
@@ -1,2 +1,18 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package events defines the event pushing and subscription service.
package events
@@ -115,9 +115,7 @@ func (m *Envelope) Field(fieldpath []string) (string, bool) {
return "", false
}
adaptor, ok := decoded.(interface {
Field([]string) (string, bool)
})
adaptor, ok := decoded.(interface{ Field([]string) (string, bool) })
if !ok {
return "", false
}
@@ -1 +1,17 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package images
@@ -1 +1,17 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package introspection
@@ -1 +1,17 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package leases
@@ -1 +1,17 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package types
@@ -0,0 +1,68 @@
// +build windows
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package archive
import (
"strconv"
"strings"
"time"
"github.com/dmcgowan/go-tar"
)
// Forked from https://github.com/golang/go/blob/master/src/archive/tar/strconv.go
// as archive/tar doesn't support CreationTime, but does handle PAX time parsing,
// and there's no need to re-invent the wheel.
// parsePAXTime takes a string of the form %d.%d as described in the PAX
// specification. Note that this implementation allows for negative timestamps,
// which is allowed for by the PAX specification, but not always portable.
func parsePAXTime(s string) (time.Time, error) {
const maxNanoSecondDigits = 9
// Split string into seconds and sub-seconds parts.
ss, sn := s, ""
if pos := strings.IndexByte(s, '.'); pos >= 0 {
ss, sn = s[:pos], s[pos+1:]
}
// Parse the seconds.
secs, err := strconv.ParseInt(ss, 10, 64)
if err != nil {
return time.Time{}, tar.ErrHeader
}
if len(sn) == 0 {
return time.Unix(secs, 0), nil // No sub-second values
}
// Parse the nanoseconds.
if strings.Trim(sn, "0123456789") != "" {
return time.Time{}, tar.ErrHeader
}
if len(sn) < maxNanoSecondDigits {
sn += strings.Repeat("0", maxNanoSecondDigits-len(sn)) // Right pad
} else {
sn = sn[:maxNanoSecondDigits] // Right truncate
}
nsecs, _ := strconv.ParseInt(sn, 10, 64) // Must succeed
if len(ss) > 0 && ss[0] == '-' {
return time.Unix(secs, -nsecs), nil // Negative correction
}
return time.Unix(secs, nsecs), nil
}
+237 -164
View File
@@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package archive
import (
@@ -13,19 +29,21 @@ import (
"syscall"
"time"
"github.com/containerd/containerd/fs"
"github.com/containerd/containerd/log"
"github.com/containerd/continuity/fs"
"github.com/dmcgowan/go-tar"
"github.com/pkg/errors"
)
var bufferPool = &sync.Pool{
var bufPool = &sync.Pool{
New: func() interface{} {
buffer := make([]byte, 32*1024)
return &buffer
},
}
var errInvalidArchive = errors.New("invalid archive")
// Diff returns a tar stream of the computed filesystem
// difference between the provided directories.
//
@@ -87,12 +105,23 @@ const (
// Apply applies a tar stream of an OCI style diff tar.
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
func Apply(ctx context.Context, root string, r io.Reader) (int64, error) {
func Apply(ctx context.Context, root string, r io.Reader, opts ...ApplyOpt) (int64, error) {
root = filepath.Clean(root)
var options ApplyOptions
for _, opt := range opts {
if err := opt(&options); err != nil {
return 0, errors.Wrap(err, "failed to apply option")
}
}
return apply(ctx, root, tar.NewReader(r), options)
}
// applyNaive applies a tar stream of an OCI style diff tar.
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) {
var (
tr = tar.NewReader(r)
size int64
dirs []*tar.Header
// Used for handling opaque directory markers which
@@ -220,6 +249,15 @@ func Apply(ctx context.Context, root string, r io.Reader) (int64, error) {
originalBase := base[len(whiteoutPrefix):]
originalPath := filepath.Join(dir, originalBase)
// Ensure originalPath is under dir
if dir[len(dir)-1] != filepath.Separator {
dir += string(filepath.Separator)
}
if !strings.HasPrefix(originalPath, dir) {
return 0, errors.Wrapf(errInvalidArchive, "invalid whiteout name: %v", base)
}
if err := os.RemoveAll(originalPath); err != nil {
return 0, err
}
@@ -285,163 +323,6 @@ func Apply(ctx context.Context, root string, r io.Reader) (int64, error) {
return size, nil
}
type changeWriter struct {
tw *tar.Writer
source string
whiteoutT time.Time
inodeSrc map[uint64]string
inodeRefs map[uint64][]string
}
func newChangeWriter(w io.Writer, source string) *changeWriter {
return &changeWriter{
tw: tar.NewWriter(w),
source: source,
whiteoutT: time.Now(),
inodeSrc: map[uint64]string{},
inodeRefs: map[uint64][]string{},
}
}
func (cw *changeWriter) HandleChange(k fs.ChangeKind, p string, f os.FileInfo, err error) error {
if err != nil {
return err
}
if k == fs.ChangeKindDelete {
whiteOutDir := filepath.Dir(p)
whiteOutBase := filepath.Base(p)
whiteOut := filepath.Join(whiteOutDir, whiteoutPrefix+whiteOutBase)
hdr := &tar.Header{
Name: whiteOut[1:],
Size: 0,
ModTime: cw.whiteoutT,
AccessTime: cw.whiteoutT,
ChangeTime: cw.whiteoutT,
}
if err := cw.tw.WriteHeader(hdr); err != nil {
return errors.Wrap(err, "failed to write whiteout header")
}
} else {
var (
link string
err error
source = filepath.Join(cw.source, p)
)
if f.Mode()&os.ModeSymlink != 0 {
if link, err = os.Readlink(source); err != nil {
return err
}
}
hdr, err := tar.FileInfoHeader(f, link)
if err != nil {
return err
}
hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode)))
name := p
if strings.HasPrefix(name, string(filepath.Separator)) {
name, err = filepath.Rel(string(filepath.Separator), name)
if err != nil {
return errors.Wrap(err, "failed to make path relative")
}
}
name, err = tarName(name)
if err != nil {
return errors.Wrap(err, "cannot canonicalize path")
}
// suffix with '/' for directories
if f.IsDir() && !strings.HasSuffix(name, "/") {
name += "/"
}
hdr.Name = name
if err := setHeaderForSpecialDevice(hdr, name, f); err != nil {
return errors.Wrap(err, "failed to set device headers")
}
// additionalLinks stores file names which must be linked to
// this file when this file is added
var additionalLinks []string
inode, isHardlink := fs.GetLinkInfo(f)
if isHardlink {
// If the inode has a source, always link to it
if source, ok := cw.inodeSrc[inode]; ok {
hdr.Typeflag = tar.TypeLink
hdr.Linkname = source
hdr.Size = 0
} else {
if k == fs.ChangeKindUnmodified {
cw.inodeRefs[inode] = append(cw.inodeRefs[inode], name)
return nil
}
cw.inodeSrc[inode] = name
additionalLinks = cw.inodeRefs[inode]
delete(cw.inodeRefs, inode)
}
} else if k == fs.ChangeKindUnmodified && !f.IsDir() {
// Nothing to write to diff
// Unmodified directories should still be written to keep
// directory permissions correct on direct unpack
return nil
}
if capability, err := getxattr(source, "security.capability"); err != nil {
return errors.Wrap(err, "failed to get capabilities xattr")
} else if capability != nil {
if hdr.PAXRecords == nil {
hdr.PAXRecords = map[string]string{}
}
hdr.PAXRecords[paxSchilyXattr+"security.capability"] = string(capability)
}
if err := cw.tw.WriteHeader(hdr); err != nil {
return errors.Wrap(err, "failed to write file header")
}
if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 {
file, err := open(source)
if err != nil {
return errors.Wrapf(err, "failed to open path: %v", source)
}
defer file.Close()
buf := bufferPool.Get().(*[]byte)
n, err := io.CopyBuffer(cw.tw, file, *buf)
bufferPool.Put(buf)
if err != nil {
return errors.Wrap(err, "failed to copy")
}
if n != hdr.Size {
return errors.New("short write copying file")
}
}
if additionalLinks != nil {
source = hdr.Name
for _, extra := range additionalLinks {
hdr.Name = extra
hdr.Typeflag = tar.TypeLink
hdr.Linkname = source
hdr.Size = 0
if err := cw.tw.WriteHeader(hdr); err != nil {
return errors.Wrap(err, "failed to write file header")
}
}
}
}
return nil
}
func (cw *changeWriter) Close() error {
if err := cw.tw.Close(); err != nil {
return errors.Wrap(err, "failed to close tar writer")
}
return nil
}
func createTarFile(ctx context.Context, path, extractDir string, hdr *tar.Header, reader io.Reader) error {
// hdr.Mode is in linux format, which we can use for syscalls,
// but for os.Foo() calls we need the mode converted to os.FileMode,
@@ -535,9 +416,201 @@ func createTarFile(ctx context.Context, path, extractDir string, hdr *tar.Header
return chtimes(path, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime))
}
type changeWriter struct {
tw *tar.Writer
source string
whiteoutT time.Time
inodeSrc map[uint64]string
inodeRefs map[uint64][]string
addedDirs map[string]struct{}
}
func newChangeWriter(w io.Writer, source string) *changeWriter {
return &changeWriter{
tw: tar.NewWriter(w),
source: source,
whiteoutT: time.Now(),
inodeSrc: map[uint64]string{},
inodeRefs: map[uint64][]string{},
addedDirs: map[string]struct{}{},
}
}
func (cw *changeWriter) HandleChange(k fs.ChangeKind, p string, f os.FileInfo, err error) error {
if err != nil {
return err
}
if k == fs.ChangeKindDelete {
whiteOutDir := filepath.Dir(p)
whiteOutBase := filepath.Base(p)
whiteOut := filepath.Join(whiteOutDir, whiteoutPrefix+whiteOutBase)
hdr := &tar.Header{
Typeflag: tar.TypeReg,
Name: whiteOut[1:],
Size: 0,
ModTime: cw.whiteoutT,
AccessTime: cw.whiteoutT,
ChangeTime: cw.whiteoutT,
}
if err := cw.includeParents(hdr); err != nil {
return err
}
if err := cw.tw.WriteHeader(hdr); err != nil {
return errors.Wrap(err, "failed to write whiteout header")
}
} else {
var (
link string
err error
source = filepath.Join(cw.source, p)
)
if f.Mode()&os.ModeSymlink != 0 {
if link, err = os.Readlink(source); err != nil {
return err
}
}
hdr, err := tar.FileInfoHeader(f, link)
if err != nil {
return err
}
hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode)))
name := p
if strings.HasPrefix(name, string(filepath.Separator)) {
name, err = filepath.Rel(string(filepath.Separator), name)
if err != nil {
return errors.Wrap(err, "failed to make path relative")
}
}
name, err = tarName(name)
if err != nil {
return errors.Wrap(err, "cannot canonicalize path")
}
// suffix with '/' for directories
if f.IsDir() && !strings.HasSuffix(name, "/") {
name += "/"
}
hdr.Name = name
if err := setHeaderForSpecialDevice(hdr, name, f); err != nil {
return errors.Wrap(err, "failed to set device headers")
}
// additionalLinks stores file names which must be linked to
// this file when this file is added
var additionalLinks []string
inode, isHardlink := fs.GetLinkInfo(f)
if isHardlink {
// If the inode has a source, always link to it
if source, ok := cw.inodeSrc[inode]; ok {
hdr.Typeflag = tar.TypeLink
hdr.Linkname = source
hdr.Size = 0
} else {
if k == fs.ChangeKindUnmodified {
cw.inodeRefs[inode] = append(cw.inodeRefs[inode], name)
return nil
}
cw.inodeSrc[inode] = name
additionalLinks = cw.inodeRefs[inode]
delete(cw.inodeRefs, inode)
}
} else if k == fs.ChangeKindUnmodified {
// Nothing to write to diff
return nil
}
if capability, err := getxattr(source, "security.capability"); err != nil {
return errors.Wrap(err, "failed to get capabilities xattr")
} else if capability != nil {
if hdr.PAXRecords == nil {
hdr.PAXRecords = map[string]string{}
}
hdr.PAXRecords[paxSchilyXattr+"security.capability"] = string(capability)
}
if err := cw.includeParents(hdr); err != nil {
return err
}
if err := cw.tw.WriteHeader(hdr); err != nil {
return errors.Wrap(err, "failed to write file header")
}
if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 {
file, err := open(source)
if err != nil {
return errors.Wrapf(err, "failed to open path: %v", source)
}
defer file.Close()
n, err := copyBuffered(context.TODO(), cw.tw, file)
if err != nil {
return errors.Wrap(err, "failed to copy")
}
if n != hdr.Size {
return errors.New("short write copying file")
}
}
if additionalLinks != nil {
source = hdr.Name
for _, extra := range additionalLinks {
hdr.Name = extra
hdr.Typeflag = tar.TypeLink
hdr.Linkname = source
hdr.Size = 0
if err := cw.includeParents(hdr); err != nil {
return err
}
if err := cw.tw.WriteHeader(hdr); err != nil {
return errors.Wrap(err, "failed to write file header")
}
}
}
}
return nil
}
func (cw *changeWriter) Close() error {
if err := cw.tw.Close(); err != nil {
return errors.Wrap(err, "failed to close tar writer")
}
return nil
}
func (cw *changeWriter) includeParents(hdr *tar.Header) error {
name := strings.TrimRight(hdr.Name, "/")
fname := filepath.Join(cw.source, name)
parent := filepath.Dir(name)
pname := filepath.Join(cw.source, parent)
// Do not include root directory as parent
if fname != cw.source && pname != cw.source {
_, ok := cw.addedDirs[parent]
if !ok {
cw.addedDirs[parent] = struct{}{}
fi, err := os.Stat(pname)
if err != nil {
return err
}
if err := cw.HandleChange(fs.ChangeKindModify, parent, fi, nil); err != nil {
return err
}
}
}
if hdr.Typeflag == tar.TypeDir {
cw.addedDirs[name] = struct{}{}
}
return nil
}
func copyBuffered(ctx context.Context, dst io.Writer, src io.Reader) (written int64, err error) {
buf := bufferPool.Get().(*[]byte)
defer bufferPool.Put(buf)
buf := bufPool.Get().(*[]byte)
defer bufPool.Put(buf)
for {
select {
@@ -0,0 +1,20 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package archive
// ApplyOpt allows setting mutable archive apply properties on creation
type ApplyOpt func(options *ApplyOptions) error
@@ -0,0 +1,23 @@
// +build !windows
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package archive
// ApplyOptions provides additional options for an Apply operation
type ApplyOptions struct {
}
@@ -0,0 +1,44 @@
// +build windows
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package archive
// ApplyOptions provides additional options for an Apply operation
type ApplyOptions struct {
ParentLayerPaths []string // Parent layer paths used for Windows layer apply
IsWindowsContainerLayer bool // True if the tar stream to be applied is a Windows Container Layer
}
// WithParentLayers adds parent layers to the apply process this is required
// for all Windows layers except the base layer.
func WithParentLayers(parentPaths []string) ApplyOpt {
return func(options *ApplyOptions) error {
options.ParentLayerPaths = parentPaths
return nil
}
}
// AsWindowsContainerLayer indicates that the tar stream to apply is that of
// a Windows Container Layer. The caller must be holding SeBackupPrivilege and
// SeRestorePrivilege.
func AsWindowsContainerLayer() ApplyOpt {
return func(options *ApplyOptions) error {
options.IsWindowsContainerLayer = true
return nil
}
}
@@ -1,8 +1,25 @@
// +build !windows
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package archive
import (
"context"
"os"
"sync"
"syscall"
@@ -28,11 +45,14 @@ func setHeaderForSpecialDevice(hdr *tar.Header, name string, fi os.FileInfo) err
return errors.New("unsupported stat type")
}
// Rdev is int32 on darwin/bsd, int64 on linux/solaris
rdev := uint64(s.Rdev) // nolint: unconvert
// Currently go does not fill in the major/minors
if s.Mode&syscall.S_IFBLK != 0 ||
s.Mode&syscall.S_IFCHR != 0 {
hdr.Devmajor = int64(unix.Major(uint64(s.Rdev)))
hdr.Devminor = int64(unix.Minor(uint64(s.Rdev)))
hdr.Devmajor = int64(unix.Major(rdev))
hdr.Devminor = int64(unix.Minor(rdev))
}
return nil
@@ -128,3 +148,9 @@ func getxattr(path, attr string) ([]byte, error) {
func setxattr(path, key, value string) error {
return sysx.LSetxattr(path, key, []byte(value), 0)
}
// apply applies a tar stream of an OCI style diff tar.
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
func apply(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) {
return applyNaive(ctx, root, tr, options)
}
@@ -1,15 +1,71 @@
// +build windows
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package archive
import (
"bufio"
"context"
"encoding/base64"
"errors"
"fmt"
"io"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"syscall"
"github.com/Microsoft/go-winio"
"github.com/Microsoft/hcsshim"
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/sys"
"github.com/dmcgowan/go-tar"
)
const (
// MSWINDOWS pax vendor extensions
hdrMSWindowsPrefix = "MSWINDOWS."
hdrFileAttributes = hdrMSWindowsPrefix + "fileattr"
hdrSecurityDescriptor = hdrMSWindowsPrefix + "sd"
hdrRawSecurityDescriptor = hdrMSWindowsPrefix + "rawsd"
hdrMountPoint = hdrMSWindowsPrefix + "mountpoint"
hdrEaPrefix = hdrMSWindowsPrefix + "xattr."
// LIBARCHIVE pax vendor extensions
hdrLibArchivePrefix = "LIBARCHIVE."
hdrCreateTime = hdrLibArchivePrefix + "creationtime"
)
var (
// mutatedFiles is a list of files that are mutated by the import process
// and must be backed up and restored.
mutatedFiles = map[string]string{
"UtilityVM/Files/EFI/Microsoft/Boot/BCD": "bcd.bak",
"UtilityVM/Files/EFI/Microsoft/Boot/BCD.LOG": "bcd.log.bak",
"UtilityVM/Files/EFI/Microsoft/Boot/BCD.LOG1": "bcd.log1.bak",
"UtilityVM/Files/EFI/Microsoft/Boot/BCD.LOG2": "bcd.log2.bak",
}
)
// tarName returns platform-specific filepath
// to canonical posix-style path for tar archival. p is relative
// path.
@@ -101,3 +157,290 @@ func setxattr(path, key, value string) error {
// since xattrs should not exist in windows diff archives
return errors.New("xattrs not supported on Windows")
}
// apply applies a tar stream of an OCI style diff tar of a Windows layer.
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
func apply(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) {
if options.IsWindowsContainerLayer {
return applyWindowsLayer(ctx, root, tr, options)
}
return applyNaive(ctx, root, tr, options)
}
// applyWindowsLayer applies a tar stream of an OCI style diff tar of a Windows layer.
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
func applyWindowsLayer(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) {
home, id := filepath.Split(root)
info := hcsshim.DriverInfo{
HomeDir: home,
}
w, err := hcsshim.NewLayerWriter(info, id, options.ParentLayerPaths)
if err != nil {
return 0, err
}
defer func() {
if err := w.Close(); err != nil {
log.G(ctx).Errorf("failed to close layer writer: %v", err)
}
}()
buf := bufio.NewWriter(nil)
hdr, nextErr := tr.Next()
// Iterate through the files in the archive.
for {
select {
case <-ctx.Done():
return 0, ctx.Err()
default:
}
if nextErr == io.EOF {
// end of tar archive
break
}
if nextErr != nil {
return 0, nextErr
}
// Note: path is used instead of filepath to prevent OS specific handling
// of the tar path
base := path.Base(hdr.Name)
if strings.HasPrefix(base, whiteoutPrefix) {
dir := path.Dir(hdr.Name)
originalBase := base[len(whiteoutPrefix):]
originalPath := path.Join(dir, originalBase)
if err := w.Remove(filepath.FromSlash(originalPath)); err != nil {
return 0, err
}
hdr, nextErr = tr.Next()
} else if hdr.Typeflag == tar.TypeLink {
err := w.AddLink(filepath.FromSlash(hdr.Name), filepath.FromSlash(hdr.Linkname))
if err != nil {
return 0, err
}
hdr, nextErr = tr.Next()
} else {
name, fileSize, fileInfo, err := fileInfoFromHeader(hdr)
if err != nil {
return 0, err
}
if err := w.Add(filepath.FromSlash(name), fileInfo); err != nil {
return 0, err
}
size += fileSize
hdr, nextErr = tarToBackupStreamWithMutatedFiles(buf, w, tr, hdr, root)
}
}
return
}
// fileInfoFromHeader retrieves basic Win32 file information from a tar header, using the additional metadata written by
// WriteTarFileFromBackupStream.
func fileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *winio.FileBasicInfo, err error) {
name = hdr.Name
if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA {
size = hdr.Size
}
fileInfo = &winio.FileBasicInfo{
LastAccessTime: syscall.NsecToFiletime(hdr.AccessTime.UnixNano()),
LastWriteTime: syscall.NsecToFiletime(hdr.ModTime.UnixNano()),
ChangeTime: syscall.NsecToFiletime(hdr.ChangeTime.UnixNano()),
// Default CreationTime to ModTime, updated below if MSWINDOWS.createtime exists
CreationTime: syscall.NsecToFiletime(hdr.ModTime.UnixNano()),
}
if attrStr, ok := hdr.PAXRecords[hdrFileAttributes]; ok {
attr, err := strconv.ParseUint(attrStr, 10, 32)
if err != nil {
return "", 0, nil, err
}
fileInfo.FileAttributes = uintptr(attr)
} else {
if hdr.Typeflag == tar.TypeDir {
fileInfo.FileAttributes |= syscall.FILE_ATTRIBUTE_DIRECTORY
}
}
if createStr, ok := hdr.PAXRecords[hdrCreateTime]; ok {
createTime, err := parsePAXTime(createStr)
if err != nil {
return "", 0, nil, err
}
fileInfo.CreationTime = syscall.NsecToFiletime(createTime.UnixNano())
}
return
}
// tarToBackupStreamWithMutatedFiles reads data from a tar stream and
// writes it to a backup stream, and also saves any files that will be mutated
// by the import layer process to a backup location.
func tarToBackupStreamWithMutatedFiles(buf *bufio.Writer, w io.Writer, t *tar.Reader, hdr *tar.Header, root string) (nextHdr *tar.Header, err error) {
var (
bcdBackup *os.File
bcdBackupWriter *winio.BackupFileWriter
)
if backupPath, ok := mutatedFiles[hdr.Name]; ok {
bcdBackup, err = os.Create(filepath.Join(root, backupPath))
if err != nil {
return nil, err
}
defer func() {
cerr := bcdBackup.Close()
if err == nil {
err = cerr
}
}()
bcdBackupWriter = winio.NewBackupFileWriter(bcdBackup, false)
defer func() {
cerr := bcdBackupWriter.Close()
if err == nil {
err = cerr
}
}()
buf.Reset(io.MultiWriter(w, bcdBackupWriter))
} else {
buf.Reset(w)
}
defer func() {
ferr := buf.Flush()
if err == nil {
err = ferr
}
}()
return writeBackupStreamFromTarFile(buf, t, hdr)
}
// writeBackupStreamFromTarFile writes a Win32 backup stream from the current tar file. Since this function may process multiple
// tar file entries in order to collect all the alternate data streams for the file, it returns the next
// tar file that was not processed, or io.EOF is there are no more.
func writeBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (*tar.Header, error) {
bw := winio.NewBackupStreamWriter(w)
var sd []byte
var err error
// Maintaining old SDDL-based behavior for backward compatibility. All new tar headers written
// by this library will have raw binary for the security descriptor.
if sddl, ok := hdr.PAXRecords[hdrSecurityDescriptor]; ok {
sd, err = winio.SddlToSecurityDescriptor(sddl)
if err != nil {
return nil, err
}
}
if sdraw, ok := hdr.PAXRecords[hdrRawSecurityDescriptor]; ok {
sd, err = base64.StdEncoding.DecodeString(sdraw)
if err != nil {
return nil, err
}
}
if len(sd) != 0 {
bhdr := winio.BackupHeader{
Id: winio.BackupSecurity,
Size: int64(len(sd)),
}
err := bw.WriteHeader(&bhdr)
if err != nil {
return nil, err
}
_, err = bw.Write(sd)
if err != nil {
return nil, err
}
}
var eas []winio.ExtendedAttribute
for k, v := range hdr.PAXRecords {
if !strings.HasPrefix(k, hdrEaPrefix) {
continue
}
data, err := base64.StdEncoding.DecodeString(v)
if err != nil {
return nil, err
}
eas = append(eas, winio.ExtendedAttribute{
Name: k[len(hdrEaPrefix):],
Value: data,
})
}
if len(eas) != 0 {
eadata, err := winio.EncodeExtendedAttributes(eas)
if err != nil {
return nil, err
}
bhdr := winio.BackupHeader{
Id: winio.BackupEaData,
Size: int64(len(eadata)),
}
err = bw.WriteHeader(&bhdr)
if err != nil {
return nil, err
}
_, err = bw.Write(eadata)
if err != nil {
return nil, err
}
}
if hdr.Typeflag == tar.TypeSymlink {
_, isMountPoint := hdr.PAXRecords[hdrMountPoint]
rp := winio.ReparsePoint{
Target: filepath.FromSlash(hdr.Linkname),
IsMountPoint: isMountPoint,
}
reparse := winio.EncodeReparsePoint(&rp)
bhdr := winio.BackupHeader{
Id: winio.BackupReparseData,
Size: int64(len(reparse)),
}
err := bw.WriteHeader(&bhdr)
if err != nil {
return nil, err
}
_, err = bw.Write(reparse)
if err != nil {
return nil, err
}
}
buf := bufPool.Get().(*[]byte)
defer bufPool.Put(buf)
if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA {
bhdr := winio.BackupHeader{
Id: winio.BackupData,
Size: hdr.Size,
}
err := bw.WriteHeader(&bhdr)
if err != nil {
return nil, err
}
_, err = io.CopyBuffer(bw, t, *buf)
if err != nil {
return nil, err
}
}
// Copy all the alternate data streams and return the next non-ADS header.
for {
ahdr, err := t.Next()
if err != nil {
return nil, err
}
if ahdr.Typeflag != tar.TypeReg || !strings.HasPrefix(ahdr.Name, hdr.Name+":") {
return ahdr, nil
}
bhdr := winio.BackupHeader{
Id: winio.BackupAlternateData,
Size: ahdr.Size,
Name: ahdr.Name[len(hdr.Name):] + ":$DATA",
}
err = bw.WriteHeader(&bhdr)
if err != nil {
return nil, err
}
_, err = io.CopyBuffer(bw, t, *buf)
if err != nil {
return nil, err
}
}
}
@@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package archive
import (
@@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package archive
import (
@@ -1,5 +1,21 @@
// +build linux freebsd solaris
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package archive
import (
@@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package archive
import (
+38 -3
View File
@@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cio
import (
@@ -6,8 +22,17 @@ import (
"io"
"os"
"sync"
"github.com/containerd/containerd/defaults"
)
var bufPool = sync.Pool{
New: func() interface{} {
buffer := make([]byte, 32<<10)
return &buffer
},
}
// Config holds the IO configurations.
type Config struct {
// Terminal is true if one has been allocated
@@ -68,6 +93,7 @@ type Streams struct {
Stdout io.Writer
Stderr io.Writer
Terminal bool
FIFODir string
}
// Opt customize options for creating a Creator or Attach
@@ -92,16 +118,25 @@ func WithStreams(stdin io.Reader, stdout, stderr io.Writer) Opt {
}
}
// WithFIFODir sets the fifo directory.
// e.g. "/run/containerd/fifo", "/run/users/1001/containerd/fifo"
func WithFIFODir(dir string) Opt {
return func(opt *Streams) {
opt.FIFODir = dir
}
}
// NewCreator returns an IO creator from the options
func NewCreator(opts ...Opt) Creator {
streams := &Streams{}
for _, opt := range opts {
opt(streams)
}
if streams.FIFODir == "" {
streams.FIFODir = defaults.DefaultFIFODir
}
return func(id string) (IO, error) {
// TODO: accept root as a param
root := "/run/containerd/fifo"
fifos, err := NewFIFOSetInDir(root, id, streams.Terminal)
fifos, err := NewFIFOSetInDir(streams.FIFODir, id, streams.Terminal)
if err != nil {
return nil, err
}
+38 -6
View File
@@ -1,5 +1,21 @@
// +build !windows
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cio
import (
@@ -47,7 +63,10 @@ func copyIO(fifos *FIFOSet, ioset *Streams) (*cio, error) {
if fifos.Stdin != "" {
go func() {
io.Copy(pipes.Stdin, ioset.Stdin)
p := bufPool.Get().(*[]byte)
defer bufPool.Put(p)
io.CopyBuffer(pipes.Stdin, ioset.Stdin, *p)
pipes.Stdin.Close()
}()
}
@@ -55,7 +74,10 @@ func copyIO(fifos *FIFOSet, ioset *Streams) (*cio, error) {
var wg = &sync.WaitGroup{}
wg.Add(1)
go func() {
io.Copy(ioset.Stdout, pipes.Stdout)
p := bufPool.Get().(*[]byte)
defer bufPool.Put(p)
io.CopyBuffer(ioset.Stdout, pipes.Stdout, *p)
pipes.Stdout.Close()
wg.Done()
}()
@@ -63,7 +85,10 @@ func copyIO(fifos *FIFOSet, ioset *Streams) (*cio, error) {
if !fifos.Terminal {
wg.Add(1)
go func() {
io.Copy(ioset.Stderr, pipes.Stderr)
p := bufPool.Get().(*[]byte)
defer bufPool.Put(p)
io.CopyBuffer(ioset.Stderr, pipes.Stderr, *p)
pipes.Stderr.Close()
wg.Done()
}()
@@ -89,17 +114,24 @@ func openFifos(ctx context.Context, fifos *FIFOSet) (pipes, error) {
if f.Stdin, err = fifo.OpenFifo(ctx, fifos.Stdin, syscall.O_WRONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); err != nil {
return f, errors.Wrapf(err, "failed to open stdin fifo")
}
defer func() {
if err != nil && f.Stdin != nil {
f.Stdin.Close()
}
}()
}
if fifos.Stdout != "" {
if f.Stdout, err = fifo.OpenFifo(ctx, fifos.Stdout, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); err != nil {
f.Stdin.Close()
return f, errors.Wrapf(err, "failed to open stdout fifo")
}
defer func() {
if err != nil && f.Stdout != nil {
f.Stdout.Close()
}
}()
}
if fifos.Stderr != "" {
if f.Stderr, err = fifo.OpenFifo(ctx, fifos.Stderr, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); err != nil {
f.Stdin.Close()
f.Stdout.Close()
return f, errors.Wrapf(err, "failed to open stderr fifo")
}
}
@@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cio
import (
@@ -47,7 +63,11 @@ func copyIO(fifos *FIFOSet, ioset *Streams) (*cio, error) {
log.L.WithError(err).Errorf("failed to accept stdin connection on %s", fifos.Stdin)
return
}
io.Copy(c, ioset.Stdin)
p := bufPool.Get().(*[]byte)
defer bufPool.Put(p)
io.CopyBuffer(c, ioset.Stdin, *p)
c.Close()
l.Close()
}()
@@ -73,7 +93,11 @@ func copyIO(fifos *FIFOSet, ioset *Streams) (*cio, error) {
log.L.WithError(err).Errorf("failed to accept stdout connection on %s", fifos.Stdout)
return
}
io.Copy(ioset.Stdout, c)
p := bufPool.Get().(*[]byte)
defer bufPool.Put(p)
io.CopyBuffer(ioset.Stdout, c, *p)
c.Close()
l.Close()
}()
@@ -99,7 +123,11 @@ func copyIO(fifos *FIFOSet, ioset *Streams) (*cio, error) {
log.L.WithError(err).Errorf("failed to accept stderr connection on %s", fifos.Stderr)
return
}
io.Copy(ioset.Stderr, c)
p := bufPool.Get().(*[]byte)
defer bufPool.Put(p)
io.CopyBuffer(ioset.Stderr, c, *p)
c.Close()
l.Close()
}()
+66 -64
View File
@@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package containerd
import (
@@ -7,8 +23,6 @@ import (
"net/http"
"runtime"
"strconv"
"strings"
"sync"
"time"
containersapi "github.com/containerd/containerd/api/services/containers/v1"
@@ -24,7 +38,6 @@ import (
"github.com/containerd/containerd/containers"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/dialer"
"github.com/containerd/containerd/diff"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/namespaces"
@@ -80,11 +93,22 @@ func New(address string, opts ...ClientOpt) (*Client, error) {
grpc.WithStreamInterceptor(stream),
)
}
conn, err := grpc.Dial(dialer.DialAddress(address), gopts...)
if err != nil {
return nil, errors.Wrapf(err, "failed to dial %q", address)
connector := func() (*grpc.ClientConn, error) {
conn, err := grpc.Dial(dialer.DialAddress(address), gopts...)
if err != nil {
return nil, errors.Wrapf(err, "failed to dial %q", address)
}
return conn, nil
}
return NewWithConn(conn, opts...)
conn, err := connector()
if err != nil {
return nil, err
}
return &Client{
conn: conn,
connector: connector,
runtime: fmt.Sprintf("%s.%s", plugin.RuntimePlugin, runtime.GOOS),
}, nil
}
// NewWithConn returns a new containerd client that is connected to the containerd
@@ -99,8 +123,23 @@ func NewWithConn(conn *grpc.ClientConn, opts ...ClientOpt) (*Client, error) {
// Client is the client to interact with containerd and its various services
// using a uniform interface
type Client struct {
conn *grpc.ClientConn
runtime string
conn *grpc.ClientConn
runtime string
connector func() (*grpc.ClientConn, error)
}
// Reconnect re-establishes the GRPC connection to the containerd daemon
func (c *Client) Reconnect() error {
if c.connector == nil {
return errors.New("unable to reconnect to containerd, no connector available")
}
c.conn.Close()
conn, err := c.connector()
if err != nil {
return err
}
c.conn = conn
return nil
}
// IsServing returns true if the client can successfully connect to the
@@ -222,11 +261,11 @@ func (c *Client) Pull(ctx context.Context, ref string, opts ...RemoteOpt) (Image
name, desc, err := pullCtx.Resolver.Resolve(ctx, ref)
if err != nil {
return nil, err
return nil, errors.Wrapf(err, "failed to resolve reference %q", ref)
}
fetcher, err := pullCtx.Resolver.Fetcher(ctx, name)
if err != nil {
return nil, err
return nil, errors.Wrapf(err, "failed to get fetcher for %q", name)
}
var (
@@ -237,10 +276,17 @@ func (c *Client) Pull(ctx context.Context, ref string, opts ...RemoteOpt) (Image
schema1Converter = schema1.NewConverter(store, fetcher)
handler = images.Handlers(append(pullCtx.BaseHandlers, schema1Converter)...)
} else {
// Get all the children for a descriptor
childrenHandler := images.ChildrenHandler(store)
// Set any children labels for that content
childrenHandler = images.SetChildrenLabels(store, childrenHandler)
// Filter the childen by the platform
childrenHandler = images.FilterPlatform(platforms.Default(), childrenHandler)
handler = images.Handlers(append(pullCtx.BaseHandlers,
remotes.FetchHandler(store, fetcher),
images.ChildrenHandler(store, platforms.Default()))...,
)
childrenHandler,
)...)
}
if err := images.Dispatch(ctx, handler, desc); err != nil {
@@ -281,7 +327,7 @@ func (c *Client) Pull(ctx context.Context, ref string, opts ...RemoteOpt) (Image
}
if pullCtx.Unpack {
if err := img.Unpack(ctx, pullCtx.Snapshotter); err != nil {
return nil, err
errors.Wrapf(err, "failed to unpack image on snapshotter %s", pullCtx.Snapshotter)
}
}
return img, nil
@@ -301,51 +347,7 @@ func (c *Client) Push(ctx context.Context, ref string, desc ocispec.Descriptor,
return err
}
var m sync.Mutex
manifestStack := []ocispec.Descriptor{}
filterHandler := images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
switch desc.MediaType {
case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest,
images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
m.Lock()
manifestStack = append(manifestStack, desc)
m.Unlock()
return nil, images.ErrStopHandler
default:
return nil, nil
}
})
cs := c.ContentStore()
pushHandler := remotes.PushHandler(cs, pusher)
handlers := append(pushCtx.BaseHandlers,
images.ChildrenHandler(cs, platforms.Default()),
filterHandler,
pushHandler,
)
if err := images.Dispatch(ctx, images.Handlers(handlers...), desc); err != nil {
return err
}
// Iterate in reverse order as seen, parent always uploaded after child
for i := len(manifestStack) - 1; i >= 0; i-- {
_, err := pushHandler(ctx, manifestStack[i])
if err != nil {
// TODO(estesp): until we have a more complete method for index push, we need to report
// missing dependencies in an index/manifest list by sensing the "400 Bad Request"
// as a marker for this problem
if (manifestStack[i].MediaType == ocispec.MediaTypeImageIndex ||
manifestStack[i].MediaType == images.MediaTypeDockerSchema2ManifestList) &&
errors.Cause(err) != nil && strings.Contains(errors.Cause(err).Error(), "400 Bad Request") {
return errors.Wrap(err, "manifest list/index references to blobs and/or manifests are missing in your target registry")
}
return err
}
}
return nil
return remotes.PushContent(ctx, pusher, desc, c.ContentStore(), pushCtx.BaseHandlers...)
}
// GetImage returns an existing image
@@ -378,11 +380,11 @@ func (c *Client) ListImages(ctx context.Context, filters ...string) ([]Image, er
// Subscribe to events that match one or more of the provided filters.
//
// Callers should listen on both the envelope channel and errs channel. If the
// errs channel returns nil or an error, the subscriber should terminate.
// Callers should listen on both the envelope and errs channels. If the errs
// channel returns nil or an error, the subscriber should terminate.
//
// To cancel shutdown reciept of events, cancel the provided context. The errs
// channel will be closed and return a nil error.
// The subscriber can stop receiving events by canceling the provided context.
// The errs channel will be closed and return a nil error.
func (c *Client) Subscribe(ctx context.Context, filters ...string) (ch <-chan *eventsapi.Envelope, errs <-chan error) {
var (
evq = make(chan *eventsapi.Envelope)
@@ -458,7 +460,7 @@ func (c *Client) ImageService() images.Store {
}
// DiffService returns the underlying Differ
func (c *Client) DiffService() diff.Differ {
func (c *Client) DiffService() DiffService {
return NewDiffServiceFromClient(diffapi.NewDiffClient(c.conn))
}
@@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package containerd
import (
+16
View File
@@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package containerd
import (
@@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package containerd
import (
@@ -1,12 +1,27 @@
// +build !windows
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package containerd
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"syscall"
@@ -16,6 +31,7 @@ import (
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/linux/runctypes"
"github.com/containerd/containerd/mount"
"github.com/containerd/containerd/platforms"
"github.com/gogo/protobuf/proto"
@@ -115,7 +131,7 @@ func WithTaskCheckpoint(im Image) NewTaskOpts {
}
}
func decodeIndex(ctx context.Context, store content.Store, id digest.Digest) (*v1.Index, error) {
func decodeIndex(ctx context.Context, store content.Provider, id digest.Digest) (*v1.Index, error) {
var index v1.Index
p, err := content.ReadBlob(ctx, store, id)
if err != nil {
@@ -166,7 +182,7 @@ func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool
if err != nil {
return err
}
if err := remapRootFS(mounts, uid, gid); err != nil {
if err := remapRootFS(ctx, mounts, uid, gid); err != nil {
snapshotter.Remove(ctx, usernsID)
return err
}
@@ -187,22 +203,10 @@ func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool
}
}
func remapRootFS(mounts []mount.Mount, uid, gid uint32) error {
root, err := ioutil.TempDir("", "ctd-remap")
if err != nil {
return err
}
defer os.Remove(root)
for _, m := range mounts {
if err := m.Mount(root); err != nil {
return err
}
}
err = filepath.Walk(root, incrementFS(root, uid, gid))
if uerr := mount.Unmount(root, 0); err == nil {
err = uerr
}
return err
func remapRootFS(ctx context.Context, mounts []mount.Mount, uid, gid uint32) error {
return mount.WithTempMount(ctx, mounts, func(root string) error {
return filepath.Walk(root, incrementFS(root, uid, gid))
})
}
func incrementFS(root string, uidInc, gidInc uint32) filepath.WalkFunc {
@@ -218,3 +222,19 @@ func incrementFS(root string, uidInc, gidInc uint32) filepath.WalkFunc {
return os.Lchown(path, u, g)
}
}
// WithNoPivotRoot instructs the runtime not to you pivot_root
func WithNoPivotRoot(_ context.Context, _ *Client, info *TaskInfo) error {
if info.Options == nil {
info.Options = &runctypes.CreateOptions{
NoPivotRoot: true,
}
return nil
}
copts, ok := info.Options.(*runctypes.CreateOptions)
if !ok {
return errors.New("invalid options type, expected runctypes.CreateOptions")
}
copts.NoPivotRoot = true
return nil
}
@@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package containers
import (
@@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package containerd
import (
@@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package content
import (

Some files were not shown because too many files have changed in this diff Show More