Merge component 'engine' from git@github.com:moby/moby master
This commit is contained in:
@@ -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{
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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, ",")...)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
+172
-116
@@ -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,4 +1,4 @@
|
||||
// +build darwin freebsd openbsd
|
||||
// +build darwin freebsd openbsd netbsd
|
||||
|
||||
package term // import "github.com/docker/docker/pkg/term"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Generated
Vendored
+2
-1
@@ -4,6 +4,7 @@
|
||||
[](https://travis-ci.org/containerd/containerd)
|
||||
[](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fcontainerd%2Fcontainerd?ref=badge_shield)
|
||||
[](https://goreportcard.com/report/github.com/containerd/containerd)
|
||||
[](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)
|
||||
|
||||
Generated
Vendored
+1
-3
@@ -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
|
||||
}
|
||||
|
||||
+16
@@ -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
|
||||
|
||||
Generated
Vendored
+16
@@ -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
|
||||
|
||||
Generated
Vendored
+1
-3
@@ -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
|
||||
}
|
||||
|
||||
Generated
Vendored
+16
@@ -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
|
||||
|
||||
Generated
Vendored
+16
@@ -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
|
||||
|
||||
Generated
Vendored
+16
@@ -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
|
||||
|
||||
+16
@@ -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
|
||||
|
||||
+68
@@ -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
@@ -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 {
|
||||
|
||||
+20
@@ -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
|
||||
Generated
Vendored
+23
@@ -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 {
|
||||
}
|
||||
Generated
Vendored
+44
@@ -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
|
||||
}
|
||||
}
|
||||
+28
-2
@@ -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)
|
||||
}
|
||||
|
||||
+343
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
@@ -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 (
|
||||
|
||||
+16
@@ -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 (
|
||||
|
||||
+16
@@ -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 (
|
||||
|
||||
Generated
Vendored
+16
@@ -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
@@ -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
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
+31
-3
@@ -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
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
+16
@@ -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
@@ -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
@@ -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 (
|
||||
|
||||
+39
-19
@@ -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
|
||||
}
|
||||
|
||||
Generated
Vendored
+16
@@ -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 (
|
||||
|
||||
+16
@@ -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
@@ -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
Reference in New Issue
Block a user