Initial commit
This commit is contained in:
9
vendor/github.com/hyperhq/hypercli/daemon/README.md
generated
vendored
Normal file
9
vendor/github.com/hyperhq/hypercli/daemon/README.md
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
This directory contains code pertaining to running containers and storing images
|
||||
|
||||
Code pertaining to running containers:
|
||||
|
||||
- execdriver
|
||||
|
||||
Code pertaining to storing images:
|
||||
|
||||
- graphdriver
|
||||
429
vendor/github.com/hyperhq/hypercli/daemon/archive.go
generated
vendored
Normal file
429
vendor/github.com/hyperhq/hypercli/daemon/archive.go
generated
vendored
Normal file
@@ -0,0 +1,429 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/hyperhq/hypercli/builder"
|
||||
"github.com/hyperhq/hypercli/container"
|
||||
"github.com/hyperhq/hypercli/pkg/archive"
|
||||
"github.com/hyperhq/hypercli/pkg/chrootarchive"
|
||||
"github.com/hyperhq/hypercli/pkg/idtools"
|
||||
"github.com/hyperhq/hypercli/pkg/ioutils"
|
||||
"github.com/docker/engine-api/types"
|
||||
)
|
||||
|
||||
// ErrExtractPointNotDirectory is used to convey that the operation to extract
|
||||
// a tar archive to a directory in a container has failed because the specified
|
||||
// path does not refer to a directory.
|
||||
var ErrExtractPointNotDirectory = errors.New("extraction point is not a directory")
|
||||
|
||||
// ContainerCopy performs a deprecated operation of archiving the resource at
|
||||
// the specified path in the container identified by the given name.
|
||||
func (daemon *Daemon) ContainerCopy(name string, res string) (io.ReadCloser, error) {
|
||||
container, err := daemon.GetContainer(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res[0] == '/' || res[0] == '\\' {
|
||||
res = res[1:]
|
||||
}
|
||||
|
||||
return daemon.containerCopy(container, res)
|
||||
}
|
||||
|
||||
// ContainerStatPath stats the filesystem resource at the specified path in the
|
||||
// container identified by the given name.
|
||||
func (daemon *Daemon) ContainerStatPath(name string, path string) (stat *types.ContainerPathStat, err error) {
|
||||
container, err := daemon.GetContainer(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return daemon.containerStatPath(container, path)
|
||||
}
|
||||
|
||||
// ContainerArchivePath creates an archive of the filesystem resource at the
|
||||
// specified path in the container identified by the given name. Returns a
|
||||
// tar archive of the resource and whether it was a directory or a single file.
|
||||
func (daemon *Daemon) ContainerArchivePath(name string, path string) (content io.ReadCloser, stat *types.ContainerPathStat, err error) {
|
||||
container, err := daemon.GetContainer(name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return daemon.containerArchivePath(container, path)
|
||||
}
|
||||
|
||||
// ContainerExtractToDir extracts the given archive to the specified location
|
||||
// in the filesystem of the container identified by the given name. The given
|
||||
// path must be of a directory in the container. If it is not, the error will
|
||||
// be ErrExtractPointNotDirectory. If noOverwriteDirNonDir is true then it will
|
||||
// be an error if unpacking the given content would cause an existing directory
|
||||
// to be replaced with a non-directory and vice versa.
|
||||
func (daemon *Daemon) ContainerExtractToDir(name, path string, noOverwriteDirNonDir bool, content io.Reader) error {
|
||||
container, err := daemon.GetContainer(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return daemon.containerExtractToDir(container, path, noOverwriteDirNonDir, content)
|
||||
}
|
||||
|
||||
// containerStatPath stats the filesystem resource at the specified path in this
|
||||
// container. Returns stat info about the resource.
|
||||
func (daemon *Daemon) containerStatPath(container *container.Container, path string) (stat *types.ContainerPathStat, err error) {
|
||||
container.Lock()
|
||||
defer container.Unlock()
|
||||
|
||||
if err = daemon.Mount(container); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer daemon.Unmount(container)
|
||||
|
||||
err = daemon.mountVolumes(container)
|
||||
defer container.UnmountVolumes(true, daemon.LogVolumeEvent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolvedPath, absPath, err := container.ResolvePath(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return container.StatPath(resolvedPath, absPath)
|
||||
}
|
||||
|
||||
// containerArchivePath creates an archive of the filesystem resource at the specified
|
||||
// path in this container. Returns a tar archive of the resource and stat info
|
||||
// about the resource.
|
||||
func (daemon *Daemon) containerArchivePath(container *container.Container, path string) (content io.ReadCloser, stat *types.ContainerPathStat, err error) {
|
||||
container.Lock()
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// Wait to unlock the container until the archive is fully read
|
||||
// (see the ReadCloseWrapper func below) or if there is an error
|
||||
// before that occurs.
|
||||
container.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
if err = daemon.Mount(container); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// unmount any volumes
|
||||
container.UnmountVolumes(true, daemon.LogVolumeEvent)
|
||||
// unmount the container's rootfs
|
||||
daemon.Unmount(container)
|
||||
}
|
||||
}()
|
||||
|
||||
if err = daemon.mountVolumes(container); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
resolvedPath, absPath, err := container.ResolvePath(path)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
stat, err = container.StatPath(resolvedPath, absPath)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// We need to rebase the archive entries if the last element of the
|
||||
// resolved path was a symlink that was evaluated and is now different
|
||||
// than the requested path. For example, if the given path was "/foo/bar/",
|
||||
// but it resolved to "/var/lib/docker/containers/{id}/foo/baz/", we want
|
||||
// to ensure that the archive entries start with "bar" and not "baz". This
|
||||
// also catches the case when the root directory of the container is
|
||||
// requested: we want the archive entries to start with "/" and not the
|
||||
// container ID.
|
||||
data, err := archive.TarResourceRebase(resolvedPath, filepath.Base(absPath))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
content = ioutils.NewReadCloserWrapper(data, func() error {
|
||||
err := data.Close()
|
||||
container.UnmountVolumes(true, daemon.LogVolumeEvent)
|
||||
daemon.Unmount(container)
|
||||
container.Unlock()
|
||||
return err
|
||||
})
|
||||
|
||||
daemon.LogContainerEvent(container, "archive-path")
|
||||
|
||||
return content, stat, nil
|
||||
}
|
||||
|
||||
// containerExtractToDir extracts the given tar archive to the specified location in the
|
||||
// filesystem of this container. The given path must be of a directory in the
|
||||
// container. If it is not, the error will be ErrExtractPointNotDirectory. If
|
||||
// noOverwriteDirNonDir is true then it will be an error if unpacking the
|
||||
// given content would cause an existing directory to be replaced with a non-
|
||||
// directory and vice versa.
|
||||
func (daemon *Daemon) containerExtractToDir(container *container.Container, path string, noOverwriteDirNonDir bool, content io.Reader) (err error) {
|
||||
container.Lock()
|
||||
defer container.Unlock()
|
||||
|
||||
if err = daemon.Mount(container); err != nil {
|
||||
return err
|
||||
}
|
||||
defer daemon.Unmount(container)
|
||||
|
||||
err = daemon.mountVolumes(container)
|
||||
defer container.UnmountVolumes(true, daemon.LogVolumeEvent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The destination path needs to be resolved to a host path, with all
|
||||
// symbolic links followed in the scope of the container's rootfs. Note
|
||||
// that we do not use `container.ResolvePath(path)` here because we need
|
||||
// to also evaluate the last path element if it is a symlink. This is so
|
||||
// that you can extract an archive to a symlink that points to a directory.
|
||||
|
||||
// Consider the given path as an absolute path in the container.
|
||||
absPath := archive.PreserveTrailingDotOrSeparator(filepath.Join(string(filepath.Separator), path), path)
|
||||
|
||||
// This will evaluate the last path element if it is a symlink.
|
||||
resolvedPath, err := container.GetResourcePath(absPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stat, err := os.Lstat(resolvedPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !stat.IsDir() {
|
||||
return ErrExtractPointNotDirectory
|
||||
}
|
||||
|
||||
// Need to check if the path is in a volume. If it is, it cannot be in a
|
||||
// read-only volume. If it is not in a volume, the container cannot be
|
||||
// configured with a read-only rootfs.
|
||||
|
||||
// Use the resolved path relative to the container rootfs as the new
|
||||
// absPath. This way we fully follow any symlinks in a volume that may
|
||||
// lead back outside the volume.
|
||||
//
|
||||
// The Windows implementation of filepath.Rel in golang 1.4 does not
|
||||
// support volume style file path semantics. On Windows when using the
|
||||
// filter driver, we are guaranteed that the path will always be
|
||||
// a volume file path.
|
||||
var baseRel string
|
||||
if strings.HasPrefix(resolvedPath, `\\?\Volume{`) {
|
||||
if strings.HasPrefix(resolvedPath, container.BaseFS) {
|
||||
baseRel = resolvedPath[len(container.BaseFS):]
|
||||
if baseRel[:1] == `\` {
|
||||
baseRel = baseRel[1:]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
baseRel, err = filepath.Rel(container.BaseFS, resolvedPath)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Make it an absolute path.
|
||||
absPath = filepath.Join(string(filepath.Separator), baseRel)
|
||||
|
||||
toVolume, err := checkIfPathIsInAVolume(container, absPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !toVolume && container.HostConfig.ReadonlyRootfs {
|
||||
return ErrRootFSReadOnly
|
||||
}
|
||||
|
||||
options := &archive.TarOptions{
|
||||
ChownOpts: &archive.TarChownOptions{
|
||||
UID: 0, GID: 0, // TODO: use config.User? Remap to userns root?
|
||||
},
|
||||
NoOverwriteDirNonDir: noOverwriteDirNonDir,
|
||||
}
|
||||
|
||||
if err := chrootarchive.Untar(content, resolvedPath, options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
daemon.LogContainerEvent(container, "extract-to-dir")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (daemon *Daemon) containerCopy(container *container.Container, resource string) (rc io.ReadCloser, err error) {
|
||||
container.Lock()
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// Wait to unlock the container until the archive is fully read
|
||||
// (see the ReadCloseWrapper func below) or if there is an error
|
||||
// before that occurs.
|
||||
container.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
if err := daemon.Mount(container); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// unmount any volumes
|
||||
container.UnmountVolumes(true, daemon.LogVolumeEvent)
|
||||
// unmount the container's rootfs
|
||||
daemon.Unmount(container)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := daemon.mountVolumes(container); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
basePath, err := container.GetResourcePath(resource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stat, err := os.Stat(basePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var filter []string
|
||||
if !stat.IsDir() {
|
||||
d, f := filepath.Split(basePath)
|
||||
basePath = d
|
||||
filter = []string{f}
|
||||
} else {
|
||||
filter = []string{filepath.Base(basePath)}
|
||||
basePath = filepath.Dir(basePath)
|
||||
}
|
||||
archive, err := archive.TarWithOptions(basePath, &archive.TarOptions{
|
||||
Compression: archive.Uncompressed,
|
||||
IncludeFiles: filter,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reader := ioutils.NewReadCloserWrapper(archive, func() error {
|
||||
err := archive.Close()
|
||||
container.UnmountVolumes(true, daemon.LogVolumeEvent)
|
||||
daemon.Unmount(container)
|
||||
container.Unlock()
|
||||
return err
|
||||
})
|
||||
daemon.LogContainerEvent(container, "copy")
|
||||
return reader, nil
|
||||
}
|
||||
|
||||
// CopyOnBuild copies/extracts a source FileInfo to a destination path inside a container
|
||||
// specified by a container object.
|
||||
// TODO: make sure callers don't unnecessarily convert destPath with filepath.FromSlash (Copy does it already).
|
||||
// CopyOnBuild should take in abstract paths (with slashes) and the implementation should convert it to OS-specific paths.
|
||||
func (daemon *Daemon) CopyOnBuild(cID string, destPath string, src builder.FileInfo, decompress bool) error {
|
||||
srcPath := src.Path()
|
||||
destExists := true
|
||||
destDir := false
|
||||
rootUID, rootGID := daemon.GetRemappedUIDGID()
|
||||
|
||||
// Work in daemon-local OS specific file paths
|
||||
destPath = filepath.FromSlash(destPath)
|
||||
|
||||
c, err := daemon.GetContainer(cID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = daemon.Mount(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer daemon.Unmount(c)
|
||||
|
||||
dest, err := c.GetResourcePath(destPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Preserve the trailing slash
|
||||
// TODO: why are we appending another path separator if there was already one?
|
||||
if strings.HasSuffix(destPath, string(os.PathSeparator)) || destPath == "." {
|
||||
destDir = true
|
||||
dest += string(os.PathSeparator)
|
||||
}
|
||||
|
||||
destPath = dest
|
||||
|
||||
destStat, err := os.Stat(destPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
//logrus.Errorf("Error performing os.Stat on %s. %s", destPath, err)
|
||||
return err
|
||||
}
|
||||
destExists = false
|
||||
}
|
||||
|
||||
uidMaps, gidMaps := daemon.GetUIDGIDMaps()
|
||||
archiver := &archive.Archiver{
|
||||
Untar: chrootarchive.Untar,
|
||||
UIDMaps: uidMaps,
|
||||
GIDMaps: gidMaps,
|
||||
}
|
||||
|
||||
if src.IsDir() {
|
||||
// copy as directory
|
||||
if err := archiver.CopyWithTar(srcPath, destPath); err != nil {
|
||||
return err
|
||||
}
|
||||
return fixPermissions(srcPath, destPath, rootUID, rootGID, destExists)
|
||||
}
|
||||
if decompress && archive.IsArchivePath(srcPath) {
|
||||
// Only try to untar if it is a file and that we've been told to decompress (when ADD-ing a remote file)
|
||||
|
||||
// First try to unpack the source as an archive
|
||||
// to support the untar feature we need to clean up the path a little bit
|
||||
// because tar is very forgiving. First we need to strip off the archive's
|
||||
// filename from the path but this is only added if it does not end in slash
|
||||
tarDest := destPath
|
||||
if strings.HasSuffix(tarDest, string(os.PathSeparator)) {
|
||||
tarDest = filepath.Dir(destPath)
|
||||
}
|
||||
|
||||
// try to successfully untar the orig
|
||||
err := archiver.UntarPath(srcPath, tarDest)
|
||||
/*
|
||||
if err != nil {
|
||||
logrus.Errorf("Couldn't untar to %s: %v", tarDest, err)
|
||||
}
|
||||
*/
|
||||
return err
|
||||
}
|
||||
|
||||
// only needed for fixPermissions, but might as well put it before CopyFileWithTar
|
||||
if destDir || (destExists && destStat.IsDir()) {
|
||||
destPath = filepath.Join(destPath, src.Name())
|
||||
}
|
||||
|
||||
if err := idtools.MkdirAllNewAs(filepath.Dir(destPath), 0755, rootUID, rootGID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := archiver.CopyFileWithTar(srcPath, destPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fixPermissions(srcPath, destPath, rootUID, rootGID, destExists)
|
||||
}
|
||||
57
vendor/github.com/hyperhq/hypercli/daemon/archive_unix.go
generated
vendored
Normal file
57
vendor/github.com/hyperhq/hypercli/daemon/archive_unix.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
// +build !windows
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"github.com/hyperhq/hypercli/container"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// checkIfPathIsInAVolume checks if the path is in a volume. If it is, it
|
||||
// cannot be in a read-only volume. If it is not in a volume, the container
|
||||
// cannot be configured with a read-only rootfs.
|
||||
func checkIfPathIsInAVolume(container *container.Container, absPath string) (bool, error) {
|
||||
var toVolume bool
|
||||
for _, mnt := range container.MountPoints {
|
||||
if toVolume = mnt.HasResource(absPath); toVolume {
|
||||
if mnt.RW {
|
||||
break
|
||||
}
|
||||
return false, ErrVolumeReadonly
|
||||
}
|
||||
}
|
||||
return toVolume, nil
|
||||
}
|
||||
|
||||
func fixPermissions(source, destination string, uid, gid int, destExisted bool) error {
|
||||
// If the destination didn't already exist, or the destination isn't a
|
||||
// directory, then we should Lchown the destination. Otherwise, we shouldn't
|
||||
// Lchown the destination.
|
||||
destStat, err := os.Stat(destination)
|
||||
if err != nil {
|
||||
// This should *never* be reached, because the destination must've already
|
||||
// been created while untar-ing the context.
|
||||
return err
|
||||
}
|
||||
doChownDestination := !destExisted || !destStat.IsDir()
|
||||
|
||||
// We Walk on the source rather than on the destination because we don't
|
||||
// want to change permissions on things we haven't created or modified.
|
||||
return filepath.Walk(source, func(fullpath string, info os.FileInfo, err error) error {
|
||||
// Do not alter the walk root iff. it existed before, as it doesn't fall under
|
||||
// the domain of "things we should chown".
|
||||
if !doChownDestination && (source == fullpath) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Path is prefixed by source: substitute with destination instead.
|
||||
cleaned, err := filepath.Rel(source, fullpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fullpath = filepath.Join(destination, cleaned)
|
||||
return os.Lchown(fullpath, uid, gid)
|
||||
})
|
||||
}
|
||||
18
vendor/github.com/hyperhq/hypercli/daemon/archive_windows.go
generated
vendored
Normal file
18
vendor/github.com/hyperhq/hypercli/daemon/archive_windows.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
package daemon
|
||||
|
||||
import "github.com/hyperhq/hypercli/container"
|
||||
|
||||
// checkIfPathIsInAVolume checks if the path is in a volume. If it is, it
|
||||
// cannot be in a read-only volume. If it is not in a volume, the container
|
||||
// cannot be configured with a read-only rootfs.
|
||||
//
|
||||
// This is a no-op on Windows which does not support read-only volumes, or
|
||||
// extracting to a mount point inside a volume. TODO Windows: FIXME Post-TP4
|
||||
func checkIfPathIsInAVolume(container *container.Container, absPath string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func fixPermissions(source, destination string, uid, gid int, destExisted bool) error {
|
||||
// chown is not supported on Windows
|
||||
return nil
|
||||
}
|
||||
167
vendor/github.com/hyperhq/hypercli/daemon/attach.go
generated
vendored
Normal file
167
vendor/github.com/hyperhq/hypercli/daemon/attach.go
generated
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/hyperhq/hypercli/container"
|
||||
"github.com/hyperhq/hypercli/daemon/logger"
|
||||
derr "github.com/hyperhq/hypercli/errors"
|
||||
"github.com/hyperhq/hypercli/pkg/stdcopy"
|
||||
)
|
||||
|
||||
// ContainerAttachWithLogsConfig holds the streams to use when connecting to a container to view logs.
|
||||
type ContainerAttachWithLogsConfig struct {
|
||||
Hijacker http.Hijacker
|
||||
Upgrade bool
|
||||
UseStdin bool
|
||||
UseStdout bool
|
||||
UseStderr bool
|
||||
Logs bool
|
||||
Stream bool
|
||||
DetachKeys []byte
|
||||
}
|
||||
|
||||
// ContainerAttachWithLogs attaches to logs according to the config passed in. See ContainerAttachWithLogsConfig.
|
||||
func (daemon *Daemon) ContainerAttachWithLogs(prefixOrName string, c *ContainerAttachWithLogsConfig) error {
|
||||
if c.Hijacker == nil {
|
||||
return derr.ErrorCodeNoHijackConnection.WithArgs(prefixOrName)
|
||||
}
|
||||
container, err := daemon.GetContainer(prefixOrName)
|
||||
if err != nil {
|
||||
return derr.ErrorCodeNoSuchContainer.WithArgs(prefixOrName)
|
||||
}
|
||||
if container.IsPaused() {
|
||||
return derr.ErrorCodePausedContainer.WithArgs(prefixOrName)
|
||||
}
|
||||
|
||||
conn, _, err := c.Hijacker.Hijack()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
// Flush the options to make sure the client sets the raw mode
|
||||
conn.Write([]byte{})
|
||||
inStream := conn.(io.ReadCloser)
|
||||
outStream := conn.(io.Writer)
|
||||
|
||||
if c.Upgrade {
|
||||
fmt.Fprintf(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n")
|
||||
} else {
|
||||
fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
|
||||
}
|
||||
|
||||
var errStream io.Writer
|
||||
|
||||
if !container.Config.Tty {
|
||||
errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr)
|
||||
outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
|
||||
} else {
|
||||
errStream = outStream
|
||||
}
|
||||
|
||||
var stdin io.ReadCloser
|
||||
var stdout, stderr io.Writer
|
||||
|
||||
if c.UseStdin {
|
||||
stdin = inStream
|
||||
}
|
||||
if c.UseStdout {
|
||||
stdout = outStream
|
||||
}
|
||||
if c.UseStderr {
|
||||
stderr = errStream
|
||||
}
|
||||
|
||||
if err := daemon.attachWithLogs(container, stdin, stdout, stderr, c.Logs, c.Stream, c.DetachKeys); err != nil {
|
||||
fmt.Fprintf(outStream, "Error attaching: %s\n", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContainerWsAttachWithLogsConfig attach with websockets, since all
|
||||
// stream data is delegated to the websocket to handle there.
|
||||
type ContainerWsAttachWithLogsConfig struct {
|
||||
InStream io.ReadCloser
|
||||
OutStream, ErrStream io.Writer
|
||||
Logs, Stream bool
|
||||
DetachKeys []byte
|
||||
}
|
||||
|
||||
// ContainerWsAttachWithLogs websocket connection
|
||||
func (daemon *Daemon) ContainerWsAttachWithLogs(prefixOrName string, c *ContainerWsAttachWithLogsConfig) error {
|
||||
container, err := daemon.GetContainer(prefixOrName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return daemon.attachWithLogs(container, c.InStream, c.OutStream, c.ErrStream, c.Logs, c.Stream, c.DetachKeys)
|
||||
}
|
||||
|
||||
// ContainerAttachOnBuild attaches streams to the container cID. If stream is true, it streams the output.
|
||||
func (daemon *Daemon) ContainerAttachOnBuild(cID string, stdin io.ReadCloser, stdout, stderr io.Writer, stream bool) error {
|
||||
return daemon.ContainerWsAttachWithLogs(cID, &ContainerWsAttachWithLogsConfig{
|
||||
InStream: stdin,
|
||||
OutStream: stdout,
|
||||
ErrStream: stderr,
|
||||
Stream: stream,
|
||||
})
|
||||
}
|
||||
|
||||
func (daemon *Daemon) attachWithLogs(container *container.Container, stdin io.ReadCloser, stdout, stderr io.Writer, logs, stream bool, keys []byte) error {
|
||||
if logs {
|
||||
logDriver, err := daemon.getLogger(container)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cLog, ok := logDriver.(logger.LogReader)
|
||||
if !ok {
|
||||
return logger.ErrReadLogsNotSupported
|
||||
}
|
||||
logs := cLog.ReadLogs(logger.ReadConfig{Tail: -1})
|
||||
|
||||
LogLoop:
|
||||
for {
|
||||
select {
|
||||
case msg, ok := <-logs.Msg:
|
||||
if !ok {
|
||||
break LogLoop
|
||||
}
|
||||
if msg.Source == "stdout" && stdout != nil {
|
||||
stdout.Write(msg.Line)
|
||||
}
|
||||
if msg.Source == "stderr" && stderr != nil {
|
||||
stderr.Write(msg.Line)
|
||||
}
|
||||
case err := <-logs.Err:
|
||||
logrus.Errorf("Error streaming logs: %v", err)
|
||||
break LogLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
daemon.LogContainerEvent(container, "attach")
|
||||
|
||||
//stream
|
||||
if stream {
|
||||
var stdinPipe io.ReadCloser
|
||||
if stdin != nil {
|
||||
r, w := io.Pipe()
|
||||
go func() {
|
||||
defer w.Close()
|
||||
defer logrus.Debugf("Closing buffered stdin pipe")
|
||||
io.Copy(w, stdin)
|
||||
}()
|
||||
stdinPipe = r
|
||||
}
|
||||
<-container.Attach(stdinPipe, stdout, stderr, keys)
|
||||
// If we are in stdinonce mode, wait for the process to end
|
||||
// otherwise, simply return
|
||||
if container.Config.StdinOnce && !container.Config.Tty {
|
||||
container.WaitStop(-1 * time.Second)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
15
vendor/github.com/hyperhq/hypercli/daemon/changes.go
generated
vendored
Normal file
15
vendor/github.com/hyperhq/hypercli/daemon/changes.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
package daemon
|
||||
|
||||
import "github.com/hyperhq/hypercli/pkg/archive"
|
||||
|
||||
// ContainerChanges returns a list of container fs changes
|
||||
func (daemon *Daemon) ContainerChanges(name string) ([]archive.Change, error) {
|
||||
container, err := daemon.GetContainer(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
container.Lock()
|
||||
defer container.Unlock()
|
||||
return daemon.changes(container)
|
||||
}
|
||||
228
vendor/github.com/hyperhq/hypercli/daemon/commit.go
generated
vendored
Normal file
228
vendor/github.com/hyperhq/hypercli/daemon/commit.go
generated
vendored
Normal file
@@ -0,0 +1,228 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hyperhq/hypercli/container"
|
||||
"github.com/hyperhq/hypercli/dockerversion"
|
||||
"github.com/hyperhq/hypercli/image"
|
||||
"github.com/hyperhq/hypercli/layer"
|
||||
"github.com/hyperhq/hypercli/pkg/archive"
|
||||
"github.com/hyperhq/hypercli/pkg/ioutils"
|
||||
"github.com/hyperhq/hypercli/reference"
|
||||
"github.com/docker/engine-api/types"
|
||||
containertypes "github.com/docker/engine-api/types/container"
|
||||
"github.com/docker/go-connections/nat"
|
||||
)
|
||||
|
||||
// merge merges two Config, the image container configuration (defaults values),
|
||||
// and the user container configuration, either passed by the API or generated
|
||||
// by the cli.
|
||||
// It will mutate the specified user configuration (userConf) with the image
|
||||
// configuration where the user configuration is incomplete.
|
||||
func merge(userConf, imageConf *containertypes.Config) error {
|
||||
if userConf.User == "" {
|
||||
userConf.User = imageConf.User
|
||||
}
|
||||
if len(userConf.ExposedPorts) == 0 {
|
||||
userConf.ExposedPorts = imageConf.ExposedPorts
|
||||
} else if imageConf.ExposedPorts != nil {
|
||||
if userConf.ExposedPorts == nil {
|
||||
userConf.ExposedPorts = make(nat.PortSet)
|
||||
}
|
||||
for port := range imageConf.ExposedPorts {
|
||||
if _, exists := userConf.ExposedPorts[port]; !exists {
|
||||
userConf.ExposedPorts[port] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(userConf.Env) == 0 {
|
||||
userConf.Env = imageConf.Env
|
||||
} else {
|
||||
for _, imageEnv := range imageConf.Env {
|
||||
found := false
|
||||
imageEnvKey := strings.Split(imageEnv, "=")[0]
|
||||
for _, userEnv := range userConf.Env {
|
||||
userEnvKey := strings.Split(userEnv, "=")[0]
|
||||
if imageEnvKey == userEnvKey {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
userConf.Env = append(userConf.Env, imageEnv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if userConf.Labels == nil {
|
||||
userConf.Labels = map[string]string{}
|
||||
}
|
||||
if imageConf.Labels != nil {
|
||||
for l := range userConf.Labels {
|
||||
imageConf.Labels[l] = userConf.Labels[l]
|
||||
}
|
||||
userConf.Labels = imageConf.Labels
|
||||
}
|
||||
|
||||
if userConf.Entrypoint.Len() == 0 {
|
||||
if userConf.Cmd.Len() == 0 {
|
||||
userConf.Cmd = imageConf.Cmd
|
||||
}
|
||||
|
||||
if userConf.Entrypoint == nil {
|
||||
userConf.Entrypoint = imageConf.Entrypoint
|
||||
}
|
||||
}
|
||||
if userConf.WorkingDir == "" {
|
||||
userConf.WorkingDir = imageConf.WorkingDir
|
||||
}
|
||||
if len(userConf.Volumes) == 0 {
|
||||
userConf.Volumes = imageConf.Volumes
|
||||
} else {
|
||||
for k, v := range imageConf.Volumes {
|
||||
userConf.Volumes[k] = v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Commit creates a new filesystem image from the current state of a container.
|
||||
// The image can optionally be tagged into a repository.
|
||||
func (daemon *Daemon) Commit(name string, c *types.ContainerCommitConfig) (string, error) {
|
||||
container, err := daemon.GetContainer(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// It is not possible to commit a running container on Windows
|
||||
if runtime.GOOS == "windows" && container.IsRunning() {
|
||||
return "", fmt.Errorf("Windows does not support commit of a running container")
|
||||
}
|
||||
|
||||
if c.Pause && !container.IsPaused() {
|
||||
daemon.containerPause(container)
|
||||
defer daemon.containerUnpause(container)
|
||||
}
|
||||
|
||||
if c.MergeConfigs {
|
||||
if err := merge(c.Config, container.Config); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
rwTar, err := daemon.exportContainerRw(container)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() {
|
||||
if rwTar != nil {
|
||||
rwTar.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
var history []image.History
|
||||
rootFS := image.NewRootFS()
|
||||
|
||||
if container.ImageID != "" {
|
||||
img, err := daemon.imageStore.Get(container.ImageID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
history = img.History
|
||||
rootFS = img.RootFS
|
||||
}
|
||||
|
||||
l, err := daemon.layerStore.Register(rwTar, rootFS.ChainID())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer layer.ReleaseAndLog(daemon.layerStore, l)
|
||||
|
||||
h := image.History{
|
||||
Author: c.Author,
|
||||
Created: time.Now().UTC(),
|
||||
CreatedBy: strings.Join(container.Config.Cmd.Slice(), " "),
|
||||
Comment: c.Comment,
|
||||
EmptyLayer: true,
|
||||
}
|
||||
|
||||
if diffID := l.DiffID(); layer.DigestSHA256EmptyTar != diffID {
|
||||
h.EmptyLayer = false
|
||||
rootFS.Append(diffID)
|
||||
}
|
||||
|
||||
history = append(history, h)
|
||||
|
||||
config, err := json.Marshal(&image.Image{
|
||||
V1Image: image.V1Image{
|
||||
DockerVersion: dockerversion.Version,
|
||||
Config: c.Config,
|
||||
Architecture: runtime.GOARCH,
|
||||
OS: runtime.GOOS,
|
||||
Container: container.ID,
|
||||
ContainerConfig: *container.Config,
|
||||
Author: c.Author,
|
||||
Created: h.Created,
|
||||
},
|
||||
RootFS: rootFS,
|
||||
History: history,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
id, err := daemon.imageStore.Create(config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if container.ImageID != "" {
|
||||
if err := daemon.imageStore.SetParent(id, container.ImageID); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if c.Repo != "" {
|
||||
newTag, err := reference.WithName(c.Repo) // todo: should move this to API layer
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if c.Tag != "" {
|
||||
if newTag, err = reference.WithTag(newTag, c.Tag); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if err := daemon.TagImage(newTag, id.String()); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
attributes := map[string]string{
|
||||
"comment": c.Comment,
|
||||
}
|
||||
daemon.LogContainerEventWithAttributes(container, "commit", attributes)
|
||||
return id.String(), nil
|
||||
}
|
||||
|
||||
func (daemon *Daemon) exportContainerRw(container *container.Container) (archive.Archive, error) {
|
||||
if err := daemon.Mount(container); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
archive, err := container.RWLayer.TarStream()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ioutils.NewReadCloserWrapper(archive, func() error {
|
||||
archive.Close()
|
||||
return container.RWLayer.Unmount()
|
||||
}),
|
||||
nil
|
||||
}
|
||||
296
vendor/github.com/hyperhq/hypercli/daemon/config.go
generated
vendored
Normal file
296
vendor/github.com/hyperhq/hypercli/daemon/config.go
generated
vendored
Normal file
@@ -0,0 +1,296 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/hyperhq/hypercli/opts"
|
||||
"github.com/hyperhq/hypercli/pkg/discovery"
|
||||
flag "github.com/hyperhq/hypercli/pkg/mflag"
|
||||
"github.com/imdario/mergo"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultNetworkMtu = 1500
|
||||
disableNetworkBridge = "none"
|
||||
)
|
||||
|
||||
// flatOptions contains configuration keys
|
||||
// that MUST NOT be parsed as deep structures.
|
||||
// Use this to differentiate these options
|
||||
// with others like the ones in CommonTLSOptions.
|
||||
var flatOptions = map[string]bool{
|
||||
"cluster-store-opts": true,
|
||||
"log-opts": true,
|
||||
}
|
||||
|
||||
// LogConfig represents the default log configuration.
|
||||
// It includes json tags to deserialize configuration from a file
|
||||
// using the same names that the flags in the command line uses.
|
||||
type LogConfig struct {
|
||||
Type string `json:"log-driver,omitempty"`
|
||||
Config map[string]string `json:"log-opts,omitempty"`
|
||||
}
|
||||
|
||||
// CommonTLSOptions defines TLS configuration for the daemon server.
|
||||
// It includes json tags to deserialize configuration from a file
|
||||
// using the same names that the flags in the command line uses.
|
||||
type CommonTLSOptions struct {
|
||||
CAFile string `json:"tlscacert,omitempty"`
|
||||
CertFile string `json:"tlscert,omitempty"`
|
||||
KeyFile string `json:"tlskey,omitempty"`
|
||||
}
|
||||
|
||||
// CommonConfig defines the configuration of a docker daemon which are
|
||||
// common across platforms.
|
||||
// It includes json tags to deserialize configuration from a file
|
||||
// using the same names that the flags in the command line uses.
|
||||
type CommonConfig struct {
|
||||
AuthorizationPlugins []string `json:"authorization-plugins,omitempty"` // AuthorizationPlugins holds list of authorization plugins
|
||||
AutoRestart bool `json:"-"`
|
||||
Context map[string][]string `json:"-"`
|
||||
DisableBridge bool `json:"-"`
|
||||
DNS []string `json:"dns,omitempty"`
|
||||
DNSOptions []string `json:"dns-opts,omitempty"`
|
||||
DNSSearch []string `json:"dns-search,omitempty"`
|
||||
ExecOptions []string `json:"exec-opts,omitempty"`
|
||||
ExecRoot string `json:"exec-root,omitempty"`
|
||||
GraphDriver string `json:"storage-driver,omitempty"`
|
||||
GraphOptions []string `json:"storage-opts,omitempty"`
|
||||
Labels []string `json:"labels,omitempty"`
|
||||
Mtu int `json:"mtu,omitempty"`
|
||||
Pidfile string `json:"pidfile,omitempty"`
|
||||
RawLogs bool `json:"raw-logs,omitempty"`
|
||||
Root string `json:"graph,omitempty"`
|
||||
SocketGroup string `json:"group,omitempty"`
|
||||
TrustKeyPath string `json:"-"`
|
||||
|
||||
// ClusterStore is the storage backend used for the cluster information. It is used by both
|
||||
// multihost networking (to store networks and endpoints information) and by the node discovery
|
||||
// mechanism.
|
||||
ClusterStore string `json:"cluster-store,omitempty"`
|
||||
|
||||
// ClusterOpts is used to pass options to the discovery package for tuning libkv settings, such
|
||||
// as TLS configuration settings.
|
||||
ClusterOpts map[string]string `json:"cluster-store-opts,omitempty"`
|
||||
|
||||
// ClusterAdvertise is the network endpoint that the Engine advertises for the purpose of node
|
||||
// discovery. This should be a 'host:port' combination on which that daemon instance is
|
||||
// reachable by other hosts.
|
||||
ClusterAdvertise string `json:"cluster-advertise,omitempty"`
|
||||
|
||||
Debug bool `json:"debug,omitempty"`
|
||||
Hosts []string `json:"hosts,omitempty"`
|
||||
LogLevel string `json:"log-level,omitempty"`
|
||||
TLS bool `json:"tls,omitempty"`
|
||||
TLSVerify bool `json:"tlsverify,omitempty"`
|
||||
|
||||
// Embedded structs that allow config
|
||||
// deserialization without the full struct.
|
||||
CommonTLSOptions
|
||||
LogConfig
|
||||
bridgeConfig // bridgeConfig holds bridge network specific configuration.
|
||||
|
||||
reloadLock sync.Mutex
|
||||
valuesSet map[string]interface{}
|
||||
}
|
||||
|
||||
// InstallCommonFlags adds command-line options to the top-level flag parser for
|
||||
// the current process.
|
||||
// Subsequent calls to `flag.Parse` will populate config with values parsed
|
||||
// from the command-line.
|
||||
func (config *Config) InstallCommonFlags(cmd *flag.FlagSet, usageFn func(string) string) {
|
||||
cmd.Var(opts.NewNamedListOptsRef("storage-opts", &config.GraphOptions, nil), []string{"-storage-opt"}, usageFn("Set storage driver options"))
|
||||
cmd.Var(opts.NewNamedListOptsRef("authorization-plugins", &config.AuthorizationPlugins, nil), []string{"-authorization-plugin"}, usageFn("List authorization plugins in order from first evaluator to last"))
|
||||
cmd.Var(opts.NewNamedListOptsRef("exec-opts", &config.ExecOptions, nil), []string{"-exec-opt"}, usageFn("Set exec driver options"))
|
||||
cmd.StringVar(&config.Pidfile, []string{"p", "-pidfile"}, defaultPidFile, usageFn("Path to use for daemon PID file"))
|
||||
cmd.StringVar(&config.Root, []string{"g", "-graph"}, defaultGraph, usageFn("Root of the Docker runtime"))
|
||||
cmd.StringVar(&config.ExecRoot, []string{"-exec-root"}, "/var/run/docker", usageFn("Root of the Docker execdriver"))
|
||||
cmd.BoolVar(&config.AutoRestart, []string{"#r", "#-restart"}, true, usageFn("--restart on the daemon has been deprecated in favor of --restart policies on docker run"))
|
||||
cmd.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", usageFn("Storage driver to use"))
|
||||
cmd.IntVar(&config.Mtu, []string{"#mtu", "-mtu"}, 0, usageFn("Set the containers network MTU"))
|
||||
cmd.BoolVar(&config.RawLogs, []string{"-raw-logs"}, false, usageFn("Full timestamps without ANSI coloring"))
|
||||
// FIXME: why the inconsistency between "hosts" and "sockets"?
|
||||
cmd.Var(opts.NewListOptsRef(&config.DNS, opts.ValidateIPAddress), []string{"#dns", "-dns"}, usageFn("DNS server to use"))
|
||||
cmd.Var(opts.NewNamedListOptsRef("dns-opts", &config.DNSOptions, nil), []string{"-dns-opt"}, usageFn("DNS options to use"))
|
||||
cmd.Var(opts.NewListOptsRef(&config.DNSSearch, opts.ValidateDNSSearch), []string{"-dns-search"}, usageFn("DNS search domains to use"))
|
||||
cmd.Var(opts.NewNamedListOptsRef("labels", &config.Labels, opts.ValidateLabel), []string{"-label"}, usageFn("Set key=value labels to the daemon"))
|
||||
cmd.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", usageFn("Default driver for container logs"))
|
||||
cmd.Var(opts.NewNamedMapOpts("log-opts", config.LogConfig.Config, nil), []string{"-log-opt"}, usageFn("Set log driver options"))
|
||||
cmd.StringVar(&config.ClusterAdvertise, []string{"-cluster-advertise"}, "", usageFn("Address or interface name to advertise"))
|
||||
cmd.StringVar(&config.ClusterStore, []string{"-cluster-store"}, "", usageFn("Set the cluster store"))
|
||||
cmd.Var(opts.NewNamedMapOpts("cluster-store-opts", config.ClusterOpts, nil), []string{"-cluster-store-opt"}, usageFn("Set cluster store options"))
|
||||
}
|
||||
|
||||
// IsValueSet returns true if a configuration value
|
||||
// was explicitly set in the configuration file.
|
||||
func (config *Config) IsValueSet(name string) bool {
|
||||
if config.valuesSet == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := config.valuesSet[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
func parseClusterAdvertiseSettings(clusterStore, clusterAdvertise string) (string, error) {
|
||||
if clusterAdvertise == "" {
|
||||
return "", errDiscoveryDisabled
|
||||
}
|
||||
if clusterStore == "" {
|
||||
return "", fmt.Errorf("invalid cluster configuration. --cluster-advertise must be accompanied by --cluster-store configuration")
|
||||
}
|
||||
|
||||
advertise, err := discovery.ParseAdvertise(clusterAdvertise)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("discovery advertise parsing failed (%v)", err)
|
||||
}
|
||||
return advertise, nil
|
||||
}
|
||||
|
||||
// ReloadConfiguration reads the configuration in the host and reloads the daemon and server.
|
||||
func ReloadConfiguration(configFile string, flags *flag.FlagSet, reload func(*Config)) {
|
||||
logrus.Infof("Got signal to reload configuration, reloading from: %s", configFile)
|
||||
newConfig, err := getConflictFreeConfiguration(configFile, flags)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
} else {
|
||||
reload(newConfig)
|
||||
}
|
||||
}
|
||||
|
||||
// MergeDaemonConfigurations reads a configuration file,
|
||||
// loads the file configuration in an isolated structure,
|
||||
// and merges the configuration provided from flags on top
|
||||
// if there are no conflicts.
|
||||
func MergeDaemonConfigurations(flagsConfig *Config, flags *flag.FlagSet, configFile string) (*Config, error) {
|
||||
fileConfig, err := getConflictFreeConfiguration(configFile, flags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// merge flags configuration on top of the file configuration
|
||||
if err := mergo.Merge(fileConfig, flagsConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fileConfig, nil
|
||||
}
|
||||
|
||||
// getConflictFreeConfiguration loads the configuration from a JSON file.
|
||||
// It compares that configuration with the one provided by the flags,
|
||||
// and returns an error if there are conflicts.
|
||||
func getConflictFreeConfiguration(configFile string, flags *flag.FlagSet) (*Config, error) {
|
||||
b, err := ioutil.ReadFile(configFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var config Config
|
||||
var reader io.Reader
|
||||
if flags != nil {
|
||||
var jsonConfig map[string]interface{}
|
||||
reader = bytes.NewReader(b)
|
||||
if err := json.NewDecoder(reader).Decode(&jsonConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
configSet := configValuesSet(jsonConfig)
|
||||
|
||||
if err := findConfigurationConflicts(configSet, flags); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config.valuesSet = configSet
|
||||
}
|
||||
|
||||
reader = bytes.NewReader(b)
|
||||
err = json.NewDecoder(reader).Decode(&config)
|
||||
return &config, err
|
||||
}
|
||||
|
||||
// configValuesSet returns the configuration values explicitly set in the file.
|
||||
func configValuesSet(config map[string]interface{}) map[string]interface{} {
|
||||
flatten := make(map[string]interface{})
|
||||
for k, v := range config {
|
||||
if m, isMap := v.(map[string]interface{}); isMap && !flatOptions[k] {
|
||||
for km, vm := range m {
|
||||
flatten[km] = vm
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
flatten[k] = v
|
||||
}
|
||||
return flatten
|
||||
}
|
||||
|
||||
// findConfigurationConflicts iterates over the provided flags searching for
|
||||
// duplicated configurations and unknown keys. It returns an error with all the conflicts if
|
||||
// it finds any.
|
||||
func findConfigurationConflicts(config map[string]interface{}, flags *flag.FlagSet) error {
|
||||
// 1. Search keys from the file that we don't recognize as flags.
|
||||
unknownKeys := make(map[string]interface{})
|
||||
for key, value := range config {
|
||||
flagName := "-" + key
|
||||
if flag := flags.Lookup(flagName); flag == nil {
|
||||
unknownKeys[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Discard values that implement NamedOption.
|
||||
// Their configuration name differs from their flag name, like `labels` and `label`.
|
||||
unknownNamedConflicts := func(f *flag.Flag) {
|
||||
if namedOption, ok := f.Value.(opts.NamedOption); ok {
|
||||
if _, valid := unknownKeys[namedOption.Name()]; valid {
|
||||
delete(unknownKeys, namedOption.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
flags.VisitAll(unknownNamedConflicts)
|
||||
|
||||
if len(unknownKeys) > 0 {
|
||||
var unknown []string
|
||||
for key := range unknownKeys {
|
||||
unknown = append(unknown, key)
|
||||
}
|
||||
return fmt.Errorf("the following directives don't match any configuration option: %s", strings.Join(unknown, ", "))
|
||||
}
|
||||
|
||||
var conflicts []string
|
||||
printConflict := func(name string, flagValue, fileValue interface{}) string {
|
||||
return fmt.Sprintf("%s: (from flag: %v, from file: %v)", name, flagValue, fileValue)
|
||||
}
|
||||
|
||||
// 3. Search keys that are present as a flag and as a file option.
|
||||
duplicatedConflicts := func(f *flag.Flag) {
|
||||
// search option name in the json configuration payload if the value is a named option
|
||||
if namedOption, ok := f.Value.(opts.NamedOption); ok {
|
||||
if optsValue, ok := config[namedOption.Name()]; ok {
|
||||
conflicts = append(conflicts, printConflict(namedOption.Name(), f.Value.String(), optsValue))
|
||||
}
|
||||
} else {
|
||||
// search flag name in the json configuration payload without trailing dashes
|
||||
for _, name := range f.Names {
|
||||
name = strings.TrimLeft(name, "-")
|
||||
|
||||
if value, ok := config[name]; ok {
|
||||
conflicts = append(conflicts, printConflict(name, f.Value.String(), value))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flags.Visit(duplicatedConflicts)
|
||||
|
||||
if len(conflicts) > 0 {
|
||||
return fmt.Errorf("the following directives are specified both as a flag and in the configuration file: %s", strings.Join(conflicts, ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
8
vendor/github.com/hyperhq/hypercli/daemon/config_experimental.go
generated
vendored
Normal file
8
vendor/github.com/hyperhq/hypercli/daemon/config_experimental.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
// +build experimental
|
||||
|
||||
package daemon
|
||||
|
||||
import flag "github.com/hyperhq/hypercli/pkg/mflag"
|
||||
|
||||
func (config *Config) attachExperimentalFlags(cmd *flag.FlagSet, usageFn func(string) string) {
|
||||
}
|
||||
8
vendor/github.com/hyperhq/hypercli/daemon/config_stub.go
generated
vendored
Normal file
8
vendor/github.com/hyperhq/hypercli/daemon/config_stub.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
// +build !experimental
|
||||
|
||||
package daemon
|
||||
|
||||
import flag "github.com/hyperhq/hypercli/pkg/mflag"
|
||||
|
||||
func (config *Config) attachExperimentalFlags(cmd *flag.FlagSet, usageFn func(string) string) {
|
||||
}
|
||||
210
vendor/github.com/hyperhq/hypercli/daemon/config_test.go
generated
vendored
Normal file
210
vendor/github.com/hyperhq/hypercli/daemon/config_test.go
generated
vendored
Normal file
@@ -0,0 +1,210 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hyperhq/hypercli/opts"
|
||||
"github.com/hyperhq/hypercli/pkg/mflag"
|
||||
)
|
||||
|
||||
func TestDaemonConfigurationMerge(t *testing.T) {
|
||||
f, err := ioutil.TempFile("", "docker-config-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
configFile := f.Name()
|
||||
f.Write([]byte(`{"debug": true}`))
|
||||
f.Close()
|
||||
|
||||
c := &Config{
|
||||
CommonConfig: CommonConfig{
|
||||
AutoRestart: true,
|
||||
LogConfig: LogConfig{
|
||||
Type: "syslog",
|
||||
Config: map[string]string{"tag": "test"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cc, err := MergeDaemonConfigurations(c, nil, configFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !cc.Debug {
|
||||
t.Fatalf("expected %v, got %v\n", true, cc.Debug)
|
||||
}
|
||||
if !cc.AutoRestart {
|
||||
t.Fatalf("expected %v, got %v\n", true, cc.AutoRestart)
|
||||
}
|
||||
if cc.LogConfig.Type != "syslog" {
|
||||
t.Fatalf("expected syslog config, got %q\n", cc.LogConfig)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonConfigurationNotFound(t *testing.T) {
|
||||
_, err := MergeDaemonConfigurations(&Config{}, nil, "/tmp/foo-bar-baz-docker")
|
||||
if err == nil || !os.IsNotExist(err) {
|
||||
t.Fatalf("expected does not exist error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonBrokenConfiguration(t *testing.T) {
|
||||
f, err := ioutil.TempFile("", "docker-config-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
configFile := f.Name()
|
||||
f.Write([]byte(`{"Debug": tru`))
|
||||
f.Close()
|
||||
|
||||
_, err = MergeDaemonConfigurations(&Config{}, nil, configFile)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseClusterAdvertiseSettings(t *testing.T) {
|
||||
_, err := parseClusterAdvertiseSettings("something", "")
|
||||
if err != errDiscoveryDisabled {
|
||||
t.Fatalf("expected discovery disabled error, got %v\n", err)
|
||||
}
|
||||
|
||||
_, err = parseClusterAdvertiseSettings("", "something")
|
||||
if err == nil {
|
||||
t.Fatalf("expected discovery store error, got %v\n", err)
|
||||
}
|
||||
|
||||
_, err = parseClusterAdvertiseSettings("etcd", "127.0.0.1:8080")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindConfigurationConflicts(t *testing.T) {
|
||||
config := map[string]interface{}{"authorization-plugins": "foobar"}
|
||||
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
|
||||
|
||||
flags.String([]string{"-authorization-plugins"}, "", "")
|
||||
if err := flags.Set("-authorization-plugins", "asdf"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err := findConfigurationConflicts(config, flags)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "authorization-plugins: (from flag: asdf, from file: foobar)") {
|
||||
t.Fatalf("expected authorization-plugins conflict, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindConfigurationConflictsWithNamedOptions(t *testing.T) {
|
||||
config := map[string]interface{}{"hosts": []string{"qwer"}}
|
||||
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
|
||||
|
||||
var hosts []string
|
||||
flags.Var(opts.NewNamedListOptsRef("hosts", &hosts, opts.ValidateHost), []string{"H", "-host"}, "Daemon socket(s) to connect to")
|
||||
if err := flags.Set("-host", "tcp://127.0.0.1:4444"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := flags.Set("H", "unix:///var/run/docker.sock"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err := findConfigurationConflicts(config, flags)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "hosts") {
|
||||
t.Fatalf("expected hosts conflict, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonConfigurationMergeConflicts(t *testing.T) {
|
||||
f, err := ioutil.TempFile("", "docker-config-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
configFile := f.Name()
|
||||
f.Write([]byte(`{"debug": true}`))
|
||||
f.Close()
|
||||
|
||||
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
|
||||
flags.Bool([]string{"debug"}, false, "")
|
||||
flags.Set("debug", "false")
|
||||
|
||||
_, err = MergeDaemonConfigurations(&Config{}, flags, configFile)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "debug") {
|
||||
t.Fatalf("expected debug conflict, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonConfigurationMergeConflictsWithInnerStructs(t *testing.T) {
|
||||
f, err := ioutil.TempFile("", "docker-config-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
configFile := f.Name()
|
||||
f.Write([]byte(`{"tlscacert": "/etc/certificates/ca.pem"}`))
|
||||
f.Close()
|
||||
|
||||
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
|
||||
flags.String([]string{"tlscacert"}, "", "")
|
||||
flags.Set("tlscacert", "~/.docker/ca.pem")
|
||||
|
||||
_, err = MergeDaemonConfigurations(&Config{}, flags, configFile)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "tlscacert") {
|
||||
t.Fatalf("expected tlscacert conflict, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindConfigurationConflictsWithUnknownKeys(t *testing.T) {
|
||||
config := map[string]interface{}{"tls-verify": "true"}
|
||||
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
|
||||
|
||||
flags.Bool([]string{"-tlsverify"}, false, "")
|
||||
err := findConfigurationConflicts(config, flags)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "the following directives don't match any configuration option: tls-verify") {
|
||||
t.Fatalf("expected tls-verify conflict, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindConfigurationConflictsWithMergedValues(t *testing.T) {
|
||||
var hosts []string
|
||||
config := map[string]interface{}{"hosts": "tcp://127.0.0.1:2345"}
|
||||
base := mflag.NewFlagSet("base", mflag.ContinueOnError)
|
||||
base.Var(opts.NewNamedListOptsRef("hosts", &hosts, nil), []string{"H", "-host"}, "")
|
||||
|
||||
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
|
||||
mflag.Merge(flags, base)
|
||||
|
||||
err := findConfigurationConflicts(config, flags)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
flags.Set("-host", "unix:///var/run/docker.sock")
|
||||
err = findConfigurationConflicts(config, flags)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "hosts: (from flag: [unix:///var/run/docker.sock], from file: tcp://127.0.0.1:2345)") {
|
||||
t.Fatalf("expected hosts conflict, got %v", err)
|
||||
}
|
||||
}
|
||||
86
vendor/github.com/hyperhq/hypercli/daemon/config_unix.go
generated
vendored
Normal file
86
vendor/github.com/hyperhq/hypercli/daemon/config_unix.go
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
// +build linux freebsd
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/hyperhq/hypercli/opts"
|
||||
flag "github.com/hyperhq/hypercli/pkg/mflag"
|
||||
runconfigopts "github.com/hyperhq/hypercli/runconfig/opts"
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultPidFile = "/var/run/docker.pid"
|
||||
defaultGraph = "/var/lib/docker"
|
||||
defaultExec = "native"
|
||||
)
|
||||
|
||||
// Config defines the configuration of a docker daemon.
|
||||
// It includes json tags to deserialize configuration from a file
|
||||
// using the same names that the flags in the command line uses.
|
||||
type Config struct {
|
||||
CommonConfig
|
||||
|
||||
// Fields below here are platform specific.
|
||||
|
||||
CorsHeaders string `json:"api-cors-headers,omitempty"`
|
||||
EnableCors bool `json:"api-enable-cors,omitempty"`
|
||||
EnableSelinuxSupport bool `json:"selinux-enabled,omitempty"`
|
||||
RemappedRoot string `json:"userns-remap,omitempty"`
|
||||
CgroupParent string `json:"cgroup-parent,omitempty"`
|
||||
Ulimits map[string]*units.Ulimit `json:"default-ulimits,omitempty"`
|
||||
}
|
||||
|
||||
// bridgeConfig stores all the bridge driver specific
|
||||
// configuration.
|
||||
type bridgeConfig struct {
|
||||
EnableIPv6 bool `json:"ipv6,omitempty"`
|
||||
EnableIPTables bool `json:"iptables,omitempty"`
|
||||
EnableIPForward bool `json:"ip-forward,omitempty"`
|
||||
EnableIPMasq bool `json:"ip-mask,omitempty"`
|
||||
EnableUserlandProxy bool `json:"userland-proxy,omitempty"`
|
||||
DefaultIP net.IP `json:"ip,omitempty"`
|
||||
Iface string `json:"bridge,omitempty"`
|
||||
IP string `json:"bip,omitempty"`
|
||||
FixedCIDR string `json:"fixed-cidr,omitempty"`
|
||||
FixedCIDRv6 string `json:"fixed-cidr-v6,omitempty"`
|
||||
DefaultGatewayIPv4 net.IP `json:"default-gateway,omitempty"`
|
||||
DefaultGatewayIPv6 net.IP `json:"default-gateway-v6,omitempty"`
|
||||
InterContainerCommunication bool `json:"icc,omitempty"`
|
||||
}
|
||||
|
||||
// InstallFlags adds command-line options to the top-level flag parser for
|
||||
// the current process.
|
||||
// Subsequent calls to `flag.Parse` will populate config with values parsed
|
||||
// from the command-line.
|
||||
func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) string) {
|
||||
// First handle install flags which are consistent cross-platform
|
||||
config.InstallCommonFlags(cmd, usageFn)
|
||||
|
||||
// Then platform-specific install flags
|
||||
cmd.BoolVar(&config.EnableSelinuxSupport, []string{"-selinux-enabled"}, false, usageFn("Enable selinux support"))
|
||||
cmd.StringVar(&config.SocketGroup, []string{"G", "-group"}, "docker", usageFn("Group for the unix socket"))
|
||||
config.Ulimits = make(map[string]*units.Ulimit)
|
||||
cmd.Var(runconfigopts.NewUlimitOpt(&config.Ulimits), []string{"-default-ulimit"}, usageFn("Set default ulimits for containers"))
|
||||
cmd.BoolVar(&config.bridgeConfig.EnableIPTables, []string{"#iptables", "-iptables"}, true, usageFn("Enable addition of iptables rules"))
|
||||
cmd.BoolVar(&config.bridgeConfig.EnableIPForward, []string{"#ip-forward", "-ip-forward"}, true, usageFn("Enable net.ipv4.ip_forward"))
|
||||
cmd.BoolVar(&config.bridgeConfig.EnableIPMasq, []string{"-ip-masq"}, true, usageFn("Enable IP masquerading"))
|
||||
cmd.BoolVar(&config.bridgeConfig.EnableIPv6, []string{"-ipv6"}, false, usageFn("Enable IPv6 networking"))
|
||||
cmd.StringVar(&config.bridgeConfig.IP, []string{"#bip", "-bip"}, "", usageFn("Specify network bridge IP"))
|
||||
cmd.StringVar(&config.bridgeConfig.Iface, []string{"b", "-bridge"}, "", usageFn("Attach containers to a network bridge"))
|
||||
cmd.StringVar(&config.bridgeConfig.FixedCIDR, []string{"-fixed-cidr"}, "", usageFn("IPv4 subnet for fixed IPs"))
|
||||
cmd.StringVar(&config.bridgeConfig.FixedCIDRv6, []string{"-fixed-cidr-v6"}, "", usageFn("IPv6 subnet for fixed IPs"))
|
||||
cmd.Var(opts.NewIPOpt(&config.bridgeConfig.DefaultGatewayIPv4, ""), []string{"-default-gateway"}, usageFn("Container default gateway IPv4 address"))
|
||||
cmd.Var(opts.NewIPOpt(&config.bridgeConfig.DefaultGatewayIPv6, ""), []string{"-default-gateway-v6"}, usageFn("Container default gateway IPv6 address"))
|
||||
cmd.BoolVar(&config.bridgeConfig.InterContainerCommunication, []string{"#icc", "-icc"}, true, usageFn("Enable inter-container communication"))
|
||||
cmd.Var(opts.NewIPOpt(&config.bridgeConfig.DefaultIP, "0.0.0.0"), []string{"#ip", "-ip"}, usageFn("Default IP when binding container ports"))
|
||||
cmd.BoolVar(&config.bridgeConfig.EnableUserlandProxy, []string{"-userland-proxy"}, true, usageFn("Use userland proxy for loopback traffic"))
|
||||
cmd.BoolVar(&config.EnableCors, []string{"#api-enable-cors", "#-api-enable-cors"}, false, usageFn("Enable CORS headers in the remote API, this is deprecated by --api-cors-header"))
|
||||
cmd.StringVar(&config.CorsHeaders, []string{"-api-cors-header"}, "", usageFn("Set CORS headers in the remote API"))
|
||||
cmd.StringVar(&config.CgroupParent, []string{"-cgroup-parent"}, "", usageFn("Set parent cgroup for all containers"))
|
||||
cmd.StringVar(&config.RemappedRoot, []string{"-userns-remap"}, "", usageFn("User/Group setting for user namespaces"))
|
||||
|
||||
config.attachExperimentalFlags(cmd, usageFn)
|
||||
}
|
||||
42
vendor/github.com/hyperhq/hypercli/daemon/config_windows.go
generated
vendored
Normal file
42
vendor/github.com/hyperhq/hypercli/daemon/config_windows.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
flag "github.com/hyperhq/hypercli/pkg/mflag"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultPidFile = os.Getenv("programdata") + string(os.PathSeparator) + "docker.pid"
|
||||
defaultGraph = os.Getenv("programdata") + string(os.PathSeparator) + "docker"
|
||||
defaultExec = "windows"
|
||||
)
|
||||
|
||||
// bridgeConfig stores all the bridge driver specific
|
||||
// configuration.
|
||||
type bridgeConfig struct {
|
||||
VirtualSwitchName string `json:"bridge,omitempty"`
|
||||
}
|
||||
|
||||
// Config defines the configuration of a docker daemon.
|
||||
// These are the configuration settings that you pass
|
||||
// to the docker daemon when you launch it with say: `docker daemon -e windows`
|
||||
type Config struct {
|
||||
CommonConfig
|
||||
|
||||
// Fields below here are platform specific. (There are none presently
|
||||
// for the Windows daemon.)
|
||||
}
|
||||
|
||||
// InstallFlags adds command-line options to the top-level flag parser for
|
||||
// the current process.
|
||||
// Subsequent calls to `flag.Parse` will populate config with values parsed
|
||||
// from the command-line.
|
||||
func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) string) {
|
||||
// First handle install flags which are consistent cross-platform
|
||||
config.InstallCommonFlags(cmd, usageFn)
|
||||
|
||||
// Then platform-specific install flags.
|
||||
cmd.StringVar(&config.bridgeConfig.VirtualSwitchName, []string{"b", "-bridge"}, "", "Attach containers to a virtual switch")
|
||||
cmd.StringVar(&config.SocketGroup, []string{"G", "-group"}, "", usageFn("Users or groups that can access the named pipe"))
|
||||
}
|
||||
9
vendor/github.com/hyperhq/hypercli/daemon/container_operations.go
generated
vendored
Normal file
9
vendor/github.com/hyperhq/hypercli/daemon/container_operations.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
package daemon
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// ErrRootFSReadOnly is returned when a container
|
||||
// rootfs is marked readonly.
|
||||
ErrRootFSReadOnly = errors.New("container rootfs is marked read-only")
|
||||
)
|
||||
1199
vendor/github.com/hyperhq/hypercli/daemon/container_operations_unix.go
generated
vendored
Normal file
1199
vendor/github.com/hyperhq/hypercli/daemon/container_operations_unix.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
189
vendor/github.com/hyperhq/hypercli/daemon/container_operations_windows.go
generated
vendored
Normal file
189
vendor/github.com/hyperhq/hypercli/daemon/container_operations_windows.go
generated
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
// +build windows
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/hyperhq/hypercli/container"
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver"
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver/windows"
|
||||
derr "github.com/hyperhq/hypercli/errors"
|
||||
"github.com/hyperhq/hypercli/layer"
|
||||
networktypes "github.com/docker/engine-api/types/network"
|
||||
"github.com/docker/libnetwork"
|
||||
)
|
||||
|
||||
func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// updateContainerNetworkSettings update the network settings
|
||||
func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container, endpointsConfig map[string]*networktypes.EndpointSettings) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (daemon *Daemon) initializeNetworking(container *container.Container) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConnectToNetwork connects a container to the network
|
||||
func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForceEndpointDelete deletes an endpoing from a network forcefully
|
||||
func (daemon *Daemon) ForceEndpointDelete(name string, n libnetwork.Network) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisconnectFromNetwork disconnects a container from the network.
|
||||
func (daemon *Daemon) DisconnectFromNetwork(container *container.Container, n libnetwork.Network, force bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (daemon *Daemon) populateCommand(c *container.Container, env []string) error {
|
||||
en := &execdriver.Network{
|
||||
Interface: nil,
|
||||
}
|
||||
|
||||
parts := strings.SplitN(string(c.HostConfig.NetworkMode), ":", 2)
|
||||
switch parts[0] {
|
||||
case "none":
|
||||
case "default", "": // empty string to support existing containers
|
||||
if !c.Config.NetworkDisabled {
|
||||
en.Interface = &execdriver.NetworkInterface{
|
||||
MacAddress: c.Config.MacAddress,
|
||||
Bridge: daemon.configStore.bridgeConfig.VirtualSwitchName,
|
||||
PortBindings: c.HostConfig.PortBindings,
|
||||
|
||||
// TODO Windows. Include IPAddress. There already is a
|
||||
// property IPAddress on execDrive.CommonNetworkInterface,
|
||||
// but there is no CLI option in docker to pass through
|
||||
// an IPAddress on docker run.
|
||||
}
|
||||
}
|
||||
default:
|
||||
return derr.ErrorCodeInvalidNetworkMode.WithArgs(c.HostConfig.NetworkMode)
|
||||
}
|
||||
|
||||
// TODO Windows. More resource controls to be implemented later.
|
||||
resources := &execdriver.Resources{
|
||||
CommonResources: execdriver.CommonResources{
|
||||
CPUShares: c.HostConfig.CPUShares,
|
||||
},
|
||||
}
|
||||
|
||||
processConfig := execdriver.ProcessConfig{
|
||||
CommonProcessConfig: execdriver.CommonProcessConfig{
|
||||
Entrypoint: c.Path,
|
||||
Arguments: c.Args,
|
||||
Tty: c.Config.Tty,
|
||||
},
|
||||
ConsoleSize: c.HostConfig.ConsoleSize,
|
||||
}
|
||||
|
||||
processConfig.Env = env
|
||||
|
||||
var layerPaths []string
|
||||
img, err := daemon.imageStore.Get(c.ImageID)
|
||||
if err != nil {
|
||||
return derr.ErrorCodeGetGraph.WithArgs(c.ImageID, err)
|
||||
}
|
||||
|
||||
if img.RootFS != nil && img.RootFS.Type == "layers+base" {
|
||||
max := len(img.RootFS.DiffIDs)
|
||||
for i := 0; i <= max; i++ {
|
||||
img.RootFS.DiffIDs = img.RootFS.DiffIDs[:i]
|
||||
path, err := layer.GetLayerPath(daemon.layerStore, img.RootFS.ChainID())
|
||||
if err != nil {
|
||||
return derr.ErrorCodeGetLayer.WithArgs(err)
|
||||
}
|
||||
// Reverse order, expecting parent most first
|
||||
layerPaths = append([]string{path}, layerPaths...)
|
||||
}
|
||||
}
|
||||
|
||||
m, err := c.RWLayer.Metadata()
|
||||
if err != nil {
|
||||
return derr.ErrorCodeGetLayerMetadata.WithArgs(err)
|
||||
}
|
||||
layerFolder := m["dir"]
|
||||
|
||||
var hvPartition bool
|
||||
// Work out the isolation (whether it is a hypervisor partition)
|
||||
if c.HostConfig.Isolation.IsDefault() {
|
||||
// Not specified by caller. Take daemon default
|
||||
hvPartition = windows.DefaultIsolation.IsHyperV()
|
||||
} else {
|
||||
// Take value specified by caller
|
||||
hvPartition = c.HostConfig.Isolation.IsHyperV()
|
||||
}
|
||||
|
||||
c.Command = &execdriver.Command{
|
||||
CommonCommand: execdriver.CommonCommand{
|
||||
ID: c.ID,
|
||||
Rootfs: c.BaseFS,
|
||||
WorkingDir: c.Config.WorkingDir,
|
||||
Network: en,
|
||||
MountLabel: c.GetMountLabel(),
|
||||
Resources: resources,
|
||||
ProcessConfig: processConfig,
|
||||
ProcessLabel: c.GetProcessLabel(),
|
||||
},
|
||||
FirstStart: !c.HasBeenStartedBefore,
|
||||
LayerFolder: layerFolder,
|
||||
LayerPaths: layerPaths,
|
||||
Hostname: c.Config.Hostname,
|
||||
Isolation: string(c.HostConfig.Isolation),
|
||||
ArgsEscaped: c.Config.ArgsEscaped,
|
||||
HvPartition: hvPartition,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getSize returns real size & virtual size
|
||||
func (daemon *Daemon) getSize(container *container.Container) (int64, int64) {
|
||||
// TODO Windows
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// setNetworkNamespaceKey is a no-op on Windows.
|
||||
func (daemon *Daemon) setNetworkNamespaceKey(containerID string, pid int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// allocateNetwork is a no-op on Windows.
|
||||
func (daemon *Daemon) allocateNetwork(container *container.Container) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (daemon *Daemon) updateNetwork(container *container.Container) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (daemon *Daemon) releaseNetwork(container *container.Container) {
|
||||
}
|
||||
|
||||
func (daemon *Daemon) setupIpcDirs(container *container.Container) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO Windows: Fix Post-TP4. This is a hack to allow docker cp to work
|
||||
// against containers which have volumes. You will still be able to cp
|
||||
// to somewhere on the container drive, but not to any mounted volumes
|
||||
// inside the container. Without this fix, docker cp is broken to any
|
||||
// container which has a volume, regardless of where the file is inside the
|
||||
// container.
|
||||
func (daemon *Daemon) mountVolumes(container *container.Container) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func detachMounted(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func killProcessDirectly(container *container.Container) error {
|
||||
return nil
|
||||
}
|
||||
184
vendor/github.com/hyperhq/hypercli/daemon/create.go
generated
vendored
Normal file
184
vendor/github.com/hyperhq/hypercli/daemon/create.go
generated
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/hyperhq/hypercli/container"
|
||||
derr "github.com/hyperhq/hypercli/errors"
|
||||
"github.com/hyperhq/hypercli/image"
|
||||
"github.com/hyperhq/hypercli/layer"
|
||||
"github.com/hyperhq/hypercli/pkg/idtools"
|
||||
"github.com/hyperhq/hypercli/pkg/stringid"
|
||||
volumestore "github.com/hyperhq/hypercli/volume/store"
|
||||
"github.com/docker/engine-api/types"
|
||||
containertypes "github.com/docker/engine-api/types/container"
|
||||
networktypes "github.com/docker/engine-api/types/network"
|
||||
"github.com/opencontainers/runc/libcontainer/label"
|
||||
)
|
||||
|
||||
// ContainerCreate creates a container.
|
||||
func (daemon *Daemon) ContainerCreate(params types.ContainerCreateConfig) (types.ContainerCreateResponse, error) {
|
||||
if params.Config == nil {
|
||||
return types.ContainerCreateResponse{}, derr.ErrorCodeEmptyConfig
|
||||
}
|
||||
|
||||
warnings, err := daemon.verifyContainerSettings(params.HostConfig, params.Config)
|
||||
if err != nil {
|
||||
return types.ContainerCreateResponse{Warnings: warnings}, err
|
||||
}
|
||||
|
||||
err = daemon.verifyNetworkingConfig(params.NetworkingConfig)
|
||||
if err != nil {
|
||||
return types.ContainerCreateResponse{}, err
|
||||
}
|
||||
|
||||
if params.HostConfig == nil {
|
||||
params.HostConfig = &containertypes.HostConfig{}
|
||||
}
|
||||
err = daemon.adaptContainerSettings(params.HostConfig, params.AdjustCPUShares)
|
||||
if err != nil {
|
||||
return types.ContainerCreateResponse{Warnings: warnings}, err
|
||||
}
|
||||
|
||||
container, err := daemon.create(params)
|
||||
if err != nil {
|
||||
return types.ContainerCreateResponse{Warnings: warnings}, daemon.imageNotExistToErrcode(err)
|
||||
}
|
||||
|
||||
return types.ContainerCreateResponse{ID: container.ID, Warnings: warnings}, nil
|
||||
}
|
||||
|
||||
// Create creates a new container from the given configuration with a given name.
|
||||
func (daemon *Daemon) create(params types.ContainerCreateConfig) (retC *container.Container, retErr error) {
|
||||
var (
|
||||
container *container.Container
|
||||
img *image.Image
|
||||
imgID image.ID
|
||||
err error
|
||||
)
|
||||
|
||||
if params.Config.Image != "" {
|
||||
img, err = daemon.GetImage(params.Config.Image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imgID = img.ID()
|
||||
}
|
||||
|
||||
if err := daemon.mergeAndVerifyConfig(params.Config, img); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if container, err = daemon.newContainer(params.Name, params.Config, imgID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
if err := daemon.ContainerRm(container.ID, &types.ContainerRmConfig{ForceRemove: true}); err != nil {
|
||||
logrus.Errorf("Clean up Error! Cannot destroy container %s: %v", container.ID, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err := daemon.setSecurityOptions(container, params.HostConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set RWLayer for container after mount labels have been set
|
||||
if err := daemon.setRWLayer(container); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := daemon.Register(container); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rootUID, rootGID, err := idtools.GetRootUIDGID(daemon.uidMaps, daemon.gidMaps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := idtools.MkdirAs(container.Root, 0700, rootUID, rootGID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := daemon.setHostConfig(container, params.HostConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
if err := daemon.removeMountPoints(container, true); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err := daemon.createContainerPlatformSpecificSettings(container, params.Config, params.HostConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var endpointsConfigs map[string]*networktypes.EndpointSettings
|
||||
if params.NetworkingConfig != nil {
|
||||
endpointsConfigs = params.NetworkingConfig.EndpointsConfig
|
||||
}
|
||||
|
||||
if err := daemon.updateContainerNetworkSettings(container, endpointsConfigs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := container.ToDiskLocking(); err != nil {
|
||||
logrus.Errorf("Error saving new container to disk: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
daemon.LogContainerEvent(container, "create")
|
||||
return container, nil
|
||||
}
|
||||
|
||||
func (daemon *Daemon) generateSecurityOpt(ipcMode containertypes.IpcMode, pidMode containertypes.PidMode) ([]string, error) {
|
||||
if ipcMode.IsHost() || pidMode.IsHost() {
|
||||
return label.DisableSecOpt(), nil
|
||||
}
|
||||
if ipcContainer := ipcMode.Container(); ipcContainer != "" {
|
||||
c, err := daemon.GetContainer(ipcContainer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return label.DupSecOpt(c.ProcessLabel), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (daemon *Daemon) setRWLayer(container *container.Container) error {
|
||||
var layerID layer.ChainID
|
||||
if container.ImageID != "" {
|
||||
img, err := daemon.imageStore.Get(container.ImageID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
layerID = img.RootFS.ChainID()
|
||||
}
|
||||
rwLayer, err := daemon.layerStore.CreateRWLayer(container.ID, layerID, container.MountLabel, daemon.setupInitLayer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.RWLayer = rwLayer
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VolumeCreate creates a volume with the specified name, driver, and opts
|
||||
// This is called directly from the remote API
|
||||
func (daemon *Daemon) VolumeCreate(name, driverName string, opts map[string]string) (*types.Volume, error) {
|
||||
if name == "" {
|
||||
name = stringid.GenerateNonCryptoID()
|
||||
}
|
||||
|
||||
v, err := daemon.volumes.Create(name, driverName, opts)
|
||||
if err != nil {
|
||||
if volumestore.IsNameConflict(err) {
|
||||
return nil, derr.ErrorVolumeNameTaken.WithArgs(name)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
daemon.LogVolumeEvent(v.Name(), "create", map[string]string{"driver": v.DriverName()})
|
||||
return volumeToAPIType(v), nil
|
||||
}
|
||||
76
vendor/github.com/hyperhq/hypercli/daemon/create_unix.go
generated
vendored
Normal file
76
vendor/github.com/hyperhq/hypercli/daemon/create_unix.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
// +build !windows
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/hyperhq/hypercli/container"
|
||||
derr "github.com/hyperhq/hypercli/errors"
|
||||
"github.com/hyperhq/hypercli/pkg/stringid"
|
||||
containertypes "github.com/docker/engine-api/types/container"
|
||||
"github.com/opencontainers/runc/libcontainer/label"
|
||||
)
|
||||
|
||||
// createContainerPlatformSpecificSettings performs platform specific container create functionality
|
||||
func (daemon *Daemon) createContainerPlatformSpecificSettings(container *container.Container, config *containertypes.Config, hostConfig *containertypes.HostConfig) error {
|
||||
if err := daemon.Mount(container); err != nil {
|
||||
return err
|
||||
}
|
||||
defer daemon.Unmount(container)
|
||||
|
||||
if err := container.SetupWorkingDirectory(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for spec := range config.Volumes {
|
||||
name := stringid.GenerateNonCryptoID()
|
||||
destination := filepath.Clean(spec)
|
||||
|
||||
// Skip volumes for which we already have something mounted on that
|
||||
// destination because of a --volume-from.
|
||||
if container.IsDestinationMounted(destination) {
|
||||
continue
|
||||
}
|
||||
path, err := container.GetResourcePath(destination)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stat, err := os.Stat(path)
|
||||
if err == nil && !stat.IsDir() {
|
||||
return derr.ErrorCodeMountOverFile.WithArgs(path)
|
||||
}
|
||||
|
||||
v, err := daemon.volumes.CreateWithRef(name, hostConfig.VolumeDriver, container.ID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := label.Relabel(v.Path(), container.MountLabel, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
container.AddMountPointWithVolume(destination, v, true)
|
||||
}
|
||||
return daemon.populateVolumes(container)
|
||||
}
|
||||
|
||||
// populateVolumes copies data from the container's rootfs into the volume for non-binds.
|
||||
// this is only called when the container is created.
|
||||
func (daemon *Daemon) populateVolumes(c *container.Container) error {
|
||||
for _, mnt := range c.MountPoints {
|
||||
// skip binds and volumes referenced by other containers (ie, volumes-from)
|
||||
if mnt.Driver == "" || mnt.Volume == nil || len(daemon.volumes.Refs(mnt.Volume)) > 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
logrus.Debugf("copying image data from %s:%s, to %s", c.ID, mnt.Destination, mnt.Name)
|
||||
if err := c.CopyImagePathContent(mnt.Volume, mnt.Destination); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
75
vendor/github.com/hyperhq/hypercli/daemon/create_windows.go
generated
vendored
Normal file
75
vendor/github.com/hyperhq/hypercli/daemon/create_windows.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hyperhq/hypercli/container"
|
||||
"github.com/hyperhq/hypercli/pkg/stringid"
|
||||
"github.com/hyperhq/hypercli/volume"
|
||||
containertypes "github.com/docker/engine-api/types/container"
|
||||
)
|
||||
|
||||
// createContainerPlatformSpecificSettings performs platform specific container create functionality
|
||||
func (daemon *Daemon) createContainerPlatformSpecificSettings(container *container.Container, config *containertypes.Config, hostConfig *containertypes.HostConfig) error {
|
||||
for spec := range config.Volumes {
|
||||
|
||||
mp, err := volume.ParseMountSpec(spec, hostConfig.VolumeDriver)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unrecognised volume spec: %v", err)
|
||||
}
|
||||
|
||||
// If the mountpoint doesn't have a name, generate one.
|
||||
if len(mp.Name) == 0 {
|
||||
mp.Name = stringid.GenerateNonCryptoID()
|
||||
}
|
||||
|
||||
// Skip volumes for which we already have something mounted on that
|
||||
// destination because of a --volume-from.
|
||||
if container.IsDestinationMounted(mp.Destination) {
|
||||
continue
|
||||
}
|
||||
|
||||
volumeDriver := hostConfig.VolumeDriver
|
||||
|
||||
// Create the volume in the volume driver. If it doesn't exist,
|
||||
// a new one will be created.
|
||||
v, err := daemon.volumes.CreateWithRef(mp.Name, volumeDriver, container.ID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// FIXME Windows: This code block is present in the Linux version and
|
||||
// allows the contents to be copied to the container FS prior to it
|
||||
// being started. However, the function utilizes the FollowSymLinkInScope
|
||||
// path which does not cope with Windows volume-style file paths. There
|
||||
// is a separate effort to resolve this (@swernli), so this processing
|
||||
// is deferred for now. A case where this would be useful is when
|
||||
// a dockerfile includes a VOLUME statement, but something is created
|
||||
// in that directory during the dockerfile processing. What this means
|
||||
// on Windows for TP4 is that in that scenario, the contents will not
|
||||
// copied, but that's (somewhat) OK as HCS will bomb out soon after
|
||||
// at it doesn't support mapped directories which have contents in the
|
||||
// destination path anyway.
|
||||
//
|
||||
// Example for repro later:
|
||||
// FROM windowsservercore
|
||||
// RUN mkdir c:\myvol
|
||||
// RUN copy c:\windows\system32\ntdll.dll c:\myvol
|
||||
// VOLUME "c:\myvol"
|
||||
//
|
||||
// Then
|
||||
// docker build -t vol .
|
||||
// docker run -it --rm vol cmd <-- This is where HCS will error out.
|
||||
//
|
||||
// // never attempt to copy existing content in a container FS to a shared volume
|
||||
// if v.DriverName() == volume.DefaultDriverName {
|
||||
// if err := container.CopyImagePathContent(v, mp.Destination); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
|
||||
// Add it to container.MountPoints
|
||||
container.AddMountPointWithVolume(mp.Destination, v, mp.RW)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
1664
vendor/github.com/hyperhq/hypercli/daemon/daemon.go
generated
vendored
Normal file
1664
vendor/github.com/hyperhq/hypercli/daemon/daemon.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
9
vendor/github.com/hyperhq/hypercli/daemon/daemon_experimental.go
generated
vendored
Normal file
9
vendor/github.com/hyperhq/hypercli/daemon/daemon_experimental.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// +build experimental
|
||||
|
||||
package daemon
|
||||
|
||||
import "github.com/docker/engine-api/types/container"
|
||||
|
||||
func (daemon *Daemon) verifyExperimentalContainerSettings(hostConfig *container.HostConfig, config *container.Config) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
60
vendor/github.com/hyperhq/hypercli/daemon/daemon_linux.go
generated
vendored
Normal file
60
vendor/github.com/hyperhq/hypercli/daemon/daemon_linux.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/hyperhq/hypercli/pkg/mount"
|
||||
)
|
||||
|
||||
// cleanupMounts umounts shm/mqueue mounts for old containers
|
||||
func (daemon *Daemon) cleanupMounts() error {
|
||||
logrus.Debugf("Cleaning up old shm/mqueue mounts: start.")
|
||||
f, err := os.Open("/proc/self/mountinfo")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return daemon.cleanupMountsFromReader(f, mount.Unmount)
|
||||
}
|
||||
|
||||
func (daemon *Daemon) cleanupMountsFromReader(reader io.Reader, unmount func(target string) error) error {
|
||||
if daemon.repository == "" {
|
||||
return nil
|
||||
}
|
||||
sc := bufio.NewScanner(reader)
|
||||
var errors []string
|
||||
for sc.Scan() {
|
||||
line := sc.Text()
|
||||
fields := strings.Fields(line)
|
||||
if strings.HasPrefix(fields[4], daemon.repository) {
|
||||
logrus.Debugf("Mount base: %v, repository %s", fields[4], daemon.repository)
|
||||
mnt := fields[4]
|
||||
mountBase := filepath.Base(mnt)
|
||||
if mountBase == "mqueue" || mountBase == "shm" {
|
||||
logrus.Debugf("Unmounting %v", mnt)
|
||||
if err := unmount(mnt); err != nil {
|
||||
logrus.Error(err)
|
||||
errors = append(errors, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := sc.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
return fmt.Errorf("Error cleaningup mounts:\n%v", strings.Join(errors, "\n"))
|
||||
}
|
||||
|
||||
logrus.Debugf("Cleaning up old shm/mqueue mounts: done.")
|
||||
return nil
|
||||
}
|
||||
74
vendor/github.com/hyperhq/hypercli/daemon/daemon_linux_test.go
generated
vendored
Normal file
74
vendor/github.com/hyperhq/hypercli/daemon/daemon_linux_test.go
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
// +build linux
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCleanupMounts(t *testing.T) {
|
||||
fixture := `230 138 0:60 / / rw,relatime - overlay overlay rw,lowerdir=/var/lib/docker/overlay/0ef9f93d5d365c1385b09d54bbee6afff3d92002c16f22eccb6e1549b2ff97d8/root,upperdir=/var/lib/docker/overlay/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb/upper,workdir=/var/lib/docker/overlay/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb/work
|
||||
231 230 0:56 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw
|
||||
232 230 0:57 / /dev rw,nosuid - tmpfs tmpfs rw,mode=755
|
||||
233 232 0:58 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
|
||||
234 232 0:59 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k
|
||||
235 232 0:55 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw
|
||||
236 230 0:61 / /sys rw,nosuid,nodev,noexec,relatime - sysfs sysfs rw
|
||||
237 236 0:62 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw
|
||||
238 237 0:21 /system.slice/docker.service /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
|
||||
239 237 0:23 /docker/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,perf_event
|
||||
240 237 0:24 /docker/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpuset,clone_children
|
||||
241 237 0:25 /docker/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,devices
|
||||
242 237 0:26 /docker/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,freezer
|
||||
243 237 0:27 /docker/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpu,cpuacct
|
||||
244 237 0:28 /docker/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,blkio
|
||||
245 237 0:29 /docker/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,net_cls,net_prio
|
||||
246 237 0:30 /docker/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,hugetlb
|
||||
247 237 0:31 /docker/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,memory
|
||||
248 230 253:1 /var/lib/docker/volumes/510cc41ac68c48bd4eac932e3e09711673876287abf1b185312cfbfe6261a111/_data /var/lib/docker rw,relatime - ext4 /dev/disk/by-uuid/ba70ea0c-1a8f-4ee4-9687-cb393730e2b5 rw,errors=remount-ro,data=ordered
|
||||
250 230 253:1 /var/lib/docker/containers/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb/hostname /etc/hostname rw,relatime - ext4 /dev/disk/by-uuid/ba70ea0c-1a8f-4ee4-9687-cb393730e2b5 rw,errors=remount-ro,data=ordered
|
||||
251 230 253:1 /var/lib/docker/containers/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb/hosts /etc/hosts rw,relatime - ext4 /dev/disk/by-uuid/ba70ea0c-1a8f-4ee4-9687-cb393730e2b5 rw,errors=remount-ro,data=ordered
|
||||
252 232 0:13 /1 /dev/console rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=000
|
||||
139 236 0:11 / /sys/kernel/security rw,relatime - securityfs none rw
|
||||
140 230 0:54 / /tmp rw,relatime - tmpfs none rw
|
||||
145 230 0:3 / /run/docker/netns/default rw - nsfs nsfs rw
|
||||
130 140 0:45 / /tmp/docker_recursive_mount_test312125472/tmpfs rw,relatime - tmpfs tmpfs rw
|
||||
131 230 0:3 / /run/docker/netns/47903e2e6701 rw - nsfs nsfs rw
|
||||
133 230 0:55 / /go/src/github.com/hyperhq/hypercli/bundles/1.9.0-dev/test-integration-cli/d45526097/graph/containers/47903e2e67014246eba27607809d5f5c2437c3bf84c2986393448f84093cc40b/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw`
|
||||
|
||||
d := &Daemon{
|
||||
repository: "/go/src/github.com/hyperhq/hypercli/bundles/1.9.0-dev/test-integration-cli/d45526097/graph/containers/",
|
||||
}
|
||||
|
||||
expected := "/go/src/github.com/hyperhq/hypercli/bundles/1.9.0-dev/test-integration-cli/d45526097/graph/containers/47903e2e67014246eba27607809d5f5c2437c3bf84c2986393448f84093cc40b/mqueue"
|
||||
var unmounted bool
|
||||
unmount := func(target string) error {
|
||||
if target == expected {
|
||||
unmounted = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
d.cleanupMountsFromReader(strings.NewReader(fixture), unmount)
|
||||
|
||||
if !unmounted {
|
||||
t.Fatalf("Expected to unmount the mqueue")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotCleanupMounts(t *testing.T) {
|
||||
d := &Daemon{
|
||||
repository: "",
|
||||
}
|
||||
var unmounted bool
|
||||
unmount := func(target string) error {
|
||||
unmounted = true
|
||||
return nil
|
||||
}
|
||||
mountInfo := `234 232 0:59 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k`
|
||||
d.cleanupMountsFromReader(strings.NewReader(mountInfo), unmount)
|
||||
if unmounted {
|
||||
t.Fatalf("Expected not to clean up /dev/shm")
|
||||
}
|
||||
}
|
||||
9
vendor/github.com/hyperhq/hypercli/daemon/daemon_stub.go
generated
vendored
Normal file
9
vendor/github.com/hyperhq/hypercli/daemon/daemon_stub.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// +build !experimental
|
||||
|
||||
package daemon
|
||||
|
||||
import "github.com/docker/engine-api/types/container"
|
||||
|
||||
func (daemon *Daemon) verifyExperimentalContainerSettings(hostConfig *container.HostConfig, config *container.Config) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
423
vendor/github.com/hyperhq/hypercli/daemon/daemon_test.go
generated
vendored
Normal file
423
vendor/github.com/hyperhq/hypercli/daemon/daemon_test.go
generated
vendored
Normal file
@@ -0,0 +1,423 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hyperhq/hypercli/container"
|
||||
"github.com/hyperhq/hypercli/pkg/discovery"
|
||||
_ "github.com/hyperhq/hypercli/pkg/discovery/memory"
|
||||
"github.com/hyperhq/hypercli/pkg/registrar"
|
||||
"github.com/hyperhq/hypercli/pkg/truncindex"
|
||||
"github.com/hyperhq/hypercli/volume"
|
||||
volumedrivers "github.com/hyperhq/hypercli/volume/drivers"
|
||||
"github.com/hyperhq/hypercli/volume/local"
|
||||
"github.com/hyperhq/hypercli/volume/store"
|
||||
containertypes "github.com/docker/engine-api/types/container"
|
||||
"github.com/docker/go-connections/nat"
|
||||
)
|
||||
|
||||
//
|
||||
// https://github.com/hyperhq/hypercli/issues/8069
|
||||
//
|
||||
|
||||
func TestGetContainer(t *testing.T) {
|
||||
c1 := &container.Container{
|
||||
CommonContainer: container.CommonContainer{
|
||||
ID: "5a4ff6a163ad4533d22d69a2b8960bf7fafdcba06e72d2febdba229008b0bf57",
|
||||
Name: "tender_bardeen",
|
||||
},
|
||||
}
|
||||
|
||||
c2 := &container.Container{
|
||||
CommonContainer: container.CommonContainer{
|
||||
ID: "3cdbd1aa394fd68559fd1441d6eff2ab7c1e6363582c82febfaa8045df3bd8de",
|
||||
Name: "drunk_hawking",
|
||||
},
|
||||
}
|
||||
|
||||
c3 := &container.Container{
|
||||
CommonContainer: container.CommonContainer{
|
||||
ID: "3cdbd1aa394fd68559fd1441d6eff2abfafdcba06e72d2febdba229008b0bf57",
|
||||
Name: "3cdbd1aa",
|
||||
},
|
||||
}
|
||||
|
||||
c4 := &container.Container{
|
||||
CommonContainer: container.CommonContainer{
|
||||
ID: "75fb0b800922abdbef2d27e60abcdfaf7fb0698b2a96d22d3354da361a6ff4a5",
|
||||
Name: "5a4ff6a163ad4533d22d69a2b8960bf7fafdcba06e72d2febdba229008b0bf57",
|
||||
},
|
||||
}
|
||||
|
||||
c5 := &container.Container{
|
||||
CommonContainer: container.CommonContainer{
|
||||
ID: "d22d69a2b8960bf7fafdcba06e72d2febdba960bf7fafdcba06e72d2f9008b060b",
|
||||
Name: "d22d69a2b896",
|
||||
},
|
||||
}
|
||||
|
||||
store := container.NewMemoryStore()
|
||||
store.Add(c1.ID, c1)
|
||||
store.Add(c2.ID, c2)
|
||||
store.Add(c3.ID, c3)
|
||||
store.Add(c4.ID, c4)
|
||||
store.Add(c5.ID, c5)
|
||||
|
||||
index := truncindex.NewTruncIndex([]string{})
|
||||
index.Add(c1.ID)
|
||||
index.Add(c2.ID)
|
||||
index.Add(c3.ID)
|
||||
index.Add(c4.ID)
|
||||
index.Add(c5.ID)
|
||||
|
||||
daemon := &Daemon{
|
||||
containers: store,
|
||||
idIndex: index,
|
||||
nameIndex: registrar.NewRegistrar(),
|
||||
}
|
||||
|
||||
daemon.reserveName(c1.ID, c1.Name)
|
||||
daemon.reserveName(c2.ID, c2.Name)
|
||||
daemon.reserveName(c3.ID, c3.Name)
|
||||
daemon.reserveName(c4.ID, c4.Name)
|
||||
daemon.reserveName(c5.ID, c5.Name)
|
||||
|
||||
if container, _ := daemon.GetContainer("3cdbd1aa394fd68559fd1441d6eff2ab7c1e6363582c82febfaa8045df3bd8de"); container != c2 {
|
||||
t.Fatal("Should explicitly match full container IDs")
|
||||
}
|
||||
|
||||
if container, _ := daemon.GetContainer("75fb0b8009"); container != c4 {
|
||||
t.Fatal("Should match a partial ID")
|
||||
}
|
||||
|
||||
if container, _ := daemon.GetContainer("drunk_hawking"); container != c2 {
|
||||
t.Fatal("Should match a full name")
|
||||
}
|
||||
|
||||
// c3.Name is a partial match for both c3.ID and c2.ID
|
||||
if c, _ := daemon.GetContainer("3cdbd1aa"); c != c3 {
|
||||
t.Fatal("Should match a full name even though it collides with another container's ID")
|
||||
}
|
||||
|
||||
if container, _ := daemon.GetContainer("d22d69a2b896"); container != c5 {
|
||||
t.Fatal("Should match a container where the provided prefix is an exact match to the it's name, and is also a prefix for it's ID")
|
||||
}
|
||||
|
||||
if _, err := daemon.GetContainer("3cdbd1"); err == nil {
|
||||
t.Fatal("Should return an error when provided a prefix that partially matches multiple container ID's")
|
||||
}
|
||||
|
||||
if _, err := daemon.GetContainer("nothing"); err == nil {
|
||||
t.Fatal("Should return an error when provided a prefix that is neither a name or a partial match to an ID")
|
||||
}
|
||||
}
|
||||
|
||||
func initDaemonWithVolumeStore(tmp string) (*Daemon, error) {
|
||||
daemon := &Daemon{
|
||||
repository: tmp,
|
||||
root: tmp,
|
||||
volumes: store.New(),
|
||||
}
|
||||
|
||||
volumesDriver, err := local.New(tmp, 0, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
volumedrivers.Register(volumesDriver, volumesDriver.Name())
|
||||
|
||||
return daemon, nil
|
||||
}
|
||||
|
||||
func TestValidContainerNames(t *testing.T) {
|
||||
invalidNames := []string{"-rm", "&sdfsfd", "safd%sd"}
|
||||
validNames := []string{"word-word", "word_word", "1weoid"}
|
||||
|
||||
for _, name := range invalidNames {
|
||||
if validContainerNamePattern.MatchString(name) {
|
||||
t.Fatalf("%q is not a valid container name and was returned as valid.", name)
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range validNames {
|
||||
if !validContainerNamePattern.MatchString(name) {
|
||||
t.Fatalf("%q is a valid container name and was returned as invalid.", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainerInitDNS(t *testing.T) {
|
||||
tmp, err := ioutil.TempDir("", "docker-container-test-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
containerID := "d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e"
|
||||
containerPath := filepath.Join(tmp, containerID)
|
||||
if err := os.MkdirAll(containerPath, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
config := `{"State":{"Running":true,"Paused":false,"Restarting":false,"OOMKilled":false,"Dead":false,"Pid":2464,"ExitCode":0,
|
||||
"Error":"","StartedAt":"2015-05-26T16:48:53.869308965Z","FinishedAt":"0001-01-01T00:00:00Z"},
|
||||
"ID":"d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e","Created":"2015-05-26T16:48:53.7987917Z","Path":"top",
|
||||
"Args":[],"Config":{"Hostname":"d59df5276e7b","Domainname":"","User":"","Memory":0,"MemorySwap":0,"CpuShares":0,"Cpuset":"",
|
||||
"AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"PortSpecs":null,"ExposedPorts":null,"Tty":true,"OpenStdin":true,
|
||||
"StdinOnce":false,"Env":null,"Cmd":["top"],"Image":"ubuntu:latest","Volumes":null,"WorkingDir":"","Entrypoint":null,
|
||||
"NetworkDisabled":false,"MacAddress":"","OnBuild":null,"Labels":{}},"Image":"07f8e8c5e66084bef8f848877857537ffe1c47edd01a93af27e7161672ad0e95",
|
||||
"NetworkSettings":{"IPAddress":"172.17.0.1","IPPrefixLen":16,"MacAddress":"02:42:ac:11:00:01","LinkLocalIPv6Address":"fe80::42:acff:fe11:1",
|
||||
"LinkLocalIPv6PrefixLen":64,"GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"Gateway":"172.17.42.1","IPv6Gateway":"","Bridge":"docker0","Ports":{}},
|
||||
"ResolvConfPath":"/var/lib/docker/containers/d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e/resolv.conf",
|
||||
"HostnamePath":"/var/lib/docker/containers/d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e/hostname",
|
||||
"HostsPath":"/var/lib/docker/containers/d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e/hosts",
|
||||
"LogPath":"/var/lib/docker/containers/d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e/d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e-json.log",
|
||||
"Name":"/ubuntu","Driver":"aufs","MountLabel":"","ProcessLabel":"","AppArmorProfile":"","RestartCount":0,
|
||||
"UpdateDns":false,"Volumes":{},"VolumesRW":{},"AppliedVolumesFrom":null}`
|
||||
|
||||
// Container struct only used to retrieve path to config file
|
||||
container := &container.Container{CommonContainer: container.CommonContainer{Root: containerPath}}
|
||||
configPath, err := container.ConfigPath()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = ioutil.WriteFile(configPath, []byte(config), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hostConfig := `{"Binds":[],"ContainerIDFile":"","Memory":0,"MemorySwap":0,"CpuShares":0,"CpusetCpus":"",
|
||||
"Privileged":false,"PortBindings":{},"Links":null,"PublishAllPorts":false,"Dns":null,"DnsOptions":null,"DnsSearch":null,"ExtraHosts":null,"VolumesFrom":null,
|
||||
"Devices":[],"NetworkMode":"bridge","IpcMode":"","PidMode":"","CapAdd":null,"CapDrop":null,"RestartPolicy":{"Name":"no","MaximumRetryCount":0},
|
||||
"SecurityOpt":null,"ReadonlyRootfs":false,"Ulimits":null,"LogConfig":{"Type":"","Config":null},"CgroupParent":""}`
|
||||
|
||||
hostConfigPath, err := container.HostConfigPath()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = ioutil.WriteFile(hostConfigPath, []byte(hostConfig), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
daemon, err := initDaemonWithVolumeStore(tmp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer volumedrivers.Unregister(volume.DefaultDriverName)
|
||||
|
||||
c, err := daemon.load(containerID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if c.HostConfig.DNS == nil {
|
||||
t.Fatal("Expected container DNS to not be nil")
|
||||
}
|
||||
|
||||
if c.HostConfig.DNSSearch == nil {
|
||||
t.Fatal("Expected container DNSSearch to not be nil")
|
||||
}
|
||||
|
||||
if c.HostConfig.DNSOptions == nil {
|
||||
t.Fatal("Expected container DNSOptions to not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func newPortNoError(proto, port string) nat.Port {
|
||||
p, _ := nat.NewPort(proto, port)
|
||||
return p
|
||||
}
|
||||
|
||||
func TestMerge(t *testing.T) {
|
||||
volumesImage := make(map[string]struct{})
|
||||
volumesImage["/test1"] = struct{}{}
|
||||
volumesImage["/test2"] = struct{}{}
|
||||
portsImage := make(nat.PortSet)
|
||||
portsImage[newPortNoError("tcp", "1111")] = struct{}{}
|
||||
portsImage[newPortNoError("tcp", "2222")] = struct{}{}
|
||||
configImage := &containertypes.Config{
|
||||
ExposedPorts: portsImage,
|
||||
Env: []string{"VAR1=1", "VAR2=2"},
|
||||
Volumes: volumesImage,
|
||||
}
|
||||
|
||||
portsUser := make(nat.PortSet)
|
||||
portsUser[newPortNoError("tcp", "2222")] = struct{}{}
|
||||
portsUser[newPortNoError("tcp", "3333")] = struct{}{}
|
||||
volumesUser := make(map[string]struct{})
|
||||
volumesUser["/test3"] = struct{}{}
|
||||
configUser := &containertypes.Config{
|
||||
ExposedPorts: portsUser,
|
||||
Env: []string{"VAR2=3", "VAR3=3"},
|
||||
Volumes: volumesUser,
|
||||
}
|
||||
|
||||
if err := merge(configUser, configImage); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if len(configUser.ExposedPorts) != 3 {
|
||||
t.Fatalf("Expected 3 ExposedPorts, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts))
|
||||
}
|
||||
for portSpecs := range configUser.ExposedPorts {
|
||||
if portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" {
|
||||
t.Fatalf("Expected 1111 or 2222 or 3333, found %s", portSpecs)
|
||||
}
|
||||
}
|
||||
if len(configUser.Env) != 3 {
|
||||
t.Fatalf("Expected 3 env var, VAR1=1, VAR2=3 and VAR3=3, found %d", len(configUser.Env))
|
||||
}
|
||||
for _, env := range configUser.Env {
|
||||
if env != "VAR1=1" && env != "VAR2=3" && env != "VAR3=3" {
|
||||
t.Fatalf("Expected VAR1=1 or VAR2=3 or VAR3=3, found %s", env)
|
||||
}
|
||||
}
|
||||
|
||||
if len(configUser.Volumes) != 3 {
|
||||
t.Fatalf("Expected 3 volumes, /test1, /test2 and /test3, found %d", len(configUser.Volumes))
|
||||
}
|
||||
for v := range configUser.Volumes {
|
||||
if v != "/test1" && v != "/test2" && v != "/test3" {
|
||||
t.Fatalf("Expected /test1 or /test2 or /test3, found %s", v)
|
||||
}
|
||||
}
|
||||
|
||||
ports, _, err := nat.ParsePortSpecs([]string{"0000"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
configImage2 := &containertypes.Config{
|
||||
ExposedPorts: ports,
|
||||
}
|
||||
|
||||
if err := merge(configUser, configImage2); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if len(configUser.ExposedPorts) != 4 {
|
||||
t.Fatalf("Expected 4 ExposedPorts, 0000, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts))
|
||||
}
|
||||
for portSpecs := range configUser.ExposedPorts {
|
||||
if portSpecs.Port() != "0" && portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" {
|
||||
t.Fatalf("Expected %q or %q or %q or %q, found %s", 0, 1111, 2222, 3333, portSpecs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonReloadLabels(t *testing.T) {
|
||||
daemon := &Daemon{}
|
||||
daemon.configStore = &Config{
|
||||
CommonConfig: CommonConfig{
|
||||
Labels: []string{"foo:bar"},
|
||||
},
|
||||
}
|
||||
|
||||
newConfig := &Config{
|
||||
CommonConfig: CommonConfig{
|
||||
Labels: []string{"foo:baz"},
|
||||
},
|
||||
}
|
||||
|
||||
daemon.Reload(newConfig)
|
||||
label := daemon.configStore.Labels[0]
|
||||
if label != "foo:baz" {
|
||||
t.Fatalf("Expected daemon label `foo:baz`, got %s", label)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonDiscoveryReload(t *testing.T) {
|
||||
daemon := &Daemon{}
|
||||
daemon.configStore = &Config{
|
||||
CommonConfig: CommonConfig{
|
||||
ClusterStore: "memory://127.0.0.1",
|
||||
ClusterAdvertise: "127.0.0.1:3333",
|
||||
},
|
||||
}
|
||||
|
||||
if err := daemon.initDiscovery(daemon.configStore); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := discovery.Entries{
|
||||
&discovery.Entry{Host: "127.0.0.1", Port: "3333"},
|
||||
}
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
defer close(stopCh)
|
||||
ch, errCh := daemon.discoveryWatcher.Watch(stopCh)
|
||||
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Fatal("failed to get discovery advertisements in time")
|
||||
case e := <-ch:
|
||||
if !reflect.DeepEqual(e, expected) {
|
||||
t.Fatalf("expected %v, got %v\n", expected, e)
|
||||
}
|
||||
case e := <-errCh:
|
||||
t.Fatal(e)
|
||||
}
|
||||
|
||||
newConfig := &Config{
|
||||
CommonConfig: CommonConfig{
|
||||
ClusterStore: "memory://127.0.0.1:2222",
|
||||
ClusterAdvertise: "127.0.0.1:5555",
|
||||
},
|
||||
}
|
||||
|
||||
expected = discovery.Entries{
|
||||
&discovery.Entry{Host: "127.0.0.1", Port: "5555"},
|
||||
}
|
||||
|
||||
if err := daemon.reloadClusterDiscovery(newConfig); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ch, errCh = daemon.discoveryWatcher.Watch(stopCh)
|
||||
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Fatal("failed to get discovery advertisements in time")
|
||||
case e := <-ch:
|
||||
if !reflect.DeepEqual(e, expected) {
|
||||
t.Fatalf("expected %v, got %v\n", expected, e)
|
||||
}
|
||||
case e := <-errCh:
|
||||
t.Fatal(e)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonDiscoveryReloadFromEmptyDiscovery(t *testing.T) {
|
||||
daemon := &Daemon{}
|
||||
daemon.configStore = &Config{}
|
||||
|
||||
newConfig := &Config{
|
||||
CommonConfig: CommonConfig{
|
||||
ClusterStore: "memory://127.0.0.1:2222",
|
||||
ClusterAdvertise: "127.0.0.1:5555",
|
||||
},
|
||||
}
|
||||
|
||||
expected := discovery.Entries{
|
||||
&discovery.Entry{Host: "127.0.0.1", Port: "5555"},
|
||||
}
|
||||
|
||||
if err := daemon.reloadClusterDiscovery(newConfig); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stopCh := make(chan struct{})
|
||||
defer close(stopCh)
|
||||
ch, errCh := daemon.discoveryWatcher.Watch(stopCh)
|
||||
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Fatal("failed to get discovery advertisements in time")
|
||||
case e := <-ch:
|
||||
if !reflect.DeepEqual(e, expected) {
|
||||
t.Fatalf("expected %v, got %v\n", expected, e)
|
||||
}
|
||||
case e := <-errCh:
|
||||
t.Fatal(e)
|
||||
}
|
||||
}
|
||||
982
vendor/github.com/hyperhq/hypercli/daemon/daemon_unix.go
generated
vendored
Normal file
982
vendor/github.com/hyperhq/hypercli/daemon/daemon_unix.go
generated
vendored
Normal file
@@ -0,0 +1,982 @@
|
||||
// +build linux freebsd
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/hyperhq/hypercli/container"
|
||||
derr "github.com/hyperhq/hypercli/errors"
|
||||
"github.com/hyperhq/hypercli/image"
|
||||
"github.com/hyperhq/hypercli/layer"
|
||||
"github.com/hyperhq/hypercli/pkg/idtools"
|
||||
"github.com/hyperhq/hypercli/pkg/parsers"
|
||||
"github.com/hyperhq/hypercli/pkg/parsers/kernel"
|
||||
"github.com/hyperhq/hypercli/pkg/sysinfo"
|
||||
"github.com/hyperhq/hypercli/reference"
|
||||
"github.com/hyperhq/hypercli/runconfig"
|
||||
runconfigopts "github.com/hyperhq/hypercli/runconfig/opts"
|
||||
pblkiodev "github.com/docker/engine-api/types/blkiodev"
|
||||
containertypes "github.com/docker/engine-api/types/container"
|
||||
"github.com/docker/libnetwork"
|
||||
nwconfig "github.com/docker/libnetwork/config"
|
||||
"github.com/docker/libnetwork/drivers/bridge"
|
||||
"github.com/docker/libnetwork/ipamutils"
|
||||
"github.com/docker/libnetwork/netlabel"
|
||||
"github.com/docker/libnetwork/options"
|
||||
"github.com/docker/libnetwork/types"
|
||||
blkiodev "github.com/opencontainers/runc/libcontainer/configs"
|
||||
"github.com/opencontainers/runc/libcontainer/label"
|
||||
"github.com/opencontainers/runc/libcontainer/user"
|
||||
)
|
||||
|
||||
const (
|
||||
// See https://git.kernel.org/cgit/linux/kernel/git/tip/tip.git/tree/kernel/sched/sched.h?id=8cd9234c64c584432f6992fe944ca9e46ca8ea76#n269
|
||||
linuxMinCPUShares = 2
|
||||
linuxMaxCPUShares = 262144
|
||||
platformSupported = true
|
||||
// It's not kernel limit, we want this 4M limit to supply a reasonable functional container
|
||||
linuxMinMemory = 4194304
|
||||
// constants for remapped root settings
|
||||
defaultIDSpecifier string = "default"
|
||||
defaultRemappedID string = "dockremap"
|
||||
)
|
||||
|
||||
func getBlkioWeightDevices(config *containertypes.HostConfig) ([]*blkiodev.WeightDevice, error) {
|
||||
var stat syscall.Stat_t
|
||||
var blkioWeightDevices []*blkiodev.WeightDevice
|
||||
|
||||
for _, weightDevice := range config.BlkioWeightDevice {
|
||||
if err := syscall.Stat(weightDevice.Path, &stat); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
weightDevice := blkiodev.NewWeightDevice(int64(stat.Rdev/256), int64(stat.Rdev%256), weightDevice.Weight, 0)
|
||||
blkioWeightDevices = append(blkioWeightDevices, weightDevice)
|
||||
}
|
||||
|
||||
return blkioWeightDevices, nil
|
||||
}
|
||||
|
||||
func parseSecurityOpt(container *container.Container, config *containertypes.HostConfig) error {
|
||||
var (
|
||||
labelOpts []string
|
||||
err error
|
||||
)
|
||||
|
||||
for _, opt := range config.SecurityOpt {
|
||||
con := strings.SplitN(opt, ":", 2)
|
||||
if len(con) == 1 {
|
||||
return fmt.Errorf("Invalid --security-opt: %q", opt)
|
||||
}
|
||||
switch con[0] {
|
||||
case "label":
|
||||
labelOpts = append(labelOpts, con[1])
|
||||
case "apparmor":
|
||||
container.AppArmorProfile = con[1]
|
||||
case "seccomp":
|
||||
container.SeccompProfile = con[1]
|
||||
default:
|
||||
return fmt.Errorf("Invalid --security-opt: %q", opt)
|
||||
}
|
||||
}
|
||||
|
||||
container.ProcessLabel, container.MountLabel, err = label.InitLabels(labelOpts)
|
||||
return err
|
||||
}
|
||||
|
||||
func getBlkioReadIOpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) {
|
||||
var blkioReadIOpsDevice []*blkiodev.ThrottleDevice
|
||||
var stat syscall.Stat_t
|
||||
|
||||
for _, iopsDevice := range config.BlkioDeviceReadIOps {
|
||||
if err := syscall.Stat(iopsDevice.Path, &stat); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
readIOpsDevice := blkiodev.NewThrottleDevice(int64(stat.Rdev/256), int64(stat.Rdev%256), iopsDevice.Rate)
|
||||
blkioReadIOpsDevice = append(blkioReadIOpsDevice, readIOpsDevice)
|
||||
}
|
||||
|
||||
return blkioReadIOpsDevice, nil
|
||||
}
|
||||
|
||||
func getBlkioWriteIOpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) {
|
||||
var blkioWriteIOpsDevice []*blkiodev.ThrottleDevice
|
||||
var stat syscall.Stat_t
|
||||
|
||||
for _, iopsDevice := range config.BlkioDeviceWriteIOps {
|
||||
if err := syscall.Stat(iopsDevice.Path, &stat); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
writeIOpsDevice := blkiodev.NewThrottleDevice(int64(stat.Rdev/256), int64(stat.Rdev%256), iopsDevice.Rate)
|
||||
blkioWriteIOpsDevice = append(blkioWriteIOpsDevice, writeIOpsDevice)
|
||||
}
|
||||
|
||||
return blkioWriteIOpsDevice, nil
|
||||
}
|
||||
|
||||
func getBlkioReadBpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) {
|
||||
var blkioReadBpsDevice []*blkiodev.ThrottleDevice
|
||||
var stat syscall.Stat_t
|
||||
|
||||
for _, bpsDevice := range config.BlkioDeviceReadBps {
|
||||
if err := syscall.Stat(bpsDevice.Path, &stat); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
readBpsDevice := blkiodev.NewThrottleDevice(int64(stat.Rdev/256), int64(stat.Rdev%256), bpsDevice.Rate)
|
||||
blkioReadBpsDevice = append(blkioReadBpsDevice, readBpsDevice)
|
||||
}
|
||||
|
||||
return blkioReadBpsDevice, nil
|
||||
}
|
||||
|
||||
func getBlkioWriteBpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) {
|
||||
var blkioWriteBpsDevice []*blkiodev.ThrottleDevice
|
||||
var stat syscall.Stat_t
|
||||
|
||||
for _, bpsDevice := range config.BlkioDeviceWriteBps {
|
||||
if err := syscall.Stat(bpsDevice.Path, &stat); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
writeBpsDevice := blkiodev.NewThrottleDevice(int64(stat.Rdev/256), int64(stat.Rdev%256), bpsDevice.Rate)
|
||||
blkioWriteBpsDevice = append(blkioWriteBpsDevice, writeBpsDevice)
|
||||
}
|
||||
|
||||
return blkioWriteBpsDevice, nil
|
||||
}
|
||||
|
||||
func checkKernelVersion(k, major, minor int) bool {
|
||||
if v, err := kernel.GetKernelVersion(); err != nil {
|
||||
logrus.Warnf("%s", err)
|
||||
} else {
|
||||
if kernel.CompareKernelVersion(*v, kernel.VersionInfo{Kernel: k, Major: major, Minor: minor}) < 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func checkKernel() error {
|
||||
// Check for unsupported kernel versions
|
||||
// FIXME: it would be cleaner to not test for specific versions, but rather
|
||||
// test for specific functionalities.
|
||||
// Unfortunately we can't test for the feature "does not cause a kernel panic"
|
||||
// without actually causing a kernel panic, so we need this workaround until
|
||||
// the circumstances of pre-3.10 crashes are clearer.
|
||||
// For details see https://github.com/hyperhq/hypercli/issues/407
|
||||
if !checkKernelVersion(3, 10, 0) {
|
||||
v, _ := kernel.GetKernelVersion()
|
||||
if os.Getenv("DOCKER_NOWARN_KERNEL_VERSION") == "" {
|
||||
logrus.Warnf("Your Linux kernel version %s can be unstable running docker. Please upgrade your kernel to 3.10.0.", v.String())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// adaptContainerSettings is called during container creation to modify any
|
||||
// settings necessary in the HostConfig structure.
|
||||
func (daemon *Daemon) adaptContainerSettings(hostConfig *containertypes.HostConfig, adjustCPUShares bool) error {
|
||||
if adjustCPUShares && hostConfig.CPUShares > 0 {
|
||||
// Handle unsupported CPUShares
|
||||
if hostConfig.CPUShares < linuxMinCPUShares {
|
||||
logrus.Warnf("Changing requested CPUShares of %d to minimum allowed of %d", hostConfig.CPUShares, linuxMinCPUShares)
|
||||
hostConfig.CPUShares = linuxMinCPUShares
|
||||
} else if hostConfig.CPUShares > linuxMaxCPUShares {
|
||||
logrus.Warnf("Changing requested CPUShares of %d to maximum allowed of %d", hostConfig.CPUShares, linuxMaxCPUShares)
|
||||
hostConfig.CPUShares = linuxMaxCPUShares
|
||||
}
|
||||
}
|
||||
if hostConfig.Memory > 0 && hostConfig.MemorySwap == 0 {
|
||||
// By default, MemorySwap is set to twice the size of Memory.
|
||||
hostConfig.MemorySwap = hostConfig.Memory * 2
|
||||
}
|
||||
if hostConfig.ShmSize == 0 {
|
||||
hostConfig.ShmSize = container.DefaultSHMSize
|
||||
}
|
||||
var err error
|
||||
if hostConfig.SecurityOpt == nil {
|
||||
hostConfig.SecurityOpt, err = daemon.generateSecurityOpt(hostConfig.IpcMode, hostConfig.PidMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if hostConfig.MemorySwappiness == nil {
|
||||
defaultSwappiness := int64(-1)
|
||||
hostConfig.MemorySwappiness = &defaultSwappiness
|
||||
}
|
||||
if hostConfig.OomKillDisable == nil {
|
||||
defaultOomKillDisable := false
|
||||
hostConfig.OomKillDisable = &defaultOomKillDisable
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyContainerResources(resources *containertypes.Resources, sysInfo *sysinfo.SysInfo) ([]string, error) {
|
||||
warnings := []string{}
|
||||
|
||||
// memory subsystem checks and adjustments
|
||||
if resources.Memory != 0 && resources.Memory < linuxMinMemory {
|
||||
return warnings, fmt.Errorf("Minimum memory limit allowed is 4MB")
|
||||
}
|
||||
if resources.Memory > 0 && !sysInfo.MemoryLimit {
|
||||
warnings = append(warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.")
|
||||
logrus.Warnf("Your kernel does not support memory limit capabilities. Limitation discarded.")
|
||||
resources.Memory = 0
|
||||
resources.MemorySwap = -1
|
||||
}
|
||||
if resources.Memory > 0 && resources.MemorySwap != -1 && !sysInfo.SwapLimit {
|
||||
warnings = append(warnings, "Your kernel does not support swap limit capabilities, memory limited without swap.")
|
||||
logrus.Warnf("Your kernel does not support swap limit capabilities, memory limited without swap.")
|
||||
resources.MemorySwap = -1
|
||||
}
|
||||
if resources.Memory > 0 && resources.MemorySwap > 0 && resources.MemorySwap < resources.Memory {
|
||||
return warnings, fmt.Errorf("Minimum memoryswap limit should be larger than memory limit, see usage.")
|
||||
}
|
||||
if resources.Memory == 0 && resources.MemorySwap > 0 {
|
||||
return warnings, fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.")
|
||||
}
|
||||
if resources.MemorySwappiness != nil && *resources.MemorySwappiness != -1 && !sysInfo.MemorySwappiness {
|
||||
warnings = append(warnings, "Your kernel does not support memory swappiness capabilities, memory swappiness discarded.")
|
||||
logrus.Warnf("Your kernel does not support memory swappiness capabilities, memory swappiness discarded.")
|
||||
resources.MemorySwappiness = nil
|
||||
}
|
||||
if resources.MemorySwappiness != nil {
|
||||
swappiness := *resources.MemorySwappiness
|
||||
if swappiness < -1 || swappiness > 100 {
|
||||
return warnings, fmt.Errorf("Invalid value: %v, valid memory swappiness range is 0-100.", swappiness)
|
||||
}
|
||||
}
|
||||
if resources.MemoryReservation > 0 && !sysInfo.MemoryReservation {
|
||||
warnings = append(warnings, "Your kernel does not support memory soft limit capabilities. Limitation discarded.")
|
||||
logrus.Warnf("Your kernel does not support memory soft limit capabilities. Limitation discarded.")
|
||||
resources.MemoryReservation = 0
|
||||
}
|
||||
if resources.Memory > 0 && resources.MemoryReservation > 0 && resources.Memory < resources.MemoryReservation {
|
||||
return warnings, fmt.Errorf("Minimum memory limit should be larger than memory reservation limit, see usage.")
|
||||
}
|
||||
if resources.KernelMemory > 0 && !sysInfo.KernelMemory {
|
||||
warnings = append(warnings, "Your kernel does not support kernel memory limit capabilities. Limitation discarded.")
|
||||
logrus.Warnf("Your kernel does not support kernel memory limit capabilities. Limitation discarded.")
|
||||
resources.KernelMemory = 0
|
||||
}
|
||||
if resources.KernelMemory > 0 && resources.KernelMemory < linuxMinMemory {
|
||||
return warnings, fmt.Errorf("Minimum kernel memory limit allowed is 4MB")
|
||||
}
|
||||
if resources.KernelMemory > 0 && !checkKernelVersion(4, 0, 0) {
|
||||
warnings = append(warnings, "You specified a kernel memory limit on a kernel older than 4.0. Kernel memory limits are experimental on older kernels, it won't work as expected and can cause your system to be unstable.")
|
||||
logrus.Warnf("You specified a kernel memory limit on a kernel older than 4.0. Kernel memory limits are experimental on older kernels, it won't work as expected and can cause your system to be unstable.")
|
||||
}
|
||||
if resources.OomKillDisable != nil && !sysInfo.OomKillDisable {
|
||||
// only produce warnings if the setting wasn't to *disable* the OOM Kill; no point
|
||||
// warning the caller if they already wanted the feature to be off
|
||||
if *resources.OomKillDisable {
|
||||
warnings = append(warnings, "Your kernel does not support OomKillDisable, OomKillDisable discarded.")
|
||||
logrus.Warnf("Your kernel does not support OomKillDisable, OomKillDisable discarded.")
|
||||
}
|
||||
resources.OomKillDisable = nil
|
||||
}
|
||||
|
||||
// cpu subsystem checks and adjustments
|
||||
if resources.CPUShares > 0 && !sysInfo.CPUShares {
|
||||
warnings = append(warnings, "Your kernel does not support CPU shares. Shares discarded.")
|
||||
logrus.Warnf("Your kernel does not support CPU shares. Shares discarded.")
|
||||
resources.CPUShares = 0
|
||||
}
|
||||
if resources.CPUPeriod > 0 && !sysInfo.CPUCfsPeriod {
|
||||
warnings = append(warnings, "Your kernel does not support CPU cfs period. Period discarded.")
|
||||
logrus.Warnf("Your kernel does not support CPU cfs period. Period discarded.")
|
||||
resources.CPUPeriod = 0
|
||||
}
|
||||
if resources.CPUQuota > 0 && !sysInfo.CPUCfsQuota {
|
||||
warnings = append(warnings, "Your kernel does not support CPU cfs quota. Quota discarded.")
|
||||
logrus.Warnf("Your kernel does not support CPU cfs quota. Quota discarded.")
|
||||
resources.CPUQuota = 0
|
||||
}
|
||||
|
||||
// cpuset subsystem checks and adjustments
|
||||
if (resources.CpusetCpus != "" || resources.CpusetMems != "") && !sysInfo.Cpuset {
|
||||
warnings = append(warnings, "Your kernel does not support cpuset. Cpuset discarded.")
|
||||
logrus.Warnf("Your kernel does not support cpuset. Cpuset discarded.")
|
||||
resources.CpusetCpus = ""
|
||||
resources.CpusetMems = ""
|
||||
}
|
||||
cpusAvailable, err := sysInfo.IsCpusetCpusAvailable(resources.CpusetCpus)
|
||||
if err != nil {
|
||||
return warnings, derr.ErrorCodeInvalidCpusetCpus.WithArgs(resources.CpusetCpus)
|
||||
}
|
||||
if !cpusAvailable {
|
||||
return warnings, derr.ErrorCodeNotAvailableCpusetCpus.WithArgs(resources.CpusetCpus, sysInfo.Cpus)
|
||||
}
|
||||
memsAvailable, err := sysInfo.IsCpusetMemsAvailable(resources.CpusetMems)
|
||||
if err != nil {
|
||||
return warnings, derr.ErrorCodeInvalidCpusetMems.WithArgs(resources.CpusetMems)
|
||||
}
|
||||
if !memsAvailable {
|
||||
return warnings, derr.ErrorCodeNotAvailableCpusetMems.WithArgs(resources.CpusetMems, sysInfo.Mems)
|
||||
}
|
||||
|
||||
// blkio subsystem checks and adjustments
|
||||
if resources.BlkioWeight > 0 && !sysInfo.BlkioWeight {
|
||||
warnings = append(warnings, "Your kernel does not support Block I/O weight. Weight discarded.")
|
||||
logrus.Warnf("Your kernel does not support Block I/O weight. Weight discarded.")
|
||||
resources.BlkioWeight = 0
|
||||
}
|
||||
if resources.BlkioWeight > 0 && (resources.BlkioWeight < 10 || resources.BlkioWeight > 1000) {
|
||||
return warnings, fmt.Errorf("Range of blkio weight is from 10 to 1000.")
|
||||
}
|
||||
if len(resources.BlkioWeightDevice) > 0 && !sysInfo.BlkioWeightDevice {
|
||||
warnings = append(warnings, "Your kernel does not support Block I/O weight_device.")
|
||||
logrus.Warnf("Your kernel does not support Block I/O weight_device. Weight-device discarded.")
|
||||
resources.BlkioWeightDevice = []*pblkiodev.WeightDevice{}
|
||||
}
|
||||
if len(resources.BlkioDeviceReadBps) > 0 && !sysInfo.BlkioReadBpsDevice {
|
||||
warnings = append(warnings, "Your kernel does not support Block read limit in bytes per second.")
|
||||
logrus.Warnf("Your kernel does not support Block I/O read limit in bytes per second. --device-read-bps discarded.")
|
||||
resources.BlkioDeviceReadBps = []*pblkiodev.ThrottleDevice{}
|
||||
}
|
||||
if len(resources.BlkioDeviceWriteBps) > 0 && !sysInfo.BlkioWriteBpsDevice {
|
||||
warnings = append(warnings, "Your kernel does not support Block write limit in bytes per second.")
|
||||
logrus.Warnf("Your kernel does not support Block I/O write limit in bytes per second. --device-write-bps discarded.")
|
||||
resources.BlkioDeviceWriteBps = []*pblkiodev.ThrottleDevice{}
|
||||
}
|
||||
if len(resources.BlkioDeviceReadIOps) > 0 && !sysInfo.BlkioReadIOpsDevice {
|
||||
warnings = append(warnings, "Your kernel does not support Block read limit in IO per second.")
|
||||
logrus.Warnf("Your kernel does not support Block I/O read limit in IO per second. -device-read-iops discarded.")
|
||||
resources.BlkioDeviceReadIOps = []*pblkiodev.ThrottleDevice{}
|
||||
}
|
||||
if len(resources.BlkioDeviceWriteIOps) > 0 && !sysInfo.BlkioWriteIOpsDevice {
|
||||
warnings = append(warnings, "Your kernel does not support Block write limit in IO per second.")
|
||||
logrus.Warnf("Your kernel does not support Block I/O write limit in IO per second. --device-write-iops discarded.")
|
||||
resources.BlkioDeviceWriteIOps = []*pblkiodev.ThrottleDevice{}
|
||||
}
|
||||
|
||||
return warnings, nil
|
||||
}
|
||||
|
||||
func usingSystemd(config *Config) bool {
|
||||
for _, option := range config.ExecOptions {
|
||||
key, val, err := parsers.ParseKeyValueOpt(option)
|
||||
if err != nil || !strings.EqualFold(key, "native.cgroupdriver") {
|
||||
continue
|
||||
}
|
||||
if val == "systemd" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (daemon *Daemon) usingSystemd() bool {
|
||||
return usingSystemd(daemon.configStore)
|
||||
}
|
||||
|
||||
// verifyPlatformContainerSettings performs platform-specific validation of the
|
||||
// hostconfig and config structures.
|
||||
func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes.HostConfig, config *containertypes.Config) ([]string, error) {
|
||||
warnings := []string{}
|
||||
sysInfo := sysinfo.New(true)
|
||||
|
||||
warnings, err := daemon.verifyExperimentalContainerSettings(hostConfig, config)
|
||||
if err != nil {
|
||||
return warnings, err
|
||||
}
|
||||
|
||||
w, err := verifyContainerResources(&hostConfig.Resources, sysInfo)
|
||||
if err != nil {
|
||||
return warnings, err
|
||||
}
|
||||
warnings = append(warnings, w...)
|
||||
|
||||
if hostConfig.ShmSize < 0 {
|
||||
return warnings, fmt.Errorf("SHM size must be greater then 0")
|
||||
}
|
||||
|
||||
if hostConfig.OomScoreAdj < -1000 || hostConfig.OomScoreAdj > 1000 {
|
||||
return warnings, fmt.Errorf("Invalid value %d, range for oom score adj is [-1000, 1000].", hostConfig.OomScoreAdj)
|
||||
}
|
||||
if sysInfo.IPv4ForwardingDisabled {
|
||||
warnings = append(warnings, "IPv4 forwarding is disabled. Networking will not work.")
|
||||
logrus.Warnf("IPv4 forwarding is disabled. Networking will not work")
|
||||
}
|
||||
// check for various conflicting options with user namespaces
|
||||
if daemon.configStore.RemappedRoot != "" {
|
||||
if hostConfig.Privileged {
|
||||
return warnings, fmt.Errorf("Privileged mode is incompatible with user namespaces")
|
||||
}
|
||||
if hostConfig.NetworkMode.IsHost() || hostConfig.NetworkMode.IsContainer() {
|
||||
return warnings, fmt.Errorf("Cannot share the host or a container's network namespace when user namespaces are enabled")
|
||||
}
|
||||
if hostConfig.PidMode.IsHost() {
|
||||
return warnings, fmt.Errorf("Cannot share the host PID namespace when user namespaces are enabled")
|
||||
}
|
||||
if hostConfig.IpcMode.IsContainer() {
|
||||
return warnings, fmt.Errorf("Cannot share a container's IPC namespace when user namespaces are enabled")
|
||||
}
|
||||
if hostConfig.ReadonlyRootfs {
|
||||
return warnings, fmt.Errorf("Cannot use the --read-only option when user namespaces are enabled")
|
||||
}
|
||||
}
|
||||
if hostConfig.CgroupParent != "" && daemon.usingSystemd() {
|
||||
// CgroupParent for systemd cgroup should be named as "xxx.slice"
|
||||
if len(hostConfig.CgroupParent) <= 6 || !strings.HasSuffix(hostConfig.CgroupParent, ".slice") {
|
||||
return warnings, fmt.Errorf("cgroup-parent for systemd cgroup should be a valid slice named as \"xxx.slice\"")
|
||||
}
|
||||
}
|
||||
return warnings, nil
|
||||
}
|
||||
|
||||
// verifyDaemonSettings performs validation of daemon config struct
|
||||
func verifyDaemonSettings(config *Config) error {
|
||||
// Check for mutually incompatible config options
|
||||
if config.bridgeConfig.Iface != "" && config.bridgeConfig.IP != "" {
|
||||
return fmt.Errorf("You specified -b & --bip, mutually exclusive options. Please specify only one")
|
||||
}
|
||||
if !config.bridgeConfig.EnableIPTables && !config.bridgeConfig.InterContainerCommunication {
|
||||
return fmt.Errorf("You specified --iptables=false with --icc=false. ICC=false uses iptables to function. Please set --icc or --iptables to true")
|
||||
}
|
||||
if !config.bridgeConfig.EnableIPTables && config.bridgeConfig.EnableIPMasq {
|
||||
config.bridgeConfig.EnableIPMasq = false
|
||||
}
|
||||
if config.CgroupParent != "" && usingSystemd(config) {
|
||||
if len(config.CgroupParent) <= 6 || !strings.HasSuffix(config.CgroupParent, ".slice") {
|
||||
return fmt.Errorf("cgroup-parent for systemd cgroup should be a valid slice named as \"xxx.slice\"")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkSystem validates platform-specific requirements
|
||||
func checkSystem() error {
|
||||
if os.Geteuid() != 0 {
|
||||
return fmt.Errorf("The Docker daemon needs to be run as root")
|
||||
}
|
||||
return checkKernel()
|
||||
}
|
||||
|
||||
// configureMaxThreads sets the Go runtime max threads threshold
|
||||
// which is 90% of the kernel setting from /proc/sys/kernel/threads-max
|
||||
func configureMaxThreads(config *Config) error {
|
||||
mt, err := ioutil.ReadFile("/proc/sys/kernel/threads-max")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mtint, err := strconv.Atoi(strings.TrimSpace(string(mt)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
maxThreads := (mtint / 100) * 90
|
||||
debug.SetMaxThreads(maxThreads)
|
||||
logrus.Debugf("Golang's threads limit set to %d", maxThreads)
|
||||
return nil
|
||||
}
|
||||
|
||||
// configureKernelSecuritySupport configures and validate security support for the kernel
|
||||
func configureKernelSecuritySupport(config *Config, driverName string) error {
|
||||
if config.EnableSelinuxSupport {
|
||||
if selinuxEnabled() {
|
||||
// As Docker on overlayFS and SELinux are incompatible at present, error on overlayfs being enabled
|
||||
if driverName == "overlay" {
|
||||
return fmt.Errorf("SELinux is not supported with the %s graph driver", driverName)
|
||||
}
|
||||
logrus.Debug("SELinux enabled successfully")
|
||||
} else {
|
||||
logrus.Warn("Docker could not enable SELinux on the host system")
|
||||
}
|
||||
} else {
|
||||
selinuxSetDisabled()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isBridgeNetworkDisabled(config *Config) bool {
|
||||
return config.bridgeConfig.Iface == disableNetworkBridge
|
||||
}
|
||||
|
||||
func (daemon *Daemon) networkOptions(dconfig *Config) ([]nwconfig.Option, error) {
|
||||
options := []nwconfig.Option{}
|
||||
if dconfig == nil {
|
||||
return options, nil
|
||||
}
|
||||
|
||||
options = append(options, nwconfig.OptionDataDir(dconfig.Root))
|
||||
|
||||
dd := runconfig.DefaultDaemonNetworkMode()
|
||||
dn := runconfig.DefaultDaemonNetworkMode().NetworkName()
|
||||
options = append(options, nwconfig.OptionDefaultDriver(string(dd)))
|
||||
options = append(options, nwconfig.OptionDefaultNetwork(dn))
|
||||
|
||||
if strings.TrimSpace(dconfig.ClusterStore) != "" {
|
||||
kv := strings.Split(dconfig.ClusterStore, "://")
|
||||
if len(kv) != 2 {
|
||||
return nil, fmt.Errorf("kv store daemon config must be of the form KV-PROVIDER://KV-URL")
|
||||
}
|
||||
options = append(options, nwconfig.OptionKVProvider(kv[0]))
|
||||
options = append(options, nwconfig.OptionKVProviderURL(kv[1]))
|
||||
}
|
||||
if len(dconfig.ClusterOpts) > 0 {
|
||||
options = append(options, nwconfig.OptionKVOpts(dconfig.ClusterOpts))
|
||||
}
|
||||
|
||||
if daemon.discoveryWatcher != nil {
|
||||
options = append(options, nwconfig.OptionDiscoveryWatcher(daemon.discoveryWatcher))
|
||||
}
|
||||
|
||||
if dconfig.ClusterAdvertise != "" {
|
||||
options = append(options, nwconfig.OptionDiscoveryAddress(dconfig.ClusterAdvertise))
|
||||
}
|
||||
|
||||
options = append(options, nwconfig.OptionLabels(dconfig.Labels))
|
||||
options = append(options, driverOptions(dconfig)...)
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func (daemon *Daemon) initNetworkController(config *Config) (libnetwork.NetworkController, error) {
|
||||
netOptions, err := daemon.networkOptions(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
controller, err := libnetwork.New(netOptions...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error obtaining controller instance: %v", err)
|
||||
}
|
||||
|
||||
// Initialize default network on "null"
|
||||
if _, err := controller.NewNetwork("null", "none", libnetwork.NetworkOptionPersist(false)); err != nil {
|
||||
return nil, fmt.Errorf("Error creating default \"null\" network: %v", err)
|
||||
}
|
||||
|
||||
// Initialize default network on "host"
|
||||
if _, err := controller.NewNetwork("host", "host", libnetwork.NetworkOptionPersist(false)); err != nil {
|
||||
return nil, fmt.Errorf("Error creating default \"host\" network: %v", err)
|
||||
}
|
||||
|
||||
if !config.DisableBridge {
|
||||
// Initialize default driver "bridge"
|
||||
if err := initBridgeDriver(controller, config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return controller, nil
|
||||
}
|
||||
|
||||
func driverOptions(config *Config) []nwconfig.Option {
|
||||
bridgeConfig := options.Generic{
|
||||
"EnableIPForwarding": config.bridgeConfig.EnableIPForward,
|
||||
"EnableIPTables": config.bridgeConfig.EnableIPTables,
|
||||
"EnableUserlandProxy": config.bridgeConfig.EnableUserlandProxy}
|
||||
bridgeOption := options.Generic{netlabel.GenericData: bridgeConfig}
|
||||
|
||||
dOptions := []nwconfig.Option{}
|
||||
dOptions = append(dOptions, nwconfig.OptionDriverConfig("bridge", bridgeOption))
|
||||
return dOptions
|
||||
}
|
||||
|
||||
func initBridgeDriver(controller libnetwork.NetworkController, config *Config) error {
|
||||
if n, err := controller.NetworkByName("bridge"); err == nil {
|
||||
if err = n.Delete(); err != nil {
|
||||
return fmt.Errorf("could not delete the default bridge network: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
bridgeName := bridge.DefaultBridgeName
|
||||
if config.bridgeConfig.Iface != "" {
|
||||
bridgeName = config.bridgeConfig.Iface
|
||||
}
|
||||
netOption := map[string]string{
|
||||
bridge.BridgeName: bridgeName,
|
||||
bridge.DefaultBridge: strconv.FormatBool(true),
|
||||
netlabel.DriverMTU: strconv.Itoa(config.Mtu),
|
||||
bridge.EnableIPMasquerade: strconv.FormatBool(config.bridgeConfig.EnableIPMasq),
|
||||
bridge.EnableICC: strconv.FormatBool(config.bridgeConfig.InterContainerCommunication),
|
||||
}
|
||||
|
||||
// --ip processing
|
||||
if config.bridgeConfig.DefaultIP != nil {
|
||||
netOption[bridge.DefaultBindingIP] = config.bridgeConfig.DefaultIP.String()
|
||||
}
|
||||
|
||||
var (
|
||||
ipamV4Conf *libnetwork.IpamConf
|
||||
ipamV6Conf *libnetwork.IpamConf
|
||||
)
|
||||
|
||||
ipamV4Conf = &libnetwork.IpamConf{AuxAddresses: make(map[string]string)}
|
||||
|
||||
nw, nw6List, err := ipamutils.ElectInterfaceAddresses(bridgeName)
|
||||
if err == nil {
|
||||
ipamV4Conf.PreferredPool = types.GetIPNetCanonical(nw).String()
|
||||
hip, _ := types.GetHostPartIP(nw.IP, nw.Mask)
|
||||
if hip.IsGlobalUnicast() {
|
||||
ipamV4Conf.Gateway = nw.IP.String()
|
||||
}
|
||||
}
|
||||
|
||||
if config.bridgeConfig.IP != "" {
|
||||
ipamV4Conf.PreferredPool = config.bridgeConfig.IP
|
||||
ip, _, err := net.ParseCIDR(config.bridgeConfig.IP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ipamV4Conf.Gateway = ip.String()
|
||||
} else if bridgeName == bridge.DefaultBridgeName && ipamV4Conf.PreferredPool != "" {
|
||||
logrus.Infof("Default bridge (%s) is assigned with an IP address %s. Daemon option --bip can be used to set a preferred IP address", bridgeName, ipamV4Conf.PreferredPool)
|
||||
}
|
||||
|
||||
if config.bridgeConfig.FixedCIDR != "" {
|
||||
_, fCIDR, err := net.ParseCIDR(config.bridgeConfig.FixedCIDR)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ipamV4Conf.SubPool = fCIDR.String()
|
||||
}
|
||||
|
||||
if config.bridgeConfig.DefaultGatewayIPv4 != nil {
|
||||
ipamV4Conf.AuxAddresses["DefaultGatewayIPv4"] = config.bridgeConfig.DefaultGatewayIPv4.String()
|
||||
}
|
||||
|
||||
var deferIPv6Alloc bool
|
||||
if config.bridgeConfig.FixedCIDRv6 != "" {
|
||||
_, fCIDRv6, err := net.ParseCIDR(config.bridgeConfig.FixedCIDRv6)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// In case user has specified the daemon flag --fixed-cidr-v6 and the passed network has
|
||||
// at least 48 host bits, we need to guarantee the current behavior where the containers'
|
||||
// IPv6 addresses will be constructed based on the containers' interface MAC address.
|
||||
// We do so by telling libnetwork to defer the IPv6 address allocation for the endpoints
|
||||
// on this network until after the driver has created the endpoint and returned the
|
||||
// constructed address. Libnetwork will then reserve this address with the ipam driver.
|
||||
ones, _ := fCIDRv6.Mask.Size()
|
||||
deferIPv6Alloc = ones <= 80
|
||||
|
||||
if ipamV6Conf == nil {
|
||||
ipamV6Conf = &libnetwork.IpamConf{AuxAddresses: make(map[string]string)}
|
||||
}
|
||||
ipamV6Conf.PreferredPool = fCIDRv6.String()
|
||||
|
||||
// In case the --fixed-cidr-v6 is specified and the current docker0 bridge IPv6
|
||||
// address belongs to the same network, we need to inform libnetwork about it, so
|
||||
// that it can be reserved with IPAM and it will not be given away to somebody else
|
||||
for _, nw6 := range nw6List {
|
||||
if fCIDRv6.Contains(nw6.IP) {
|
||||
ipamV6Conf.Gateway = nw6.IP.String()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if config.bridgeConfig.DefaultGatewayIPv6 != nil {
|
||||
if ipamV6Conf == nil {
|
||||
ipamV6Conf = &libnetwork.IpamConf{AuxAddresses: make(map[string]string)}
|
||||
}
|
||||
ipamV6Conf.AuxAddresses["DefaultGatewayIPv6"] = config.bridgeConfig.DefaultGatewayIPv6.String()
|
||||
}
|
||||
|
||||
v4Conf := []*libnetwork.IpamConf{ipamV4Conf}
|
||||
v6Conf := []*libnetwork.IpamConf{}
|
||||
if ipamV6Conf != nil {
|
||||
v6Conf = append(v6Conf, ipamV6Conf)
|
||||
}
|
||||
// Initialize default network on "bridge" with the same name
|
||||
_, err = controller.NewNetwork("bridge", "bridge",
|
||||
libnetwork.NetworkOptionGeneric(options.Generic{
|
||||
netlabel.GenericData: netOption,
|
||||
netlabel.EnableIPv6: config.bridgeConfig.EnableIPv6,
|
||||
}),
|
||||
libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf, nil),
|
||||
libnetwork.NetworkOptionDeferIPv6Alloc(deferIPv6Alloc))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating default \"bridge\" network: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// setupInitLayer populates a directory with mountpoints suitable
|
||||
// for bind-mounting things into the container.
|
||||
//
|
||||
// This extra layer is used by all containers as the top-most ro layer. It protects
|
||||
// the container from unwanted side-effects on the rw layer.
|
||||
func setupInitLayer(initLayer string, rootUID, rootGID int) error {
|
||||
for pth, typ := range map[string]string{
|
||||
"/dev/pts": "dir",
|
||||
"/dev/shm": "dir",
|
||||
"/proc": "dir",
|
||||
"/sys": "dir",
|
||||
"/.dockerenv": "file",
|
||||
"/etc/resolv.conf": "file",
|
||||
"/etc/hosts": "file",
|
||||
"/etc/hostname": "file",
|
||||
"/dev/console": "file",
|
||||
"/etc/mtab": "/proc/mounts",
|
||||
} {
|
||||
parts := strings.Split(pth, "/")
|
||||
prev := "/"
|
||||
for _, p := range parts[1:] {
|
||||
prev = filepath.Join(prev, p)
|
||||
syscall.Unlink(filepath.Join(initLayer, prev))
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filepath.Join(initLayer, pth)); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if err := idtools.MkdirAllNewAs(filepath.Join(initLayer, filepath.Dir(pth)), 0755, rootUID, rootGID); err != nil {
|
||||
return err
|
||||
}
|
||||
switch typ {
|
||||
case "dir":
|
||||
if err := idtools.MkdirAllNewAs(filepath.Join(initLayer, pth), 0755, rootUID, rootGID); err != nil {
|
||||
return err
|
||||
}
|
||||
case "file":
|
||||
f, err := os.OpenFile(filepath.Join(initLayer, pth), os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.Chown(rootUID, rootGID)
|
||||
f.Close()
|
||||
default:
|
||||
if err := os.Symlink(typ, filepath.Join(initLayer, pth)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Layer is ready to use, if it wasn't before.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse the remapped root (user namespace) option, which can be one of:
|
||||
// username - valid username from /etc/passwd
|
||||
// username:groupname - valid username; valid groupname from /etc/group
|
||||
// uid - 32-bit unsigned int valid Linux UID value
|
||||
// uid:gid - uid value; 32-bit unsigned int Linux GID value
|
||||
//
|
||||
// If no groupname is specified, and a username is specified, an attempt
|
||||
// will be made to lookup a gid for that username as a groupname
|
||||
//
|
||||
// If names are used, they are verified to exist in passwd/group
|
||||
func parseRemappedRoot(usergrp string) (string, string, error) {
|
||||
|
||||
var (
|
||||
userID, groupID int
|
||||
username, groupname string
|
||||
)
|
||||
|
||||
idparts := strings.Split(usergrp, ":")
|
||||
if len(idparts) > 2 {
|
||||
return "", "", fmt.Errorf("Invalid user/group specification in --userns-remap: %q", usergrp)
|
||||
}
|
||||
|
||||
if uid, err := strconv.ParseInt(idparts[0], 10, 32); err == nil {
|
||||
// must be a uid; take it as valid
|
||||
userID = int(uid)
|
||||
luser, err := user.LookupUid(userID)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("Uid %d has no entry in /etc/passwd: %v", userID, err)
|
||||
}
|
||||
username = luser.Name
|
||||
if len(idparts) == 1 {
|
||||
// if the uid was numeric and no gid was specified, take the uid as the gid
|
||||
groupID = userID
|
||||
lgrp, err := user.LookupGid(groupID)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("Gid %d has no entry in /etc/group: %v", groupID, err)
|
||||
}
|
||||
groupname = lgrp.Name
|
||||
}
|
||||
} else {
|
||||
lookupName := idparts[0]
|
||||
// special case: if the user specified "default", they want Docker to create or
|
||||
// use (after creation) the "dockremap" user/group for root remapping
|
||||
if lookupName == defaultIDSpecifier {
|
||||
lookupName = defaultRemappedID
|
||||
}
|
||||
luser, err := user.LookupUser(lookupName)
|
||||
if err != nil && idparts[0] != defaultIDSpecifier {
|
||||
// error if the name requested isn't the special "dockremap" ID
|
||||
return "", "", fmt.Errorf("Error during uid lookup for %q: %v", lookupName, err)
|
||||
} else if err != nil {
|
||||
// special case-- if the username == "default", then we have been asked
|
||||
// to create a new entry pair in /etc/{passwd,group} for which the /etc/sub{uid,gid}
|
||||
// ranges will be used for the user and group mappings in user namespaced containers
|
||||
_, _, err := idtools.AddNamespaceRangesUser(defaultRemappedID)
|
||||
if err == nil {
|
||||
return defaultRemappedID, defaultRemappedID, nil
|
||||
}
|
||||
return "", "", fmt.Errorf("Error during %q user creation: %v", defaultRemappedID, err)
|
||||
}
|
||||
userID = luser.Uid
|
||||
username = luser.Name
|
||||
if len(idparts) == 1 {
|
||||
// we only have a string username, and no group specified; look up gid from username as group
|
||||
group, err := user.LookupGroup(lookupName)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("Error during gid lookup for %q: %v", lookupName, err)
|
||||
}
|
||||
groupID = group.Gid
|
||||
groupname = group.Name
|
||||
}
|
||||
}
|
||||
|
||||
if len(idparts) == 2 {
|
||||
// groupname or gid is separately specified and must be resolved
|
||||
// to a unsigned 32-bit gid
|
||||
if gid, err := strconv.ParseInt(idparts[1], 10, 32); err == nil {
|
||||
// must be a gid, take it as valid
|
||||
groupID = int(gid)
|
||||
lgrp, err := user.LookupGid(groupID)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("Gid %d has no entry in /etc/passwd: %v", groupID, err)
|
||||
}
|
||||
groupname = lgrp.Name
|
||||
} else {
|
||||
// not a number; attempt a lookup
|
||||
group, err := user.LookupGroup(idparts[1])
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("Error during gid lookup for %q: %v", idparts[1], err)
|
||||
}
|
||||
groupID = group.Gid
|
||||
groupname = idparts[1]
|
||||
}
|
||||
}
|
||||
return username, groupname, nil
|
||||
}
|
||||
|
||||
func setupRemappedRoot(config *Config) ([]idtools.IDMap, []idtools.IDMap, error) {
|
||||
if runtime.GOOS != "linux" && config.RemappedRoot != "" {
|
||||
return nil, nil, fmt.Errorf("User namespaces are only supported on Linux")
|
||||
}
|
||||
|
||||
// if the daemon was started with remapped root option, parse
|
||||
// the config option to the int uid,gid values
|
||||
var (
|
||||
uidMaps, gidMaps []idtools.IDMap
|
||||
)
|
||||
if config.RemappedRoot != "" {
|
||||
username, groupname, err := parseRemappedRoot(config.RemappedRoot)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if username == "root" {
|
||||
// Cannot setup user namespaces with a 1-to-1 mapping; "--root=0:0" is a no-op
|
||||
// effectively
|
||||
logrus.Warnf("User namespaces: root cannot be remapped with itself; user namespaces are OFF")
|
||||
return uidMaps, gidMaps, nil
|
||||
}
|
||||
logrus.Infof("User namespaces: ID ranges will be mapped to subuid/subgid ranges of: %s:%s", username, groupname)
|
||||
// update remapped root setting now that we have resolved them to actual names
|
||||
config.RemappedRoot = fmt.Sprintf("%s:%s", username, groupname)
|
||||
|
||||
uidMaps, gidMaps, err = idtools.CreateIDMappings(username, groupname)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Can't create ID mappings: %v", err)
|
||||
}
|
||||
}
|
||||
return uidMaps, gidMaps, nil
|
||||
}
|
||||
|
||||
func setupDaemonRoot(config *Config, rootDir string, rootUID, rootGID int) error {
|
||||
config.Root = rootDir
|
||||
// the docker root metadata directory needs to have execute permissions for all users (o+x)
|
||||
// so that syscalls executing as non-root, operating on subdirectories of the graph root
|
||||
// (e.g. mounted layers of a container) can traverse this path.
|
||||
// The user namespace support will create subdirectories for the remapped root host uid:gid
|
||||
// pair owned by that same uid:gid pair for proper write access to those needed metadata and
|
||||
// layer content subtrees.
|
||||
if _, err := os.Stat(rootDir); err == nil {
|
||||
// root current exists; verify the access bits are correct by setting them
|
||||
if err = os.Chmod(rootDir, 0701); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if os.IsNotExist(err) {
|
||||
// no root exists yet, create it 0701 with root:root ownership
|
||||
if err := os.MkdirAll(rootDir, 0701); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// if user namespaces are enabled we will create a subtree underneath the specified root
|
||||
// with any/all specified remapped root uid/gid options on the daemon creating
|
||||
// a new subdirectory with ownership set to the remapped uid/gid (so as to allow
|
||||
// `chdir()` to work for containers namespaced to that uid/gid)
|
||||
if config.RemappedRoot != "" {
|
||||
config.Root = filepath.Join(rootDir, fmt.Sprintf("%d.%d", rootUID, rootGID))
|
||||
logrus.Debugf("Creating user namespaced daemon root: %s", config.Root)
|
||||
// Create the root directory if it doesn't exists
|
||||
if err := idtools.MkdirAllAs(config.Root, 0700, rootUID, rootGID); err != nil {
|
||||
return fmt.Errorf("Cannot create daemon root: %s: %v", config.Root, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// registerLinks writes the links to a file.
|
||||
func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *containertypes.HostConfig) error {
|
||||
if hostConfig == nil || hostConfig.NetworkMode.IsUserDefined() {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, l := range hostConfig.Links {
|
||||
name, alias, err := runconfigopts.ParseLink(l)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
child, err := daemon.GetContainer(name)
|
||||
if err != nil {
|
||||
//An error from daemon.GetContainer() means this name could not be found
|
||||
return fmt.Errorf("Could not get container for %s", name)
|
||||
}
|
||||
for child.HostConfig.NetworkMode.IsContainer() {
|
||||
parts := strings.SplitN(string(child.HostConfig.NetworkMode), ":", 2)
|
||||
child, err = daemon.GetContainer(parts[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not get container for %s", parts[1])
|
||||
}
|
||||
}
|
||||
if child.HostConfig.NetworkMode.IsHost() {
|
||||
return runconfig.ErrConflictHostNetworkAndLinks
|
||||
}
|
||||
if err := daemon.registerLink(container, child, alias); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// After we load all the links into the daemon
|
||||
// set them to nil on the hostconfig
|
||||
return container.WriteHostConfig()
|
||||
}
|
||||
|
||||
// conditionalMountOnStart is a platform specific helper function during the
|
||||
// container start to call mount.
|
||||
func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error {
|
||||
return daemon.Mount(container)
|
||||
}
|
||||
|
||||
// conditionalUnmountOnCleanup is a platform specific helper function called
|
||||
// during the cleanup of a container to unmount.
|
||||
func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) {
|
||||
daemon.Unmount(container)
|
||||
}
|
||||
|
||||
func restoreCustomImage(is image.Store, ls layer.Store, rs reference.Store) error {
|
||||
// Unix has no custom images to register
|
||||
return nil
|
||||
}
|
||||
157
vendor/github.com/hyperhq/hypercli/daemon/daemon_unix_test.go
generated
vendored
Normal file
157
vendor/github.com/hyperhq/hypercli/daemon/daemon_unix_test.go
generated
vendored
Normal file
@@ -0,0 +1,157 @@
|
||||
// +build !windows
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/hyperhq/hypercli/container"
|
||||
containertypes "github.com/docker/engine-api/types/container"
|
||||
)
|
||||
|
||||
// Unix test as uses settings which are not available on Windows
|
||||
func TestAdjustCPUShares(t *testing.T) {
|
||||
tmp, err := ioutil.TempDir("", "docker-daemon-unix-test-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
daemon := &Daemon{
|
||||
repository: tmp,
|
||||
root: tmp,
|
||||
}
|
||||
|
||||
hostConfig := &containertypes.HostConfig{
|
||||
Resources: containertypes.Resources{CPUShares: linuxMinCPUShares - 1},
|
||||
}
|
||||
daemon.adaptContainerSettings(hostConfig, true)
|
||||
if hostConfig.CPUShares != linuxMinCPUShares {
|
||||
t.Errorf("Expected CPUShares to be %d", linuxMinCPUShares)
|
||||
}
|
||||
|
||||
hostConfig.CPUShares = linuxMaxCPUShares + 1
|
||||
daemon.adaptContainerSettings(hostConfig, true)
|
||||
if hostConfig.CPUShares != linuxMaxCPUShares {
|
||||
t.Errorf("Expected CPUShares to be %d", linuxMaxCPUShares)
|
||||
}
|
||||
|
||||
hostConfig.CPUShares = 0
|
||||
daemon.adaptContainerSettings(hostConfig, true)
|
||||
if hostConfig.CPUShares != 0 {
|
||||
t.Error("Expected CPUShares to be unchanged")
|
||||
}
|
||||
|
||||
hostConfig.CPUShares = 1024
|
||||
daemon.adaptContainerSettings(hostConfig, true)
|
||||
if hostConfig.CPUShares != 1024 {
|
||||
t.Error("Expected CPUShares to be unchanged")
|
||||
}
|
||||
}
|
||||
|
||||
// Unix test as uses settings which are not available on Windows
|
||||
func TestAdjustCPUSharesNoAdjustment(t *testing.T) {
|
||||
tmp, err := ioutil.TempDir("", "docker-daemon-unix-test-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
daemon := &Daemon{
|
||||
repository: tmp,
|
||||
root: tmp,
|
||||
}
|
||||
|
||||
hostConfig := &containertypes.HostConfig{
|
||||
Resources: containertypes.Resources{CPUShares: linuxMinCPUShares - 1},
|
||||
}
|
||||
daemon.adaptContainerSettings(hostConfig, false)
|
||||
if hostConfig.CPUShares != linuxMinCPUShares-1 {
|
||||
t.Errorf("Expected CPUShares to be %d", linuxMinCPUShares-1)
|
||||
}
|
||||
|
||||
hostConfig.CPUShares = linuxMaxCPUShares + 1
|
||||
daemon.adaptContainerSettings(hostConfig, false)
|
||||
if hostConfig.CPUShares != linuxMaxCPUShares+1 {
|
||||
t.Errorf("Expected CPUShares to be %d", linuxMaxCPUShares+1)
|
||||
}
|
||||
|
||||
hostConfig.CPUShares = 0
|
||||
daemon.adaptContainerSettings(hostConfig, false)
|
||||
if hostConfig.CPUShares != 0 {
|
||||
t.Error("Expected CPUShares to be unchanged")
|
||||
}
|
||||
|
||||
hostConfig.CPUShares = 1024
|
||||
daemon.adaptContainerSettings(hostConfig, false)
|
||||
if hostConfig.CPUShares != 1024 {
|
||||
t.Error("Expected CPUShares to be unchanged")
|
||||
}
|
||||
}
|
||||
|
||||
// Unix test as uses settings which are not available on Windows
|
||||
func TestParseSecurityOpt(t *testing.T) {
|
||||
container := &container.Container{}
|
||||
config := &containertypes.HostConfig{}
|
||||
|
||||
// test apparmor
|
||||
config.SecurityOpt = []string{"apparmor:test_profile"}
|
||||
if err := parseSecurityOpt(container, config); err != nil {
|
||||
t.Fatalf("Unexpected parseSecurityOpt error: %v", err)
|
||||
}
|
||||
if container.AppArmorProfile != "test_profile" {
|
||||
t.Fatalf("Unexpected AppArmorProfile, expected: \"test_profile\", got %q", container.AppArmorProfile)
|
||||
}
|
||||
|
||||
// test seccomp
|
||||
sp := "/path/to/seccomp_test.json"
|
||||
config.SecurityOpt = []string{"seccomp:" + sp}
|
||||
if err := parseSecurityOpt(container, config); err != nil {
|
||||
t.Fatalf("Unexpected parseSecurityOpt error: %v", err)
|
||||
}
|
||||
if container.SeccompProfile != sp {
|
||||
t.Fatalf("Unexpected AppArmorProfile, expected: %q, got %q", sp, container.SeccompProfile)
|
||||
}
|
||||
|
||||
// test valid label
|
||||
config.SecurityOpt = []string{"label:user:USER"}
|
||||
if err := parseSecurityOpt(container, config); err != nil {
|
||||
t.Fatalf("Unexpected parseSecurityOpt error: %v", err)
|
||||
}
|
||||
|
||||
// test invalid label
|
||||
config.SecurityOpt = []string{"label"}
|
||||
if err := parseSecurityOpt(container, config); err == nil {
|
||||
t.Fatal("Expected parseSecurityOpt error, got nil")
|
||||
}
|
||||
|
||||
// test invalid opt
|
||||
config.SecurityOpt = []string{"test"}
|
||||
if err := parseSecurityOpt(container, config); err == nil {
|
||||
t.Fatal("Expected parseSecurityOpt error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetworkOptions(t *testing.T) {
|
||||
daemon := &Daemon{}
|
||||
dconfigCorrect := &Config{
|
||||
CommonConfig: CommonConfig{
|
||||
ClusterStore: "consul://localhost:8500",
|
||||
ClusterAdvertise: "192.168.0.1:8000",
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := daemon.networkOptions(dconfigCorrect); err != nil {
|
||||
t.Fatalf("Expect networkOptions sucess, got error: %v", err)
|
||||
}
|
||||
|
||||
dconfigWrong := &Config{
|
||||
CommonConfig: CommonConfig{
|
||||
ClusterStore: "consul://localhost:8500://test://bbb",
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := daemon.networkOptions(dconfigWrong); err == nil {
|
||||
t.Fatalf("Expected networkOptions error, got nil")
|
||||
}
|
||||
}
|
||||
5
vendor/github.com/hyperhq/hypercli/daemon/daemon_unsupported.go
generated
vendored
Normal file
5
vendor/github.com/hyperhq/hypercli/daemon/daemon_unsupported.go
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
// +build !linux,!freebsd,!windows
|
||||
|
||||
package daemon
|
||||
|
||||
const platformSupported = false
|
||||
253
vendor/github.com/hyperhq/hypercli/daemon/daemon_windows.go
generated
vendored
Normal file
253
vendor/github.com/hyperhq/hypercli/daemon/daemon_windows.go
generated
vendored
Normal file
@@ -0,0 +1,253 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/hyperhq/hypercli/container"
|
||||
"github.com/hyperhq/hypercli/daemon/graphdriver"
|
||||
"github.com/hyperhq/hypercli/dockerversion"
|
||||
"github.com/hyperhq/hypercli/image"
|
||||
"github.com/hyperhq/hypercli/layer"
|
||||
"github.com/hyperhq/hypercli/reference"
|
||||
containertypes "github.com/docker/engine-api/types/container"
|
||||
// register the windows graph driver
|
||||
"github.com/hyperhq/hypercli/daemon/graphdriver/windows"
|
||||
"github.com/hyperhq/hypercli/pkg/idtools"
|
||||
"github.com/hyperhq/hypercli/pkg/system"
|
||||
"github.com/docker/libnetwork"
|
||||
blkiodev "github.com/opencontainers/runc/libcontainer/configs"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultVirtualSwitch = "Virtual Switch"
|
||||
platformSupported = true
|
||||
windowsMinCPUShares = 1
|
||||
windowsMaxCPUShares = 10000
|
||||
)
|
||||
|
||||
func getBlkioWeightDevices(config *containertypes.HostConfig) ([]*blkiodev.WeightDevice, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func parseSecurityOpt(container *container.Container, config *containertypes.HostConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getBlkioReadIOpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func getBlkioWriteIOpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func getBlkioReadBpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func getBlkioWriteBpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func setupInitLayer(initLayer string, rootUID, rootGID int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkKernel() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// adaptContainerSettings is called during container creation to modify any
|
||||
// settings necessary in the HostConfig structure.
|
||||
func (daemon *Daemon) adaptContainerSettings(hostConfig *containertypes.HostConfig, adjustCPUShares bool) error {
|
||||
if hostConfig == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if hostConfig.CPUShares < 0 {
|
||||
logrus.Warnf("Changing requested CPUShares of %d to minimum allowed of %d", hostConfig.CPUShares, windowsMinCPUShares)
|
||||
hostConfig.CPUShares = windowsMinCPUShares
|
||||
} else if hostConfig.CPUShares > windowsMaxCPUShares {
|
||||
logrus.Warnf("Changing requested CPUShares of %d to maximum allowed of %d", hostConfig.CPUShares, windowsMaxCPUShares)
|
||||
hostConfig.CPUShares = windowsMaxCPUShares
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// verifyPlatformContainerSettings performs platform-specific validation of the
|
||||
// hostconfig and config structures.
|
||||
func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes.HostConfig, config *containertypes.Config) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// verifyDaemonSettings performs validation of daemon config struct
|
||||
func verifyDaemonSettings(config *Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkSystem validates platform-specific requirements
|
||||
func checkSystem() error {
|
||||
// Validate the OS version. Note that docker.exe must be manifested for this
|
||||
// call to return the correct version.
|
||||
osv, err := system.GetOSVersion()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if osv.MajorVersion < 10 {
|
||||
return fmt.Errorf("This version of Windows does not support the docker daemon")
|
||||
}
|
||||
if osv.Build < 10586 {
|
||||
return fmt.Errorf("The Windows daemon requires Windows Server 2016 Technical Preview 4, build 10586 or later")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// configureKernelSecuritySupport configures and validate security support for the kernel
|
||||
func configureKernelSecuritySupport(config *Config, driverName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// configureMaxThreads sets the Go runtime max threads threshold
|
||||
func configureMaxThreads(config *Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func isBridgeNetworkDisabled(config *Config) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (daemon *Daemon) initNetworkController(config *Config) (libnetwork.NetworkController, error) {
|
||||
// Set the name of the virtual switch if not specified by -b on daemon start
|
||||
if config.bridgeConfig.VirtualSwitchName == "" {
|
||||
config.bridgeConfig.VirtualSwitchName = defaultVirtualSwitch
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// registerLinks sets up links between containers and writes the
|
||||
// configuration out for persistence. As of Windows TP4, links are not supported.
|
||||
func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *containertypes.HostConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (daemon *Daemon) cleanupMounts() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupRemappedRoot(config *Config) ([]idtools.IDMap, []idtools.IDMap, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
func setupDaemonRoot(config *Config, rootDir string, rootUID, rootGID int) error {
|
||||
config.Root = rootDir
|
||||
// Create the root directory if it doesn't exists
|
||||
if err := system.MkdirAll(config.Root, 0700); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// conditionalMountOnStart is a platform specific helper function during the
|
||||
// container start to call mount.
|
||||
func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error {
|
||||
// We do not mount if a Hyper-V container
|
||||
if !container.HostConfig.Isolation.IsHyperV() {
|
||||
if err := daemon.Mount(container); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// conditionalUnmountOnCleanup is a platform specific helper function called
|
||||
// during the cleanup of a container to unmount.
|
||||
func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) {
|
||||
// We do not unmount if a Hyper-V container
|
||||
if !container.HostConfig.Isolation.IsHyperV() {
|
||||
daemon.Unmount(container)
|
||||
}
|
||||
}
|
||||
|
||||
func restoreCustomImage(is image.Store, ls layer.Store, rs reference.Store) error {
|
||||
type graphDriverStore interface {
|
||||
GraphDriver() graphdriver.Driver
|
||||
}
|
||||
|
||||
gds, ok := ls.(graphDriverStore)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
driver := gds.GraphDriver()
|
||||
wd, ok := driver.(*windows.Driver)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
imageInfos, err := wd.GetCustomImageInfos()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Convert imageData to valid image configuration
|
||||
for i := range imageInfos {
|
||||
name := strings.ToLower(imageInfos[i].Name)
|
||||
|
||||
type registrar interface {
|
||||
RegisterDiffID(graphID string, size int64) (layer.Layer, error)
|
||||
}
|
||||
r, ok := ls.(registrar)
|
||||
if !ok {
|
||||
return errors.New("Layerstore doesn't support RegisterDiffID")
|
||||
}
|
||||
if _, err := r.RegisterDiffID(imageInfos[i].ID, imageInfos[i].Size); err != nil {
|
||||
return err
|
||||
}
|
||||
// layer is intentionally not released
|
||||
|
||||
rootFS := image.NewRootFS()
|
||||
rootFS.BaseLayer = filepath.Base(imageInfos[i].Path)
|
||||
|
||||
// Create history for base layer
|
||||
config, err := json.Marshal(&image.Image{
|
||||
V1Image: image.V1Image{
|
||||
DockerVersion: dockerversion.Version,
|
||||
Architecture: runtime.GOARCH,
|
||||
OS: runtime.GOOS,
|
||||
Created: imageInfos[i].CreatedTime,
|
||||
},
|
||||
RootFS: rootFS,
|
||||
History: []image.History{},
|
||||
})
|
||||
|
||||
named, err := reference.ParseNamed(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ref, err := reference.WithTag(named, imageInfos[i].Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, err := is.Create(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := rs.AddTag(ref, id, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Debugf("Registered base layer %s as %s", ref, id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
21
vendor/github.com/hyperhq/hypercli/daemon/debugtrap_unix.go
generated
vendored
Normal file
21
vendor/github.com/hyperhq/hypercli/daemon/debugtrap_unix.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
// +build !windows
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
psignal "github.com/hyperhq/hypercli/pkg/signal"
|
||||
)
|
||||
|
||||
func setupDumpStackTrap() {
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, syscall.SIGUSR1)
|
||||
go func() {
|
||||
for range c {
|
||||
psignal.DumpStacks()
|
||||
}
|
||||
}()
|
||||
}
|
||||
7
vendor/github.com/hyperhq/hypercli/daemon/debugtrap_unsupported.go
generated
vendored
Normal file
7
vendor/github.com/hyperhq/hypercli/daemon/debugtrap_unsupported.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
// +build !linux,!darwin,!freebsd,!windows
|
||||
|
||||
package daemon
|
||||
|
||||
func setupDumpStackTrap() {
|
||||
return
|
||||
}
|
||||
30
vendor/github.com/hyperhq/hypercli/daemon/debugtrap_windows.go
generated
vendored
Normal file
30
vendor/github.com/hyperhq/hypercli/daemon/debugtrap_windows.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
psignal "github.com/hyperhq/hypercli/pkg/signal"
|
||||
"github.com/hyperhq/hypercli/pkg/system"
|
||||
)
|
||||
|
||||
func setupDumpStackTrap() {
|
||||
// Windows does not support signals like *nix systems. So instead of
|
||||
// trapping on SIGUSR1 to dump stacks, we wait on a Win32 event to be
|
||||
// signaled.
|
||||
go func() {
|
||||
sa := syscall.SecurityAttributes{
|
||||
Length: 0,
|
||||
}
|
||||
ev := "Global\\docker-daemon-" + fmt.Sprint(os.Getpid())
|
||||
if h, _ := system.CreateEvent(&sa, false, false, ev); h != 0 {
|
||||
logrus.Debugf("Stackdump - waiting signal at %s", ev)
|
||||
for {
|
||||
syscall.WaitForSingleObject(h, syscall.INFINITE)
|
||||
psignal.DumpStacks()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
158
vendor/github.com/hyperhq/hypercli/daemon/delete.go
generated
vendored
Normal file
158
vendor/github.com/hyperhq/hypercli/daemon/delete.go
generated
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/hyperhq/hypercli/container"
|
||||
derr "github.com/hyperhq/hypercli/errors"
|
||||
"github.com/hyperhq/hypercli/layer"
|
||||
volumestore "github.com/hyperhq/hypercli/volume/store"
|
||||
"github.com/docker/engine-api/types"
|
||||
)
|
||||
|
||||
// ContainerRm removes the container id from the filesystem. An error
|
||||
// is returned if the container is not found, or if the remove
|
||||
// fails. If the remove succeeds, the container name is released, and
|
||||
// network links are removed.
|
||||
func (daemon *Daemon) ContainerRm(name string, config *types.ContainerRmConfig) error {
|
||||
container, err := daemon.GetContainer(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Container state RemovalInProgress should be used to avoid races.
|
||||
if err = container.SetRemovalInProgress(); err != nil {
|
||||
if err == derr.ErrorCodeAlreadyRemoving {
|
||||
// do not fail when the removal is in progress started by other request.
|
||||
return nil
|
||||
}
|
||||
return derr.ErrorCodeRmState.WithArgs(container.ID, err)
|
||||
}
|
||||
defer container.ResetRemovalInProgress()
|
||||
|
||||
// check if container wasn't deregistered by previous rm since Get
|
||||
if c := daemon.containers.Get(container.ID); c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if config.RemoveLink {
|
||||
return daemon.rmLink(container, name)
|
||||
}
|
||||
|
||||
err = daemon.cleanupContainer(container, config.ForceRemove)
|
||||
if err == nil || config.ForceRemove {
|
||||
if e := daemon.removeMountPoints(container, config.RemoveVolume); e != nil {
|
||||
logrus.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (daemon *Daemon) rmLink(container *container.Container, name string) error {
|
||||
if name[0] != '/' {
|
||||
name = "/" + name
|
||||
}
|
||||
parent, n := path.Split(name)
|
||||
if parent == "/" {
|
||||
return fmt.Errorf("Conflict, cannot remove the default name of the container")
|
||||
}
|
||||
|
||||
parent = strings.TrimSuffix(parent, "/")
|
||||
pe, err := daemon.nameIndex.Get(parent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot get parent %s for name %s", parent, name)
|
||||
}
|
||||
|
||||
daemon.releaseName(name)
|
||||
parentContainer, _ := daemon.GetContainer(pe)
|
||||
if parentContainer != nil {
|
||||
daemon.linkIndex.unlink(name, container, parentContainer)
|
||||
if err := daemon.updateNetwork(parentContainer); err != nil {
|
||||
logrus.Debugf("Could not update network to remove link %s: %v", n, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// cleanupContainer unregisters a container from the daemon, stops stats
|
||||
// collection and cleanly removes contents and metadata from the filesystem.
|
||||
func (daemon *Daemon) cleanupContainer(container *container.Container, forceRemove bool) (err error) {
|
||||
if container.IsRunning() {
|
||||
if !forceRemove {
|
||||
return derr.ErrorCodeRmRunning.WithArgs(container.ID)
|
||||
}
|
||||
if err := daemon.Kill(container); err != nil {
|
||||
return derr.ErrorCodeRmFailed.WithArgs(container.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// stop collection of stats for the container regardless
|
||||
// if stats are currently getting collected.
|
||||
daemon.statsCollector.stopCollection(container)
|
||||
|
||||
if err = daemon.containerStop(container, 3); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Mark container dead. We don't want anybody to be restarting it.
|
||||
container.SetDead()
|
||||
|
||||
// Save container state to disk. So that if error happens before
|
||||
// container meta file got removed from disk, then a restart of
|
||||
// docker should not make a dead container alive.
|
||||
if err := container.ToDiskLocking(); err != nil {
|
||||
logrus.Errorf("Error saving dying container to disk: %v", err)
|
||||
}
|
||||
|
||||
// If force removal is required, delete container from various
|
||||
// indexes even if removal failed.
|
||||
defer func() {
|
||||
if err == nil || forceRemove {
|
||||
daemon.nameIndex.Delete(container.ID)
|
||||
daemon.linkIndex.delete(container)
|
||||
selinuxFreeLxcContexts(container.ProcessLabel)
|
||||
daemon.idIndex.Delete(container.ID)
|
||||
daemon.containers.Delete(container.ID)
|
||||
daemon.LogContainerEvent(container, "destroy")
|
||||
}
|
||||
}()
|
||||
|
||||
if err = os.RemoveAll(container.Root); err != nil {
|
||||
return derr.ErrorCodeRmFS.WithArgs(container.ID, err)
|
||||
}
|
||||
|
||||
metadata, err := daemon.layerStore.ReleaseRWLayer(container.RWLayer)
|
||||
layer.LogReleaseMetadata(metadata)
|
||||
if err != nil && err != layer.ErrMountDoesNotExist {
|
||||
return derr.ErrorCodeRmDriverFS.WithArgs(daemon.GraphDriverName(), container.ID, err)
|
||||
}
|
||||
|
||||
if err = daemon.execDriver.Clean(container.ID); err != nil {
|
||||
return derr.ErrorCodeRmExecDriver.WithArgs(container.ID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VolumeRm removes the volume with the given name.
|
||||
// If the volume is referenced by a container it is not removed
|
||||
// This is called directly from the remote API
|
||||
func (daemon *Daemon) VolumeRm(name string) error {
|
||||
v, err := daemon.volumes.Get(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := daemon.volumes.Remove(v); err != nil {
|
||||
if volumestore.IsInUse(err) {
|
||||
return derr.ErrorCodeRmVolumeInUse.WithArgs(err)
|
||||
}
|
||||
return derr.ErrorCodeRmVolume.WithArgs(name, err)
|
||||
}
|
||||
daemon.LogVolumeEvent(v.Name(), "destroy", map[string]string{"driver": v.DriverName()})
|
||||
return nil
|
||||
}
|
||||
44
vendor/github.com/hyperhq/hypercli/daemon/delete_test.go
generated
vendored
Normal file
44
vendor/github.com/hyperhq/hypercli/daemon/delete_test.go
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/hyperhq/hypercli/container"
|
||||
"github.com/docker/engine-api/types"
|
||||
containertypes "github.com/docker/engine-api/types/container"
|
||||
)
|
||||
|
||||
func TestContainerDoubleDelete(t *testing.T) {
|
||||
tmp, err := ioutil.TempDir("", "docker-daemon-unix-test-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
daemon := &Daemon{
|
||||
repository: tmp,
|
||||
root: tmp,
|
||||
}
|
||||
daemon.containers = container.NewMemoryStore()
|
||||
|
||||
container := &container.Container{
|
||||
CommonContainer: container.CommonContainer{
|
||||
ID: "test",
|
||||
State: container.NewState(),
|
||||
Config: &containertypes.Config{},
|
||||
},
|
||||
}
|
||||
daemon.containers.Add(container.ID, container)
|
||||
|
||||
// Mark the container as having a delete in progress
|
||||
if err := container.SetRemovalInProgress(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Try to remove the container when it's start is removalInProgress.
|
||||
// It should ignore the container and not return an error.
|
||||
if err := daemon.ContainerRm(container.ID, &types.ContainerRmConfig{ForceRemove: true}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
170
vendor/github.com/hyperhq/hypercli/daemon/discovery.go
generated
vendored
Normal file
170
vendor/github.com/hyperhq/hypercli/daemon/discovery.go
generated
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/hyperhq/hypercli/pkg/discovery"
|
||||
|
||||
// Register the libkv backends for discovery.
|
||||
_ "github.com/hyperhq/hypercli/pkg/discovery/kv"
|
||||
)
|
||||
|
||||
const (
|
||||
// defaultDiscoveryHeartbeat is the default value for discovery heartbeat interval.
|
||||
defaultDiscoveryHeartbeat = 20 * time.Second
|
||||
// defaultDiscoveryTTLFactor is the default TTL factor for discovery
|
||||
defaultDiscoveryTTLFactor = 3
|
||||
)
|
||||
|
||||
var errDiscoveryDisabled = errors.New("discovery is disabled")
|
||||
|
||||
type discoveryReloader interface {
|
||||
discovery.Watcher
|
||||
Stop()
|
||||
Reload(backend, address string, clusterOpts map[string]string) error
|
||||
}
|
||||
|
||||
type daemonDiscoveryReloader struct {
|
||||
backend discovery.Backend
|
||||
ticker *time.Ticker
|
||||
term chan bool
|
||||
}
|
||||
|
||||
func (d *daemonDiscoveryReloader) Watch(stopCh <-chan struct{}) (<-chan discovery.Entries, <-chan error) {
|
||||
return d.backend.Watch(stopCh)
|
||||
}
|
||||
|
||||
func discoveryOpts(clusterOpts map[string]string) (time.Duration, time.Duration, error) {
|
||||
var (
|
||||
heartbeat = defaultDiscoveryHeartbeat
|
||||
ttl = defaultDiscoveryTTLFactor * defaultDiscoveryHeartbeat
|
||||
)
|
||||
|
||||
if hb, ok := clusterOpts["discovery.heartbeat"]; ok {
|
||||
h, err := strconv.Atoi(hb)
|
||||
if err != nil {
|
||||
return time.Duration(0), time.Duration(0), err
|
||||
}
|
||||
heartbeat = time.Duration(h) * time.Second
|
||||
ttl = defaultDiscoveryTTLFactor * heartbeat
|
||||
}
|
||||
|
||||
if tstr, ok := clusterOpts["discovery.ttl"]; ok {
|
||||
t, err := strconv.Atoi(tstr)
|
||||
if err != nil {
|
||||
return time.Duration(0), time.Duration(0), err
|
||||
}
|
||||
ttl = time.Duration(t) * time.Second
|
||||
|
||||
if _, ok := clusterOpts["discovery.heartbeat"]; !ok {
|
||||
h := int(t / defaultDiscoveryTTLFactor)
|
||||
heartbeat = time.Duration(h) * time.Second
|
||||
}
|
||||
|
||||
if ttl <= heartbeat {
|
||||
return time.Duration(0), time.Duration(0),
|
||||
fmt.Errorf("discovery.ttl timer must be greater than discovery.heartbeat")
|
||||
}
|
||||
}
|
||||
|
||||
return heartbeat, ttl, nil
|
||||
}
|
||||
|
||||
// initDiscovery initialized the nodes discovery subsystem by connecting to the specified backend
|
||||
// and start a registration loop to advertise the current node under the specified address.
|
||||
func initDiscovery(backendAddress, advertiseAddress string, clusterOpts map[string]string) (discoveryReloader, error) {
|
||||
heartbeat, backend, err := parseDiscoveryOptions(backendAddress, clusterOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reloader := &daemonDiscoveryReloader{
|
||||
backend: backend,
|
||||
ticker: time.NewTicker(heartbeat),
|
||||
term: make(chan bool),
|
||||
}
|
||||
// We call Register() on the discovery backend in a loop for the whole lifetime of the daemon,
|
||||
// but we never actually Watch() for nodes appearing and disappearing for the moment.
|
||||
reloader.advertise(advertiseAddress)
|
||||
return reloader, nil
|
||||
}
|
||||
|
||||
func (d *daemonDiscoveryReloader) advertise(address string) {
|
||||
d.registerAddr(address)
|
||||
go d.advertiseHeartbeat(address)
|
||||
}
|
||||
|
||||
func (d *daemonDiscoveryReloader) registerAddr(addr string) {
|
||||
if err := d.backend.Register(addr); err != nil {
|
||||
log.Warnf("Registering as %q in discovery failed: %v", addr, err)
|
||||
}
|
||||
}
|
||||
|
||||
// advertiseHeartbeat registers the current node against the discovery backend using the specified
|
||||
// address. The function never returns, as registration against the backend comes with a TTL and
|
||||
// requires regular heartbeats.
|
||||
func (d *daemonDiscoveryReloader) advertiseHeartbeat(address string) {
|
||||
for {
|
||||
select {
|
||||
case <-d.ticker.C:
|
||||
d.registerAddr(address)
|
||||
case <-d.term:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reload makes the watcher to stop advertising and reconfigures it to advertise in a new address.
|
||||
func (d *daemonDiscoveryReloader) Reload(backendAddress, advertiseAddress string, clusterOpts map[string]string) error {
|
||||
d.Stop()
|
||||
|
||||
heartbeat, backend, err := parseDiscoveryOptions(backendAddress, clusterOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.backend = backend
|
||||
d.ticker = time.NewTicker(heartbeat)
|
||||
|
||||
d.advertise(advertiseAddress)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop terminates the discovery advertising.
|
||||
func (d *daemonDiscoveryReloader) Stop() {
|
||||
d.ticker.Stop()
|
||||
d.term <- true
|
||||
}
|
||||
|
||||
func parseDiscoveryOptions(backendAddress string, clusterOpts map[string]string) (time.Duration, discovery.Backend, error) {
|
||||
heartbeat, ttl, err := discoveryOpts(clusterOpts)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
backend, err := discovery.New(backendAddress, heartbeat, ttl, clusterOpts)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
return heartbeat, backend, nil
|
||||
}
|
||||
|
||||
// modifiedDiscoverySettings returns whether the discovery configuration has been modified or not.
|
||||
func modifiedDiscoverySettings(config *Config, backendType, advertise string, clusterOpts map[string]string) bool {
|
||||
if config.ClusterStore != backendType || config.ClusterAdvertise != advertise {
|
||||
return true
|
||||
}
|
||||
|
||||
if (config.ClusterOpts == nil && clusterOpts == nil) ||
|
||||
(config.ClusterOpts == nil && len(clusterOpts) == 0) ||
|
||||
(len(config.ClusterOpts) == 0 && clusterOpts == nil) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !reflect.DeepEqual(config.ClusterOpts, clusterOpts)
|
||||
}
|
||||
152
vendor/github.com/hyperhq/hypercli/daemon/discovery_test.go
generated
vendored
Normal file
152
vendor/github.com/hyperhq/hypercli/daemon/discovery_test.go
generated
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestDiscoveryOpts(t *testing.T) {
|
||||
clusterOpts := map[string]string{"discovery.heartbeat": "10", "discovery.ttl": "5"}
|
||||
heartbeat, ttl, err := discoveryOpts(clusterOpts)
|
||||
if err == nil {
|
||||
t.Fatalf("discovery.ttl < discovery.heartbeat must fail")
|
||||
}
|
||||
|
||||
clusterOpts = map[string]string{"discovery.heartbeat": "10", "discovery.ttl": "10"}
|
||||
heartbeat, ttl, err = discoveryOpts(clusterOpts)
|
||||
if err == nil {
|
||||
t.Fatalf("discovery.ttl == discovery.heartbeat must fail")
|
||||
}
|
||||
|
||||
clusterOpts = map[string]string{"discovery.heartbeat": "invalid"}
|
||||
heartbeat, ttl, err = discoveryOpts(clusterOpts)
|
||||
if err == nil {
|
||||
t.Fatalf("invalid discovery.heartbeat must fail")
|
||||
}
|
||||
|
||||
clusterOpts = map[string]string{"discovery.ttl": "invalid"}
|
||||
heartbeat, ttl, err = discoveryOpts(clusterOpts)
|
||||
if err == nil {
|
||||
t.Fatalf("invalid discovery.ttl must fail")
|
||||
}
|
||||
|
||||
clusterOpts = map[string]string{"discovery.heartbeat": "10", "discovery.ttl": "20"}
|
||||
heartbeat, ttl, err = discoveryOpts(clusterOpts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if heartbeat != 10*time.Second {
|
||||
t.Fatalf("Heatbeat - Expected : %v, Actual : %v", 10*time.Second, heartbeat)
|
||||
}
|
||||
|
||||
if ttl != 20*time.Second {
|
||||
t.Fatalf("TTL - Expected : %v, Actual : %v", 20*time.Second, ttl)
|
||||
}
|
||||
|
||||
clusterOpts = map[string]string{"discovery.heartbeat": "10"}
|
||||
heartbeat, ttl, err = discoveryOpts(clusterOpts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if heartbeat != 10*time.Second {
|
||||
t.Fatalf("Heatbeat - Expected : %v, Actual : %v", 10*time.Second, heartbeat)
|
||||
}
|
||||
|
||||
expected := 10 * defaultDiscoveryTTLFactor * time.Second
|
||||
if ttl != expected {
|
||||
t.Fatalf("TTL - Expected : %v, Actual : %v", expected, ttl)
|
||||
}
|
||||
|
||||
clusterOpts = map[string]string{"discovery.ttl": "30"}
|
||||
heartbeat, ttl, err = discoveryOpts(clusterOpts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if ttl != 30*time.Second {
|
||||
t.Fatalf("TTL - Expected : %v, Actual : %v", 30*time.Second, ttl)
|
||||
}
|
||||
|
||||
expected = 30 * time.Second / defaultDiscoveryTTLFactor
|
||||
if heartbeat != expected {
|
||||
t.Fatalf("Heatbeat - Expected : %v, Actual : %v", expected, heartbeat)
|
||||
}
|
||||
|
||||
clusterOpts = map[string]string{}
|
||||
heartbeat, ttl, err = discoveryOpts(clusterOpts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if heartbeat != defaultDiscoveryHeartbeat {
|
||||
t.Fatalf("Heatbeat - Expected : %v, Actual : %v", defaultDiscoveryHeartbeat, heartbeat)
|
||||
}
|
||||
|
||||
expected = defaultDiscoveryHeartbeat * defaultDiscoveryTTLFactor
|
||||
if ttl != expected {
|
||||
t.Fatalf("TTL - Expected : %v, Actual : %v", expected, ttl)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModifiedDiscoverySettings(t *testing.T) {
|
||||
cases := []struct {
|
||||
current *Config
|
||||
modified *Config
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
current: discoveryConfig("foo", "bar", map[string]string{}),
|
||||
modified: discoveryConfig("foo", "bar", map[string]string{}),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
current: discoveryConfig("foo", "bar", map[string]string{"foo": "bar"}),
|
||||
modified: discoveryConfig("foo", "bar", map[string]string{"foo": "bar"}),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
current: discoveryConfig("foo", "bar", map[string]string{}),
|
||||
modified: discoveryConfig("foo", "bar", nil),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
current: discoveryConfig("foo", "bar", nil),
|
||||
modified: discoveryConfig("foo", "bar", map[string]string{}),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
current: discoveryConfig("foo", "bar", nil),
|
||||
modified: discoveryConfig("baz", "bar", nil),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
current: discoveryConfig("foo", "bar", nil),
|
||||
modified: discoveryConfig("foo", "baz", nil),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
current: discoveryConfig("foo", "bar", nil),
|
||||
modified: discoveryConfig("foo", "bar", map[string]string{"foo": "bar"}),
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
got := modifiedDiscoverySettings(c.current, c.modified.ClusterStore, c.modified.ClusterAdvertise, c.modified.ClusterOpts)
|
||||
if c.expected != got {
|
||||
t.Fatalf("expected %v, got %v: current config %v, new config %v", c.expected, got, c.current, c.modified)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func discoveryConfig(backendAddr, advertiseAddr string, opts map[string]string) *Config {
|
||||
return &Config{
|
||||
CommonConfig: CommonConfig{
|
||||
ClusterStore: backendAddr,
|
||||
ClusterAdvertise: advertiseAddr,
|
||||
ClusterOpts: opts,
|
||||
},
|
||||
}
|
||||
}
|
||||
26
vendor/github.com/hyperhq/hypercli/daemon/errors.go
generated
vendored
Normal file
26
vendor/github.com/hyperhq/hypercli/daemon/errors.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
derr "github.com/hyperhq/hypercli/errors"
|
||||
"github.com/hyperhq/hypercli/reference"
|
||||
)
|
||||
|
||||
func (d *Daemon) imageNotExistToErrcode(err error) error {
|
||||
if dne, isDNE := err.(ErrImageDoesNotExist); isDNE {
|
||||
if strings.Contains(dne.RefOrID, "@") {
|
||||
return derr.ErrorCodeNoSuchImageHash.WithArgs(dne.RefOrID)
|
||||
}
|
||||
tag := reference.DefaultTag
|
||||
ref, err := reference.ParseNamed(dne.RefOrID)
|
||||
if err != nil {
|
||||
return derr.ErrorCodeNoSuchImageTag.WithArgs(dne.RefOrID, tag)
|
||||
}
|
||||
if tagged, isTagged := ref.(reference.NamedTagged); isTagged {
|
||||
tag = tagged.Tag()
|
||||
}
|
||||
return derr.ErrorCodeNoSuchImageTag.WithArgs(ref.Name(), tag)
|
||||
}
|
||||
return err
|
||||
}
|
||||
88
vendor/github.com/hyperhq/hypercli/daemon/events.go
generated
vendored
Normal file
88
vendor/github.com/hyperhq/hypercli/daemon/events.go
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/hyperhq/hypercli/container"
|
||||
"github.com/docker/engine-api/types/events"
|
||||
"github.com/docker/libnetwork"
|
||||
)
|
||||
|
||||
// LogContainerEvent generates an event related to a container with only the default attributes.
|
||||
func (daemon *Daemon) LogContainerEvent(container *container.Container, action string) {
|
||||
daemon.LogContainerEventWithAttributes(container, action, map[string]string{})
|
||||
}
|
||||
|
||||
// LogContainerEventWithAttributes generates an event related to a container with specific given attributes.
|
||||
func (daemon *Daemon) LogContainerEventWithAttributes(container *container.Container, action string, attributes map[string]string) {
|
||||
copyAttributes(attributes, container.Config.Labels)
|
||||
if container.Config.Image != "" {
|
||||
attributes["image"] = container.Config.Image
|
||||
}
|
||||
attributes["name"] = strings.TrimLeft(container.Name, "/")
|
||||
|
||||
actor := events.Actor{
|
||||
ID: container.ID,
|
||||
Attributes: attributes,
|
||||
}
|
||||
daemon.EventsService.Log(action, events.ContainerEventType, actor)
|
||||
}
|
||||
|
||||
// LogImageEvent generates an event related to a container with only the default attributes.
|
||||
func (daemon *Daemon) LogImageEvent(imageID, refName, action string) {
|
||||
daemon.LogImageEventWithAttributes(imageID, refName, action, map[string]string{})
|
||||
}
|
||||
|
||||
// LogImageEventWithAttributes generates an event related to a container with specific given attributes.
|
||||
func (daemon *Daemon) LogImageEventWithAttributes(imageID, refName, action string, attributes map[string]string) {
|
||||
img, err := daemon.GetImage(imageID)
|
||||
if err == nil && img.Config != nil {
|
||||
// image has not been removed yet.
|
||||
// it could be missing if the event is `delete`.
|
||||
copyAttributes(attributes, img.Config.Labels)
|
||||
}
|
||||
if refName != "" {
|
||||
attributes["name"] = refName
|
||||
}
|
||||
actor := events.Actor{
|
||||
ID: imageID,
|
||||
Attributes: attributes,
|
||||
}
|
||||
|
||||
daemon.EventsService.Log(action, events.ImageEventType, actor)
|
||||
}
|
||||
|
||||
// LogVolumeEvent generates an event related to a volume.
|
||||
func (daemon *Daemon) LogVolumeEvent(volumeID, action string, attributes map[string]string) {
|
||||
actor := events.Actor{
|
||||
ID: volumeID,
|
||||
Attributes: attributes,
|
||||
}
|
||||
daemon.EventsService.Log(action, events.VolumeEventType, actor)
|
||||
}
|
||||
|
||||
// LogNetworkEvent generates an event related to a network with only the default attributes.
|
||||
func (daemon *Daemon) LogNetworkEvent(nw libnetwork.Network, action string) {
|
||||
daemon.LogNetworkEventWithAttributes(nw, action, map[string]string{})
|
||||
}
|
||||
|
||||
// LogNetworkEventWithAttributes generates an event related to a network with specific given attributes.
|
||||
func (daemon *Daemon) LogNetworkEventWithAttributes(nw libnetwork.Network, action string, attributes map[string]string) {
|
||||
attributes["name"] = nw.Name()
|
||||
attributes["type"] = nw.Type()
|
||||
actor := events.Actor{
|
||||
ID: nw.ID(),
|
||||
Attributes: attributes,
|
||||
}
|
||||
daemon.EventsService.Log(action, events.NetworkEventType, actor)
|
||||
}
|
||||
|
||||
// copyAttributes guarantees that labels are not mutated by event triggers.
|
||||
func copyAttributes(attributes, labels map[string]string) {
|
||||
if labels == nil {
|
||||
return
|
||||
}
|
||||
for k, v := range labels {
|
||||
attributes[k] = v
|
||||
}
|
||||
}
|
||||
126
vendor/github.com/hyperhq/hypercli/daemon/events/events.go
generated
vendored
Normal file
126
vendor/github.com/hyperhq/hypercli/daemon/events/events.go
generated
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hyperhq/hypercli/pkg/pubsub"
|
||||
eventtypes "github.com/docker/engine-api/types/events"
|
||||
)
|
||||
|
||||
const (
|
||||
eventsLimit = 64
|
||||
bufferSize = 1024
|
||||
)
|
||||
|
||||
// Events is pubsub channel for events generated by the engine.
|
||||
type Events struct {
|
||||
mu sync.Mutex
|
||||
events []eventtypes.Message
|
||||
pub *pubsub.Publisher
|
||||
}
|
||||
|
||||
// New returns new *Events instance
|
||||
func New() *Events {
|
||||
return &Events{
|
||||
events: make([]eventtypes.Message, 0, eventsLimit),
|
||||
pub: pubsub.NewPublisher(100*time.Millisecond, bufferSize),
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe adds new listener to events, returns slice of 64 stored
|
||||
// last events, a channel in which you can expect new events (in form
|
||||
// of interface{}, so you need type assertion), and a function to call
|
||||
// to stop the stream of events.
|
||||
func (e *Events) Subscribe() ([]eventtypes.Message, chan interface{}, func()) {
|
||||
e.mu.Lock()
|
||||
current := make([]eventtypes.Message, len(e.events))
|
||||
copy(current, e.events)
|
||||
l := e.pub.Subscribe()
|
||||
e.mu.Unlock()
|
||||
|
||||
cancel := func() {
|
||||
e.Evict(l)
|
||||
}
|
||||
return current, l, cancel
|
||||
}
|
||||
|
||||
// SubscribeTopic adds new listener to events, returns slice of 64 stored
|
||||
// last events, a channel in which you can expect new events (in form
|
||||
// of interface{}, so you need type assertion).
|
||||
func (e *Events) SubscribeTopic(since, sinceNano int64, ef *Filter) ([]eventtypes.Message, chan interface{}) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
var buffered []eventtypes.Message
|
||||
topic := func(m interface{}) bool {
|
||||
return ef.Include(m.(eventtypes.Message))
|
||||
}
|
||||
|
||||
if since != -1 {
|
||||
for i := len(e.events) - 1; i >= 0; i-- {
|
||||
ev := e.events[i]
|
||||
if ev.Time < since || ((ev.Time == since) && (ev.TimeNano < sinceNano)) {
|
||||
break
|
||||
}
|
||||
if ef.filter.Len() == 0 || topic(ev) {
|
||||
buffered = append([]eventtypes.Message{ev}, buffered...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var ch chan interface{}
|
||||
if ef.filter.Len() > 0 {
|
||||
ch = e.pub.SubscribeTopic(topic)
|
||||
} else {
|
||||
// Subscribe to all events if there are no filters
|
||||
ch = e.pub.Subscribe()
|
||||
}
|
||||
|
||||
return buffered, ch
|
||||
}
|
||||
|
||||
// Evict evicts listener from pubsub
|
||||
func (e *Events) Evict(l chan interface{}) {
|
||||
e.pub.Evict(l)
|
||||
}
|
||||
|
||||
// Log broadcasts event to listeners. Each listener has 100 millisecond for
|
||||
// receiving event or it will be skipped.
|
||||
func (e *Events) Log(action, eventType string, actor eventtypes.Actor) {
|
||||
now := time.Now().UTC()
|
||||
jm := eventtypes.Message{
|
||||
Action: action,
|
||||
Type: eventType,
|
||||
Actor: actor,
|
||||
Time: now.Unix(),
|
||||
TimeNano: now.UnixNano(),
|
||||
}
|
||||
|
||||
// fill deprecated fields for container and images
|
||||
switch eventType {
|
||||
case eventtypes.ContainerEventType:
|
||||
jm.ID = actor.ID
|
||||
jm.Status = action
|
||||
jm.From = actor.Attributes["image"]
|
||||
case eventtypes.ImageEventType:
|
||||
jm.ID = actor.ID
|
||||
jm.Status = action
|
||||
}
|
||||
|
||||
e.mu.Lock()
|
||||
if len(e.events) == cap(e.events) {
|
||||
// discard oldest event
|
||||
copy(e.events, e.events[1:])
|
||||
e.events[len(e.events)-1] = jm
|
||||
} else {
|
||||
e.events = append(e.events, jm)
|
||||
}
|
||||
e.mu.Unlock()
|
||||
e.pub.Publish(jm)
|
||||
}
|
||||
|
||||
// SubscribersCount returns number of event listeners
|
||||
func (e *Events) SubscribersCount() int {
|
||||
return e.pub.Len()
|
||||
}
|
||||
152
vendor/github.com/hyperhq/hypercli/daemon/events/events_test.go
generated
vendored
Normal file
152
vendor/github.com/hyperhq/hypercli/daemon/events/events_test.go
generated
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/engine-api/types/events"
|
||||
)
|
||||
|
||||
func TestEventsLog(t *testing.T) {
|
||||
e := New()
|
||||
_, l1, _ := e.Subscribe()
|
||||
_, l2, _ := e.Subscribe()
|
||||
defer e.Evict(l1)
|
||||
defer e.Evict(l2)
|
||||
count := e.SubscribersCount()
|
||||
if count != 2 {
|
||||
t.Fatalf("Must be 2 subscribers, got %d", count)
|
||||
}
|
||||
actor := events.Actor{
|
||||
ID: "cont",
|
||||
Attributes: map[string]string{"image": "image"},
|
||||
}
|
||||
e.Log("test", events.ContainerEventType, actor)
|
||||
select {
|
||||
case msg := <-l1:
|
||||
jmsg, ok := msg.(events.Message)
|
||||
if !ok {
|
||||
t.Fatalf("Unexpected type %T", msg)
|
||||
}
|
||||
if len(e.events) != 1 {
|
||||
t.Fatalf("Must be only one event, got %d", len(e.events))
|
||||
}
|
||||
if jmsg.Status != "test" {
|
||||
t.Fatalf("Status should be test, got %s", jmsg.Status)
|
||||
}
|
||||
if jmsg.ID != "cont" {
|
||||
t.Fatalf("ID should be cont, got %s", jmsg.ID)
|
||||
}
|
||||
if jmsg.From != "image" {
|
||||
t.Fatalf("From should be image, got %s", jmsg.From)
|
||||
}
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Fatal("Timeout waiting for broadcasted message")
|
||||
}
|
||||
select {
|
||||
case msg := <-l2:
|
||||
jmsg, ok := msg.(events.Message)
|
||||
if !ok {
|
||||
t.Fatalf("Unexpected type %T", msg)
|
||||
}
|
||||
if len(e.events) != 1 {
|
||||
t.Fatalf("Must be only one event, got %d", len(e.events))
|
||||
}
|
||||
if jmsg.Status != "test" {
|
||||
t.Fatalf("Status should be test, got %s", jmsg.Status)
|
||||
}
|
||||
if jmsg.ID != "cont" {
|
||||
t.Fatalf("ID should be cont, got %s", jmsg.ID)
|
||||
}
|
||||
if jmsg.From != "image" {
|
||||
t.Fatalf("From should be image, got %s", jmsg.From)
|
||||
}
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Fatal("Timeout waiting for broadcasted message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventsLogTimeout(t *testing.T) {
|
||||
e := New()
|
||||
_, l, _ := e.Subscribe()
|
||||
defer e.Evict(l)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
actor := events.Actor{
|
||||
ID: "image",
|
||||
}
|
||||
e.Log("test", events.ImageEventType, actor)
|
||||
close(c)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-c:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("Timeout publishing message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogEvents(t *testing.T) {
|
||||
e := New()
|
||||
|
||||
for i := 0; i < eventsLimit+16; i++ {
|
||||
action := fmt.Sprintf("action_%d", i)
|
||||
id := fmt.Sprintf("cont_%d", i)
|
||||
from := fmt.Sprintf("image_%d", i)
|
||||
|
||||
actor := events.Actor{
|
||||
ID: id,
|
||||
Attributes: map[string]string{"image": from},
|
||||
}
|
||||
e.Log(action, events.ContainerEventType, actor)
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
current, l, _ := e.Subscribe()
|
||||
for i := 0; i < 10; i++ {
|
||||
num := i + eventsLimit + 16
|
||||
action := fmt.Sprintf("action_%d", num)
|
||||
id := fmt.Sprintf("cont_%d", num)
|
||||
from := fmt.Sprintf("image_%d", num)
|
||||
|
||||
actor := events.Actor{
|
||||
ID: id,
|
||||
Attributes: map[string]string{"image": from},
|
||||
}
|
||||
e.Log(action, events.ContainerEventType, actor)
|
||||
}
|
||||
if len(e.events) != eventsLimit {
|
||||
t.Fatalf("Must be %d events, got %d", eventsLimit, len(e.events))
|
||||
}
|
||||
|
||||
var msgs []events.Message
|
||||
for len(msgs) < 10 {
|
||||
m := <-l
|
||||
jm, ok := (m).(events.Message)
|
||||
if !ok {
|
||||
t.Fatalf("Unexpected type %T", m)
|
||||
}
|
||||
msgs = append(msgs, jm)
|
||||
}
|
||||
if len(current) != eventsLimit {
|
||||
t.Fatalf("Must be %d events, got %d", eventsLimit, len(current))
|
||||
}
|
||||
first := current[0]
|
||||
if first.Status != "action_16" {
|
||||
t.Fatalf("First action is %s, must be action_16", first.Status)
|
||||
}
|
||||
last := current[len(current)-1]
|
||||
if last.Status != "action_79" {
|
||||
t.Fatalf("Last action is %s, must be action_79", last.Status)
|
||||
}
|
||||
|
||||
firstC := msgs[0]
|
||||
if firstC.Status != "action_80" {
|
||||
t.Fatalf("First action is %s, must be action_80", firstC.Status)
|
||||
}
|
||||
lastC := msgs[len(msgs)-1]
|
||||
if lastC.Status != "action_89" {
|
||||
t.Fatalf("Last action is %s, must be action_89", lastC.Status)
|
||||
}
|
||||
}
|
||||
82
vendor/github.com/hyperhq/hypercli/daemon/events/filter.go
generated
vendored
Normal file
82
vendor/github.com/hyperhq/hypercli/daemon/events/filter.go
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"github.com/hyperhq/hypercli/reference"
|
||||
"github.com/docker/engine-api/types/events"
|
||||
"github.com/docker/engine-api/types/filters"
|
||||
)
|
||||
|
||||
// Filter can filter out docker events from a stream
|
||||
type Filter struct {
|
||||
filter filters.Args
|
||||
}
|
||||
|
||||
// NewFilter creates a new Filter
|
||||
func NewFilter(filter filters.Args) *Filter {
|
||||
return &Filter{filter: filter}
|
||||
}
|
||||
|
||||
// Include returns true when the event ev is included by the filters
|
||||
func (ef *Filter) Include(ev events.Message) bool {
|
||||
return ef.filter.ExactMatch("event", ev.Action) &&
|
||||
ef.filter.ExactMatch("type", ev.Type) &&
|
||||
ef.matchContainer(ev) &&
|
||||
ef.matchVolume(ev) &&
|
||||
ef.matchNetwork(ev) &&
|
||||
ef.matchImage(ev) &&
|
||||
ef.matchLabels(ev.Actor.Attributes)
|
||||
}
|
||||
|
||||
func (ef *Filter) matchLabels(attributes map[string]string) bool {
|
||||
if !ef.filter.Include("label") {
|
||||
return true
|
||||
}
|
||||
return ef.filter.MatchKVList("label", attributes)
|
||||
}
|
||||
|
||||
func (ef *Filter) matchContainer(ev events.Message) bool {
|
||||
return ef.fuzzyMatchName(ev, events.ContainerEventType)
|
||||
}
|
||||
|
||||
func (ef *Filter) matchVolume(ev events.Message) bool {
|
||||
return ef.fuzzyMatchName(ev, events.VolumeEventType)
|
||||
}
|
||||
|
||||
func (ef *Filter) matchNetwork(ev events.Message) bool {
|
||||
return ef.fuzzyMatchName(ev, events.NetworkEventType)
|
||||
}
|
||||
|
||||
func (ef *Filter) fuzzyMatchName(ev events.Message, eventType string) bool {
|
||||
return ef.filter.FuzzyMatch(eventType, ev.Actor.ID) ||
|
||||
ef.filter.FuzzyMatch(eventType, ev.Actor.Attributes["name"])
|
||||
}
|
||||
|
||||
// matchImage matches against both event.Actor.ID (for image events)
|
||||
// and event.Actor.Attributes["image"] (for container events), so that any container that was created
|
||||
// from an image will be included in the image events. Also compare both
|
||||
// against the stripped repo name without any tags.
|
||||
func (ef *Filter) matchImage(ev events.Message) bool {
|
||||
id := ev.Actor.ID
|
||||
nameAttr := "image"
|
||||
var imageName string
|
||||
|
||||
if ev.Type == events.ImageEventType {
|
||||
nameAttr = "name"
|
||||
}
|
||||
|
||||
if n, ok := ev.Actor.Attributes[nameAttr]; ok {
|
||||
imageName = n
|
||||
}
|
||||
return ef.filter.ExactMatch("image", id) ||
|
||||
ef.filter.ExactMatch("image", imageName) ||
|
||||
ef.filter.ExactMatch("image", stripTag(id)) ||
|
||||
ef.filter.ExactMatch("image", stripTag(imageName))
|
||||
}
|
||||
|
||||
func stripTag(image string) string {
|
||||
ref, err := reference.ParseNamed(image)
|
||||
if err != nil {
|
||||
return image
|
||||
}
|
||||
return ref.Name()
|
||||
}
|
||||
94
vendor/github.com/hyperhq/hypercli/daemon/events_test.go
generated
vendored
Normal file
94
vendor/github.com/hyperhq/hypercli/daemon/events_test.go
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hyperhq/hypercli/container"
|
||||
"github.com/hyperhq/hypercli/daemon/events"
|
||||
containertypes "github.com/docker/engine-api/types/container"
|
||||
eventtypes "github.com/docker/engine-api/types/events"
|
||||
)
|
||||
|
||||
func TestLogContainerEventCopyLabels(t *testing.T) {
|
||||
e := events.New()
|
||||
_, l, _ := e.Subscribe()
|
||||
defer e.Evict(l)
|
||||
|
||||
container := &container.Container{
|
||||
CommonContainer: container.CommonContainer{
|
||||
ID: "container_id",
|
||||
Name: "container_name",
|
||||
Config: &containertypes.Config{
|
||||
Image: "image_name",
|
||||
Labels: map[string]string{
|
||||
"node": "1",
|
||||
"os": "alpine",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
daemon := &Daemon{
|
||||
EventsService: e,
|
||||
}
|
||||
daemon.LogContainerEvent(container, "create")
|
||||
|
||||
if _, mutated := container.Config.Labels["image"]; mutated {
|
||||
t.Fatalf("Expected to not mutate the container labels, got %q", container.Config.Labels)
|
||||
}
|
||||
|
||||
validateTestAttributes(t, l, map[string]string{
|
||||
"node": "1",
|
||||
"os": "alpine",
|
||||
})
|
||||
}
|
||||
|
||||
func TestLogContainerEventWithAttributes(t *testing.T) {
|
||||
e := events.New()
|
||||
_, l, _ := e.Subscribe()
|
||||
defer e.Evict(l)
|
||||
|
||||
container := &container.Container{
|
||||
CommonContainer: container.CommonContainer{
|
||||
ID: "container_id",
|
||||
Name: "container_name",
|
||||
Config: &containertypes.Config{
|
||||
Labels: map[string]string{
|
||||
"node": "1",
|
||||
"os": "alpine",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
daemon := &Daemon{
|
||||
EventsService: e,
|
||||
}
|
||||
attributes := map[string]string{
|
||||
"node": "2",
|
||||
"foo": "bar",
|
||||
}
|
||||
daemon.LogContainerEventWithAttributes(container, "create", attributes)
|
||||
|
||||
validateTestAttributes(t, l, map[string]string{
|
||||
"node": "1",
|
||||
"foo": "bar",
|
||||
})
|
||||
}
|
||||
|
||||
func validateTestAttributes(t *testing.T, l chan interface{}, expectedAttributesToTest map[string]string) {
|
||||
select {
|
||||
case ev := <-l:
|
||||
event, ok := ev.(eventtypes.Message)
|
||||
if !ok {
|
||||
t.Fatalf("Unexpected event message: %q", ev)
|
||||
}
|
||||
for key, expected := range expectedAttributesToTest {
|
||||
actual, ok := event.Actor.Attributes[key]
|
||||
if !ok || actual != expected {
|
||||
t.Fatalf("Expected value for key %s to be %s, but was %s (event:%v)", key, expected, actual, event)
|
||||
}
|
||||
}
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatalf("LogEvent test timed out")
|
||||
}
|
||||
}
|
||||
314
vendor/github.com/hyperhq/hypercli/daemon/exec.go
generated
vendored
Normal file
314
vendor/github.com/hyperhq/hypercli/daemon/exec.go
generated
vendored
Normal file
@@ -0,0 +1,314 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/hyperhq/hypercli/container"
|
||||
"github.com/hyperhq/hypercli/daemon/exec"
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver"
|
||||
derr "github.com/hyperhq/hypercli/errors"
|
||||
"github.com/hyperhq/hypercli/pkg/pools"
|
||||
"github.com/hyperhq/hypercli/pkg/promise"
|
||||
"github.com/hyperhq/hypercli/pkg/term"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/strslice"
|
||||
)
|
||||
|
||||
func (d *Daemon) registerExecCommand(container *container.Container, config *exec.Config) {
|
||||
// Storing execs in container in order to kill them gracefully whenever the container is stopped or removed.
|
||||
container.ExecCommands.Add(config.ID, config)
|
||||
// Storing execs in daemon for easy access via remote API.
|
||||
d.execCommands.Add(config.ID, config)
|
||||
}
|
||||
|
||||
// ExecExists looks up the exec instance and returns a bool if it exists or not.
|
||||
// It will also return the error produced by `getConfig`
|
||||
func (d *Daemon) ExecExists(name string) (bool, error) {
|
||||
if _, err := d.getExecConfig(name); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// getExecConfig looks up the exec instance by name. If the container associated
|
||||
// with the exec instance is stopped or paused, it will return an error.
|
||||
func (d *Daemon) getExecConfig(name string) (*exec.Config, error) {
|
||||
ec := d.execCommands.Get(name)
|
||||
|
||||
// If the exec is found but its container is not in the daemon's list of
|
||||
// containers then it must have been deleted, in which case instead of
|
||||
// saying the container isn't running, we should return a 404 so that
|
||||
// the user sees the same error now that they will after the
|
||||
// 5 minute clean-up loop is run which erases old/dead execs.
|
||||
|
||||
if ec != nil {
|
||||
if container := d.containers.Get(ec.ContainerID); container != nil {
|
||||
if !container.IsRunning() {
|
||||
return nil, derr.ErrorCodeContainerNotRunning.WithArgs(container.ID, container.State.String())
|
||||
}
|
||||
if container.IsPaused() {
|
||||
return nil, derr.ErrorCodeExecPaused.WithArgs(container.ID)
|
||||
}
|
||||
if container.IsRestarting() {
|
||||
return nil, derr.ErrorCodeContainerRestarting.WithArgs(container.ID)
|
||||
}
|
||||
return ec, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, derr.ErrorCodeNoExecID.WithArgs(name)
|
||||
}
|
||||
|
||||
func (d *Daemon) unregisterExecCommand(container *container.Container, execConfig *exec.Config) {
|
||||
container.ExecCommands.Delete(execConfig.ID)
|
||||
d.execCommands.Delete(execConfig.ID)
|
||||
}
|
||||
|
||||
func (d *Daemon) getActiveContainer(name string) (*container.Container, error) {
|
||||
container, err := d.GetContainer(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !container.IsRunning() {
|
||||
return nil, derr.ErrorCodeNotRunning.WithArgs(name)
|
||||
}
|
||||
if container.IsPaused() {
|
||||
return nil, derr.ErrorCodeExecPaused.WithArgs(name)
|
||||
}
|
||||
if container.IsRestarting() {
|
||||
return nil, derr.ErrorCodeContainerRestarting.WithArgs(name)
|
||||
}
|
||||
return container, nil
|
||||
}
|
||||
|
||||
// ContainerExecCreate sets up an exec in a running container.
|
||||
func (d *Daemon) ContainerExecCreate(config *types.ExecConfig) (string, error) {
|
||||
container, err := d.getActiveContainer(config.Container)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
cmd := strslice.New(config.Cmd...)
|
||||
entrypoint, args := d.getEntrypointAndArgs(strslice.New(), cmd)
|
||||
|
||||
keys := []byte{}
|
||||
if config.DetachKeys != "" {
|
||||
keys, err = term.ToBytes(config.DetachKeys)
|
||||
if err != nil {
|
||||
logrus.Warnf("Wrong escape keys provided (%s, error: %s) using default : ctrl-p ctrl-q", config.DetachKeys, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
processConfig := &execdriver.ProcessConfig{
|
||||
CommonProcessConfig: execdriver.CommonProcessConfig{
|
||||
Tty: config.Tty,
|
||||
Entrypoint: entrypoint,
|
||||
Arguments: args,
|
||||
},
|
||||
}
|
||||
setPlatformSpecificExecProcessConfig(config, container, processConfig)
|
||||
|
||||
execConfig := exec.NewConfig()
|
||||
execConfig.OpenStdin = config.AttachStdin
|
||||
execConfig.OpenStdout = config.AttachStdout
|
||||
execConfig.OpenStderr = config.AttachStderr
|
||||
execConfig.ProcessConfig = processConfig
|
||||
execConfig.ContainerID = container.ID
|
||||
execConfig.DetachKeys = keys
|
||||
|
||||
d.registerExecCommand(container, execConfig)
|
||||
|
||||
d.LogContainerEvent(container, "exec_create: "+execConfig.ProcessConfig.Entrypoint+" "+strings.Join(execConfig.ProcessConfig.Arguments, " "))
|
||||
|
||||
return execConfig.ID, nil
|
||||
}
|
||||
|
||||
// ContainerExecStart starts a previously set up exec instance. The
|
||||
// std streams are set up.
|
||||
func (d *Daemon) ContainerExecStart(name string, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) error {
|
||||
var (
|
||||
cStdin io.ReadCloser
|
||||
cStdout, cStderr io.Writer
|
||||
)
|
||||
|
||||
ec, err := d.getExecConfig(name)
|
||||
if err != nil {
|
||||
return derr.ErrorCodeNoExecID.WithArgs(name)
|
||||
}
|
||||
|
||||
ec.Lock()
|
||||
if ec.ExitCode != nil {
|
||||
ec.Unlock()
|
||||
return derr.ErrorCodeExecExited.WithArgs(ec.ID)
|
||||
}
|
||||
|
||||
if ec.Running {
|
||||
ec.Unlock()
|
||||
return derr.ErrorCodeExecRunning.WithArgs(ec.ID)
|
||||
}
|
||||
ec.Running = true
|
||||
ec.Unlock()
|
||||
|
||||
c := d.containers.Get(ec.ContainerID)
|
||||
logrus.Debugf("starting exec command %s in container %s", ec.ID, c.ID)
|
||||
d.LogContainerEvent(c, "exec_start: "+ec.ProcessConfig.Entrypoint+" "+strings.Join(ec.ProcessConfig.Arguments, " "))
|
||||
|
||||
if ec.OpenStdin {
|
||||
r, w := io.Pipe()
|
||||
go func() {
|
||||
defer w.Close()
|
||||
defer logrus.Debugf("Closing buffered stdin pipe")
|
||||
pools.Copy(w, stdin)
|
||||
}()
|
||||
cStdin = r
|
||||
}
|
||||
if ec.OpenStdout {
|
||||
cStdout = stdout
|
||||
}
|
||||
if ec.OpenStderr {
|
||||
cStderr = stderr
|
||||
}
|
||||
|
||||
if ec.OpenStdin {
|
||||
ec.NewInputPipes()
|
||||
} else {
|
||||
ec.NewNopInputPipe()
|
||||
}
|
||||
|
||||
attachErr := container.AttachStreams(ec.StreamConfig, ec.OpenStdin, true, ec.ProcessConfig.Tty, cStdin, cStdout, cStderr, ec.DetachKeys)
|
||||
|
||||
execErr := make(chan error)
|
||||
|
||||
// Note, the ExecConfig data will be removed when the container
|
||||
// itself is deleted. This allows us to query it (for things like
|
||||
// the exitStatus) even after the cmd is done running.
|
||||
|
||||
go func() {
|
||||
execErr <- d.containerExec(c, ec)
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-attachErr:
|
||||
if err != nil {
|
||||
return derr.ErrorCodeExecAttach.WithArgs(err)
|
||||
}
|
||||
return nil
|
||||
case err := <-execErr:
|
||||
if aErr := <-attachErr; aErr != nil && err == nil {
|
||||
return derr.ErrorCodeExecAttach.WithArgs(aErr)
|
||||
}
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Maybe the container stopped while we were trying to exec
|
||||
if !c.IsRunning() {
|
||||
return derr.ErrorCodeExecContainerStopped
|
||||
}
|
||||
return derr.ErrorCodeExecCantRun.WithArgs(ec.ID, c.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Exec calls the underlying exec driver to run
|
||||
func (d *Daemon) Exec(c *container.Container, execConfig *exec.Config, pipes *execdriver.Pipes, startCallback execdriver.DriverCallback) (int, error) {
|
||||
hooks := execdriver.Hooks{
|
||||
Start: startCallback,
|
||||
}
|
||||
exitStatus, err := d.execDriver.Exec(c.Command, execConfig.ProcessConfig, pipes, hooks)
|
||||
|
||||
// On err, make sure we don't leave ExitCode at zero
|
||||
if err != nil && exitStatus == 0 {
|
||||
exitStatus = 128
|
||||
}
|
||||
|
||||
execConfig.ExitCode = &exitStatus
|
||||
execConfig.Running = false
|
||||
|
||||
return exitStatus, err
|
||||
}
|
||||
|
||||
// execCommandGC runs a ticker to clean up the daemon references
|
||||
// of exec configs that are no longer part of the container.
|
||||
func (d *Daemon) execCommandGC() {
|
||||
for range time.Tick(5 * time.Minute) {
|
||||
var (
|
||||
cleaned int
|
||||
liveExecCommands = d.containerExecIds()
|
||||
)
|
||||
for id, config := range d.execCommands.Commands() {
|
||||
if config.CanRemove {
|
||||
cleaned++
|
||||
d.execCommands.Delete(id)
|
||||
} else {
|
||||
if _, exists := liveExecCommands[id]; !exists {
|
||||
config.CanRemove = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if cleaned > 0 {
|
||||
logrus.Debugf("clean %d unused exec commands", cleaned)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// containerExecIds returns a list of all the current exec ids that are in use
|
||||
// and running inside a container.
|
||||
func (d *Daemon) containerExecIds() map[string]struct{} {
|
||||
ids := map[string]struct{}{}
|
||||
for _, c := range d.containers.List() {
|
||||
for _, id := range c.ExecCommands.List() {
|
||||
ids[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func (d *Daemon) containerExec(container *container.Container, ec *exec.Config) error {
|
||||
container.Lock()
|
||||
defer container.Unlock()
|
||||
|
||||
callback := func(processConfig *execdriver.ProcessConfig, pid int, chOOM <-chan struct{}) error {
|
||||
if processConfig.Tty {
|
||||
// The callback is called after the process Start()
|
||||
// so we are in the parent process. In TTY mode, stdin/out/err is the PtySlave
|
||||
// which we close here.
|
||||
if c, ok := processConfig.Stdout.(io.Closer); ok {
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
ec.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// We use a callback here instead of a goroutine and an chan for
|
||||
// synchronization purposes
|
||||
cErr := promise.Go(func() error { return d.monitorExec(container, ec, callback) })
|
||||
return ec.Wait(cErr)
|
||||
}
|
||||
|
||||
func (d *Daemon) monitorExec(container *container.Container, execConfig *exec.Config, callback execdriver.DriverCallback) error {
|
||||
pipes := execdriver.NewPipes(execConfig.Stdin(), execConfig.Stdout(), execConfig.Stderr(), execConfig.OpenStdin)
|
||||
exitCode, err := d.Exec(container, execConfig, pipes, callback)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error running command in existing container %s: %s", container.ID, err)
|
||||
}
|
||||
logrus.Debugf("Exec task in container %s exited with code %d", container.ID, exitCode)
|
||||
|
||||
if err := execConfig.CloseStreams(); err != nil {
|
||||
logrus.Errorf("%s: %s", container.ID, err)
|
||||
}
|
||||
|
||||
if execConfig.ProcessConfig.Terminal != nil {
|
||||
if err := execConfig.ProcessConfig.Terminal.Close(); err != nil {
|
||||
logrus.Errorf("Error closing terminal while running in container %s: %s", container.ID, err)
|
||||
}
|
||||
}
|
||||
// remove the exec command from the container's store only and not the
|
||||
// daemon's store so that the exec command can be inspected.
|
||||
container.ExecCommands.Delete(execConfig.ID)
|
||||
return err
|
||||
}
|
||||
122
vendor/github.com/hyperhq/hypercli/daemon/exec/exec.go
generated
vendored
Normal file
122
vendor/github.com/hyperhq/hypercli/daemon/exec/exec.go
generated
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
package exec
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver"
|
||||
derr "github.com/hyperhq/hypercli/errors"
|
||||
"github.com/hyperhq/hypercli/pkg/stringid"
|
||||
"github.com/hyperhq/hypercli/runconfig"
|
||||
)
|
||||
|
||||
// Config holds the configurations for execs. The Daemon keeps
|
||||
// track of both running and finished execs so that they can be
|
||||
// examined both during and after completion.
|
||||
type Config struct {
|
||||
sync.Mutex
|
||||
*runconfig.StreamConfig
|
||||
ID string
|
||||
Running bool
|
||||
ExitCode *int
|
||||
ProcessConfig *execdriver.ProcessConfig
|
||||
OpenStdin bool
|
||||
OpenStderr bool
|
||||
OpenStdout bool
|
||||
CanRemove bool
|
||||
ContainerID string
|
||||
DetachKeys []byte
|
||||
|
||||
// waitStart will be closed immediately after the exec is really started.
|
||||
waitStart chan struct{}
|
||||
}
|
||||
|
||||
// NewConfig initializes the a new exec configuration
|
||||
func NewConfig() *Config {
|
||||
return &Config{
|
||||
ID: stringid.GenerateNonCryptoID(),
|
||||
StreamConfig: runconfig.NewStreamConfig(),
|
||||
waitStart: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Store keeps track of the exec configurations.
|
||||
type Store struct {
|
||||
commands map[string]*Config
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// NewStore initializes a new exec store.
|
||||
func NewStore() *Store {
|
||||
return &Store{commands: make(map[string]*Config, 0)}
|
||||
}
|
||||
|
||||
// Commands returns the exec configurations in the store.
|
||||
func (e *Store) Commands() map[string]*Config {
|
||||
e.RLock()
|
||||
commands := make(map[string]*Config, len(e.commands))
|
||||
for id, config := range e.commands {
|
||||
commands[id] = config
|
||||
}
|
||||
e.RUnlock()
|
||||
return commands
|
||||
}
|
||||
|
||||
// Add adds a new exec configuration to the store.
|
||||
func (e *Store) Add(id string, Config *Config) {
|
||||
e.Lock()
|
||||
e.commands[id] = Config
|
||||
e.Unlock()
|
||||
}
|
||||
|
||||
// Get returns an exec configuration by its id.
|
||||
func (e *Store) Get(id string) *Config {
|
||||
e.RLock()
|
||||
res := e.commands[id]
|
||||
e.RUnlock()
|
||||
return res
|
||||
}
|
||||
|
||||
// Delete removes an exec configuration from the store.
|
||||
func (e *Store) Delete(id string) {
|
||||
e.Lock()
|
||||
delete(e.commands, id)
|
||||
e.Unlock()
|
||||
}
|
||||
|
||||
// List returns the list of exec ids in the store.
|
||||
func (e *Store) List() []string {
|
||||
var IDs []string
|
||||
e.RLock()
|
||||
for id := range e.commands {
|
||||
IDs = append(IDs, id)
|
||||
}
|
||||
e.RUnlock()
|
||||
return IDs
|
||||
}
|
||||
|
||||
// Wait waits until the exec process finishes or there is an error in the error channel.
|
||||
func (c *Config) Wait(cErr chan error) error {
|
||||
// Exec should not return until the process is actually running
|
||||
select {
|
||||
case <-c.waitStart:
|
||||
case err := <-cErr:
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the wait channel for the progress.
|
||||
func (c *Config) Close() {
|
||||
close(c.waitStart)
|
||||
}
|
||||
|
||||
// Resize changes the size of the terminal for the exec process.
|
||||
func (c *Config) Resize(h, w int) error {
|
||||
select {
|
||||
case <-c.waitStart:
|
||||
case <-time.After(time.Second):
|
||||
return derr.ErrorCodeExecResize.WithArgs(c.ID)
|
||||
}
|
||||
return c.ProcessConfig.Terminal.Resize(h, w)
|
||||
}
|
||||
21
vendor/github.com/hyperhq/hypercli/daemon/exec_unix.go
generated
vendored
Normal file
21
vendor/github.com/hyperhq/hypercli/daemon/exec_unix.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
// +build linux freebsd
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"github.com/hyperhq/hypercli/container"
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver"
|
||||
"github.com/docker/engine-api/types"
|
||||
)
|
||||
|
||||
// setPlatformSpecificExecProcessConfig sets platform-specific fields in the
|
||||
// ProcessConfig structure.
|
||||
func setPlatformSpecificExecProcessConfig(config *types.ExecConfig, container *container.Container, pc *execdriver.ProcessConfig) {
|
||||
user := config.User
|
||||
if len(user) == 0 {
|
||||
user = container.Config.User
|
||||
}
|
||||
|
||||
pc.User = user
|
||||
pc.Privileged = config.Privileged
|
||||
}
|
||||
12
vendor/github.com/hyperhq/hypercli/daemon/exec_windows.go
generated
vendored
Normal file
12
vendor/github.com/hyperhq/hypercli/daemon/exec_windows.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"github.com/hyperhq/hypercli/container"
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver"
|
||||
"github.com/docker/engine-api/types"
|
||||
)
|
||||
|
||||
// setPlatformSpecificExecProcessConfig sets platform-specific fields in the
|
||||
// ProcessConfig structure. This is a no-op on Windows
|
||||
func setPlatformSpecificExecProcessConfig(config *types.ExecConfig, container *container.Container, pc *execdriver.ProcessConfig) {
|
||||
}
|
||||
133
vendor/github.com/hyperhq/hypercli/daemon/execdriver/driver.go
generated
vendored
Normal file
133
vendor/github.com/hyperhq/hypercli/daemon/execdriver/driver.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
package execdriver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer"
|
||||
)
|
||||
|
||||
// Context is a generic key value pair that allows
|
||||
// arbitrary data to be sent
|
||||
type Context map[string]string
|
||||
|
||||
// Define error messages
|
||||
var (
|
||||
ErrNotRunning = errors.New("Container is not running")
|
||||
ErrWaitTimeoutReached = errors.New("Wait timeout reached")
|
||||
ErrDriverAlreadyRegistered = errors.New("A driver already registered this docker init function")
|
||||
ErrDriverNotFound = errors.New("The requested docker init has not been found")
|
||||
)
|
||||
|
||||
// DriverCallback defines a callback function which is used in "Run" and "Exec".
|
||||
// This allows work to be done in the parent process when the child is passing
|
||||
// through PreStart, Start and PostStop events.
|
||||
// Callbacks are provided a processConfig pointer and the pid of the child.
|
||||
// The channel will be used to notify the OOM events.
|
||||
type DriverCallback func(processConfig *ProcessConfig, pid int, chOOM <-chan struct{}) error
|
||||
|
||||
// Hooks is a struct containing function pointers to callbacks
|
||||
// used by any execdriver implementation exploiting hooks capabilities
|
||||
type Hooks struct {
|
||||
// PreStart is called before container's CMD/ENTRYPOINT is executed
|
||||
PreStart []DriverCallback
|
||||
// Start is called after the container's process is full started
|
||||
Start DriverCallback
|
||||
// PostStop is called after the container process exits
|
||||
PostStop []DriverCallback
|
||||
}
|
||||
|
||||
// Terminal represents a pseudo TTY, it is for when
|
||||
// using a container interactively.
|
||||
type Terminal interface {
|
||||
io.Closer
|
||||
Resize(height, width int) error
|
||||
}
|
||||
|
||||
// Driver is an interface for drivers to implement
|
||||
// including all basic functions a driver should have
|
||||
type Driver interface {
|
||||
// Run executes the process, blocks until the process exits and returns
|
||||
// the exit code. It's the last stage on Docker side for running a container.
|
||||
Run(c *Command, pipes *Pipes, hooks Hooks) (ExitStatus, error)
|
||||
|
||||
// Exec executes the process in an existing container, blocks until the
|
||||
// process exits and returns the exit code.
|
||||
Exec(c *Command, processConfig *ProcessConfig, pipes *Pipes, hooks Hooks) (int, error)
|
||||
|
||||
// Kill sends signals to process in container.
|
||||
Kill(c *Command, sig int) error
|
||||
|
||||
// Pause pauses a container.
|
||||
Pause(c *Command) error
|
||||
|
||||
// Unpause unpauses a container.
|
||||
Unpause(c *Command) error
|
||||
|
||||
// Name returns the name of the driver.
|
||||
Name() string
|
||||
|
||||
// GetPidsForContainer returns a list of pid for the processes running in a container.
|
||||
GetPidsForContainer(id string) ([]int, error)
|
||||
|
||||
// Terminate kills a container by sending signal SIGKILL.
|
||||
Terminate(c *Command) error
|
||||
|
||||
// Clean removes all traces of container exec.
|
||||
Clean(id string) error
|
||||
|
||||
// Stats returns resource stats for a running container
|
||||
Stats(id string) (*ResourceStats, error)
|
||||
|
||||
// Update updates resource configs for a container
|
||||
Update(c *Command) error
|
||||
|
||||
// SupportsHooks refers to the driver capability to exploit pre/post hook functionality
|
||||
SupportsHooks() bool
|
||||
}
|
||||
|
||||
// CommonResources contains the resource configs for a driver that are
|
||||
// common across platforms.
|
||||
type CommonResources struct {
|
||||
Memory int64 `json:"memory"`
|
||||
MemoryReservation int64 `json:"memory_reservation"`
|
||||
CPUShares int64 `json:"cpu_shares"`
|
||||
BlkioWeight uint16 `json:"blkio_weight"`
|
||||
}
|
||||
|
||||
// ResourceStats contains information about resource usage by a container.
|
||||
type ResourceStats struct {
|
||||
*libcontainer.Stats
|
||||
Read time.Time `json:"read"`
|
||||
MemoryLimit int64 `json:"memory_limit"`
|
||||
SystemUsage uint64 `json:"system_usage"`
|
||||
}
|
||||
|
||||
// CommonProcessConfig is the common platform agnostic part of the ProcessConfig
|
||||
// structure that describes a process that will be run inside a container.
|
||||
type CommonProcessConfig struct {
|
||||
exec.Cmd `json:"-"`
|
||||
|
||||
Tty bool `json:"tty"`
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
Arguments []string `json:"arguments"`
|
||||
Terminal Terminal `json:"-"` // standard or tty terminal
|
||||
}
|
||||
|
||||
// CommonCommand is the common platform agnostic part of the Command structure
|
||||
// which wraps an os/exec.Cmd to add more metadata
|
||||
type CommonCommand struct {
|
||||
ContainerPid int `json:"container_pid"` // the pid for the process inside a container
|
||||
ID string `json:"id"`
|
||||
MountLabel string `json:"mount_label"` // TODO Windows. More involved, but can be factored out
|
||||
Mounts []Mount `json:"mounts"`
|
||||
Network *Network `json:"network"`
|
||||
ProcessConfig ProcessConfig `json:"process_config"` // Describes the init process of the container.
|
||||
ProcessLabel string `json:"process_label"` // TODO Windows. More involved, but can be factored out
|
||||
Resources *Resources `json:"resources"`
|
||||
Rootfs string `json:"rootfs"` // root fs of the container
|
||||
WorkingDir string `json:"working_dir"`
|
||||
TmpDir string `json:"tmpdir"` // Directory used to store docker tmpdirs.
|
||||
}
|
||||
302
vendor/github.com/hyperhq/hypercli/daemon/execdriver/driver_unix.go
generated
vendored
Normal file
302
vendor/github.com/hyperhq/hypercli/daemon/execdriver/driver_unix.go
generated
vendored
Normal file
@@ -0,0 +1,302 @@
|
||||
// +build !windows
|
||||
|
||||
package execdriver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver/native/template"
|
||||
"github.com/hyperhq/hypercli/pkg/idtools"
|
||||
"github.com/hyperhq/hypercli/pkg/mount"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/opencontainers/runc/libcontainer"
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups/fs"
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
blkiodev "github.com/opencontainers/runc/libcontainer/configs"
|
||||
)
|
||||
|
||||
// Mount contains information for a mount operation.
|
||||
type Mount struct {
|
||||
Source string `json:"source"`
|
||||
Destination string `json:"destination"`
|
||||
Writable bool `json:"writable"`
|
||||
Data string `json:"data"`
|
||||
Propagation string `json:"mountpropagation"`
|
||||
}
|
||||
|
||||
// Resources contains all resource configs for a driver.
|
||||
// Currently these are all for cgroup configs.
|
||||
type Resources struct {
|
||||
CommonResources
|
||||
|
||||
// Fields below here are platform specific
|
||||
|
||||
BlkioWeightDevice []*blkiodev.WeightDevice `json:"blkio_weight_device"`
|
||||
BlkioThrottleReadBpsDevice []*blkiodev.ThrottleDevice `json:"blkio_throttle_read_bps_device"`
|
||||
BlkioThrottleWriteBpsDevice []*blkiodev.ThrottleDevice `json:"blkio_throttle_write_bps_device"`
|
||||
BlkioThrottleReadIOpsDevice []*blkiodev.ThrottleDevice `json:"blkio_throttle_read_iops_device"`
|
||||
BlkioThrottleWriteIOpsDevice []*blkiodev.ThrottleDevice `json:"blkio_throttle_write_iops_device"`
|
||||
MemorySwap int64 `json:"memory_swap"`
|
||||
KernelMemory int64 `json:"kernel_memory"`
|
||||
CPUQuota int64 `json:"cpu_quota"`
|
||||
CpusetCpus string `json:"cpuset_cpus"`
|
||||
CpusetMems string `json:"cpuset_mems"`
|
||||
CPUPeriod int64 `json:"cpu_period"`
|
||||
Rlimits []*units.Rlimit `json:"rlimits"`
|
||||
OomKillDisable bool `json:"oom_kill_disable"`
|
||||
MemorySwappiness int64 `json:"memory_swappiness"`
|
||||
}
|
||||
|
||||
// ProcessConfig is the platform specific structure that describes a process
|
||||
// that will be run inside a container.
|
||||
type ProcessConfig struct {
|
||||
CommonProcessConfig
|
||||
|
||||
// Fields below here are platform specific
|
||||
Privileged bool `json:"privileged"`
|
||||
User string `json:"user"`
|
||||
Console string `json:"-"` // dev/console path
|
||||
}
|
||||
|
||||
// Ipc settings of the container
|
||||
// It is for IPC namespace setting. Usually different containers
|
||||
// have their own IPC namespace, however this specifies to use
|
||||
// an existing IPC namespace.
|
||||
// You can join the host's or a container's IPC namespace.
|
||||
type Ipc struct {
|
||||
ContainerID string `json:"container_id"` // id of the container to join ipc.
|
||||
HostIpc bool `json:"host_ipc"`
|
||||
}
|
||||
|
||||
// Pid settings of the container
|
||||
// It is for PID namespace setting. Usually different containers
|
||||
// have their own PID namespace, however this specifies to use
|
||||
// an existing PID namespace.
|
||||
// Joining the host's PID namespace is currently the only supported
|
||||
// option.
|
||||
type Pid struct {
|
||||
HostPid bool `json:"host_pid"`
|
||||
}
|
||||
|
||||
// UTS settings of the container
|
||||
// It is for UTS namespace setting. Usually different containers
|
||||
// have their own UTS namespace, however this specifies to use
|
||||
// an existing UTS namespace.
|
||||
// Joining the host's UTS namespace is currently the only supported
|
||||
// option.
|
||||
type UTS struct {
|
||||
HostUTS bool `json:"host_uts"`
|
||||
}
|
||||
|
||||
// Network settings of the container
|
||||
type Network struct {
|
||||
Mtu int `json:"mtu"`
|
||||
ContainerID string `json:"container_id"` // id of the container to join network.
|
||||
NamespacePath string `json:"namespace_path"`
|
||||
HostNetworking bool `json:"host_networking"`
|
||||
}
|
||||
|
||||
// Command wraps an os/exec.Cmd to add more metadata
|
||||
type Command struct {
|
||||
CommonCommand
|
||||
|
||||
// Fields below here are platform specific
|
||||
|
||||
AllowedDevices []*configs.Device `json:"allowed_devices"`
|
||||
AppArmorProfile string `json:"apparmor_profile"`
|
||||
AutoCreatedDevices []*configs.Device `json:"autocreated_devices"`
|
||||
CapAdd []string `json:"cap_add"`
|
||||
CapDrop []string `json:"cap_drop"`
|
||||
CgroupParent string `json:"cgroup_parent"` // The parent cgroup for this command.
|
||||
GIDMapping []idtools.IDMap `json:"gidmapping"`
|
||||
GroupAdd []string `json:"group_add"`
|
||||
Ipc *Ipc `json:"ipc"`
|
||||
OomScoreAdj int `json:"oom_score_adj"`
|
||||
Pid *Pid `json:"pid"`
|
||||
ReadonlyRootfs bool `json:"readonly_rootfs"`
|
||||
RemappedRoot *User `json:"remap_root"`
|
||||
SeccompProfile string `json:"seccomp_profile"`
|
||||
UIDMapping []idtools.IDMap `json:"uidmapping"`
|
||||
UTS *UTS `json:"uts"`
|
||||
}
|
||||
|
||||
// SetRootPropagation sets the root mount propagation mode.
|
||||
func SetRootPropagation(config *configs.Config, propagation int) {
|
||||
config.RootPropagation = propagation
|
||||
}
|
||||
|
||||
// InitContainer is the initialization of a container config.
|
||||
// It returns the initial configs for a container. It's mostly
|
||||
// defined by the default template.
|
||||
func InitContainer(c *Command) *configs.Config {
|
||||
container := template.New()
|
||||
|
||||
container.Hostname = getEnv("HOSTNAME", c.ProcessConfig.Env)
|
||||
container.Cgroups.Name = c.ID
|
||||
container.Cgroups.Resources.AllowedDevices = c.AllowedDevices
|
||||
container.Devices = c.AutoCreatedDevices
|
||||
container.Rootfs = c.Rootfs
|
||||
container.Readonlyfs = c.ReadonlyRootfs
|
||||
// This can be overridden later by driver during mount setup based
|
||||
// on volume options
|
||||
SetRootPropagation(container, mount.RPRIVATE)
|
||||
container.Cgroups.Parent = c.CgroupParent
|
||||
|
||||
// check to see if we are running in ramdisk to disable pivot root
|
||||
container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != ""
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
func getEnv(key string, env []string) string {
|
||||
for _, pair := range env {
|
||||
parts := strings.SplitN(pair, "=", 2)
|
||||
if parts[0] == key {
|
||||
return parts[1]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// SetupCgroups setups cgroup resources for a container.
|
||||
func SetupCgroups(container *configs.Config, c *Command) error {
|
||||
if c.Resources != nil {
|
||||
container.Cgroups.Resources.CpuShares = c.Resources.CPUShares
|
||||
container.Cgroups.Resources.Memory = c.Resources.Memory
|
||||
container.Cgroups.Resources.MemoryReservation = c.Resources.MemoryReservation
|
||||
container.Cgroups.Resources.MemorySwap = c.Resources.MemorySwap
|
||||
container.Cgroups.Resources.KernelMemory = c.Resources.KernelMemory
|
||||
container.Cgroups.Resources.CpusetCpus = c.Resources.CpusetCpus
|
||||
container.Cgroups.Resources.CpusetMems = c.Resources.CpusetMems
|
||||
container.Cgroups.Resources.CpuPeriod = c.Resources.CPUPeriod
|
||||
container.Cgroups.Resources.CpuQuota = c.Resources.CPUQuota
|
||||
container.Cgroups.Resources.BlkioWeight = c.Resources.BlkioWeight
|
||||
container.Cgroups.Resources.BlkioWeightDevice = c.Resources.BlkioWeightDevice
|
||||
container.Cgroups.Resources.BlkioThrottleReadBpsDevice = c.Resources.BlkioThrottleReadBpsDevice
|
||||
container.Cgroups.Resources.BlkioThrottleWriteBpsDevice = c.Resources.BlkioThrottleWriteBpsDevice
|
||||
container.Cgroups.Resources.BlkioThrottleReadIOPSDevice = c.Resources.BlkioThrottleReadIOpsDevice
|
||||
container.Cgroups.Resources.BlkioThrottleWriteIOPSDevice = c.Resources.BlkioThrottleWriteIOpsDevice
|
||||
container.Cgroups.Resources.OomKillDisable = c.Resources.OomKillDisable
|
||||
container.Cgroups.Resources.MemorySwappiness = c.Resources.MemorySwappiness
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns the network statistics for the network interfaces represented by the NetworkRuntimeInfo.
|
||||
func getNetworkInterfaceStats(interfaceName string) (*libcontainer.NetworkInterface, error) {
|
||||
out := &libcontainer.NetworkInterface{Name: interfaceName}
|
||||
// This can happen if the network runtime information is missing - possible if the
|
||||
// container was created by an old version of libcontainer.
|
||||
if interfaceName == "" {
|
||||
return out, nil
|
||||
}
|
||||
type netStatsPair struct {
|
||||
// Where to write the output.
|
||||
Out *uint64
|
||||
// The network stats file to read.
|
||||
File string
|
||||
}
|
||||
// Ingress for host veth is from the container. Hence tx_bytes stat on the host veth is actually number of bytes received by the container.
|
||||
netStats := []netStatsPair{
|
||||
{Out: &out.RxBytes, File: "tx_bytes"},
|
||||
{Out: &out.RxPackets, File: "tx_packets"},
|
||||
{Out: &out.RxErrors, File: "tx_errors"},
|
||||
{Out: &out.RxDropped, File: "tx_dropped"},
|
||||
|
||||
{Out: &out.TxBytes, File: "rx_bytes"},
|
||||
{Out: &out.TxPackets, File: "rx_packets"},
|
||||
{Out: &out.TxErrors, File: "rx_errors"},
|
||||
{Out: &out.TxDropped, File: "rx_dropped"},
|
||||
}
|
||||
for _, netStat := range netStats {
|
||||
data, err := readSysfsNetworkStats(interfaceName, netStat.File)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
*(netStat.Out) = data
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Reads the specified statistics available under /sys/class/net/<EthInterface>/statistics
|
||||
func readSysfsNetworkStats(ethInterface, statsFile string) (uint64, error) {
|
||||
data, err := ioutil.ReadFile(filepath.Join("/sys/class/net", ethInterface, "statistics", statsFile))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64)
|
||||
}
|
||||
|
||||
// Stats collects all the resource usage information from a container.
|
||||
func Stats(containerDir string, containerMemoryLimit int64, machineMemory int64) (*ResourceStats, error) {
|
||||
f, err := os.Open(filepath.Join(containerDir, "state.json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
type network struct {
|
||||
Type string
|
||||
HostInterfaceName string
|
||||
}
|
||||
|
||||
state := struct {
|
||||
CgroupPaths map[string]string `json:"cgroup_paths"`
|
||||
Networks []network
|
||||
}{}
|
||||
|
||||
if err := json.NewDecoder(f).Decode(&state); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
now := time.Now()
|
||||
|
||||
mgr := fs.Manager{Paths: state.CgroupPaths}
|
||||
cstats, err := mgr.GetStats()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats := &libcontainer.Stats{CgroupStats: cstats}
|
||||
// if the container does not have any memory limit specified set the
|
||||
// limit to the machines memory
|
||||
memoryLimit := containerMemoryLimit
|
||||
if memoryLimit == 0 {
|
||||
memoryLimit = machineMemory
|
||||
}
|
||||
for _, iface := range state.Networks {
|
||||
switch iface.Type {
|
||||
case "veth":
|
||||
istats, err := getNetworkInterfaceStats(iface.HostInterfaceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.Interfaces = append(stats.Interfaces, istats)
|
||||
}
|
||||
}
|
||||
return &ResourceStats{
|
||||
Stats: stats,
|
||||
Read: now,
|
||||
MemoryLimit: memoryLimit,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// User contains the uid and gid representing a Unix user
|
||||
type User struct {
|
||||
UID int `json:"root_uid"`
|
||||
GID int `json:"root_gid"`
|
||||
}
|
||||
|
||||
// ExitStatus provides exit reasons for a container.
|
||||
type ExitStatus struct {
|
||||
// The exit code with which the container exited.
|
||||
ExitCode int
|
||||
|
||||
// Whether the container encountered an OOM.
|
||||
OOMKilled bool
|
||||
}
|
||||
65
vendor/github.com/hyperhq/hypercli/daemon/execdriver/driver_windows.go
generated
vendored
Normal file
65
vendor/github.com/hyperhq/hypercli/daemon/execdriver/driver_windows.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
package execdriver
|
||||
|
||||
import "github.com/docker/go-connections/nat"
|
||||
|
||||
// Mount contains information for a mount operation.
|
||||
type Mount struct {
|
||||
Source string `json:"source"`
|
||||
Destination string `json:"destination"`
|
||||
Writable bool `json:"writable"`
|
||||
}
|
||||
|
||||
// Resources contains all resource configs for a driver.
|
||||
// Currently these are all for cgroup configs.
|
||||
type Resources struct {
|
||||
CommonResources
|
||||
|
||||
// Fields below here are platform specific
|
||||
}
|
||||
|
||||
// ProcessConfig is the platform specific structure that describes a process
|
||||
// that will be run inside a container.
|
||||
type ProcessConfig struct {
|
||||
CommonProcessConfig
|
||||
|
||||
// Fields below here are platform specific
|
||||
ConsoleSize [2]int `json:"-"` // h,w of initial console size
|
||||
}
|
||||
|
||||
// Network settings of the container
|
||||
type Network struct {
|
||||
Interface *NetworkInterface `json:"interface"`
|
||||
ContainerID string `json:"container_id"` // id of the container to join network.
|
||||
}
|
||||
|
||||
// NetworkInterface contains network configs for a driver
|
||||
type NetworkInterface struct {
|
||||
MacAddress string `json:"mac"`
|
||||
Bridge string `json:"bridge"`
|
||||
IPAddress string `json:"ip"`
|
||||
|
||||
// PortBindings is the port mapping between the exposed port in the
|
||||
// container and the port on the host.
|
||||
PortBindings nat.PortMap `json:"port_bindings"`
|
||||
}
|
||||
|
||||
// Command wraps an os/exec.Cmd to add more metadata
|
||||
type Command struct {
|
||||
CommonCommand
|
||||
|
||||
// Fields below here are platform specific
|
||||
|
||||
FirstStart bool `json:"first_start"` // Optimisation for first boot of Windows
|
||||
Hostname string `json:"hostname"` // Windows sets the hostname in the execdriver
|
||||
LayerFolder string `json:"layer_folder"` // Layer folder for a command
|
||||
LayerPaths []string `json:"layer_paths"` // Layer paths for a command
|
||||
Isolation string `json:"isolation"` // Isolation level for the container
|
||||
ArgsEscaped bool `json:"args_escaped"` // True if args are already escaped
|
||||
HvPartition bool `json:"hv_partition"` // True if it's an hypervisor partition
|
||||
}
|
||||
|
||||
// ExitStatus provides exit reasons for a container.
|
||||
type ExitStatus struct {
|
||||
// The exit code with which the container exited.
|
||||
ExitCode int
|
||||
}
|
||||
15
vendor/github.com/hyperhq/hypercli/daemon/execdriver/execdrivers/execdrivers_freebsd.go
generated
vendored
Normal file
15
vendor/github.com/hyperhq/hypercli/daemon/execdriver/execdrivers/execdrivers_freebsd.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// +build freebsd
|
||||
|
||||
package execdrivers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver"
|
||||
"github.com/hyperhq/hypercli/pkg/sysinfo"
|
||||
)
|
||||
|
||||
// NewDriver returns a new execdriver.Driver from the given name configured with the provided options.
|
||||
func NewDriver(options []string, root, libPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) {
|
||||
return nil, fmt.Errorf("jail driver not yet supported on FreeBSD")
|
||||
}
|
||||
16
vendor/github.com/hyperhq/hypercli/daemon/execdriver/execdrivers/execdrivers_linux.go
generated
vendored
Normal file
16
vendor/github.com/hyperhq/hypercli/daemon/execdriver/execdrivers/execdrivers_linux.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
// +build linux
|
||||
|
||||
package execdrivers
|
||||
|
||||
import (
|
||||
"path"
|
||||
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver"
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver/native"
|
||||
"github.com/hyperhq/hypercli/pkg/sysinfo"
|
||||
)
|
||||
|
||||
// NewDriver returns a new execdriver.Driver from the given name configured with the provided options.
|
||||
func NewDriver(options []string, root, libPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) {
|
||||
return native.NewDriver(path.Join(root, "execdriver", "native"), options)
|
||||
}
|
||||
14
vendor/github.com/hyperhq/hypercli/daemon/execdriver/execdrivers/execdrivers_windows.go
generated
vendored
Normal file
14
vendor/github.com/hyperhq/hypercli/daemon/execdriver/execdrivers/execdrivers_windows.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
// +build windows
|
||||
|
||||
package execdrivers
|
||||
|
||||
import (
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver"
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver/windows"
|
||||
"github.com/hyperhq/hypercli/pkg/sysinfo"
|
||||
)
|
||||
|
||||
// NewDriver returns a new execdriver.Driver from the given name configured with the provided options.
|
||||
func NewDriver(options []string, root, libPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) {
|
||||
return windows.NewDriver(root, options)
|
||||
}
|
||||
510
vendor/github.com/hyperhq/hypercli/daemon/execdriver/native/create.go
generated
vendored
Normal file
510
vendor/github.com/hyperhq/hypercli/daemon/execdriver/native/create.go
generated
vendored
Normal file
@@ -0,0 +1,510 @@
|
||||
// +build linux,cgo
|
||||
|
||||
package native
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver"
|
||||
derr "github.com/hyperhq/hypercli/errors"
|
||||
"github.com/hyperhq/hypercli/pkg/mount"
|
||||
"github.com/hyperhq/hypercli/profiles/seccomp"
|
||||
|
||||
"github.com/hyperhq/hypercli/volume"
|
||||
"github.com/opencontainers/runc/libcontainer/apparmor"
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
"github.com/opencontainers/runc/libcontainer/devices"
|
||||
)
|
||||
|
||||
// createContainer populates and configures the container type with the
|
||||
// data provided by the execdriver.Command
|
||||
func (d *Driver) createContainer(c *execdriver.Command, hooks execdriver.Hooks) (container *configs.Config, err error) {
|
||||
container = execdriver.InitContainer(c)
|
||||
|
||||
if err := d.createIpc(container, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := d.createPid(container, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := d.createUTS(container, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := d.setupRemappedRoot(container, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := d.createNetwork(container, c, hooks); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.ProcessConfig.Privileged {
|
||||
if !container.Readonlyfs {
|
||||
// clear readonly for /sys
|
||||
for i := range container.Mounts {
|
||||
if container.Mounts[i].Destination == "/sys" {
|
||||
container.Mounts[i].Flags &= ^syscall.MS_RDONLY
|
||||
}
|
||||
}
|
||||
container.ReadonlyPaths = nil
|
||||
}
|
||||
|
||||
// clear readonly for cgroup
|
||||
for i := range container.Mounts {
|
||||
if container.Mounts[i].Device == "cgroup" {
|
||||
container.Mounts[i].Flags &= ^syscall.MS_RDONLY
|
||||
}
|
||||
}
|
||||
|
||||
container.MaskPaths = nil
|
||||
if err := d.setPrivileged(container); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err := d.setCapabilities(container, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.SeccompProfile == "" {
|
||||
container.Seccomp = seccomp.GetDefaultProfile()
|
||||
}
|
||||
}
|
||||
// add CAP_ prefix to all caps for new libcontainer update to match
|
||||
// the spec format.
|
||||
for i, s := range container.Capabilities {
|
||||
if !strings.HasPrefix(s, "CAP_") {
|
||||
container.Capabilities[i] = fmt.Sprintf("CAP_%s", s)
|
||||
}
|
||||
}
|
||||
container.AdditionalGroups = c.GroupAdd
|
||||
|
||||
if c.AppArmorProfile != "" {
|
||||
container.AppArmorProfile = c.AppArmorProfile
|
||||
}
|
||||
|
||||
if c.SeccompProfile != "" && c.SeccompProfile != "unconfined" {
|
||||
container.Seccomp, err = seccomp.LoadProfile(c.SeccompProfile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := execdriver.SetupCgroups(container, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
container.OomScoreAdj = c.OomScoreAdj
|
||||
|
||||
if container.Readonlyfs {
|
||||
for i := range container.Mounts {
|
||||
switch container.Mounts[i].Destination {
|
||||
case "/proc", "/dev", "/dev/pts":
|
||||
continue
|
||||
}
|
||||
container.Mounts[i].Flags |= syscall.MS_RDONLY
|
||||
}
|
||||
|
||||
/* These paths must be remounted as r/o */
|
||||
container.ReadonlyPaths = append(container.ReadonlyPaths, "/dev")
|
||||
}
|
||||
|
||||
if err := d.setupMounts(container, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d.setupLabels(container, c)
|
||||
d.setupRlimits(container, c)
|
||||
return container, nil
|
||||
}
|
||||
|
||||
func (d *Driver) createNetwork(container *configs.Config, c *execdriver.Command, hooks execdriver.Hooks) error {
|
||||
if c.Network == nil {
|
||||
return nil
|
||||
}
|
||||
if c.Network.ContainerID != "" {
|
||||
d.Lock()
|
||||
active := d.activeContainers[c.Network.ContainerID]
|
||||
d.Unlock()
|
||||
|
||||
if active == nil {
|
||||
return fmt.Errorf("%s is not a valid running container to join", c.Network.ContainerID)
|
||||
}
|
||||
|
||||
state, err := active.State()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
container.Namespaces.Add(configs.NEWNET, state.NamespacePaths[configs.NEWNET])
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.Network.NamespacePath != "" {
|
||||
container.Namespaces.Add(configs.NEWNET, c.Network.NamespacePath)
|
||||
return nil
|
||||
}
|
||||
// only set up prestart hook if the namespace path is not set (this should be
|
||||
// all cases *except* for --net=host shared networking)
|
||||
container.Hooks = &configs.Hooks{
|
||||
Prestart: []configs.Hook{
|
||||
configs.NewFunctionHook(func(s configs.HookState) error {
|
||||
if len(hooks.PreStart) > 0 {
|
||||
for _, fnHook := range hooks.PreStart {
|
||||
// A closed channel for OOM is returned here as it will be
|
||||
// non-blocking and return the correct result when read.
|
||||
chOOM := make(chan struct{})
|
||||
close(chOOM)
|
||||
if err := fnHook(&c.ProcessConfig, s.Pid, chOOM); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) createIpc(container *configs.Config, c *execdriver.Command) error {
|
||||
if c.Ipc.HostIpc {
|
||||
container.Namespaces.Remove(configs.NEWIPC)
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.Ipc.ContainerID != "" {
|
||||
d.Lock()
|
||||
active := d.activeContainers[c.Ipc.ContainerID]
|
||||
d.Unlock()
|
||||
|
||||
if active == nil {
|
||||
return fmt.Errorf("%s is not a valid running container to join", c.Ipc.ContainerID)
|
||||
}
|
||||
|
||||
state, err := active.State()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.Namespaces.Add(configs.NEWIPC, state.NamespacePaths[configs.NEWIPC])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) createPid(container *configs.Config, c *execdriver.Command) error {
|
||||
if c.Pid.HostPid {
|
||||
container.Namespaces.Remove(configs.NEWPID)
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) createUTS(container *configs.Config, c *execdriver.Command) error {
|
||||
if c.UTS.HostUTS {
|
||||
container.Namespaces.Remove(configs.NEWUTS)
|
||||
container.Hostname = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) setupRemappedRoot(container *configs.Config, c *execdriver.Command) error {
|
||||
if c.RemappedRoot.UID == 0 {
|
||||
container.Namespaces.Remove(configs.NEWUSER)
|
||||
return nil
|
||||
}
|
||||
|
||||
// convert the Docker daemon id map to the libcontainer variant of the same struct
|
||||
// this keeps us from having to import libcontainer code across Docker client + daemon packages
|
||||
cuidMaps := []configs.IDMap{}
|
||||
cgidMaps := []configs.IDMap{}
|
||||
for _, idMap := range c.UIDMapping {
|
||||
cuidMaps = append(cuidMaps, configs.IDMap(idMap))
|
||||
}
|
||||
for _, idMap := range c.GIDMapping {
|
||||
cgidMaps = append(cgidMaps, configs.IDMap(idMap))
|
||||
}
|
||||
container.UidMappings = cuidMaps
|
||||
container.GidMappings = cgidMaps
|
||||
|
||||
for _, node := range container.Devices {
|
||||
node.Uid = uint32(c.RemappedRoot.UID)
|
||||
node.Gid = uint32(c.RemappedRoot.GID)
|
||||
}
|
||||
// TODO: until a kernel/mount solution exists for handling remount in a user namespace,
|
||||
// we must clear the readonly flag for the cgroups mount (@mrunalp concurs)
|
||||
for i := range container.Mounts {
|
||||
if container.Mounts[i].Device == "cgroup" {
|
||||
container.Mounts[i].Flags &= ^syscall.MS_RDONLY
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) setPrivileged(container *configs.Config) (err error) {
|
||||
container.Capabilities = execdriver.GetAllCapabilities()
|
||||
container.Cgroups.Resources.AllowAllDevices = true
|
||||
|
||||
hostDevices, err := devices.HostDevices()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.Devices = hostDevices
|
||||
|
||||
if apparmor.IsEnabled() {
|
||||
container.AppArmorProfile = "unconfined"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) setCapabilities(container *configs.Config, c *execdriver.Command) (err error) {
|
||||
container.Capabilities, err = execdriver.TweakCapabilities(container.Capabilities, c.CapAdd, c.CapDrop)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Driver) setupRlimits(container *configs.Config, c *execdriver.Command) {
|
||||
if c.Resources == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, rlimit := range c.Resources.Rlimits {
|
||||
container.Rlimits = append(container.Rlimits, configs.Rlimit{
|
||||
Type: rlimit.Type,
|
||||
Hard: rlimit.Hard,
|
||||
Soft: rlimit.Soft,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// If rootfs mount propagation is RPRIVATE, that means all the volumes are
|
||||
// going to be private anyway. There is no need to apply per volume
|
||||
// propagation on top. This is just an optimization so that cost of per volume
|
||||
// propagation is paid only if user decides to make some volume non-private
|
||||
// which will force rootfs mount propagation to be non RPRIVATE.
|
||||
func checkResetVolumePropagation(container *configs.Config) {
|
||||
if container.RootPropagation != mount.RPRIVATE {
|
||||
return
|
||||
}
|
||||
for _, m := range container.Mounts {
|
||||
m.PropagationFlags = nil
|
||||
}
|
||||
}
|
||||
|
||||
func getMountInfo(mountinfo []*mount.Info, dir string) *mount.Info {
|
||||
for _, m := range mountinfo {
|
||||
if m.Mountpoint == dir {
|
||||
return m
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the source mount point of directory passed in as argument. Also return
|
||||
// optional fields.
|
||||
func getSourceMount(source string) (string, string, error) {
|
||||
// Ensure any symlinks are resolved.
|
||||
sourcePath, err := filepath.EvalSymlinks(source)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
mountinfos, err := mount.GetMounts()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
mountinfo := getMountInfo(mountinfos, sourcePath)
|
||||
if mountinfo != nil {
|
||||
return sourcePath, mountinfo.Optional, nil
|
||||
}
|
||||
|
||||
path := sourcePath
|
||||
for {
|
||||
path = filepath.Dir(path)
|
||||
|
||||
mountinfo = getMountInfo(mountinfos, path)
|
||||
if mountinfo != nil {
|
||||
return path, mountinfo.Optional, nil
|
||||
}
|
||||
|
||||
if path == "/" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If we are here, we did not find parent mount. Something is wrong.
|
||||
return "", "", fmt.Errorf("Could not find source mount of %s", source)
|
||||
}
|
||||
|
||||
// Ensure mount point on which path is mounted, is shared.
|
||||
func ensureShared(path string) error {
|
||||
sharedMount := false
|
||||
|
||||
sourceMount, optionalOpts, err := getSourceMount(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Make sure source mount point is shared.
|
||||
optsSplit := strings.Split(optionalOpts, " ")
|
||||
for _, opt := range optsSplit {
|
||||
if strings.HasPrefix(opt, "shared:") {
|
||||
sharedMount = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !sharedMount {
|
||||
return fmt.Errorf("Path %s is mounted on %s but it is not a shared mount.", path, sourceMount)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ensure mount point on which path is mounted, is either shared or slave.
|
||||
func ensureSharedOrSlave(path string) error {
|
||||
sharedMount := false
|
||||
slaveMount := false
|
||||
|
||||
sourceMount, optionalOpts, err := getSourceMount(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Make sure source mount point is shared.
|
||||
optsSplit := strings.Split(optionalOpts, " ")
|
||||
for _, opt := range optsSplit {
|
||||
if strings.HasPrefix(opt, "shared:") {
|
||||
sharedMount = true
|
||||
break
|
||||
} else if strings.HasPrefix(opt, "master:") {
|
||||
slaveMount = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !sharedMount && !slaveMount {
|
||||
return fmt.Errorf("Path %s is mounted on %s but it is not a shared or slave mount.", path, sourceMount)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) setupMounts(container *configs.Config, c *execdriver.Command) error {
|
||||
userMounts := make(map[string]struct{})
|
||||
for _, m := range c.Mounts {
|
||||
userMounts[m.Destination] = struct{}{}
|
||||
}
|
||||
|
||||
// Filter out mounts that are overridden by user supplied mounts
|
||||
var defaultMounts []*configs.Mount
|
||||
_, mountDev := userMounts["/dev"]
|
||||
for _, m := range container.Mounts {
|
||||
if _, ok := userMounts[m.Destination]; !ok {
|
||||
if mountDev && strings.HasPrefix(m.Destination, "/dev/") {
|
||||
container.Devices = nil
|
||||
continue
|
||||
}
|
||||
defaultMounts = append(defaultMounts, m)
|
||||
}
|
||||
}
|
||||
container.Mounts = defaultMounts
|
||||
|
||||
mountPropagationMap := map[string]int{
|
||||
"private": mount.PRIVATE,
|
||||
"rprivate": mount.RPRIVATE,
|
||||
"shared": mount.SHARED,
|
||||
"rshared": mount.RSHARED,
|
||||
"slave": mount.SLAVE,
|
||||
"rslave": mount.RSLAVE,
|
||||
}
|
||||
|
||||
for _, m := range c.Mounts {
|
||||
for _, cm := range container.Mounts {
|
||||
if cm.Destination == m.Destination {
|
||||
return derr.ErrorCodeMountDup.WithArgs(m.Destination)
|
||||
}
|
||||
}
|
||||
|
||||
if m.Source == "tmpfs" {
|
||||
var (
|
||||
data = "size=65536k"
|
||||
flags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
|
||||
err error
|
||||
)
|
||||
if m.Data != "" {
|
||||
flags, data, err = mount.ParseTmpfsOptions(m.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
container.Mounts = append(container.Mounts, &configs.Mount{
|
||||
Source: m.Source,
|
||||
Destination: m.Destination,
|
||||
Data: data,
|
||||
Device: "tmpfs",
|
||||
Flags: flags,
|
||||
PropagationFlags: []int{mountPropagationMap[volume.DefaultPropagationMode]},
|
||||
})
|
||||
continue
|
||||
}
|
||||
flags := syscall.MS_BIND | syscall.MS_REC
|
||||
var pFlag int
|
||||
if !m.Writable {
|
||||
flags |= syscall.MS_RDONLY
|
||||
}
|
||||
|
||||
// Determine property of RootPropagation based on volume
|
||||
// properties. If a volume is shared, then keep root propagation
|
||||
// shared. This should work for slave and private volumes too.
|
||||
//
|
||||
// For slave volumes, it can be either [r]shared/[r]slave.
|
||||
//
|
||||
// For private volumes any root propagation value should work.
|
||||
|
||||
pFlag = mountPropagationMap[m.Propagation]
|
||||
if pFlag == mount.SHARED || pFlag == mount.RSHARED {
|
||||
if err := ensureShared(m.Source); err != nil {
|
||||
return err
|
||||
}
|
||||
rootpg := container.RootPropagation
|
||||
if rootpg != mount.SHARED && rootpg != mount.RSHARED {
|
||||
execdriver.SetRootPropagation(container, mount.SHARED)
|
||||
}
|
||||
} else if pFlag == mount.SLAVE || pFlag == mount.RSLAVE {
|
||||
if err := ensureSharedOrSlave(m.Source); err != nil {
|
||||
return err
|
||||
}
|
||||
rootpg := container.RootPropagation
|
||||
if rootpg != mount.SHARED && rootpg != mount.RSHARED && rootpg != mount.SLAVE && rootpg != mount.RSLAVE {
|
||||
execdriver.SetRootPropagation(container, mount.RSLAVE)
|
||||
}
|
||||
}
|
||||
|
||||
mount := &configs.Mount{
|
||||
Source: m.Source,
|
||||
Destination: m.Destination,
|
||||
Device: "bind",
|
||||
Flags: flags,
|
||||
}
|
||||
|
||||
if pFlag != 0 {
|
||||
mount.PropagationFlags = []int{pFlag}
|
||||
}
|
||||
|
||||
container.Mounts = append(container.Mounts, mount)
|
||||
}
|
||||
|
||||
checkResetVolumePropagation(container)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) setupLabels(container *configs.Config, c *execdriver.Command) {
|
||||
container.ProcessLabel = c.ProcessLabel
|
||||
container.MountLabel = c.MountLabel
|
||||
}
|
||||
582
vendor/github.com/hyperhq/hypercli/daemon/execdriver/native/driver.go
generated
vendored
Normal file
582
vendor/github.com/hyperhq/hypercli/daemon/execdriver/native/driver.go
generated
vendored
Normal file
@@ -0,0 +1,582 @@
|
||||
// +build linux,cgo
|
||||
|
||||
package native
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver"
|
||||
"github.com/hyperhq/hypercli/pkg/parsers"
|
||||
"github.com/hyperhq/hypercli/pkg/pools"
|
||||
"github.com/hyperhq/hypercli/pkg/reexec"
|
||||
sysinfo "github.com/hyperhq/hypercli/pkg/system"
|
||||
"github.com/hyperhq/hypercli/pkg/term"
|
||||
aaprofile "github.com/hyperhq/hypercli/profiles/apparmor"
|
||||
"github.com/opencontainers/runc/libcontainer"
|
||||
"github.com/opencontainers/runc/libcontainer/apparmor"
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups/systemd"
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
"github.com/opencontainers/runc/libcontainer/system"
|
||||
"github.com/opencontainers/runc/libcontainer/utils"
|
||||
)
|
||||
|
||||
// Define constants for native driver
|
||||
const (
|
||||
DriverName = "native"
|
||||
Version = "0.2"
|
||||
|
||||
defaultApparmorProfile = "docker-default"
|
||||
)
|
||||
|
||||
// Driver contains all information for native driver,
|
||||
// it implements execdriver.Driver.
|
||||
type Driver struct {
|
||||
root string
|
||||
activeContainers map[string]libcontainer.Container
|
||||
machineMemory int64
|
||||
factory libcontainer.Factory
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewDriver returns a new native driver, called from NewDriver of execdriver.
|
||||
func NewDriver(root string, options []string) (*Driver, error) {
|
||||
meminfo, err := sysinfo.ReadMemInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := sysinfo.MkdirAll(root, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if apparmor.IsEnabled() {
|
||||
if err := aaprofile.InstallDefault(defaultApparmorProfile); err != nil {
|
||||
apparmorProfiles := []string{defaultApparmorProfile}
|
||||
|
||||
// Allow daemon to run if loading failed, but are active
|
||||
// (possibly through another run, manually, or via system startup)
|
||||
for _, policy := range apparmorProfiles {
|
||||
if err := aaprofile.IsLoaded(policy); err != nil {
|
||||
return nil, fmt.Errorf("AppArmor enabled on system but the %s profile could not be loaded.", policy)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// choose cgroup manager
|
||||
// this makes sure there are no breaking changes to people
|
||||
// who upgrade from versions without native.cgroupdriver opt
|
||||
cgm := libcontainer.Cgroupfs
|
||||
|
||||
// parse the options
|
||||
for _, option := range options {
|
||||
key, val, err := parsers.ParseKeyValueOpt(option)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key = strings.ToLower(key)
|
||||
switch key {
|
||||
case "native.cgroupdriver":
|
||||
// override the default if they set options
|
||||
switch val {
|
||||
case "systemd":
|
||||
if systemd.UseSystemd() {
|
||||
cgm = libcontainer.SystemdCgroups
|
||||
} else {
|
||||
// warn them that they chose the wrong driver
|
||||
logrus.Warn("You cannot use systemd as native.cgroupdriver, using cgroupfs instead")
|
||||
}
|
||||
case "cgroupfs":
|
||||
cgm = libcontainer.Cgroupfs
|
||||
default:
|
||||
return nil, fmt.Errorf("Unknown native.cgroupdriver given %q. try cgroupfs or systemd", val)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("Unknown option %s\n", key)
|
||||
}
|
||||
}
|
||||
|
||||
f, err := libcontainer.New(
|
||||
root,
|
||||
cgm,
|
||||
libcontainer.InitPath(reexec.Self(), DriverName),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Driver{
|
||||
root: root,
|
||||
activeContainers: make(map[string]libcontainer.Container),
|
||||
machineMemory: meminfo.MemTotal,
|
||||
factory: f,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type execOutput struct {
|
||||
exitCode int
|
||||
err error
|
||||
}
|
||||
|
||||
// Run implements the exec driver Driver interface,
|
||||
// it calls libcontainer APIs to run a container.
|
||||
func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execdriver.Hooks) (execdriver.ExitStatus, error) {
|
||||
destroyed := false
|
||||
var err error
|
||||
c.TmpDir, err = ioutil.TempDir("", c.ID)
|
||||
if err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
defer os.RemoveAll(c.TmpDir)
|
||||
|
||||
// take the Command and populate the libcontainer.Config from it
|
||||
container, err := d.createContainer(c, hooks)
|
||||
if err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
|
||||
p := &libcontainer.Process{
|
||||
Args: append([]string{c.ProcessConfig.Entrypoint}, c.ProcessConfig.Arguments...),
|
||||
Env: c.ProcessConfig.Env,
|
||||
Cwd: c.WorkingDir,
|
||||
User: c.ProcessConfig.User,
|
||||
}
|
||||
|
||||
if err := setupPipes(container, &c.ProcessConfig, p, pipes); err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
|
||||
cont, err := d.factory.Create(c.ID, container)
|
||||
if err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
d.Lock()
|
||||
d.activeContainers[c.ID] = cont
|
||||
d.Unlock()
|
||||
defer func() {
|
||||
if !destroyed {
|
||||
cont.Destroy()
|
||||
}
|
||||
d.cleanContainer(c.ID)
|
||||
}()
|
||||
|
||||
if err := cont.Start(p); err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
|
||||
// 'oom' is used to emit 'oom' events to the eventstream, 'oomKilled' is used
|
||||
// to set the 'OOMKilled' flag in state
|
||||
oom := notifyOnOOM(cont)
|
||||
oomKilled := notifyOnOOM(cont)
|
||||
if hooks.Start != nil {
|
||||
pid, err := p.Pid()
|
||||
if err != nil {
|
||||
p.Signal(os.Kill)
|
||||
p.Wait()
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
hooks.Start(&c.ProcessConfig, pid, oom)
|
||||
}
|
||||
|
||||
waitF := p.Wait
|
||||
if nss := cont.Config().Namespaces; !nss.Contains(configs.NEWPID) {
|
||||
// we need such hack for tracking processes with inherited fds,
|
||||
// because cmd.Wait() waiting for all streams to be copied
|
||||
waitF = waitInPIDHost(p, cont)
|
||||
}
|
||||
ps, err := waitF()
|
||||
if err != nil {
|
||||
execErr, ok := err.(*exec.ExitError)
|
||||
if !ok {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
ps = execErr.ProcessState
|
||||
}
|
||||
cont.Destroy()
|
||||
destroyed = true
|
||||
// oomKilled will have an oom event if any process within the container was
|
||||
// OOM killed at any time, not only if the init process OOMed.
|
||||
//
|
||||
// Perhaps we only want the OOMKilled flag to be set if the OOM
|
||||
// resulted in a container death, but there isn't a good way to do this
|
||||
// because the kernel's cgroup oom notification does not provide information
|
||||
// such as the PID. This could be heuristically done by checking that the OOM
|
||||
// happened within some very small time slice for the container dying (and
|
||||
// optionally exit-code 137), but I don't think the cgroup oom notification
|
||||
// can be used to reliably determine this
|
||||
//
|
||||
// Even if there were multiple OOMs, it's sufficient to read one value
|
||||
// because libcontainer's oom notify will discard the channel after the
|
||||
// cgroup is destroyed
|
||||
_, oomKill := <-oomKilled
|
||||
return execdriver.ExitStatus{ExitCode: utils.ExitStatus(ps.Sys().(syscall.WaitStatus)), OOMKilled: oomKill}, nil
|
||||
}
|
||||
|
||||
// notifyOnOOM returns a channel that signals if the container received an OOM notification
|
||||
// for any process. If it is unable to subscribe to OOM notifications then a closed
|
||||
// channel is returned as it will be non-blocking and return the correct result when read.
|
||||
func notifyOnOOM(container libcontainer.Container) <-chan struct{} {
|
||||
oom, err := container.NotifyOOM()
|
||||
if err != nil {
|
||||
logrus.Warnf("Your kernel does not support OOM notifications: %s", err)
|
||||
c := make(chan struct{})
|
||||
close(c)
|
||||
return c
|
||||
}
|
||||
return oom
|
||||
}
|
||||
|
||||
func killCgroupProcs(c libcontainer.Container) {
|
||||
var procs []*os.Process
|
||||
if err := c.Pause(); err != nil {
|
||||
logrus.Warn(err)
|
||||
}
|
||||
pids, err := c.Processes()
|
||||
if err != nil {
|
||||
// don't care about childs if we can't get them, this is mostly because cgroup already deleted
|
||||
logrus.Warnf("Failed to get processes from container %s: %v", c.ID(), err)
|
||||
}
|
||||
for _, pid := range pids {
|
||||
if p, err := os.FindProcess(pid); err == nil {
|
||||
procs = append(procs, p)
|
||||
if err := p.Kill(); err != nil {
|
||||
logrus.Warn(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := c.Resume(); err != nil {
|
||||
logrus.Warn(err)
|
||||
}
|
||||
for _, p := range procs {
|
||||
if _, err := p.Wait(); err != nil {
|
||||
logrus.Warn(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func waitInPIDHost(p *libcontainer.Process, c libcontainer.Container) func() (*os.ProcessState, error) {
|
||||
return func() (*os.ProcessState, error) {
|
||||
pid, err := p.Pid()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
process, err := os.FindProcess(pid)
|
||||
s, err := process.Wait()
|
||||
if err != nil {
|
||||
execErr, ok := err.(*exec.ExitError)
|
||||
if !ok {
|
||||
return s, err
|
||||
}
|
||||
s = execErr.ProcessState
|
||||
}
|
||||
killCgroupProcs(c)
|
||||
p.Wait()
|
||||
return s, err
|
||||
}
|
||||
}
|
||||
|
||||
// Kill implements the exec driver Driver interface.
|
||||
func (d *Driver) Kill(c *execdriver.Command, sig int) error {
|
||||
d.Lock()
|
||||
active := d.activeContainers[c.ID]
|
||||
d.Unlock()
|
||||
if active == nil {
|
||||
return fmt.Errorf("active container for %s does not exist", c.ID)
|
||||
}
|
||||
state, err := active.State()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return syscall.Kill(state.InitProcessPid, syscall.Signal(sig))
|
||||
}
|
||||
|
||||
// Pause implements the exec driver Driver interface,
|
||||
// it calls libcontainer API to pause a container.
|
||||
func (d *Driver) Pause(c *execdriver.Command) error {
|
||||
d.Lock()
|
||||
active := d.activeContainers[c.ID]
|
||||
d.Unlock()
|
||||
if active == nil {
|
||||
return fmt.Errorf("active container for %s does not exist", c.ID)
|
||||
}
|
||||
return active.Pause()
|
||||
}
|
||||
|
||||
// Unpause implements the exec driver Driver interface,
|
||||
// it calls libcontainer API to unpause a container.
|
||||
func (d *Driver) Unpause(c *execdriver.Command) error {
|
||||
d.Lock()
|
||||
active := d.activeContainers[c.ID]
|
||||
d.Unlock()
|
||||
if active == nil {
|
||||
return fmt.Errorf("active container for %s does not exist", c.ID)
|
||||
}
|
||||
return active.Resume()
|
||||
}
|
||||
|
||||
// Terminate implements the exec driver Driver interface.
|
||||
func (d *Driver) Terminate(c *execdriver.Command) error {
|
||||
defer d.cleanContainer(c.ID)
|
||||
container, err := d.factory.Load(c.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer container.Destroy()
|
||||
state, err := container.State()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pid := state.InitProcessPid
|
||||
currentStartTime, err := system.GetProcessStartTime(pid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if state.InitProcessStartTime == currentStartTime {
|
||||
err = syscall.Kill(pid, 9)
|
||||
syscall.Wait4(pid, nil, 0, nil)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Name implements the exec driver Driver interface.
|
||||
func (d *Driver) Name() string {
|
||||
return fmt.Sprintf("%s-%s", DriverName, Version)
|
||||
}
|
||||
|
||||
// GetPidsForContainer implements the exec driver Driver interface.
|
||||
func (d *Driver) GetPidsForContainer(id string) ([]int, error) {
|
||||
d.Lock()
|
||||
active := d.activeContainers[id]
|
||||
d.Unlock()
|
||||
|
||||
if active == nil {
|
||||
return nil, fmt.Errorf("active container for %s does not exist", id)
|
||||
}
|
||||
return active.Processes()
|
||||
}
|
||||
|
||||
func (d *Driver) cleanContainer(id string) error {
|
||||
d.Lock()
|
||||
delete(d.activeContainers, id)
|
||||
d.Unlock()
|
||||
return os.RemoveAll(filepath.Join(d.root, id))
|
||||
}
|
||||
|
||||
func (d *Driver) createContainerRoot(id string) error {
|
||||
return os.MkdirAll(filepath.Join(d.root, id), 0655)
|
||||
}
|
||||
|
||||
// Clean implements the exec driver Driver interface.
|
||||
func (d *Driver) Clean(id string) error {
|
||||
return os.RemoveAll(filepath.Join(d.root, id))
|
||||
}
|
||||
|
||||
// Stats implements the exec driver Driver interface.
|
||||
func (d *Driver) Stats(id string) (*execdriver.ResourceStats, error) {
|
||||
d.Lock()
|
||||
c := d.activeContainers[id]
|
||||
d.Unlock()
|
||||
if c == nil {
|
||||
return nil, execdriver.ErrNotRunning
|
||||
}
|
||||
now := time.Now()
|
||||
stats, err := c.Stats()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
memoryLimit := c.Config().Cgroups.Resources.Memory
|
||||
// if the container does not have any memory limit specified set the
|
||||
// limit to the machines memory
|
||||
if memoryLimit == 0 {
|
||||
memoryLimit = d.machineMemory
|
||||
}
|
||||
return &execdriver.ResourceStats{
|
||||
Stats: stats,
|
||||
Read: now,
|
||||
MemoryLimit: memoryLimit,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Update updates configs for a container
|
||||
func (d *Driver) Update(c *execdriver.Command) error {
|
||||
d.Lock()
|
||||
cont := d.activeContainers[c.ID]
|
||||
d.Unlock()
|
||||
if cont == nil {
|
||||
return execdriver.ErrNotRunning
|
||||
}
|
||||
config := cont.Config()
|
||||
if err := execdriver.SetupCgroups(&config, c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cont.Set(config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TtyConsole implements the exec driver Terminal interface.
|
||||
type TtyConsole struct {
|
||||
console libcontainer.Console
|
||||
}
|
||||
|
||||
// NewTtyConsole returns a new TtyConsole struct.
|
||||
func NewTtyConsole(console libcontainer.Console, pipes *execdriver.Pipes) (*TtyConsole, error) {
|
||||
tty := &TtyConsole{
|
||||
console: console,
|
||||
}
|
||||
|
||||
if err := tty.AttachPipes(pipes); err != nil {
|
||||
tty.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tty, nil
|
||||
}
|
||||
|
||||
// Resize implements Resize method of Terminal interface
|
||||
func (t *TtyConsole) Resize(h, w int) error {
|
||||
return term.SetWinsize(t.console.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)})
|
||||
}
|
||||
|
||||
// AttachPipes attaches given pipes to TtyConsole
|
||||
func (t *TtyConsole) AttachPipes(pipes *execdriver.Pipes) error {
|
||||
go func() {
|
||||
if wb, ok := pipes.Stdout.(interface {
|
||||
CloseWriters() error
|
||||
}); ok {
|
||||
defer wb.CloseWriters()
|
||||
}
|
||||
|
||||
pools.Copy(pipes.Stdout, t.console)
|
||||
}()
|
||||
|
||||
if pipes.Stdin != nil {
|
||||
go func() {
|
||||
pools.Copy(t.console, pipes.Stdin)
|
||||
|
||||
pipes.Stdin.Close()
|
||||
}()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements Close method of Terminal interface
|
||||
func (t *TtyConsole) Close() error {
|
||||
return t.console.Close()
|
||||
}
|
||||
|
||||
func setupPipes(container *configs.Config, processConfig *execdriver.ProcessConfig, p *libcontainer.Process, pipes *execdriver.Pipes) error {
|
||||
|
||||
rootuid, err := container.HostUID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if processConfig.Tty {
|
||||
cons, err := p.NewConsole(rootuid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
term, err := NewTtyConsole(cons, pipes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
processConfig.Terminal = term
|
||||
return nil
|
||||
}
|
||||
// not a tty--set up stdio pipes
|
||||
term := &execdriver.StdConsole{}
|
||||
processConfig.Terminal = term
|
||||
|
||||
// if we are not in a user namespace, there is no reason to go through
|
||||
// the hassle of setting up os-level pipes with proper (remapped) ownership
|
||||
// so we will do the prior shortcut for non-userns containers
|
||||
if rootuid == 0 {
|
||||
p.Stdout = pipes.Stdout
|
||||
p.Stderr = pipes.Stderr
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pipes.Stdin != nil {
|
||||
go func() {
|
||||
io.Copy(w, pipes.Stdin)
|
||||
w.Close()
|
||||
}()
|
||||
p.Stdin = r
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// if we have user namespaces enabled (rootuid != 0), we will set
|
||||
// up os pipes for stderr, stdout, stdin so we can chown them to
|
||||
// the proper ownership to allow for proper access to the underlying
|
||||
// fds
|
||||
var fds []int
|
||||
|
||||
//setup stdout
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fds = append(fds, int(r.Fd()), int(w.Fd()))
|
||||
if pipes.Stdout != nil {
|
||||
go io.Copy(pipes.Stdout, r)
|
||||
}
|
||||
term.Closers = append(term.Closers, r)
|
||||
p.Stdout = w
|
||||
|
||||
//setup stderr
|
||||
r, w, err = os.Pipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fds = append(fds, int(r.Fd()), int(w.Fd()))
|
||||
if pipes.Stderr != nil {
|
||||
go io.Copy(pipes.Stderr, r)
|
||||
}
|
||||
term.Closers = append(term.Closers, r)
|
||||
p.Stderr = w
|
||||
|
||||
//setup stdin
|
||||
r, w, err = os.Pipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fds = append(fds, int(r.Fd()), int(w.Fd()))
|
||||
if pipes.Stdin != nil {
|
||||
go func() {
|
||||
io.Copy(w, pipes.Stdin)
|
||||
w.Close()
|
||||
}()
|
||||
p.Stdin = r
|
||||
}
|
||||
for _, fd := range fds {
|
||||
if err := syscall.Fchown(fd, rootuid, rootuid); err != nil {
|
||||
return fmt.Errorf("Failed to chown pipes fd: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SupportsHooks implements the execdriver Driver interface.
|
||||
// The libcontainer/runC-based native execdriver does exploit the hook mechanism
|
||||
func (d *Driver) SupportsHooks() bool {
|
||||
return true
|
||||
}
|
||||
14
vendor/github.com/hyperhq/hypercli/daemon/execdriver/native/driver_unsupported.go
generated
vendored
Normal file
14
vendor/github.com/hyperhq/hypercli/daemon/execdriver/native/driver_unsupported.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
// +build !linux
|
||||
|
||||
package native
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver"
|
||||
)
|
||||
|
||||
// NewDriver returns a new native driver, called from NewDriver of execdriver.
|
||||
func NewDriver(root string, options []string) (execdriver.Driver, error) {
|
||||
return nil, fmt.Errorf("native driver not supported on non-linux")
|
||||
}
|
||||
14
vendor/github.com/hyperhq/hypercli/daemon/execdriver/native/driver_unsupported_nocgo.go
generated
vendored
Normal file
14
vendor/github.com/hyperhq/hypercli/daemon/execdriver/native/driver_unsupported_nocgo.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
// +build linux,!cgo
|
||||
|
||||
package native
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver"
|
||||
)
|
||||
|
||||
// NewDriver returns a new native driver, called from NewDriver of execdriver.
|
||||
func NewDriver(root string, options []string) (execdriver.Driver, error) {
|
||||
return nil, fmt.Errorf("native driver not supported on non-linux")
|
||||
}
|
||||
87
vendor/github.com/hyperhq/hypercli/daemon/execdriver/native/exec.go
generated
vendored
Normal file
87
vendor/github.com/hyperhq/hypercli/daemon/execdriver/native/exec.go
generated
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
// +build linux
|
||||
|
||||
package native
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver"
|
||||
"github.com/opencontainers/runc/libcontainer"
|
||||
// Blank import 'nsenter' so that init in that package will call c
|
||||
// function 'nsexec()' to do 'setns' before Go runtime take over,
|
||||
// it's used for join to exist ns like 'docker exec' command.
|
||||
_ "github.com/opencontainers/runc/libcontainer/nsenter"
|
||||
"github.com/opencontainers/runc/libcontainer/utils"
|
||||
)
|
||||
|
||||
// Exec implements the exec driver Driver interface,
|
||||
// it calls libcontainer APIs to execute a container.
|
||||
func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, hooks execdriver.Hooks) (int, error) {
|
||||
active := d.activeContainers[c.ID]
|
||||
if active == nil {
|
||||
return -1, fmt.Errorf("No active container exists with ID %s", c.ID)
|
||||
}
|
||||
|
||||
user := processConfig.User
|
||||
if c.RemappedRoot.UID != 0 && user == "" {
|
||||
//if user namespaces are enabled, set user explicitly so uid/gid is set to 0
|
||||
//otherwise we end up with the overflow id and no permissions (65534)
|
||||
user = "0"
|
||||
}
|
||||
|
||||
p := &libcontainer.Process{
|
||||
Args: append([]string{processConfig.Entrypoint}, processConfig.Arguments...),
|
||||
Env: c.ProcessConfig.Env,
|
||||
Cwd: c.WorkingDir,
|
||||
User: user,
|
||||
}
|
||||
|
||||
if processConfig.Privileged {
|
||||
p.Capabilities = execdriver.GetAllCapabilities()
|
||||
}
|
||||
// add CAP_ prefix to all caps for new libcontainer update to match
|
||||
// the spec format.
|
||||
for i, s := range p.Capabilities {
|
||||
if !strings.HasPrefix(s, "CAP_") {
|
||||
p.Capabilities[i] = fmt.Sprintf("CAP_%s", s)
|
||||
}
|
||||
}
|
||||
|
||||
config := active.Config()
|
||||
if err := setupPipes(&config, processConfig, p, pipes); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
if err := active.Start(p); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
if hooks.Start != nil {
|
||||
pid, err := p.Pid()
|
||||
if err != nil {
|
||||
p.Signal(os.Kill)
|
||||
p.Wait()
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// A closed channel for OOM is returned here as it will be
|
||||
// non-blocking and return the correct result when read.
|
||||
chOOM := make(chan struct{})
|
||||
close(chOOM)
|
||||
hooks.Start(&c.ProcessConfig, pid, chOOM)
|
||||
}
|
||||
|
||||
ps, err := p.Wait()
|
||||
if err != nil {
|
||||
exitErr, ok := err.(*exec.ExitError)
|
||||
if !ok {
|
||||
return -1, err
|
||||
}
|
||||
ps = exitErr.ProcessState
|
||||
}
|
||||
return utils.ExitStatus(ps.Sys().(syscall.WaitStatus)), nil
|
||||
}
|
||||
45
vendor/github.com/hyperhq/hypercli/daemon/execdriver/native/init.go
generated
vendored
Normal file
45
vendor/github.com/hyperhq/hypercli/daemon/execdriver/native/init.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
// +build linux
|
||||
|
||||
package native
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/hyperhq/hypercli/pkg/reexec"
|
||||
"github.com/opencontainers/runc/libcontainer"
|
||||
)
|
||||
|
||||
func init() {
|
||||
reexec.Register(DriverName, initializer)
|
||||
}
|
||||
|
||||
func fatal(err error) {
|
||||
if lerr, ok := err.(libcontainer.Error); ok {
|
||||
lerr.Detail(os.Stderr)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func initializer() {
|
||||
runtime.GOMAXPROCS(1)
|
||||
runtime.LockOSThread()
|
||||
factory, err := libcontainer.New("")
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
if err := factory.StartInitialization(); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func writeError(err error) {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
100
vendor/github.com/hyperhq/hypercli/daemon/execdriver/native/template/default_template_linux.go
generated
vendored
Normal file
100
vendor/github.com/hyperhq/hypercli/daemon/execdriver/native/template/default_template_linux.go
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer/apparmor"
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
)
|
||||
|
||||
const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
|
||||
|
||||
// New returns the docker default configuration for libcontainer
|
||||
func New() *configs.Config {
|
||||
container := &configs.Config{
|
||||
Capabilities: []string{
|
||||
"CHOWN",
|
||||
"DAC_OVERRIDE",
|
||||
"FSETID",
|
||||
"FOWNER",
|
||||
"MKNOD",
|
||||
"NET_RAW",
|
||||
"SETGID",
|
||||
"SETUID",
|
||||
"SETFCAP",
|
||||
"SETPCAP",
|
||||
"NET_BIND_SERVICE",
|
||||
"SYS_CHROOT",
|
||||
"KILL",
|
||||
"AUDIT_WRITE",
|
||||
},
|
||||
Namespaces: configs.Namespaces([]configs.Namespace{
|
||||
{Type: "NEWNS"},
|
||||
{Type: "NEWUTS"},
|
||||
{Type: "NEWIPC"},
|
||||
{Type: "NEWPID"},
|
||||
{Type: "NEWNET"},
|
||||
{Type: "NEWUSER"},
|
||||
}),
|
||||
Cgroups: &configs.Cgroup{
|
||||
ScopePrefix: "docker", // systemd only
|
||||
Resources: &configs.Resources{
|
||||
AllowAllDevices: false,
|
||||
MemorySwappiness: -1,
|
||||
},
|
||||
},
|
||||
Mounts: []*configs.Mount{
|
||||
{
|
||||
Source: "proc",
|
||||
Destination: "/proc",
|
||||
Device: "proc",
|
||||
Flags: defaultMountFlags,
|
||||
},
|
||||
{
|
||||
Source: "tmpfs",
|
||||
Destination: "/dev",
|
||||
Device: "tmpfs",
|
||||
Flags: syscall.MS_NOSUID | syscall.MS_STRICTATIME,
|
||||
Data: "mode=755",
|
||||
},
|
||||
{
|
||||
Source: "devpts",
|
||||
Destination: "/dev/pts",
|
||||
Device: "devpts",
|
||||
Flags: syscall.MS_NOSUID | syscall.MS_NOEXEC,
|
||||
Data: "newinstance,ptmxmode=0666,mode=0620,gid=5",
|
||||
},
|
||||
{
|
||||
Source: "sysfs",
|
||||
Destination: "/sys",
|
||||
Device: "sysfs",
|
||||
Flags: defaultMountFlags | syscall.MS_RDONLY,
|
||||
},
|
||||
{
|
||||
Source: "cgroup",
|
||||
Destination: "/sys/fs/cgroup",
|
||||
Device: "cgroup",
|
||||
Flags: defaultMountFlags | syscall.MS_RDONLY,
|
||||
},
|
||||
},
|
||||
MaskPaths: []string{
|
||||
"/proc/kcore",
|
||||
"/proc/latency_stats",
|
||||
"/proc/timer_stats",
|
||||
},
|
||||
ReadonlyPaths: []string{
|
||||
"/proc/asound",
|
||||
"/proc/bus",
|
||||
"/proc/fs",
|
||||
"/proc/irq",
|
||||
"/proc/sys",
|
||||
"/proc/sysrq-trigger",
|
||||
},
|
||||
}
|
||||
|
||||
if apparmor.IsEnabled() {
|
||||
container.AppArmorProfile = "docker-default"
|
||||
}
|
||||
|
||||
return container
|
||||
}
|
||||
3
vendor/github.com/hyperhq/hypercli/daemon/execdriver/native/template/default_template_unsupported.go
generated
vendored
Normal file
3
vendor/github.com/hyperhq/hypercli/daemon/execdriver/native/template/default_template_unsupported.go
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
// +build !linux
|
||||
|
||||
package template
|
||||
24
vendor/github.com/hyperhq/hypercli/daemon/execdriver/pipes.go
generated
vendored
Normal file
24
vendor/github.com/hyperhq/hypercli/daemon/execdriver/pipes.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
package execdriver
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// Pipes is a wrapper around a container's output for
|
||||
// stdin, stdout, stderr
|
||||
type Pipes struct {
|
||||
Stdin io.ReadCloser
|
||||
Stdout, Stderr io.Writer
|
||||
}
|
||||
|
||||
// NewPipes returns a wrapper around a container's output
|
||||
func NewPipes(stdin io.ReadCloser, stdout, stderr io.Writer, useStdin bool) *Pipes {
|
||||
p := &Pipes{
|
||||
Stdout: stdout,
|
||||
Stderr: stderr,
|
||||
}
|
||||
if useStdin {
|
||||
p.Stdin = stdin
|
||||
}
|
||||
return p
|
||||
}
|
||||
55
vendor/github.com/hyperhq/hypercli/daemon/execdriver/termconsole.go
generated
vendored
Normal file
55
vendor/github.com/hyperhq/hypercli/daemon/execdriver/termconsole.go
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
package execdriver
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// StdConsole defines standard console operations for execdriver
|
||||
type StdConsole struct {
|
||||
// Closers holds io.Closer references for closing at terminal close time
|
||||
Closers []io.Closer
|
||||
}
|
||||
|
||||
// NewStdConsole returns a new StdConsole struct
|
||||
func NewStdConsole(processConfig *ProcessConfig, pipes *Pipes) (*StdConsole, error) {
|
||||
std := &StdConsole{}
|
||||
|
||||
if err := std.AttachPipes(&processConfig.Cmd, pipes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return std, nil
|
||||
}
|
||||
|
||||
// AttachPipes attaches given pipes to exec.Cmd
|
||||
func (s *StdConsole) AttachPipes(command *exec.Cmd, pipes *Pipes) error {
|
||||
command.Stdout = pipes.Stdout
|
||||
command.Stderr = pipes.Stderr
|
||||
|
||||
if pipes.Stdin != nil {
|
||||
stdin, err := command.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer stdin.Close()
|
||||
io.Copy(stdin, pipes.Stdin)
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resize implements Resize method of Terminal interface
|
||||
func (s *StdConsole) Resize(h, w int) error {
|
||||
// we do not need to resize a non tty
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements Close method of Terminal interface
|
||||
func (s *StdConsole) Close() error {
|
||||
for _, c := range s.Closers {
|
||||
c.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
125
vendor/github.com/hyperhq/hypercli/daemon/execdriver/utils_unix.go
generated
vendored
Normal file
125
vendor/github.com/hyperhq/hypercli/daemon/execdriver/utils_unix.go
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
// +build !windows
|
||||
|
||||
package execdriver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hyperhq/hypercli/pkg/stringutils"
|
||||
"github.com/syndtr/gocapability/capability"
|
||||
)
|
||||
|
||||
var capabilityList Capabilities
|
||||
|
||||
func init() {
|
||||
last := capability.CAP_LAST_CAP
|
||||
// hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap
|
||||
if last == capability.Cap(63) {
|
||||
last = capability.CAP_BLOCK_SUSPEND
|
||||
}
|
||||
for _, cap := range capability.List() {
|
||||
if cap > last {
|
||||
continue
|
||||
}
|
||||
capabilityList = append(capabilityList,
|
||||
&CapabilityMapping{
|
||||
Key: strings.ToUpper(cap.String()),
|
||||
Value: cap,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
type (
|
||||
// CapabilityMapping maps linux capability name to its value of capability.Cap type
|
||||
// Capabilities is one of the security systems in Linux Security Module (LSM)
|
||||
// framework provided by the kernel.
|
||||
// For more details on capabilities, see http://man7.org/linux/man-pages/man7/capabilities.7.html
|
||||
CapabilityMapping struct {
|
||||
Key string `json:"key,omitempty"`
|
||||
Value capability.Cap `json:"value,omitempty"`
|
||||
}
|
||||
// Capabilities contains all CapabilityMapping
|
||||
Capabilities []*CapabilityMapping
|
||||
)
|
||||
|
||||
// String returns <key> of CapabilityMapping
|
||||
func (c *CapabilityMapping) String() string {
|
||||
return c.Key
|
||||
}
|
||||
|
||||
// GetCapability returns CapabilityMapping which contains specific key
|
||||
func GetCapability(key string) *CapabilityMapping {
|
||||
for _, capp := range capabilityList {
|
||||
if capp.Key == key {
|
||||
cpy := *capp
|
||||
return &cpy
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAllCapabilities returns all of the capabilities
|
||||
func GetAllCapabilities() []string {
|
||||
output := make([]string, len(capabilityList))
|
||||
for i, capability := range capabilityList {
|
||||
output[i] = capability.String()
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
// TweakCapabilities can tweak capabilities by adding or dropping capabilities
|
||||
// based on the basics capabilities.
|
||||
func TweakCapabilities(basics, adds, drops []string) ([]string, error) {
|
||||
var (
|
||||
newCaps []string
|
||||
allCaps = GetAllCapabilities()
|
||||
)
|
||||
|
||||
// look for invalid cap in the drop list
|
||||
for _, cap := range drops {
|
||||
if strings.ToLower(cap) == "all" {
|
||||
continue
|
||||
}
|
||||
if !stringutils.InSlice(allCaps, cap) {
|
||||
return nil, fmt.Errorf("Unknown capability drop: %q", cap)
|
||||
}
|
||||
}
|
||||
|
||||
// handle --cap-add=all
|
||||
if stringutils.InSlice(adds, "all") {
|
||||
basics = allCaps
|
||||
}
|
||||
|
||||
if !stringutils.InSlice(drops, "all") {
|
||||
for _, cap := range basics {
|
||||
// skip `all` already handled above
|
||||
if strings.ToLower(cap) == "all" {
|
||||
continue
|
||||
}
|
||||
|
||||
// if we don't drop `all`, add back all the non-dropped caps
|
||||
if !stringutils.InSlice(drops, cap) {
|
||||
newCaps = append(newCaps, strings.ToUpper(cap))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, cap := range adds {
|
||||
// skip `all` already handled above
|
||||
if strings.ToLower(cap) == "all" {
|
||||
continue
|
||||
}
|
||||
|
||||
if !stringutils.InSlice(allCaps, cap) {
|
||||
return nil, fmt.Errorf("Unknown capability to add: %q", cap)
|
||||
}
|
||||
|
||||
// add cap if not already in the list
|
||||
if !stringutils.InSlice(newCaps, cap) {
|
||||
newCaps = append(newCaps, strings.ToUpper(cap))
|
||||
}
|
||||
}
|
||||
return newCaps, nil
|
||||
}
|
||||
8
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/clean.go
generated
vendored
Normal file
8
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/clean.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
// Clean implements the exec driver Driver interface.
|
||||
func (d *Driver) Clean(id string) error {
|
||||
return nil
|
||||
}
|
||||
36
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/commandlinebuilder.go
generated
vendored
Normal file
36
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/commandlinebuilder.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
//+build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver"
|
||||
)
|
||||
|
||||
// createCommandLine creates a command line from the Entrypoint and args
|
||||
// of the ProcessConfig. It escapes the arguments if they are not already
|
||||
// escaped
|
||||
func createCommandLine(processConfig *execdriver.ProcessConfig, alreadyEscaped bool) (commandLine string, err error) {
|
||||
// While this should get caught earlier, just in case, validate that we
|
||||
// have something to run.
|
||||
if processConfig.Entrypoint == "" {
|
||||
return "", errors.New("No entrypoint specified")
|
||||
}
|
||||
|
||||
// Build the command line of the process
|
||||
commandLine = processConfig.Entrypoint
|
||||
logrus.Debugf("Entrypoint: %s", processConfig.Entrypoint)
|
||||
for _, arg := range processConfig.Arguments {
|
||||
logrus.Debugf("appending %s", arg)
|
||||
if !alreadyEscaped {
|
||||
arg = syscall.EscapeArg(arg)
|
||||
}
|
||||
commandLine += " " + arg
|
||||
}
|
||||
|
||||
logrus.Debugf("commandLine: %s", commandLine)
|
||||
return commandLine, nil
|
||||
}
|
||||
89
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/exec.go
generated
vendored
Normal file
89
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/exec.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Microsoft/hcsshim"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver"
|
||||
)
|
||||
|
||||
// Exec implements the exec driver Driver interface.
|
||||
func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, hooks execdriver.Hooks) (int, error) {
|
||||
|
||||
var (
|
||||
term execdriver.Terminal
|
||||
err error
|
||||
exitCode int32
|
||||
errno uint32
|
||||
)
|
||||
|
||||
active := d.activeContainers[c.ID]
|
||||
if active == nil {
|
||||
return -1, fmt.Errorf("Exec - No active container exists with ID %s", c.ID)
|
||||
}
|
||||
|
||||
createProcessParms := hcsshim.CreateProcessParams{
|
||||
EmulateConsole: processConfig.Tty, // Note NOT c.ProcessConfig.Tty
|
||||
WorkingDirectory: c.WorkingDir,
|
||||
}
|
||||
|
||||
// Configure the environment for the process // Note NOT c.ProcessConfig.Env
|
||||
createProcessParms.Environment = setupEnvironmentVariables(processConfig.Env)
|
||||
|
||||
// Create the commandline for the process // Note NOT c.ProcessConfig
|
||||
createProcessParms.CommandLine, err = createCommandLine(processConfig, false)
|
||||
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// Start the command running in the container.
|
||||
pid, stdin, stdout, stderr, rc, err := hcsshim.CreateProcessInComputeSystem(c.ID, pipes.Stdin != nil, true, !processConfig.Tty, createProcessParms)
|
||||
if err != nil {
|
||||
// TODO Windows: TP4 Workaround. In Hyper-V containers, there is a limitation
|
||||
// of one exec per container. This should be fixed post TP4. CreateProcessInComputeSystem
|
||||
// will return a specific error which we handle here to give a good error message
|
||||
// back to the user instead of an inactionable "An invalid argument was supplied"
|
||||
if rc == hcsshim.Win32InvalidArgument {
|
||||
return -1, fmt.Errorf("The limit of docker execs per Hyper-V container has been exceeded")
|
||||
}
|
||||
logrus.Errorf("CreateProcessInComputeSystem() failed %s", err)
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// Now that the process has been launched, begin copying data to and from
|
||||
// the named pipes for the std handles.
|
||||
setupPipes(stdin, stdout, stderr, pipes)
|
||||
|
||||
// Note NOT c.ProcessConfig.Tty
|
||||
if processConfig.Tty {
|
||||
term = NewTtyConsole(c.ID, pid)
|
||||
} else {
|
||||
term = NewStdConsole()
|
||||
}
|
||||
processConfig.Terminal = term
|
||||
|
||||
// Invoke the start callback
|
||||
if hooks.Start != nil {
|
||||
// A closed channel for OOM is returned here as it will be
|
||||
// non-blocking and return the correct result when read.
|
||||
chOOM := make(chan struct{})
|
||||
close(chOOM)
|
||||
hooks.Start(&c.ProcessConfig, int(pid), chOOM)
|
||||
}
|
||||
|
||||
if exitCode, errno, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid, hcsshim.TimeoutInfinite); err != nil {
|
||||
if errno == hcsshim.Win32PipeHasBeenEnded {
|
||||
logrus.Debugf("Exiting Run() after WaitForProcessInComputeSystem failed with recognised error 0x%X", errno)
|
||||
return hcsshim.WaitErrExecFailed, nil
|
||||
}
|
||||
logrus.Warnf("WaitForProcessInComputeSystem failed (container may have been killed): 0x%X %s", errno, err)
|
||||
return -1, err
|
||||
}
|
||||
|
||||
logrus.Debugln("Exiting Run()", c.ID)
|
||||
return int(exitCode), nil
|
||||
}
|
||||
11
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/getpids.go
generated
vendored
Normal file
11
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/getpids.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import "fmt"
|
||||
|
||||
// GetPidsForContainer implements the exec driver Driver interface.
|
||||
func (d *Driver) GetPidsForContainer(id string) ([]int, error) {
|
||||
// TODO Windows: Implementation required.
|
||||
return nil, fmt.Errorf("GetPidsForContainer: GetPidsForContainer() not implemented")
|
||||
}
|
||||
63
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/namedpipes.go
generated
vendored
Normal file
63
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/namedpipes.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver"
|
||||
)
|
||||
|
||||
// General comment. Handling I/O for a container is very different to Linux.
|
||||
// We use a named pipe to HCS to copy I/O both in and out of the container,
|
||||
// very similar to how docker daemon communicates with a CLI.
|
||||
|
||||
// startStdinCopy asynchronously copies an io.Reader to the container's
|
||||
// process's stdin pipe and closes the pipe when there is no more data to copy.
|
||||
func startStdinCopy(dst io.WriteCloser, src io.Reader) {
|
||||
|
||||
// Anything that comes from the client stdin should be copied
|
||||
// across to the stdin named pipe of the container.
|
||||
go func() {
|
||||
defer dst.Close()
|
||||
bytes, err := io.Copy(dst, src)
|
||||
log := fmt.Sprintf("Copied %d bytes from stdin.", bytes)
|
||||
if err != nil {
|
||||
log = log + " err=" + err.Error()
|
||||
}
|
||||
logrus.Debugf(log)
|
||||
}()
|
||||
}
|
||||
|
||||
// startStdouterrCopy asynchronously copies data from the container's process's
|
||||
// stdout or stderr pipe to an io.Writer and closes the pipe when there is no
|
||||
// more data to copy.
|
||||
func startStdouterrCopy(dst io.Writer, src io.ReadCloser, name string) {
|
||||
// Anything that comes from the container named pipe stdout/err should be copied
|
||||
// across to the stdout/err of the client
|
||||
go func() {
|
||||
defer src.Close()
|
||||
bytes, err := io.Copy(dst, src)
|
||||
log := fmt.Sprintf("Copied %d bytes from %s.", bytes, name)
|
||||
if err != nil {
|
||||
log = log + " err=" + err.Error()
|
||||
}
|
||||
logrus.Debugf(log)
|
||||
}()
|
||||
}
|
||||
|
||||
// setupPipes starts the asynchronous copying of data to and from the named
|
||||
// pipes used byt he HCS for the std handles.
|
||||
func setupPipes(stdin io.WriteCloser, stdout, stderr io.ReadCloser, pipes *execdriver.Pipes) {
|
||||
if stdin != nil {
|
||||
startStdinCopy(stdin, pipes.Stdin)
|
||||
}
|
||||
if stdout != nil {
|
||||
startStdouterrCopy(pipes.Stdout, stdout, "stdout")
|
||||
}
|
||||
if stderr != nil {
|
||||
startStdouterrCopy(pipes.Stderr, stderr, "stderr")
|
||||
}
|
||||
}
|
||||
19
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/pauseunpause.go
generated
vendored
Normal file
19
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/pauseunpause.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver"
|
||||
)
|
||||
|
||||
// Pause implements the exec driver Driver interface.
|
||||
func (d *Driver) Pause(c *execdriver.Command) error {
|
||||
return fmt.Errorf("Windows: Containers cannot be paused")
|
||||
}
|
||||
|
||||
// Unpause implements the exec driver Driver interface.
|
||||
func (d *Driver) Unpause(c *execdriver.Command) error {
|
||||
return fmt.Errorf("Windows: Containers cannot be paused")
|
||||
}
|
||||
359
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/run.go
generated
vendored
Normal file
359
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/run.go
generated
vendored
Normal file
@@ -0,0 +1,359 @@
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Microsoft/hcsshim"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver"
|
||||
)
|
||||
|
||||
// defaultContainerNAT is the default name of the container NAT device that is
|
||||
// preconfigured on the server.
|
||||
const defaultContainerNAT = "ContainerNAT"
|
||||
|
||||
type layer struct {
|
||||
ID string
|
||||
Path string
|
||||
}
|
||||
|
||||
type defConfig struct {
|
||||
DefFile string
|
||||
}
|
||||
|
||||
type portBinding struct {
|
||||
Protocol string
|
||||
InternalPort int
|
||||
ExternalPort int
|
||||
}
|
||||
|
||||
type natSettings struct {
|
||||
Name string
|
||||
PortBindings []portBinding
|
||||
}
|
||||
|
||||
type networkConnection struct {
|
||||
NetworkName string
|
||||
// TODO Windows: Add Ip4Address string to this structure when hooked up in
|
||||
// docker CLI. This is present in the HCS JSON handler.
|
||||
EnableNat bool
|
||||
Nat natSettings
|
||||
}
|
||||
type networkSettings struct {
|
||||
MacAddress string
|
||||
}
|
||||
|
||||
type device struct {
|
||||
DeviceType string
|
||||
Connection interface{}
|
||||
Settings interface{}
|
||||
}
|
||||
|
||||
type mappedDir struct {
|
||||
HostPath string
|
||||
ContainerPath string
|
||||
ReadOnly bool
|
||||
}
|
||||
|
||||
type containerInit struct {
|
||||
SystemType string // HCS requires this to be hard-coded to "Container"
|
||||
Name string // Name of the container. We use the docker ID.
|
||||
Owner string // The management platform that created this container
|
||||
IsDummy bool // Used for development purposes.
|
||||
VolumePath string // Windows volume path for scratch space
|
||||
Devices []device // Devices used by the container
|
||||
IgnoreFlushesDuringBoot bool // Optimisation hint for container startup in Windows
|
||||
LayerFolderPath string // Where the layer folders are located
|
||||
Layers []layer // List of storage layers
|
||||
ProcessorWeight int64 `json:",omitempty"` // CPU Shares 0..10000 on Windows; where 0 will be ommited and HCS will default.
|
||||
HostName string // Hostname
|
||||
MappedDirectories []mappedDir // List of mapped directories (volumes/mounts)
|
||||
SandboxPath string // Location of unmounted sandbox (used for Hyper-V containers, not Windows Server containers)
|
||||
HvPartition bool // True if it a Hyper-V Container
|
||||
}
|
||||
|
||||
// defaultOwner is a tag passed to HCS to allow it to differentiate between
|
||||
// container creator management stacks. We hard code "docker" in the case
|
||||
// of docker.
|
||||
const defaultOwner = "docker"
|
||||
|
||||
// Run implements the exec driver Driver interface
|
||||
func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execdriver.Hooks) (execdriver.ExitStatus, error) {
|
||||
|
||||
var (
|
||||
term execdriver.Terminal
|
||||
err error
|
||||
)
|
||||
|
||||
cu := &containerInit{
|
||||
SystemType: "Container",
|
||||
Name: c.ID,
|
||||
Owner: defaultOwner,
|
||||
IsDummy: dummyMode,
|
||||
VolumePath: c.Rootfs,
|
||||
IgnoreFlushesDuringBoot: c.FirstStart,
|
||||
LayerFolderPath: c.LayerFolder,
|
||||
ProcessorWeight: c.Resources.CPUShares,
|
||||
HostName: c.Hostname,
|
||||
}
|
||||
|
||||
cu.HvPartition = c.HvPartition
|
||||
|
||||
if cu.HvPartition {
|
||||
cu.SandboxPath = filepath.Dir(c.LayerFolder)
|
||||
} else {
|
||||
cu.VolumePath = c.Rootfs
|
||||
cu.LayerFolderPath = c.LayerFolder
|
||||
}
|
||||
|
||||
for _, layerPath := range c.LayerPaths {
|
||||
_, filename := filepath.Split(layerPath)
|
||||
g, err := hcsshim.NameToGuid(filename)
|
||||
if err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
cu.Layers = append(cu.Layers, layer{
|
||||
ID: g.ToString(),
|
||||
Path: layerPath,
|
||||
})
|
||||
}
|
||||
|
||||
// Add the mounts (volumes, bind mounts etc) to the structure
|
||||
mds := make([]mappedDir, len(c.Mounts))
|
||||
for i, mount := range c.Mounts {
|
||||
mds[i] = mappedDir{
|
||||
HostPath: mount.Source,
|
||||
ContainerPath: mount.Destination,
|
||||
ReadOnly: !mount.Writable}
|
||||
}
|
||||
cu.MappedDirectories = mds
|
||||
|
||||
// TODO Windows. At some point, when there is CLI on docker run to
|
||||
// enable the IP Address of the container to be passed into docker run,
|
||||
// the IP Address needs to be wired through to HCS in the JSON. It
|
||||
// would be present in c.Network.Interface.IPAddress. See matching
|
||||
// TODO in daemon\container_windows.go, function populateCommand.
|
||||
|
||||
if c.Network.Interface != nil {
|
||||
|
||||
var pbs []portBinding
|
||||
|
||||
// Enumerate through the port bindings specified by the user and convert
|
||||
// them into the internal structure matching the JSON blob that can be
|
||||
// understood by the HCS.
|
||||
for i, v := range c.Network.Interface.PortBindings {
|
||||
proto := strings.ToUpper(i.Proto())
|
||||
if proto != "TCP" && proto != "UDP" {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid protocol %s", i.Proto())
|
||||
}
|
||||
|
||||
if len(v) > 1 {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("Windows does not support more than one host port in NAT settings")
|
||||
}
|
||||
|
||||
for _, v2 := range v {
|
||||
var (
|
||||
iPort, ePort int
|
||||
err error
|
||||
)
|
||||
if len(v2.HostIP) != 0 {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("Windows does not support host IP addresses in NAT settings")
|
||||
}
|
||||
if ePort, err = strconv.Atoi(v2.HostPort); err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid container port %s: %s", v2.HostPort, err)
|
||||
}
|
||||
if iPort, err = strconv.Atoi(i.Port()); err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid internal port %s: %s", i.Port(), err)
|
||||
}
|
||||
if iPort < 0 || iPort > 65535 || ePort < 0 || ePort > 65535 {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("specified NAT port is not in allowed range")
|
||||
}
|
||||
pbs = append(pbs,
|
||||
portBinding{ExternalPort: ePort,
|
||||
InternalPort: iPort,
|
||||
Protocol: proto})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Windows: TP3 workaround. Allow the user to override the name of
|
||||
// the Container NAT device through an environment variable. This will
|
||||
// ultimately be a global daemon parameter on Windows, similar to -b
|
||||
// for the name of the virtual switch (aka bridge).
|
||||
cn := os.Getenv("DOCKER_CONTAINER_NAT")
|
||||
if len(cn) == 0 {
|
||||
cn = defaultContainerNAT
|
||||
}
|
||||
|
||||
dev := device{
|
||||
DeviceType: "Network",
|
||||
Connection: &networkConnection{
|
||||
NetworkName: c.Network.Interface.Bridge,
|
||||
// TODO Windows: Fixme, next line. Needs HCS fix.
|
||||
EnableNat: false,
|
||||
Nat: natSettings{
|
||||
Name: cn,
|
||||
PortBindings: pbs,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if c.Network.Interface.MacAddress != "" {
|
||||
windowsStyleMAC := strings.Replace(
|
||||
c.Network.Interface.MacAddress, ":", "-", -1)
|
||||
dev.Settings = networkSettings{
|
||||
MacAddress: windowsStyleMAC,
|
||||
}
|
||||
}
|
||||
cu.Devices = append(cu.Devices, dev)
|
||||
} else {
|
||||
logrus.Debugln("No network interface")
|
||||
}
|
||||
|
||||
configurationb, err := json.Marshal(cu)
|
||||
if err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
|
||||
configuration := string(configurationb)
|
||||
|
||||
// TODO Windows TP5 timeframe. Remove when TP4 is no longer supported.
|
||||
// The following a workaround for Windows TP4 which has a networking
|
||||
// bug which fairly frequently returns an error. Back off and retry.
|
||||
maxAttempts := 1
|
||||
if TP4RetryHack {
|
||||
maxAttempts = 5
|
||||
}
|
||||
i := 0
|
||||
for i < maxAttempts {
|
||||
i++
|
||||
err = hcsshim.CreateComputeSystem(c.ID, configuration)
|
||||
if err != nil {
|
||||
if TP4RetryHack {
|
||||
if !strings.Contains(err.Error(), `Win32 API call returned error r1=0x800401f3`) && // Invalid class string
|
||||
!strings.Contains(err.Error(), `Win32 API call returned error r1=0x80070490`) && // Element not found
|
||||
!strings.Contains(err.Error(), `Win32 API call returned error r1=0x80070002`) && // The system cannot find the file specified
|
||||
!strings.Contains(err.Error(), `Win32 API call returned error r1=0x800704c6`) && // The network is not present or not started
|
||||
!strings.Contains(err.Error(), `Win32 API call returned error r1=0x800700a1`) { // The specified path is invalid
|
||||
logrus.Debugln("Failed to create temporary container ", err)
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
logrus.Warnf("Invoking Windows TP4 retry hack (%d of %d)", i, maxAttempts-1)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Start the container
|
||||
logrus.Debugln("Starting container ", c.ID)
|
||||
err = hcsshim.StartComputeSystem(c.ID)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to start compute system: %s", err)
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
defer func() {
|
||||
// Stop the container
|
||||
if forceKill {
|
||||
logrus.Debugf("Forcibly terminating container %s", c.ID)
|
||||
if errno, err := hcsshim.TerminateComputeSystem(c.ID, hcsshim.TimeoutInfinite, "exec-run-defer"); err != nil {
|
||||
logrus.Warnf("Ignoring error from TerminateComputeSystem 0x%X %s", errno, err)
|
||||
}
|
||||
} else {
|
||||
logrus.Debugf("Shutting down container %s", c.ID)
|
||||
if errno, err := hcsshim.ShutdownComputeSystem(c.ID, hcsshim.TimeoutInfinite, "exec-run-defer"); err != nil {
|
||||
if errno != hcsshim.Win32SystemShutdownIsInProgress &&
|
||||
errno != hcsshim.Win32SpecifiedPathInvalid &&
|
||||
errno != hcsshim.Win32SystemCannotFindThePathSpecified {
|
||||
logrus.Warnf("Ignoring error from ShutdownComputeSystem 0x%X %s", errno, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
createProcessParms := hcsshim.CreateProcessParams{
|
||||
EmulateConsole: c.ProcessConfig.Tty,
|
||||
WorkingDirectory: c.WorkingDir,
|
||||
ConsoleSize: c.ProcessConfig.ConsoleSize,
|
||||
}
|
||||
|
||||
// Configure the environment for the process
|
||||
createProcessParms.Environment = setupEnvironmentVariables(c.ProcessConfig.Env)
|
||||
|
||||
createProcessParms.CommandLine, err = createCommandLine(&c.ProcessConfig, c.ArgsEscaped)
|
||||
|
||||
if err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
|
||||
// Start the command running in the container.
|
||||
pid, stdin, stdout, stderr, _, err := hcsshim.CreateProcessInComputeSystem(c.ID, pipes.Stdin != nil, true, !c.ProcessConfig.Tty, createProcessParms)
|
||||
if err != nil {
|
||||
logrus.Errorf("CreateProcessInComputeSystem() failed %s", err)
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
|
||||
// Now that the process has been launched, begin copying data to and from
|
||||
// the named pipes for the std handles.
|
||||
setupPipes(stdin, stdout, stderr, pipes)
|
||||
|
||||
//Save the PID as we'll need this in Kill()
|
||||
logrus.Debugf("PID %d", pid)
|
||||
c.ContainerPid = int(pid)
|
||||
|
||||
if c.ProcessConfig.Tty {
|
||||
term = NewTtyConsole(c.ID, pid)
|
||||
} else {
|
||||
term = NewStdConsole()
|
||||
}
|
||||
c.ProcessConfig.Terminal = term
|
||||
|
||||
// Maintain our list of active containers. We'll need this later for exec
|
||||
// and other commands.
|
||||
d.Lock()
|
||||
d.activeContainers[c.ID] = &activeContainer{
|
||||
command: c,
|
||||
}
|
||||
d.Unlock()
|
||||
|
||||
if hooks.Start != nil {
|
||||
// A closed channel for OOM is returned here as it will be
|
||||
// non-blocking and return the correct result when read.
|
||||
chOOM := make(chan struct{})
|
||||
close(chOOM)
|
||||
hooks.Start(&c.ProcessConfig, int(pid), chOOM)
|
||||
}
|
||||
|
||||
var (
|
||||
exitCode int32
|
||||
errno uint32
|
||||
)
|
||||
exitCode, errno, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid, hcsshim.TimeoutInfinite)
|
||||
if err != nil {
|
||||
if errno != hcsshim.Win32PipeHasBeenEnded {
|
||||
logrus.Warnf("WaitForProcessInComputeSystem failed (container may have been killed): %s", err)
|
||||
}
|
||||
// Do NOT return err here as the container would have
|
||||
// started, otherwise docker will deadlock. It's perfectly legitimate
|
||||
// for WaitForProcessInComputeSystem to fail in situations such
|
||||
// as the container being killed on another thread.
|
||||
return execdriver.ExitStatus{ExitCode: hcsshim.WaitErrExecFailed}, nil
|
||||
}
|
||||
|
||||
logrus.Debugf("Exiting Run() exitCode %d id=%s", exitCode, c.ID)
|
||||
return execdriver.ExitStatus{ExitCode: int(exitCode)}, nil
|
||||
}
|
||||
|
||||
// SupportsHooks implements the execdriver Driver interface.
|
||||
// The windows driver does not support the hook mechanism
|
||||
func (d *Driver) SupportsHooks() bool {
|
||||
return false
|
||||
}
|
||||
14
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/stats.go
generated
vendored
Normal file
14
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/stats.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver"
|
||||
)
|
||||
|
||||
// Stats implements the exec driver Driver interface.
|
||||
func (d *Driver) Stats(id string) (*execdriver.ResourceStats, error) {
|
||||
return nil, fmt.Errorf("Windows: Stats not implemented")
|
||||
}
|
||||
24
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/stdconsole.go
generated
vendored
Normal file
24
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/stdconsole.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
// StdConsole is for when using a container non-interactively
|
||||
type StdConsole struct {
|
||||
}
|
||||
|
||||
// NewStdConsole returns a new StdConsole struct.
|
||||
func NewStdConsole() *StdConsole {
|
||||
return &StdConsole{}
|
||||
}
|
||||
|
||||
// Resize implements Resize method of Terminal interface.
|
||||
func (s *StdConsole) Resize(h, w int) error {
|
||||
// we do not need to resize a non tty
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements Close method of Terminal interface.
|
||||
func (s *StdConsole) Close() error {
|
||||
// nothing to close here
|
||||
return nil
|
||||
}
|
||||
49
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/terminatekill.go
generated
vendored
Normal file
49
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/terminatekill.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
|
||||
"github.com/Microsoft/hcsshim"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver"
|
||||
)
|
||||
|
||||
// Terminate implements the exec driver Driver interface.
|
||||
func (d *Driver) Terminate(p *execdriver.Command) error {
|
||||
return kill(p.ID, p.ContainerPid, syscall.SIGTERM)
|
||||
}
|
||||
|
||||
// Kill implements the exec driver Driver interface.
|
||||
func (d *Driver) Kill(p *execdriver.Command, sig int) error {
|
||||
return kill(p.ID, p.ContainerPid, syscall.Signal(sig))
|
||||
}
|
||||
|
||||
func kill(id string, pid int, sig syscall.Signal) error {
|
||||
logrus.Debugf("WindowsExec: kill() id=%s pid=%d sig=%d", id, pid, sig)
|
||||
var err error
|
||||
context := fmt.Sprintf("kill: sig=%d pid=%d", sig, pid)
|
||||
|
||||
if sig == syscall.SIGKILL || forceKill {
|
||||
// Terminate the compute system
|
||||
if errno, err := hcsshim.TerminateComputeSystem(id, hcsshim.TimeoutInfinite, context); err != nil {
|
||||
logrus.Errorf("Failed to terminate %s - 0x%X %q", id, errno, err)
|
||||
}
|
||||
|
||||
} else {
|
||||
// Terminate Process
|
||||
if err = hcsshim.TerminateProcessInComputeSystem(id, uint32(pid)); err != nil {
|
||||
logrus.Warnf("Failed to terminate pid %d in %s: %q", pid, id, err)
|
||||
// Ignore errors
|
||||
err = nil
|
||||
}
|
||||
|
||||
// Shutdown the compute system
|
||||
if errno, err := hcsshim.ShutdownComputeSystem(id, hcsshim.TimeoutInfinite, context); err != nil {
|
||||
logrus.Errorf("Failed to shutdown %s - 0x%X %q", id, errno, err)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
32
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/ttyconsole.go
generated
vendored
Normal file
32
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/ttyconsole.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"github.com/Microsoft/hcsshim"
|
||||
)
|
||||
|
||||
// TtyConsole implements the exec driver Terminal interface.
|
||||
type TtyConsole struct {
|
||||
id string
|
||||
processid uint32
|
||||
}
|
||||
|
||||
// NewTtyConsole returns a new TtyConsole struct.
|
||||
func NewTtyConsole(id string, processid uint32) *TtyConsole {
|
||||
tty := &TtyConsole{
|
||||
id: id,
|
||||
processid: processid,
|
||||
}
|
||||
return tty
|
||||
}
|
||||
|
||||
// Resize implements Resize method of Terminal interface.
|
||||
func (t *TtyConsole) Resize(h, w int) error {
|
||||
return hcsshim.ResizeConsoleInComputeSystem(t.id, t.processid, h, w)
|
||||
}
|
||||
|
||||
// Close implements Close method of Terminal interface.
|
||||
func (t *TtyConsole) Close() error {
|
||||
return nil
|
||||
}
|
||||
14
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/unsupported.go
generated
vendored
Normal file
14
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/unsupported.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
// +build !windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver"
|
||||
)
|
||||
|
||||
// NewDriver returns a new execdriver.Driver
|
||||
func NewDriver(root, initPath string) (execdriver.Driver, error) {
|
||||
return nil, fmt.Errorf("Windows driver not supported on non-Windows")
|
||||
}
|
||||
14
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/update.go
generated
vendored
Normal file
14
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/update.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver"
|
||||
)
|
||||
|
||||
// Update updates resource configs for a container.
|
||||
func (d *Driver) Update(c *execdriver.Command) error {
|
||||
return fmt.Errorf("Windows: Update not implemented")
|
||||
}
|
||||
146
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/windows.go
generated
vendored
Normal file
146
vendor/github.com/hyperhq/hypercli/daemon/execdriver/windows/windows.go
generated
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/hyperhq/hypercli/daemon/execdriver"
|
||||
"github.com/hyperhq/hypercli/dockerversion"
|
||||
"github.com/hyperhq/hypercli/pkg/parsers"
|
||||
"github.com/docker/engine-api/types/container"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
// TP4RetryHack is a hack to retry CreateComputeSystem if it fails with
|
||||
// known return codes from Windows due to bugs in TP4.
|
||||
var TP4RetryHack bool
|
||||
|
||||
// This is a daemon development variable only and should not be
|
||||
// used for running production containers on Windows.
|
||||
var dummyMode bool
|
||||
|
||||
// This allows the daemon to terminate containers rather than shutdown
|
||||
// This allows the daemon to force kill (HCS terminate) rather than shutdown
|
||||
var forceKill bool
|
||||
|
||||
// DefaultIsolation allows users to specify a default isolation mode for
|
||||
// when running a container on Windows. For example docker daemon -D
|
||||
// --exec-opt isolation=hyperv will cause Windows to always run containers
|
||||
// as Hyper-V containers unless otherwise specified.
|
||||
var DefaultIsolation container.IsolationLevel = "process"
|
||||
|
||||
// Define name and version for windows
|
||||
var (
|
||||
DriverName = "Windows 1854"
|
||||
Version = dockerversion.Version + " " + dockerversion.GitCommit
|
||||
)
|
||||
|
||||
type activeContainer struct {
|
||||
command *execdriver.Command
|
||||
}
|
||||
|
||||
// Driver contains all information for windows driver,
|
||||
// it implements execdriver.Driver
|
||||
type Driver struct {
|
||||
root string
|
||||
activeContainers map[string]*activeContainer
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// Name implements the exec driver Driver interface.
|
||||
func (d *Driver) Name() string {
|
||||
return fmt.Sprintf("\n Name: %s\n Build: %s \n Default Isolation: %s", DriverName, Version, DefaultIsolation)
|
||||
}
|
||||
|
||||
// NewDriver returns a new windows driver, called from NewDriver of execdriver.
|
||||
func NewDriver(root string, options []string) (*Driver, error) {
|
||||
|
||||
for _, option := range options {
|
||||
key, val, err := parsers.ParseKeyValueOpt(option)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key = strings.ToLower(key)
|
||||
switch key {
|
||||
|
||||
case "dummy":
|
||||
switch val {
|
||||
case "1":
|
||||
dummyMode = true
|
||||
logrus.Warn("Using dummy mode in Windows exec driver. This is for development use only!")
|
||||
}
|
||||
|
||||
case "forcekill":
|
||||
switch val {
|
||||
case "1":
|
||||
forceKill = true
|
||||
logrus.Warn("Using force kill mode in Windows exec driver. This is for testing purposes only.")
|
||||
}
|
||||
|
||||
case "isolation":
|
||||
if !container.IsolationLevel(val).IsValid() {
|
||||
return nil, fmt.Errorf("Unrecognised exec driver option 'isolation':'%s'", val)
|
||||
}
|
||||
if container.IsolationLevel(val).IsHyperV() {
|
||||
DefaultIsolation = "hyperv"
|
||||
}
|
||||
logrus.Infof("Windows default isolation level: '%s'", val)
|
||||
default:
|
||||
return nil, fmt.Errorf("Unrecognised exec driver option %s\n", key)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Windows TP5 timeframe. Remove this next block of code once TP4
|
||||
// is no longer supported. Also remove the workaround in run.go.
|
||||
//
|
||||
// Hack for TP4 - determine the version of Windows from the registry.
|
||||
// This overcomes an issue on TP4 which causes CreateComputeSystem to
|
||||
// intermittently fail. It's predominantly here to make Windows to Windows
|
||||
// CI more reliable.
|
||||
TP4RetryHack = false
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return &Driver{}, err
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
s, _, err := k.GetStringValue("BuildLab")
|
||||
if err != nil {
|
||||
return &Driver{}, err
|
||||
}
|
||||
parts := strings.Split(s, ".")
|
||||
if len(parts) < 1 {
|
||||
return &Driver{}, err
|
||||
}
|
||||
var val int
|
||||
if val, err = strconv.Atoi(parts[0]); err != nil {
|
||||
return &Driver{}, err
|
||||
}
|
||||
if val < 14250 {
|
||||
TP4RetryHack = true
|
||||
}
|
||||
// End of Windows TP4 hack
|
||||
|
||||
return &Driver{
|
||||
root: root,
|
||||
activeContainers: make(map[string]*activeContainer),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// setupEnvironmentVariables convert a string array of environment variables
|
||||
// into a map as required by the HCS. Source array is in format [v1=k1] [v2=k2] etc.
|
||||
func setupEnvironmentVariables(a []string) map[string]string {
|
||||
r := make(map[string]string)
|
||||
for _, s := range a {
|
||||
arr := strings.Split(s, "=")
|
||||
if len(arr) == 2 {
|
||||
r[arr[0]] = arr[1]
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
55
vendor/github.com/hyperhq/hypercli/daemon/export.go
generated
vendored
Normal file
55
vendor/github.com/hyperhq/hypercli/daemon/export.go
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/hyperhq/hypercli/container"
|
||||
derr "github.com/hyperhq/hypercli/errors"
|
||||
"github.com/hyperhq/hypercli/pkg/archive"
|
||||
"github.com/hyperhq/hypercli/pkg/ioutils"
|
||||
)
|
||||
|
||||
// ContainerExport writes the contents of the container to the given
|
||||
// writer. An error is returned if the container cannot be found.
|
||||
func (daemon *Daemon) ContainerExport(name string, out io.Writer) error {
|
||||
container, err := daemon.GetContainer(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := daemon.containerExport(container)
|
||||
if err != nil {
|
||||
return derr.ErrorCodeExportFailed.WithArgs(name, err)
|
||||
}
|
||||
defer data.Close()
|
||||
|
||||
// Stream the entire contents of the container (basically a volatile snapshot)
|
||||
if _, err := io.Copy(out, data); err != nil {
|
||||
return derr.ErrorCodeExportFailed.WithArgs(name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (daemon *Daemon) containerExport(container *container.Container) (archive.Archive, error) {
|
||||
if err := daemon.Mount(container); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uidMaps, gidMaps := daemon.GetUIDGIDMaps()
|
||||
archive, err := archive.TarWithOptions(container.BaseFS, &archive.TarOptions{
|
||||
Compression: archive.Uncompressed,
|
||||
UIDMaps: uidMaps,
|
||||
GIDMaps: gidMaps,
|
||||
})
|
||||
if err != nil {
|
||||
daemon.Unmount(container)
|
||||
return nil, err
|
||||
}
|
||||
arch := ioutils.NewReadCloserWrapper(archive, func() error {
|
||||
err := archive.Close()
|
||||
daemon.Unmount(container)
|
||||
return err
|
||||
})
|
||||
daemon.LogContainerEvent(container, "export")
|
||||
return arch, err
|
||||
}
|
||||
557
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/aufs/aufs.go
generated
vendored
Normal file
557
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/aufs/aufs.go
generated
vendored
Normal file
@@ -0,0 +1,557 @@
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
|
||||
aufs driver directory structure
|
||||
|
||||
.
|
||||
├── layers // Metadata of layers
|
||||
│ ├── 1
|
||||
│ ├── 2
|
||||
│ └── 3
|
||||
├── diff // Content of the layer
|
||||
│ ├── 1 // Contains layers that need to be mounted for the id
|
||||
│ ├── 2
|
||||
│ └── 3
|
||||
└── mnt // Mount points for the rw layers to be mounted
|
||||
├── 1
|
||||
├── 2
|
||||
└── 3
|
||||
|
||||
*/
|
||||
|
||||
package aufs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/hyperhq/hypercli/daemon/graphdriver"
|
||||
"github.com/hyperhq/hypercli/pkg/archive"
|
||||
"github.com/hyperhq/hypercli/pkg/chrootarchive"
|
||||
"github.com/hyperhq/hypercli/pkg/directory"
|
||||
"github.com/hyperhq/hypercli/pkg/idtools"
|
||||
mountpk "github.com/hyperhq/hypercli/pkg/mount"
|
||||
"github.com/hyperhq/hypercli/pkg/stringid"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer/label"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrAufsNotSupported is returned if aufs is not supported by the host.
|
||||
ErrAufsNotSupported = fmt.Errorf("AUFS was not found in /proc/filesystems")
|
||||
incompatibleFsMagic = []graphdriver.FsMagic{
|
||||
graphdriver.FsMagicBtrfs,
|
||||
graphdriver.FsMagicAufs,
|
||||
}
|
||||
backingFs = "<unknown>"
|
||||
|
||||
enableDirpermLock sync.Once
|
||||
enableDirperm bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
graphdriver.Register("aufs", Init)
|
||||
}
|
||||
|
||||
type data struct {
|
||||
referenceCount int
|
||||
path string
|
||||
}
|
||||
|
||||
// Driver contains information about the filesystem mounted.
|
||||
// root of the filesystem
|
||||
// sync.Mutex to protect against concurrent modifications
|
||||
// active maps mount id to the count
|
||||
type Driver struct {
|
||||
root string
|
||||
uidMaps []idtools.IDMap
|
||||
gidMaps []idtools.IDMap
|
||||
sync.Mutex // Protects concurrent modification to active
|
||||
active map[string]*data
|
||||
}
|
||||
|
||||
// Init returns a new AUFS driver.
|
||||
// An error is returned if AUFS is not supported.
|
||||
func Init(root string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
|
||||
|
||||
// Try to load the aufs kernel module
|
||||
if err := supportsAufs(); err != nil {
|
||||
return nil, graphdriver.ErrNotSupported
|
||||
}
|
||||
|
||||
fsMagic, err := graphdriver.GetFSMagic(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fsName, ok := graphdriver.FsNames[fsMagic]; ok {
|
||||
backingFs = fsName
|
||||
}
|
||||
|
||||
for _, magic := range incompatibleFsMagic {
|
||||
if fsMagic == magic {
|
||||
return nil, graphdriver.ErrIncompatibleFS
|
||||
}
|
||||
}
|
||||
|
||||
paths := []string{
|
||||
"mnt",
|
||||
"diff",
|
||||
"layers",
|
||||
}
|
||||
|
||||
a := &Driver{
|
||||
root: root,
|
||||
active: make(map[string]*data),
|
||||
uidMaps: uidMaps,
|
||||
gidMaps: gidMaps,
|
||||
}
|
||||
|
||||
rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Create the root aufs driver dir and return
|
||||
// if it already exists
|
||||
// If not populate the dir structure
|
||||
if err := idtools.MkdirAllAs(root, 0700, rootUID, rootGID); err != nil {
|
||||
if os.IsExist(err) {
|
||||
return a, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := mountpk.MakePrivate(root); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Populate the dir structure
|
||||
for _, p := range paths {
|
||||
if err := idtools.MkdirAllAs(path.Join(root, p), 0700, rootUID, rootGID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// Return a nil error if the kernel supports aufs
|
||||
// We cannot modprobe because inside dind modprobe fails
|
||||
// to run
|
||||
func supportsAufs() error {
|
||||
// We can try to modprobe aufs first before looking at
|
||||
// proc/filesystems for when aufs is supported
|
||||
exec.Command("modprobe", "aufs").Run()
|
||||
|
||||
f, err := os.Open("/proc/filesystems")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
s := bufio.NewScanner(f)
|
||||
for s.Scan() {
|
||||
if strings.Contains(s.Text(), "aufs") {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return ErrAufsNotSupported
|
||||
}
|
||||
|
||||
func (a *Driver) rootPath() string {
|
||||
return a.root
|
||||
}
|
||||
|
||||
func (*Driver) String() string {
|
||||
return "aufs"
|
||||
}
|
||||
|
||||
// Status returns current information about the filesystem such as root directory, number of directories mounted, etc.
|
||||
func (a *Driver) Status() [][2]string {
|
||||
ids, _ := loadIds(path.Join(a.rootPath(), "layers"))
|
||||
return [][2]string{
|
||||
{"Root Dir", a.rootPath()},
|
||||
{"Backing Filesystem", backingFs},
|
||||
{"Dirs", fmt.Sprintf("%d", len(ids))},
|
||||
{"Dirperm1 Supported", fmt.Sprintf("%v", useDirperm())},
|
||||
}
|
||||
}
|
||||
|
||||
// GetMetadata not implemented
|
||||
func (a *Driver) GetMetadata(id string) (map[string]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Exists returns true if the given id is registered with
|
||||
// this driver
|
||||
func (a *Driver) Exists(id string) bool {
|
||||
if _, err := os.Lstat(path.Join(a.rootPath(), "layers", id)); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Create three folders for each id
|
||||
// mnt, layers, and diff
|
||||
func (a *Driver) Create(id, parent, mountLabel string) error {
|
||||
if err := a.createDirsFor(id); err != nil {
|
||||
return err
|
||||
}
|
||||
// Write the layers metadata
|
||||
f, err := os.Create(path.Join(a.rootPath(), "layers", id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if parent != "" {
|
||||
ids, err := getParentIds(a.rootPath(), parent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintln(f, parent); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, i := range ids {
|
||||
if _, err := fmt.Fprintln(f, i); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
a.active[id] = &data{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Driver) createDirsFor(id string) error {
|
||||
paths := []string{
|
||||
"mnt",
|
||||
"diff",
|
||||
}
|
||||
|
||||
rootUID, rootGID, err := idtools.GetRootUIDGID(a.uidMaps, a.gidMaps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, p := range paths {
|
||||
if err := idtools.MkdirAllAs(path.Join(a.rootPath(), p, id), 0755, rootUID, rootGID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove will unmount and remove the given id.
|
||||
func (a *Driver) Remove(id string) error {
|
||||
// Protect the a.active from concurrent access
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
m := a.active[id]
|
||||
if m != nil {
|
||||
if m.referenceCount > 0 {
|
||||
return nil
|
||||
}
|
||||
// Make sure the dir is umounted first
|
||||
if err := a.unmount(m); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
tmpDirs := []string{
|
||||
"mnt",
|
||||
"diff",
|
||||
}
|
||||
|
||||
// Atomically remove each directory in turn by first moving it out of the
|
||||
// way (so that docker doesn't find it anymore) before doing removal of
|
||||
// the whole tree.
|
||||
for _, p := range tmpDirs {
|
||||
realPath := path.Join(a.rootPath(), p, id)
|
||||
tmpPath := path.Join(a.rootPath(), p, fmt.Sprintf("%s-removing", id))
|
||||
if err := os.Rename(realPath, tmpPath); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmpPath)
|
||||
}
|
||||
// Remove the layers file for the id
|
||||
if err := os.Remove(path.Join(a.rootPath(), "layers", id)); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns the rootfs path for the id.
|
||||
// This will mount the dir at it's given path
|
||||
func (a *Driver) Get(id, mountLabel string) (string, error) {
|
||||
ids, err := getParentIds(a.rootPath(), id)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
ids = []string{}
|
||||
}
|
||||
|
||||
// Protect the a.active from concurrent access
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
m := a.active[id]
|
||||
if m == nil {
|
||||
m = &data{}
|
||||
a.active[id] = m
|
||||
}
|
||||
|
||||
// If a dir does not have a parent ( no layers )do not try to mount
|
||||
// just return the diff path to the data
|
||||
m.path = path.Join(a.rootPath(), "diff", id)
|
||||
if len(ids) > 0 {
|
||||
m.path = path.Join(a.rootPath(), "mnt", id)
|
||||
if m.referenceCount == 0 {
|
||||
if err := a.mount(id, m, mountLabel); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
m.referenceCount++
|
||||
return m.path, nil
|
||||
}
|
||||
|
||||
// Put unmounts and updates list of active mounts.
|
||||
func (a *Driver) Put(id string) error {
|
||||
// Protect the a.active from concurrent access
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
m := a.active[id]
|
||||
if m == nil {
|
||||
// but it might be still here
|
||||
if a.Exists(id) {
|
||||
path := path.Join(a.rootPath(), "mnt", id)
|
||||
err := Unmount(path)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to unmount %s aufs: %v", id, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if count := m.referenceCount; count > 1 {
|
||||
m.referenceCount = count - 1
|
||||
} else {
|
||||
ids, _ := getParentIds(a.rootPath(), id)
|
||||
// We only mounted if there are any parents
|
||||
if ids != nil && len(ids) > 0 {
|
||||
a.unmount(m)
|
||||
}
|
||||
delete(a.active, id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Diff produces an archive of the changes between the specified
|
||||
// layer and its parent layer which may be "".
|
||||
func (a *Driver) Diff(id, parent string) (archive.Archive, error) {
|
||||
// AUFS doesn't need the parent layer to produce a diff.
|
||||
return archive.TarWithOptions(path.Join(a.rootPath(), "diff", id), &archive.TarOptions{
|
||||
Compression: archive.Uncompressed,
|
||||
ExcludePatterns: []string{archive.WhiteoutMetaPrefix + "*", "!" + archive.WhiteoutOpaqueDir},
|
||||
UIDMaps: a.uidMaps,
|
||||
GIDMaps: a.gidMaps,
|
||||
})
|
||||
}
|
||||
|
||||
// DiffPath returns path to the directory that contains files for the layer
|
||||
// differences. Used for direct access for tar-split.
|
||||
func (a *Driver) DiffPath(id string) (string, func() error, error) {
|
||||
return path.Join(a.rootPath(), "diff", id), func() error { return nil }, nil
|
||||
}
|
||||
|
||||
func (a *Driver) applyDiff(id string, diff archive.Reader) error {
|
||||
return chrootarchive.UntarUncompressed(diff, path.Join(a.rootPath(), "diff", id), &archive.TarOptions{
|
||||
UIDMaps: a.uidMaps,
|
||||
GIDMaps: a.gidMaps,
|
||||
})
|
||||
}
|
||||
|
||||
// DiffSize calculates the changes between the specified id
|
||||
// and its parent and returns the size in bytes of the changes
|
||||
// relative to its base filesystem directory.
|
||||
func (a *Driver) DiffSize(id, parent string) (size int64, err error) {
|
||||
// AUFS doesn't need the parent layer to calculate the diff size.
|
||||
return directory.Size(path.Join(a.rootPath(), "diff", id))
|
||||
}
|
||||
|
||||
// ApplyDiff extracts the changeset from the given diff into the
|
||||
// layer with the specified id and parent, returning the size of the
|
||||
// new layer in bytes.
|
||||
func (a *Driver) ApplyDiff(id, parent string, diff archive.Reader) (size int64, err error) {
|
||||
// AUFS doesn't need the parent id to apply the diff.
|
||||
if err = a.applyDiff(id, diff); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return a.DiffSize(id, parent)
|
||||
}
|
||||
|
||||
// Changes produces a list of changes between the specified layer
|
||||
// and its parent layer. If parent is "", then all changes will be ADD changes.
|
||||
func (a *Driver) Changes(id, parent string) ([]archive.Change, error) {
|
||||
// AUFS doesn't have snapshots, so we need to get changes from all parent
|
||||
// layers.
|
||||
layers, err := a.getParentLayerPaths(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return archive.Changes(layers, path.Join(a.rootPath(), "diff", id))
|
||||
}
|
||||
|
||||
func (a *Driver) getParentLayerPaths(id string) ([]string, error) {
|
||||
parentIds, err := getParentIds(a.rootPath(), id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
layers := make([]string, len(parentIds))
|
||||
|
||||
// Get the diff paths for all the parent ids
|
||||
for i, p := range parentIds {
|
||||
layers[i] = path.Join(a.rootPath(), "diff", p)
|
||||
}
|
||||
return layers, nil
|
||||
}
|
||||
|
||||
func (a *Driver) mount(id string, m *data, mountLabel string) error {
|
||||
// If the id is mounted or we get an error return
|
||||
if mounted, err := a.mounted(m); err != nil || mounted {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
target = m.path
|
||||
rw = path.Join(a.rootPath(), "diff", id)
|
||||
)
|
||||
|
||||
layers, err := a.getParentLayerPaths(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := a.aufsMount(layers, rw, target, mountLabel); err != nil {
|
||||
return fmt.Errorf("error creating aufs mount to %s: %v", target, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Driver) unmount(m *data) error {
|
||||
if mounted, err := a.mounted(m); err != nil || !mounted {
|
||||
return err
|
||||
}
|
||||
return Unmount(m.path)
|
||||
}
|
||||
|
||||
func (a *Driver) mounted(m *data) (bool, error) {
|
||||
return mountpk.Mounted(m.path)
|
||||
}
|
||||
|
||||
// Cleanup aufs and unmount all mountpoints
|
||||
func (a *Driver) Cleanup() error {
|
||||
for id, m := range a.active {
|
||||
if err := a.unmount(m); err != nil {
|
||||
logrus.Errorf("Unmounting %s: %s", stringid.TruncateID(id), err)
|
||||
}
|
||||
}
|
||||
return mountpk.Unmount(a.root)
|
||||
}
|
||||
|
||||
func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
Unmount(target)
|
||||
}
|
||||
}()
|
||||
|
||||
// Mount options are clipped to page size(4096 bytes). If there are more
|
||||
// layers then these are remounted individually using append.
|
||||
|
||||
offset := 54
|
||||
if useDirperm() {
|
||||
offset += len("dirperm1")
|
||||
}
|
||||
b := make([]byte, syscall.Getpagesize()-len(mountLabel)-offset) // room for xino & mountLabel
|
||||
bp := copy(b, fmt.Sprintf("br:%s=rw", rw))
|
||||
|
||||
firstMount := true
|
||||
i := 0
|
||||
|
||||
for {
|
||||
for ; i < len(ro); i++ {
|
||||
layer := fmt.Sprintf(":%s=ro+wh", ro[i])
|
||||
|
||||
if firstMount {
|
||||
if bp+len(layer) > len(b) {
|
||||
break
|
||||
}
|
||||
bp += copy(b[bp:], layer)
|
||||
} else {
|
||||
data := label.FormatMountLabel(fmt.Sprintf("append%s", layer), mountLabel)
|
||||
if err = mount("none", target, "aufs", syscall.MS_REMOUNT, data); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if firstMount {
|
||||
opts := "dio,xino=/dev/shm/aufs.xino"
|
||||
if useDirperm() {
|
||||
opts += ",dirperm1"
|
||||
}
|
||||
data := label.FormatMountLabel(fmt.Sprintf("%s,%s", string(b[:bp]), opts), mountLabel)
|
||||
if err = mount("none", target, "aufs", 0, data); err != nil {
|
||||
return
|
||||
}
|
||||
firstMount = false
|
||||
}
|
||||
|
||||
if i == len(ro) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// useDirperm checks dirperm1 mount option can be used with the current
|
||||
// version of aufs.
|
||||
func useDirperm() bool {
|
||||
enableDirpermLock.Do(func() {
|
||||
base, err := ioutil.TempDir("", "docker-aufs-base")
|
||||
if err != nil {
|
||||
logrus.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)
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(union)
|
||||
|
||||
opts := fmt.Sprintf("br:%s,dirperm1,xino=/dev/shm/aufs.xino", base)
|
||||
if err := mount("none", union, "aufs", 0, opts); err != nil {
|
||||
return
|
||||
}
|
||||
enableDirperm = true
|
||||
if err := Unmount(union); err != nil {
|
||||
logrus.Errorf("error checking dirperm1: failed to unmount %v", err)
|
||||
}
|
||||
})
|
||||
return enableDirperm
|
||||
}
|
||||
734
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/aufs/aufs_test.go
generated
vendored
Normal file
734
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/aufs/aufs_test.go
generated
vendored
Normal file
@@ -0,0 +1,734 @@
|
||||
// +build linux
|
||||
|
||||
package aufs
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/hyperhq/hypercli/daemon/graphdriver"
|
||||
"github.com/hyperhq/hypercli/pkg/archive"
|
||||
"github.com/hyperhq/hypercli/pkg/reexec"
|
||||
)
|
||||
|
||||
var (
|
||||
tmpOuter = path.Join(os.TempDir(), "aufs-tests")
|
||||
tmp = path.Join(tmpOuter, "aufs")
|
||||
)
|
||||
|
||||
func init() {
|
||||
reexec.Init()
|
||||
}
|
||||
|
||||
func testInit(dir string, t *testing.T) graphdriver.Driver {
|
||||
d, err := Init(dir, nil, nil, nil)
|
||||
if err != nil {
|
||||
if err == graphdriver.ErrNotSupported {
|
||||
t.Skip(err)
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func newDriver(t *testing.T) *Driver {
|
||||
if err := os.MkdirAll(tmp, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
d := testInit(tmp, t)
|
||||
return d.(*Driver)
|
||||
}
|
||||
|
||||
func TestNewDriver(t *testing.T) {
|
||||
if err := os.MkdirAll(tmp, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
d := testInit(tmp, t)
|
||||
defer os.RemoveAll(tmp)
|
||||
if d == nil {
|
||||
t.Fatalf("Driver should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAufsString(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
if d.String() != "aufs" {
|
||||
t.Fatalf("Expected aufs got %s", d.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateDirStructure(t *testing.T) {
|
||||
newDriver(t)
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
paths := []string{
|
||||
"mnt",
|
||||
"layers",
|
||||
"diff",
|
||||
}
|
||||
|
||||
for _, p := range paths {
|
||||
if _, err := os.Stat(path.Join(tmp, p)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We should be able to create two drivers with the same dir structure
|
||||
func TestNewDriverFromExistingDir(t *testing.T) {
|
||||
if err := os.MkdirAll(tmp, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testInit(tmp, t)
|
||||
testInit(tmp, t)
|
||||
os.RemoveAll(tmp)
|
||||
}
|
||||
|
||||
func TestCreateNewDir(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
if err := d.Create("1", "", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateNewDirStructure(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
if err := d.Create("1", "", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
paths := []string{
|
||||
"mnt",
|
||||
"diff",
|
||||
"layers",
|
||||
}
|
||||
|
||||
for _, p := range paths {
|
||||
if _, err := os.Stat(path.Join(tmp, p, "1")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveImage(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
if err := d.Create("1", "", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := d.Remove("1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
paths := []string{
|
||||
"mnt",
|
||||
"diff",
|
||||
"layers",
|
||||
}
|
||||
|
||||
for _, p := range paths {
|
||||
if _, err := os.Stat(path.Join(tmp, p, "1")); err == nil {
|
||||
t.Fatalf("Error should not be nil because dirs with id 1 should be delted: %s", p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetWithoutParent(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
if err := d.Create("1", "", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
diffPath, err := d.Get("1", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := path.Join(tmp, "diff", "1")
|
||||
if diffPath != expected {
|
||||
t.Fatalf("Expected path %s got %s", expected, diffPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanupWithNoDirs(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
if err := d.Cleanup(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanupWithDir(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
if err := d.Create("1", "", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := d.Cleanup(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMountedFalseResponse(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
if err := d.Create("1", "", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
response, err := d.mounted(d.active["1"])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if response != false {
|
||||
t.Fatalf("Response if dir id 1 is mounted should be false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMountedTrueReponse(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer os.RemoveAll(tmp)
|
||||
defer d.Cleanup()
|
||||
|
||||
if err := d.Create("1", "", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := d.Create("2", "1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err := d.Get("2", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
response, err := d.mounted(d.active["2"])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if response != true {
|
||||
t.Fatalf("Response if dir id 2 is mounted should be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMountWithParent(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
if err := d.Create("1", "", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := d.Create("2", "1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := d.Cleanup(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
mntPath, err := d.Get("2", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if mntPath == "" {
|
||||
t.Fatal("mntPath should not be empty string")
|
||||
}
|
||||
|
||||
expected := path.Join(tmp, "mnt", "2")
|
||||
if mntPath != expected {
|
||||
t.Fatalf("Expected %s got %s", expected, mntPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveMountedDir(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
if err := d.Create("1", "", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := d.Create("2", "1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := d.Cleanup(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
mntPath, err := d.Get("2", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if mntPath == "" {
|
||||
t.Fatal("mntPath should not be empty string")
|
||||
}
|
||||
|
||||
mounted, err := d.mounted(d.active["2"])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !mounted {
|
||||
t.Fatalf("Dir id 2 should be mounted")
|
||||
}
|
||||
|
||||
if err := d.Remove("2"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateWithInvalidParent(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
if err := d.Create("1", "docker", ""); err == nil {
|
||||
t.Fatalf("Error should not be nil with parent does not exist")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDiff(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
if err := d.Create("1", "", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
diffPath, err := d.Get("1", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Add a file to the diff path with a fixed size
|
||||
size := int64(1024)
|
||||
|
||||
f, err := os.Create(path.Join(diffPath, "test_file"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := f.Truncate(size); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
a, err := d.Diff("1", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if a == nil {
|
||||
t.Fatalf("Archive should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestChanges(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
if err := d.Create("1", "", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := d.Create("2", "1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := d.Cleanup(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
mntPoint, err := d.Get("2", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create a file to save in the mountpoint
|
||||
f, err := os.Create(path.Join(mntPoint, "test.txt"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := f.WriteString("testline"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
changes, err := d.Changes("2", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(changes) != 1 {
|
||||
t.Fatalf("Dir 2 should have one change from parent got %d", len(changes))
|
||||
}
|
||||
change := changes[0]
|
||||
|
||||
expectedPath := "/test.txt"
|
||||
if change.Path != expectedPath {
|
||||
t.Fatalf("Expected path %s got %s", expectedPath, change.Path)
|
||||
}
|
||||
|
||||
if change.Kind != archive.ChangeAdd {
|
||||
t.Fatalf("Change kind should be ChangeAdd got %s", change.Kind)
|
||||
}
|
||||
|
||||
if err := d.Create("3", "2", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mntPoint, err = d.Get("3", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create a file to save in the mountpoint
|
||||
f, err = os.Create(path.Join(mntPoint, "test2.txt"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := f.WriteString("testline"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
changes, err = d.Changes("3", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(changes) != 1 {
|
||||
t.Fatalf("Dir 2 should have one change from parent got %d", len(changes))
|
||||
}
|
||||
change = changes[0]
|
||||
|
||||
expectedPath = "/test2.txt"
|
||||
if change.Path != expectedPath {
|
||||
t.Fatalf("Expected path %s got %s", expectedPath, change.Path)
|
||||
}
|
||||
|
||||
if change.Kind != archive.ChangeAdd {
|
||||
t.Fatalf("Change kind should be ChangeAdd got %s", change.Kind)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiffSize(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
if err := d.Create("1", "", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
diffPath, err := d.Get("1", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Add a file to the diff path with a fixed size
|
||||
size := int64(1024)
|
||||
|
||||
f, err := os.Create(path.Join(diffPath, "test_file"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := f.Truncate(size); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s, err := f.Stat()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
size = s.Size()
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
diffSize, err := d.DiffSize("1", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diffSize != size {
|
||||
t.Fatalf("Expected size to be %d got %d", size, diffSize)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChildDiffSize(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer os.RemoveAll(tmp)
|
||||
defer d.Cleanup()
|
||||
|
||||
if err := d.Create("1", "", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
diffPath, err := d.Get("1", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Add a file to the diff path with a fixed size
|
||||
size := int64(1024)
|
||||
|
||||
f, err := os.Create(path.Join(diffPath, "test_file"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := f.Truncate(size); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s, err := f.Stat()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
size = s.Size()
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
diffSize, err := d.DiffSize("1", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diffSize != size {
|
||||
t.Fatalf("Expected size to be %d got %d", size, diffSize)
|
||||
}
|
||||
|
||||
if err := d.Create("2", "1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
diffSize, err = d.DiffSize("2", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// The diff size for the child should be zero
|
||||
if diffSize != 0 {
|
||||
t.Fatalf("Expected size to be %d got %d", 0, diffSize)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExists(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer os.RemoveAll(tmp)
|
||||
defer d.Cleanup()
|
||||
|
||||
if err := d.Create("1", "", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if d.Exists("none") {
|
||||
t.Fatal("id name should not exist in the driver")
|
||||
}
|
||||
|
||||
if !d.Exists("1") {
|
||||
t.Fatal("id 1 should exist in the driver")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatus(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer os.RemoveAll(tmp)
|
||||
defer d.Cleanup()
|
||||
|
||||
if err := d.Create("1", "", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
status := d.Status()
|
||||
if status == nil || len(status) == 0 {
|
||||
t.Fatal("Status should not be nil or empty")
|
||||
}
|
||||
rootDir := status[0]
|
||||
dirs := status[2]
|
||||
if rootDir[0] != "Root Dir" {
|
||||
t.Fatalf("Expected Root Dir got %s", rootDir[0])
|
||||
}
|
||||
if rootDir[1] != d.rootPath() {
|
||||
t.Fatalf("Expected %s got %s", d.rootPath(), rootDir[1])
|
||||
}
|
||||
if dirs[0] != "Dirs" {
|
||||
t.Fatalf("Expected Dirs got %s", dirs[0])
|
||||
}
|
||||
if dirs[1] != "1" {
|
||||
t.Fatalf("Expected 1 got %s", dirs[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyDiff(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer os.RemoveAll(tmp)
|
||||
defer d.Cleanup()
|
||||
|
||||
if err := d.Create("1", "", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
diffPath, err := d.Get("1", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Add a file to the diff path with a fixed size
|
||||
size := int64(1024)
|
||||
|
||||
f, err := os.Create(path.Join(diffPath, "test_file"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := f.Truncate(size); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
diff, err := d.Diff("1", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := d.Create("2", "", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := d.Create("3", "2", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := d.applyDiff("3", diff); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Ensure that the file is in the mount point for id 3
|
||||
|
||||
mountPoint, err := d.Get("3", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := os.Stat(path.Join(mountPoint, "test_file")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func hash(c string) string {
|
||||
h := sha256.New()
|
||||
fmt.Fprint(h, c)
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func testMountMoreThan42Layers(t *testing.T, mountPath string) {
|
||||
if err := os.MkdirAll(mountPath, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer os.RemoveAll(mountPath)
|
||||
d := testInit(mountPath, t).(*Driver)
|
||||
defer d.Cleanup()
|
||||
var last string
|
||||
var expected int
|
||||
|
||||
for i := 1; i < 127; i++ {
|
||||
expected++
|
||||
var (
|
||||
parent = fmt.Sprintf("%d", i-1)
|
||||
current = fmt.Sprintf("%d", i)
|
||||
)
|
||||
|
||||
if parent == "0" {
|
||||
parent = ""
|
||||
} else {
|
||||
parent = hash(parent)
|
||||
}
|
||||
current = hash(current)
|
||||
|
||||
if err := d.Create(current, parent, ""); err != nil {
|
||||
t.Logf("Current layer %d", i)
|
||||
t.Error(err)
|
||||
}
|
||||
point, err := d.Get(current, "")
|
||||
if err != nil {
|
||||
t.Logf("Current layer %d", i)
|
||||
t.Error(err)
|
||||
}
|
||||
f, err := os.Create(path.Join(point, current))
|
||||
if err != nil {
|
||||
t.Logf("Current layer %d", i)
|
||||
t.Error(err)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
if i%10 == 0 {
|
||||
if err := os.Remove(path.Join(point, parent)); err != nil {
|
||||
t.Logf("Current layer %d", i)
|
||||
t.Error(err)
|
||||
}
|
||||
expected--
|
||||
}
|
||||
last = current
|
||||
}
|
||||
|
||||
// Perform the actual mount for the top most image
|
||||
point, err := d.Get(last, "")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
files, err := ioutil.ReadDir(point)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if len(files) != expected {
|
||||
t.Errorf("Expected %d got %d", expected, len(files))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMountMoreThan42Layers(t *testing.T) {
|
||||
os.RemoveAll(tmpOuter)
|
||||
testMountMoreThan42Layers(t, tmp)
|
||||
}
|
||||
|
||||
func TestMountMoreThan42LayersMatchingPathLength(t *testing.T) {
|
||||
defer os.RemoveAll(tmpOuter)
|
||||
zeroes := "0"
|
||||
for {
|
||||
// This finds a mount path so that when combined into aufs mount options
|
||||
// 4096 byte boundary would be in between the paths or in permission
|
||||
// section. For '/tmp' it will use '/tmp/aufs-tests/00000000/aufs'
|
||||
mountPath := path.Join(tmpOuter, zeroes, "aufs")
|
||||
pathLength := 77 + len(mountPath)
|
||||
|
||||
if mod := 4095 % pathLength; mod == 0 || mod > pathLength-2 {
|
||||
t.Logf("Using path: %s", mountPath)
|
||||
testMountMoreThan42Layers(t, mountPath)
|
||||
return
|
||||
}
|
||||
zeroes += "0"
|
||||
}
|
||||
}
|
||||
48
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/aufs/dirs.go
generated
vendored
Normal file
48
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/aufs/dirs.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
// +build linux
|
||||
|
||||
package aufs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
// Return all the directories
|
||||
func loadIds(root string) ([]string, error) {
|
||||
dirs, err := ioutil.ReadDir(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := []string{}
|
||||
for _, d := range dirs {
|
||||
if !d.IsDir() {
|
||||
out = append(out, d.Name())
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Read the layers file for the current id and return all the
|
||||
// layers represented by new lines in the file
|
||||
//
|
||||
// If there are no lines in the file then the id has no parent
|
||||
// and an empty slice is returned.
|
||||
func getParentIds(root, id string) ([]string, error) {
|
||||
f, err := os.Open(path.Join(root, "layers", id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
out := []string{}
|
||||
s := bufio.NewScanner(f)
|
||||
|
||||
for s.Scan() {
|
||||
if t := s.Text(); t != "" {
|
||||
out = append(out, s.Text())
|
||||
}
|
||||
}
|
||||
return out, s.Err()
|
||||
}
|
||||
21
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/aufs/mount.go
generated
vendored
Normal file
21
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/aufs/mount.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
// +build linux
|
||||
|
||||
package aufs
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"syscall"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Unmount the target specified.
|
||||
func Unmount(target string) error {
|
||||
if err := exec.Command("auplink", target, "flush").Run(); err != nil {
|
||||
logrus.Errorf("Couldn't run auplink before unmount: %s", err)
|
||||
}
|
||||
if err := syscall.Unmount(target, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
7
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/aufs/mount_linux.go
generated
vendored
Normal file
7
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/aufs/mount_linux.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
package aufs
|
||||
|
||||
import "syscall"
|
||||
|
||||
func mount(source string, target string, fstype string, flags uintptr, data string) error {
|
||||
return syscall.Mount(source, target, fstype, flags, data)
|
||||
}
|
||||
12
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/aufs/mount_unsupported.go
generated
vendored
Normal file
12
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/aufs/mount_unsupported.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// +build !linux
|
||||
|
||||
package aufs
|
||||
|
||||
import "errors"
|
||||
|
||||
// MsRemount declared to specify a non-linux system mount.
|
||||
const MsRemount = 0
|
||||
|
||||
func mount(source string, target string, fstype string, flags uintptr, data string) (err error) {
|
||||
return errors.New("mount is not implemented on this platform")
|
||||
}
|
||||
321
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/btrfs/btrfs.go
generated
vendored
Normal file
321
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/btrfs/btrfs.go
generated
vendored
Normal file
@@ -0,0 +1,321 @@
|
||||
// +build linux
|
||||
|
||||
package btrfs
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#include <dirent.h>
|
||||
#include <btrfs/ioctl.h>
|
||||
#include <btrfs/ctree.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/hyperhq/hypercli/daemon/graphdriver"
|
||||
"github.com/hyperhq/hypercli/pkg/idtools"
|
||||
"github.com/hyperhq/hypercli/pkg/mount"
|
||||
"github.com/opencontainers/runc/libcontainer/label"
|
||||
)
|
||||
|
||||
func init() {
|
||||
graphdriver.Register("btrfs", Init)
|
||||
}
|
||||
|
||||
// Init returns a new BTRFS driver.
|
||||
// An error is returned if BTRFS is not supported.
|
||||
func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
|
||||
|
||||
fsMagic, err := graphdriver.GetFSMagic(home)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if fsMagic != graphdriver.FsMagicBtrfs {
|
||||
return nil, graphdriver.ErrPrerequisites
|
||||
}
|
||||
|
||||
rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := idtools.MkdirAllAs(home, 0700, rootUID, rootGID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := mount.MakePrivate(home); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
driver := &Driver{
|
||||
home: home,
|
||||
uidMaps: uidMaps,
|
||||
gidMaps: gidMaps,
|
||||
}
|
||||
|
||||
return graphdriver.NewNaiveDiffDriver(driver, uidMaps, gidMaps), nil
|
||||
}
|
||||
|
||||
// Driver contains information about the filesystem mounted.
|
||||
type Driver struct {
|
||||
//root of the file system
|
||||
home string
|
||||
uidMaps []idtools.IDMap
|
||||
gidMaps []idtools.IDMap
|
||||
}
|
||||
|
||||
// String prints the name of the driver (btrfs).
|
||||
func (d *Driver) String() string {
|
||||
return "btrfs"
|
||||
}
|
||||
|
||||
// Status returns current driver information in a two dimensional string array.
|
||||
// Output contains "Build Version" and "Library Version" of the btrfs libraries used.
|
||||
// Version information can be used to check compatibility with your kernel.
|
||||
func (d *Driver) Status() [][2]string {
|
||||
status := [][2]string{}
|
||||
if bv := btrfsBuildVersion(); bv != "-" {
|
||||
status = append(status, [2]string{"Build Version", bv})
|
||||
}
|
||||
if lv := btrfsLibVersion(); lv != -1 {
|
||||
status = append(status, [2]string{"Library Version", fmt.Sprintf("%d", lv)})
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
// GetMetadata returns empty metadata for this driver.
|
||||
func (d *Driver) GetMetadata(id string) (map[string]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Cleanup unmounts the home directory.
|
||||
func (d *Driver) Cleanup() error {
|
||||
return mount.Unmount(d.home)
|
||||
}
|
||||
|
||||
func free(p *C.char) {
|
||||
C.free(unsafe.Pointer(p))
|
||||
}
|
||||
|
||||
func openDir(path string) (*C.DIR, error) {
|
||||
Cpath := C.CString(path)
|
||||
defer free(Cpath)
|
||||
|
||||
dir := C.opendir(Cpath)
|
||||
if dir == nil {
|
||||
return nil, fmt.Errorf("Can't open dir")
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
func closeDir(dir *C.DIR) {
|
||||
if dir != nil {
|
||||
C.closedir(dir)
|
||||
}
|
||||
}
|
||||
|
||||
func getDirFd(dir *C.DIR) uintptr {
|
||||
return uintptr(C.dirfd(dir))
|
||||
}
|
||||
|
||||
func subvolCreate(path, name string) error {
|
||||
dir, err := openDir(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeDir(dir)
|
||||
|
||||
var args C.struct_btrfs_ioctl_vol_args
|
||||
for i, c := range []byte(name) {
|
||||
args.name[i] = C.char(c)
|
||||
}
|
||||
|
||||
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_SUBVOL_CREATE,
|
||||
uintptr(unsafe.Pointer(&args)))
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("Failed to create btrfs subvolume: %v", errno.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func subvolSnapshot(src, dest, name string) error {
|
||||
srcDir, err := openDir(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeDir(srcDir)
|
||||
|
||||
destDir, err := openDir(dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeDir(destDir)
|
||||
|
||||
var args C.struct_btrfs_ioctl_vol_args_v2
|
||||
args.fd = C.__s64(getDirFd(srcDir))
|
||||
for i, c := range []byte(name) {
|
||||
args.name[i] = C.char(c)
|
||||
}
|
||||
|
||||
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(destDir), C.BTRFS_IOC_SNAP_CREATE_V2,
|
||||
uintptr(unsafe.Pointer(&args)))
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("Failed to create btrfs snapshot: %v", errno.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isSubvolume(p string) (bool, error) {
|
||||
var bufStat syscall.Stat_t
|
||||
if err := syscall.Lstat(p, &bufStat); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// return true if it is a btrfs subvolume
|
||||
return bufStat.Ino == C.BTRFS_FIRST_FREE_OBJECTID, nil
|
||||
}
|
||||
|
||||
func subvolDelete(dirpath, name string) error {
|
||||
dir, err := openDir(dirpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeDir(dir)
|
||||
fullPath := path.Join(dirpath, name)
|
||||
|
||||
var args C.struct_btrfs_ioctl_vol_args
|
||||
|
||||
// walk the btrfs subvolumes
|
||||
walkSubvolumes := func(p string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) && p != fullPath {
|
||||
// missing most likely because the path was a subvolume that got removed in the previous iteration
|
||||
// since it's gone anyway, we don't care
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("error walking subvolumes: %v", err)
|
||||
}
|
||||
// we want to check children only so skip itself
|
||||
// it will be removed after the filepath walk anyways
|
||||
if f.IsDir() && p != fullPath {
|
||||
sv, err := isSubvolume(p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to test if %s is a btrfs subvolume: %v", p, err)
|
||||
}
|
||||
if sv {
|
||||
if err := subvolDelete(path.Dir(p), f.Name()); err != nil {
|
||||
return fmt.Errorf("Failed to destroy btrfs child subvolume (%s) of parent (%s): %v", p, dirpath, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := filepath.Walk(path.Join(dirpath, name), walkSubvolumes); err != nil {
|
||||
return fmt.Errorf("Recursively walking subvolumes for %s failed: %v", dirpath, err)
|
||||
}
|
||||
|
||||
// all subvolumes have been removed
|
||||
// now remove the one originally passed in
|
||||
for i, c := range []byte(name) {
|
||||
args.name[i] = C.char(c)
|
||||
}
|
||||
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_SNAP_DESTROY,
|
||||
uintptr(unsafe.Pointer(&args)))
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("Failed to destroy btrfs snapshot %s for %s: %v", dirpath, name, errno.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) subvolumesDir() string {
|
||||
return path.Join(d.home, "subvolumes")
|
||||
}
|
||||
|
||||
func (d *Driver) subvolumesDirID(id string) string {
|
||||
return path.Join(d.subvolumesDir(), id)
|
||||
}
|
||||
|
||||
// Create the filesystem with given id.
|
||||
func (d *Driver) Create(id, parent, mountLabel string) error {
|
||||
subvolumes := path.Join(d.home, "subvolumes")
|
||||
rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := idtools.MkdirAllAs(subvolumes, 0700, rootUID, rootGID); err != nil {
|
||||
return err
|
||||
}
|
||||
if parent == "" {
|
||||
if err := subvolCreate(subvolumes, id); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
parentDir, err := d.Get(parent, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := subvolSnapshot(parentDir, subvolumes, id); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// if we have a remapped root (user namespaces enabled), change the created snapshot
|
||||
// dir ownership to match
|
||||
if rootUID != 0 || rootGID != 0 {
|
||||
if err := os.Chown(path.Join(subvolumes, id), rootUID, rootGID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return label.Relabel(path.Join(subvolumes, id), mountLabel, false)
|
||||
}
|
||||
|
||||
// Remove the filesystem with given id.
|
||||
func (d *Driver) Remove(id string) error {
|
||||
dir := d.subvolumesDirID(id)
|
||||
if _, err := os.Stat(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := subvolDelete(d.subvolumesDir(), id); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.RemoveAll(dir); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the requested filesystem id.
|
||||
func (d *Driver) Get(id, mountLabel string) (string, error) {
|
||||
dir := d.subvolumesDirID(id)
|
||||
st, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !st.IsDir() {
|
||||
return "", fmt.Errorf("%s: not a directory", dir)
|
||||
}
|
||||
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
// Put is not implemented for BTRFS as there is no cleanup required for the id.
|
||||
func (d *Driver) Put(id string) error {
|
||||
// Get() creates no runtime resources (like e.g. mounts)
|
||||
// so this doesn't need to do anything.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exists checks if the id exists in the filesystem.
|
||||
func (d *Driver) Exists(id string) bool {
|
||||
dir := d.subvolumesDirID(id)
|
||||
_, err := os.Stat(dir)
|
||||
return err == nil
|
||||
}
|
||||
63
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/btrfs/btrfs_test.go
generated
vendored
Normal file
63
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/btrfs/btrfs_test.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
// +build linux
|
||||
|
||||
package btrfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/hyperhq/hypercli/daemon/graphdriver/graphtest"
|
||||
)
|
||||
|
||||
// This avoids creating a new driver for each test if all tests are run
|
||||
// Make sure to put new tests between TestBtrfsSetup and TestBtrfsTeardown
|
||||
func TestBtrfsSetup(t *testing.T) {
|
||||
graphtest.GetDriver(t, "btrfs")
|
||||
}
|
||||
|
||||
func TestBtrfsCreateEmpty(t *testing.T) {
|
||||
graphtest.DriverTestCreateEmpty(t, "btrfs")
|
||||
}
|
||||
|
||||
func TestBtrfsCreateBase(t *testing.T) {
|
||||
graphtest.DriverTestCreateBase(t, "btrfs")
|
||||
}
|
||||
|
||||
func TestBtrfsCreateSnap(t *testing.T) {
|
||||
graphtest.DriverTestCreateSnap(t, "btrfs")
|
||||
}
|
||||
|
||||
func TestBtrfsSubvolDelete(t *testing.T) {
|
||||
d := graphtest.GetDriver(t, "btrfs")
|
||||
if err := d.Create("test", "", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer graphtest.PutDriver(t)
|
||||
|
||||
dir, err := d.Get("test", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer d.Put("test")
|
||||
|
||||
if err := subvolCreate(dir, "subvoltest"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(path.Join(dir, "subvoltest")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := d.Remove("test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(path.Join(dir, "subvoltest")); !os.IsNotExist(err) {
|
||||
t.Fatalf("expected not exist error on nested subvol, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBtrfsTeardown(t *testing.T) {
|
||||
graphtest.PutDriver(t)
|
||||
}
|
||||
3
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/btrfs/dummy_unsupported.go
generated
vendored
Normal file
3
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/btrfs/dummy_unsupported.go
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
// +build !linux !cgo
|
||||
|
||||
package btrfs
|
||||
26
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/btrfs/version.go
generated
vendored
Normal file
26
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/btrfs/version.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
// +build linux,!btrfs_noversion
|
||||
|
||||
package btrfs
|
||||
|
||||
/*
|
||||
#include <btrfs/version.h>
|
||||
|
||||
// around version 3.16, they did not define lib version yet
|
||||
#ifndef BTRFS_LIB_VERSION
|
||||
#define BTRFS_LIB_VERSION -1
|
||||
#endif
|
||||
|
||||
// upstream had removed it, but now it will be coming back
|
||||
#ifndef BTRFS_BUILD_VERSION
|
||||
#define BTRFS_BUILD_VERSION "-"
|
||||
#endif
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func btrfsBuildVersion() string {
|
||||
return string(C.BTRFS_BUILD_VERSION)
|
||||
}
|
||||
|
||||
func btrfsLibVersion() int {
|
||||
return int(C.BTRFS_LIB_VERSION)
|
||||
}
|
||||
14
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/btrfs/version_none.go
generated
vendored
Normal file
14
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/btrfs/version_none.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
// +build linux,btrfs_noversion
|
||||
|
||||
package btrfs
|
||||
|
||||
// TODO(vbatts) remove this work-around once supported linux distros are on
|
||||
// btrfs utilities of >= 3.16.1
|
||||
|
||||
func btrfsBuildVersion() string {
|
||||
return "-"
|
||||
}
|
||||
|
||||
func btrfsLibVersion() int {
|
||||
return -1
|
||||
}
|
||||
13
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/btrfs/version_test.go
generated
vendored
Normal file
13
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/btrfs/version_test.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// +build linux,!btrfs_noversion
|
||||
|
||||
package btrfs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLibVersion(t *testing.T) {
|
||||
if btrfsLibVersion() <= 0 {
|
||||
t.Errorf("expected output from btrfs lib version > 0")
|
||||
}
|
||||
}
|
||||
97
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/devmapper/README.md
generated
vendored
Normal file
97
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/devmapper/README.md
generated
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
## devicemapper - a storage backend based on Device Mapper
|
||||
|
||||
### Theory of operation
|
||||
|
||||
The device mapper graphdriver uses the device mapper thin provisioning
|
||||
module (dm-thinp) to implement CoW snapshots. The preferred model is
|
||||
to have a thin pool reserved outside of Docker and passed to the
|
||||
daemon via the `--storage-opt dm.thinpooldev` option.
|
||||
|
||||
As a fallback if no thin pool is provided, loopback files will be
|
||||
created. Loopback is very slow, but can be used without any
|
||||
pre-configuration of storage. It is strongly recommended that you do
|
||||
not use loopback in production. Ensure your Docker daemon has a
|
||||
`--storage-opt dm.thinpooldev` argument provided.
|
||||
|
||||
In loopback, a thin pool is created at `/var/lib/docker/devicemapper`
|
||||
(devicemapper graph location) based on two block devices, one for
|
||||
data and one for metadata. By default these block devices are created
|
||||
automatically by using loopback mounts of automatically created sparse
|
||||
files.
|
||||
|
||||
The default loopback files used are
|
||||
`/var/lib/docker/devicemapper/devicemapper/data` and
|
||||
`/var/lib/docker/devicemapper/devicemapper/metadata`. Additional metadata
|
||||
required to map from docker entities to the corresponding devicemapper
|
||||
volumes is stored in the `/var/lib/docker/devicemapper/devicemapper/json`
|
||||
file (encoded as Json).
|
||||
|
||||
In order to support multiple devicemapper graphs on a system, the thin
|
||||
pool will be named something like: `docker-0:33-19478248-pool`, where
|
||||
the `0:33` part is the minor/major device nr and `19478248` is the
|
||||
inode number of the `/var/lib/docker/devicemapper` directory.
|
||||
|
||||
On the thin pool, docker automatically creates a base thin device,
|
||||
called something like `docker-0:33-19478248-base` of a fixed
|
||||
size. This is automatically formatted with an empty filesystem on
|
||||
creation. This device is the base of all docker images and
|
||||
containers. All base images are snapshots of this device and those
|
||||
images are then in turn used as snapshots for other images and
|
||||
eventually containers.
|
||||
|
||||
### Information on `docker info`
|
||||
|
||||
As of docker-1.4.1, `docker info` when using the `devicemapper` storage driver
|
||||
will display something like:
|
||||
|
||||
$ sudo docker info
|
||||
[...]
|
||||
Storage Driver: devicemapper
|
||||
Pool Name: docker-253:1-17538953-pool
|
||||
Pool Blocksize: 65.54 kB
|
||||
Base Device Size: 107.4 GB
|
||||
Data file: /dev/loop4
|
||||
Metadata file: /dev/loop4
|
||||
Data Space Used: 2.536 GB
|
||||
Data Space Total: 107.4 GB
|
||||
Data Space Available: 104.8 GB
|
||||
Metadata Space Used: 7.93 MB
|
||||
Metadata Space Total: 2.147 GB
|
||||
Metadata Space Available: 2.14 GB
|
||||
Udev Sync Supported: true
|
||||
Data loop file: /home/docker/devicemapper/devicemapper/data
|
||||
Metadata loop file: /home/docker/devicemapper/devicemapper/metadata
|
||||
Library Version: 1.02.82-git (2013-10-04)
|
||||
[...]
|
||||
|
||||
#### status items
|
||||
|
||||
Each item in the indented section under `Storage Driver: devicemapper` are
|
||||
status information about the driver.
|
||||
* `Pool Name` name of the devicemapper pool for this driver.
|
||||
* `Pool Blocksize` tells the blocksize the thin pool was initialized with. This only changes on creation.
|
||||
* `Base Device Size` tells the maximum size of a container and image
|
||||
* `Data file` blockdevice file used for the devicemapper data
|
||||
* `Metadata file` blockdevice file used for the devicemapper metadata
|
||||
* `Data Space Used` tells how much of `Data file` is currently used
|
||||
* `Data Space Total` tells max size the `Data file`
|
||||
* `Data Space Available` tells how much free space there is in the `Data file`. If you are using a loop device this will report the actual space available to the loop device on the underlying filesystem.
|
||||
* `Metadata Space Used` tells how much of `Metadata file` is currently used
|
||||
* `Metadata Space Total` tells max size the `Metadata file`
|
||||
* `Metadata Space Available` tells how much free space there is in the `Metadata file`. If you are using a loop device this will report the actual space available to the loop device on the underlying filesystem.
|
||||
* `Udev Sync Supported` tells whether devicemapper is able to sync with Udev. Should be `true`.
|
||||
* `Data loop file` file attached to `Data file`, if loopback device is used
|
||||
* `Metadata loop file` file attached to `Metadata file`, if loopback device is used
|
||||
* `Library Version` from the libdevmapper used
|
||||
|
||||
### About the devicemapper options
|
||||
|
||||
The devicemapper backend supports some options that you can specify
|
||||
when starting the docker daemon using the `--storage-opt` flags.
|
||||
This uses the `dm` prefix and would be used something like `docker daemon --storage-opt dm.foo=bar`.
|
||||
|
||||
These options are currently documented both in [the man
|
||||
page](../../../man/docker.1.md) and in [the online
|
||||
documentation](https://docs.docker.com/reference/commandline/daemon/#docker-
|
||||
execdriver-option). If you add an options, update both the `man` page and the
|
||||
documentation.
|
||||
2530
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/devmapper/deviceset.go
generated
vendored
Normal file
2530
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/devmapper/deviceset.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
106
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/devmapper/devmapper_doc.go
generated
vendored
Normal file
106
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/devmapper/devmapper_doc.go
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
package devmapper
|
||||
|
||||
// Definition of struct dm_task and sub structures (from lvm2)
|
||||
//
|
||||
// struct dm_ioctl {
|
||||
// /*
|
||||
// * The version number is made up of three parts:
|
||||
// * major - no backward or forward compatibility,
|
||||
// * minor - only backwards compatible,
|
||||
// * patch - both backwards and forwards compatible.
|
||||
// *
|
||||
// * All clients of the ioctl interface should fill in the
|
||||
// * version number of the interface that they were
|
||||
// * compiled with.
|
||||
// *
|
||||
// * All recognized ioctl commands (ie. those that don't
|
||||
// * return -ENOTTY) fill out this field, even if the
|
||||
// * command failed.
|
||||
// */
|
||||
// uint32_t version[3]; /* in/out */
|
||||
// uint32_t data_size; /* total size of data passed in
|
||||
// * including this struct */
|
||||
|
||||
// uint32_t data_start; /* offset to start of data
|
||||
// * relative to start of this struct */
|
||||
|
||||
// uint32_t target_count; /* in/out */
|
||||
// int32_t open_count; /* out */
|
||||
// uint32_t flags; /* in/out */
|
||||
|
||||
// /*
|
||||
// * event_nr holds either the event number (input and output) or the
|
||||
// * udev cookie value (input only).
|
||||
// * The DM_DEV_WAIT ioctl takes an event number as input.
|
||||
// * The DM_SUSPEND, DM_DEV_REMOVE and DM_DEV_RENAME ioctls
|
||||
// * use the field as a cookie to return in the DM_COOKIE
|
||||
// * variable with the uevents they issue.
|
||||
// * For output, the ioctls return the event number, not the cookie.
|
||||
// */
|
||||
// uint32_t event_nr; /* in/out */
|
||||
// uint32_t padding;
|
||||
|
||||
// uint64_t dev; /* in/out */
|
||||
|
||||
// char name[DM_NAME_LEN]; /* device name */
|
||||
// char uuid[DM_UUID_LEN]; /* unique identifier for
|
||||
// * the block device */
|
||||
// char data[7]; /* padding or data */
|
||||
// };
|
||||
|
||||
// struct target {
|
||||
// uint64_t start;
|
||||
// uint64_t length;
|
||||
// char *type;
|
||||
// char *params;
|
||||
|
||||
// struct target *next;
|
||||
// };
|
||||
|
||||
// typedef enum {
|
||||
// DM_ADD_NODE_ON_RESUME, /* add /dev/mapper node with dmsetup resume */
|
||||
// DM_ADD_NODE_ON_CREATE /* add /dev/mapper node with dmsetup create */
|
||||
// } dm_add_node_t;
|
||||
|
||||
// struct dm_task {
|
||||
// int type;
|
||||
// char *dev_name;
|
||||
// char *mangled_dev_name;
|
||||
|
||||
// struct target *head, *tail;
|
||||
|
||||
// int read_only;
|
||||
// uint32_t event_nr;
|
||||
// int major;
|
||||
// int minor;
|
||||
// int allow_default_major_fallback;
|
||||
// uid_t uid;
|
||||
// gid_t gid;
|
||||
// mode_t mode;
|
||||
// uint32_t read_ahead;
|
||||
// uint32_t read_ahead_flags;
|
||||
// union {
|
||||
// struct dm_ioctl *v4;
|
||||
// } dmi;
|
||||
// char *newname;
|
||||
// char *message;
|
||||
// char *geometry;
|
||||
// uint64_t sector;
|
||||
// int no_flush;
|
||||
// int no_open_count;
|
||||
// int skip_lockfs;
|
||||
// int query_inactive_table;
|
||||
// int suppress_identical_reload;
|
||||
// dm_add_node_t add_node;
|
||||
// uint64_t existing_table_size;
|
||||
// int cookie_set;
|
||||
// int new_uuid;
|
||||
// int secure_data;
|
||||
// int retry_remove;
|
||||
// int enable_checks;
|
||||
// int expected_errno;
|
||||
|
||||
// char *uuid;
|
||||
// char *mangled_uuid;
|
||||
// };
|
||||
//
|
||||
110
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/devmapper/devmapper_test.go
generated
vendored
Normal file
110
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/devmapper/devmapper_test.go
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
// +build linux
|
||||
|
||||
package devmapper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hyperhq/hypercli/daemon/graphdriver"
|
||||
"github.com/hyperhq/hypercli/daemon/graphdriver/graphtest"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Reduce the size the the base fs and loopback for the tests
|
||||
defaultDataLoopbackSize = 300 * 1024 * 1024
|
||||
defaultMetaDataLoopbackSize = 200 * 1024 * 1024
|
||||
defaultBaseFsSize = 300 * 1024 * 1024
|
||||
defaultUdevSyncOverride = true
|
||||
if err := graphtest.InitLoopbacks(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// This avoids creating a new driver for each test if all tests are run
|
||||
// Make sure to put new tests between TestDevmapperSetup and TestDevmapperTeardown
|
||||
func TestDevmapperSetup(t *testing.T) {
|
||||
graphtest.GetDriver(t, "devicemapper")
|
||||
}
|
||||
|
||||
func TestDevmapperCreateEmpty(t *testing.T) {
|
||||
graphtest.DriverTestCreateEmpty(t, "devicemapper")
|
||||
}
|
||||
|
||||
func TestDevmapperCreateBase(t *testing.T) {
|
||||
graphtest.DriverTestCreateBase(t, "devicemapper")
|
||||
}
|
||||
|
||||
func TestDevmapperCreateSnap(t *testing.T) {
|
||||
graphtest.DriverTestCreateSnap(t, "devicemapper")
|
||||
}
|
||||
|
||||
func TestDevmapperTeardown(t *testing.T) {
|
||||
graphtest.PutDriver(t)
|
||||
}
|
||||
|
||||
func TestDevmapperReduceLoopBackSize(t *testing.T) {
|
||||
tenMB := int64(10 * 1024 * 1024)
|
||||
testChangeLoopBackSize(t, -tenMB, defaultDataLoopbackSize, defaultMetaDataLoopbackSize)
|
||||
}
|
||||
|
||||
func TestDevmapperIncreaseLoopBackSize(t *testing.T) {
|
||||
tenMB := int64(10 * 1024 * 1024)
|
||||
testChangeLoopBackSize(t, tenMB, defaultDataLoopbackSize+tenMB, defaultMetaDataLoopbackSize+tenMB)
|
||||
}
|
||||
|
||||
func testChangeLoopBackSize(t *testing.T, delta, expectDataSize, expectMetaDataSize int64) {
|
||||
driver := graphtest.GetDriver(t, "devicemapper").(*graphtest.Driver).Driver.(*graphdriver.NaiveDiffDriver).ProtoDriver.(*Driver)
|
||||
defer graphtest.PutDriver(t)
|
||||
// make sure data or metadata loopback size are the default size
|
||||
if s := driver.DeviceSet.Status(); s.Data.Total != uint64(defaultDataLoopbackSize) || s.Metadata.Total != uint64(defaultMetaDataLoopbackSize) {
|
||||
t.Fatalf("data or metadata loop back size is incorrect")
|
||||
}
|
||||
if err := driver.Cleanup(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
//Reload
|
||||
d, err := Init(driver.home, []string{
|
||||
fmt.Sprintf("dm.loopdatasize=%d", defaultDataLoopbackSize+delta),
|
||||
fmt.Sprintf("dm.loopmetadatasize=%d", defaultMetaDataLoopbackSize+delta),
|
||||
}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating devicemapper driver: %v", err)
|
||||
}
|
||||
driver = d.(*graphdriver.NaiveDiffDriver).ProtoDriver.(*Driver)
|
||||
if s := driver.DeviceSet.Status(); s.Data.Total != uint64(expectDataSize) || s.Metadata.Total != uint64(expectMetaDataSize) {
|
||||
t.Fatalf("data or metadata loop back size is incorrect")
|
||||
}
|
||||
if err := driver.Cleanup(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure devices.Lock() has been release upon return from cleanupDeletedDevices() function
|
||||
func TestDevmapperLockReleasedDeviceDeletion(t *testing.T) {
|
||||
driver := graphtest.GetDriver(t, "devicemapper").(*graphtest.Driver).Driver.(*graphdriver.NaiveDiffDriver).ProtoDriver.(*Driver)
|
||||
defer graphtest.PutDriver(t)
|
||||
|
||||
// Call cleanupDeletedDevices() and after the call take and release
|
||||
// DeviceSet Lock. If lock has not been released, this will hang.
|
||||
driver.DeviceSet.cleanupDeletedDevices()
|
||||
|
||||
doneChan := make(chan bool)
|
||||
|
||||
go func() {
|
||||
driver.DeviceSet.Lock()
|
||||
defer driver.DeviceSet.Unlock()
|
||||
doneChan <- true
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-time.After(time.Second * 5):
|
||||
// Timer expired. That means lock was not released upon
|
||||
// function return and we are deadlocked. Release lock
|
||||
// here so that cleanup could succeed and fail the test.
|
||||
driver.DeviceSet.Unlock()
|
||||
t.Fatalf("Could not acquire devices lock after call to cleanupDeletedDevices()")
|
||||
case <-doneChan:
|
||||
}
|
||||
}
|
||||
204
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/devmapper/driver.go
generated
vendored
Normal file
204
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/devmapper/driver.go
generated
vendored
Normal file
@@ -0,0 +1,204 @@
|
||||
// +build linux
|
||||
|
||||
package devmapper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/hyperhq/hypercli/daemon/graphdriver"
|
||||
"github.com/hyperhq/hypercli/pkg/devicemapper"
|
||||
"github.com/hyperhq/hypercli/pkg/idtools"
|
||||
"github.com/hyperhq/hypercli/pkg/mount"
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
|
||||
func init() {
|
||||
graphdriver.Register("devicemapper", Init)
|
||||
}
|
||||
|
||||
// Driver contains the device set mounted and the home directory
|
||||
type Driver struct {
|
||||
*DeviceSet
|
||||
home string
|
||||
uidMaps []idtools.IDMap
|
||||
gidMaps []idtools.IDMap
|
||||
}
|
||||
|
||||
// Init creates a driver with the given home and the set of options.
|
||||
func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
|
||||
deviceSet, err := NewDeviceSet(home, true, options, uidMaps, gidMaps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := mount.MakePrivate(home); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d := &Driver{
|
||||
DeviceSet: deviceSet,
|
||||
home: home,
|
||||
uidMaps: uidMaps,
|
||||
gidMaps: gidMaps,
|
||||
}
|
||||
|
||||
return graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps), nil
|
||||
}
|
||||
|
||||
func (d *Driver) String() string {
|
||||
return "devicemapper"
|
||||
}
|
||||
|
||||
// Status returns the status about the driver in a printable format.
|
||||
// Information returned contains Pool Name, Data File, Metadata file, disk usage by
|
||||
// the data and metadata, etc.
|
||||
func (d *Driver) Status() [][2]string {
|
||||
s := d.DeviceSet.Status()
|
||||
|
||||
status := [][2]string{
|
||||
{"Pool Name", s.PoolName},
|
||||
{"Pool Blocksize", fmt.Sprintf("%s", units.HumanSize(float64(s.SectorSize)))},
|
||||
{"Base Device Size", fmt.Sprintf("%s", units.HumanSize(float64(s.BaseDeviceSize)))},
|
||||
{"Backing Filesystem", s.BaseDeviceFS},
|
||||
{"Data file", s.DataFile},
|
||||
{"Metadata file", s.MetadataFile},
|
||||
{"Data Space Used", fmt.Sprintf("%s", units.HumanSize(float64(s.Data.Used)))},
|
||||
{"Data Space Total", fmt.Sprintf("%s", units.HumanSize(float64(s.Data.Total)))},
|
||||
{"Data Space Available", fmt.Sprintf("%s", units.HumanSize(float64(s.Data.Available)))},
|
||||
{"Metadata Space Used", fmt.Sprintf("%s", units.HumanSize(float64(s.Metadata.Used)))},
|
||||
{"Metadata Space Total", fmt.Sprintf("%s", units.HumanSize(float64(s.Metadata.Total)))},
|
||||
{"Metadata Space Available", fmt.Sprintf("%s", units.HumanSize(float64(s.Metadata.Available)))},
|
||||
{"Udev Sync Supported", fmt.Sprintf("%v", s.UdevSyncSupported)},
|
||||
{"Deferred Removal Enabled", fmt.Sprintf("%v", s.DeferredRemoveEnabled)},
|
||||
{"Deferred Deletion Enabled", fmt.Sprintf("%v", s.DeferredDeleteEnabled)},
|
||||
{"Deferred Deleted Device Count", fmt.Sprintf("%v", s.DeferredDeletedDeviceCount)},
|
||||
}
|
||||
if len(s.DataLoopback) > 0 {
|
||||
status = append(status, [2]string{"Data loop file", s.DataLoopback})
|
||||
}
|
||||
if len(s.MetadataLoopback) > 0 {
|
||||
status = append(status, [2]string{"Metadata loop file", s.MetadataLoopback})
|
||||
}
|
||||
if vStr, err := devicemapper.GetLibraryVersion(); err == nil {
|
||||
status = append(status, [2]string{"Library Version", vStr})
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
// GetMetadata returns a map of information about the device.
|
||||
func (d *Driver) GetMetadata(id string) (map[string]string, error) {
|
||||
m, err := d.DeviceSet.exportDeviceMetadata(id)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metadata := make(map[string]string)
|
||||
metadata["DeviceId"] = strconv.Itoa(m.deviceID)
|
||||
metadata["DeviceSize"] = strconv.FormatUint(m.deviceSize, 10)
|
||||
metadata["DeviceName"] = m.deviceName
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
// Cleanup unmounts a device.
|
||||
func (d *Driver) Cleanup() error {
|
||||
err := d.DeviceSet.Shutdown()
|
||||
|
||||
if err2 := mount.Unmount(d.home); err == nil {
|
||||
err = err2
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Create adds a device with a given id and the parent.
|
||||
func (d *Driver) Create(id, parent, mountLabel string) error {
|
||||
if err := d.DeviceSet.AddDevice(id, parent); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove removes a device with a given id, unmounts the filesystem.
|
||||
func (d *Driver) Remove(id string) error {
|
||||
if !d.DeviceSet.HasDevice(id) {
|
||||
// Consider removing a non-existing device a no-op
|
||||
// This is useful to be able to progress on container removal
|
||||
// if the underlying device has gone away due to earlier errors
|
||||
return nil
|
||||
}
|
||||
|
||||
// This assumes the device has been properly Get/Put:ed and thus is unmounted
|
||||
if err := d.DeviceSet.DeleteDevice(id, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mp := path.Join(d.home, "mnt", id)
|
||||
if err := os.RemoveAll(mp); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get mounts a device with given id into the root filesystem
|
||||
func (d *Driver) Get(id, mountLabel string) (string, error) {
|
||||
mp := path.Join(d.home, "mnt", id)
|
||||
|
||||
uid, gid, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Create the target directories if they don't exist
|
||||
if err := idtools.MkdirAllAs(path.Join(d.home, "mnt"), 0755, uid, gid); err != nil && !os.IsExist(err) {
|
||||
return "", err
|
||||
}
|
||||
if err := idtools.MkdirAs(mp, 0755, uid, gid); err != nil && !os.IsExist(err) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Mount the device
|
||||
if err := d.DeviceSet.MountDevice(id, mp, mountLabel); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
rootFs := path.Join(mp, "rootfs")
|
||||
if err := idtools.MkdirAllAs(rootFs, 0755, uid, gid); err != nil && !os.IsExist(err) {
|
||||
d.DeviceSet.UnmountDevice(id, mp)
|
||||
return "", err
|
||||
}
|
||||
|
||||
idFile := path.Join(mp, "id")
|
||||
if _, err := os.Stat(idFile); err != nil && os.IsNotExist(err) {
|
||||
// Create an "id" file with the container/image id in it to help reconstruct this in case
|
||||
// of later problems
|
||||
if err := ioutil.WriteFile(idFile, []byte(id), 0600); err != nil {
|
||||
d.DeviceSet.UnmountDevice(id, mp)
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return rootFs, nil
|
||||
}
|
||||
|
||||
// Put unmounts a device and removes it.
|
||||
func (d *Driver) Put(id string) error {
|
||||
mp := path.Join(d.home, "mnt", id)
|
||||
err := d.DeviceSet.UnmountDevice(id, mp)
|
||||
if err != nil {
|
||||
logrus.Errorf("devmapper: Error unmounting device %s: %s", id, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Exists checks to see if the device exists.
|
||||
func (d *Driver) Exists(id string) bool {
|
||||
return d.DeviceSet.HasDevice(id)
|
||||
}
|
||||
89
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/devmapper/mount.go
generated
vendored
Normal file
89
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/devmapper/mount.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
// +build linux
|
||||
|
||||
package devmapper
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// FIXME: this is copy-pasted from the aufs driver.
|
||||
// It should be moved into the core.
|
||||
|
||||
// Mounted returns true if a mount point exists.
|
||||
func Mounted(mountpoint string) (bool, error) {
|
||||
mntpoint, err := os.Stat(mountpoint)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
parent, err := os.Stat(filepath.Join(mountpoint, ".."))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
mntpointSt := mntpoint.Sys().(*syscall.Stat_t)
|
||||
parentSt := parent.Sys().(*syscall.Stat_t)
|
||||
return mntpointSt.Dev != parentSt.Dev, nil
|
||||
}
|
||||
|
||||
type probeData struct {
|
||||
fsName string
|
||||
magic string
|
||||
offset uint64
|
||||
}
|
||||
|
||||
// ProbeFsType returns the filesystem name for the given device id.
|
||||
func ProbeFsType(device string) (string, error) {
|
||||
probes := []probeData{
|
||||
{"btrfs", "_BHRfS_M", 0x10040},
|
||||
{"ext4", "\123\357", 0x438},
|
||||
{"xfs", "XFSB", 0},
|
||||
}
|
||||
|
||||
maxLen := uint64(0)
|
||||
for _, p := range probes {
|
||||
l := p.offset + uint64(len(p.magic))
|
||||
if l > maxLen {
|
||||
maxLen = l
|
||||
}
|
||||
}
|
||||
|
||||
file, err := os.Open(device)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
buffer := make([]byte, maxLen)
|
||||
l, err := file.Read(buffer)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if uint64(l) != maxLen {
|
||||
return "", fmt.Errorf("devmapper: unable to detect filesystem type of %s, short read", device)
|
||||
}
|
||||
|
||||
for _, p := range probes {
|
||||
if bytes.Equal([]byte(p.magic), buffer[p.offset:p.offset+uint64(len(p.magic))]) {
|
||||
return p.fsName, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("devmapper: Unknown filesystem type on %s", device)
|
||||
}
|
||||
|
||||
func joinMountOptions(a, b string) string {
|
||||
if a == "" {
|
||||
return b
|
||||
}
|
||||
if b == "" {
|
||||
return a
|
||||
}
|
||||
return a + "," + b
|
||||
}
|
||||
218
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/driver.go
generated
vendored
Normal file
218
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/driver.go
generated
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
package graphdriver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/hyperhq/hypercli/pkg/archive"
|
||||
"github.com/hyperhq/hypercli/pkg/idtools"
|
||||
)
|
||||
|
||||
// FsMagic unsigned id of the filesystem in use.
|
||||
type FsMagic uint32
|
||||
|
||||
const (
|
||||
// FsMagicUnsupported is a predefined constant value other than a valid filesystem id.
|
||||
FsMagicUnsupported = FsMagic(0x00000000)
|
||||
)
|
||||
|
||||
var (
|
||||
// All registered drivers
|
||||
drivers map[string]InitFunc
|
||||
|
||||
// ErrNotSupported returned when driver is not supported.
|
||||
ErrNotSupported = errors.New("driver not supported")
|
||||
// ErrPrerequisites retuned when driver does not meet prerequisites.
|
||||
ErrPrerequisites = errors.New("prerequisites for driver not satisfied (wrong filesystem?)")
|
||||
// ErrIncompatibleFS returned when file system is not supported.
|
||||
ErrIncompatibleFS = fmt.Errorf("backing file system is unsupported for this graph driver")
|
||||
)
|
||||
|
||||
// InitFunc initializes the storage driver.
|
||||
type InitFunc func(root string, options []string, uidMaps, gidMaps []idtools.IDMap) (Driver, error)
|
||||
|
||||
// ProtoDriver defines the basic capabilities of a driver.
|
||||
// This interface exists solely to be a minimum set of methods
|
||||
// for client code which choose not to implement the entire Driver
|
||||
// interface and use the NaiveDiffDriver wrapper constructor.
|
||||
//
|
||||
// Use of ProtoDriver directly by client code is not recommended.
|
||||
type ProtoDriver interface {
|
||||
// String returns a string representation of this driver.
|
||||
String() string
|
||||
// Create creates a new, empty, filesystem layer with the
|
||||
// specified id and parent and mountLabel. Parent and mountLabel may be "".
|
||||
Create(id, parent, mountLabel string) error
|
||||
// Remove attempts to remove the filesystem layer with this id.
|
||||
Remove(id string) error
|
||||
// Get returns the mountpoint for the layered filesystem referred
|
||||
// to by this id. You can optionally specify a mountLabel or "".
|
||||
// Returns the absolute path to the mounted layered filesystem.
|
||||
Get(id, mountLabel string) (dir string, err error)
|
||||
// Put releases the system resources for the specified id,
|
||||
// e.g, unmounting layered filesystem.
|
||||
Put(id string) error
|
||||
// Exists returns whether a filesystem layer with the specified
|
||||
// ID exists on this driver.
|
||||
Exists(id string) bool
|
||||
// Status returns a set of key-value pairs which give low
|
||||
// level diagnostic status about this driver.
|
||||
Status() [][2]string
|
||||
// Returns a set of key-value pairs which give low level information
|
||||
// about the image/container driver is managing.
|
||||
GetMetadata(id string) (map[string]string, error)
|
||||
// Cleanup performs necessary tasks to release resources
|
||||
// held by the driver, e.g., unmounting all layered filesystems
|
||||
// known to this driver.
|
||||
Cleanup() error
|
||||
}
|
||||
|
||||
// Driver is the interface for layered/snapshot file system drivers.
|
||||
type Driver interface {
|
||||
ProtoDriver
|
||||
// Diff produces an archive of the changes between the specified
|
||||
// layer and its parent layer which may be "".
|
||||
Diff(id, parent string) (archive.Archive, error)
|
||||
// Changes produces a list of changes between the specified layer
|
||||
// and its parent layer. If parent is "", then all changes will be ADD changes.
|
||||
Changes(id, parent string) ([]archive.Change, error)
|
||||
// ApplyDiff extracts the changeset from the given diff into the
|
||||
// layer with the specified id and parent, returning the size of the
|
||||
// new layer in bytes.
|
||||
// The archive.Reader must be an uncompressed stream.
|
||||
ApplyDiff(id, parent string, diff archive.Reader) (size int64, err error)
|
||||
// DiffSize calculates the changes between the specified id
|
||||
// and its parent and returns the size in bytes of the changes
|
||||
// relative to its base filesystem directory.
|
||||
DiffSize(id, parent string) (size int64, err error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
drivers = make(map[string]InitFunc)
|
||||
}
|
||||
|
||||
// Register registers a InitFunc for the driver.
|
||||
func Register(name string, initFunc InitFunc) error {
|
||||
if _, exists := drivers[name]; exists {
|
||||
return fmt.Errorf("Name already registered %s", name)
|
||||
}
|
||||
drivers[name] = initFunc
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDriver initializes and returns the registered driver
|
||||
func GetDriver(name, home string, options []string, uidMaps, gidMaps []idtools.IDMap) (Driver, error) {
|
||||
if initFunc, exists := drivers[name]; exists {
|
||||
return initFunc(filepath.Join(home, name), options, uidMaps, gidMaps)
|
||||
}
|
||||
if pluginDriver, err := lookupPlugin(name, home, options); err == nil {
|
||||
return pluginDriver, nil
|
||||
}
|
||||
logrus.Errorf("Failed to GetDriver graph %s %s", name, home)
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
// getBuiltinDriver initializes and returns the registered driver, but does not try to load from plugins
|
||||
func getBuiltinDriver(name, home string, options []string, uidMaps, gidMaps []idtools.IDMap) (Driver, error) {
|
||||
if initFunc, exists := drivers[name]; exists {
|
||||
return initFunc(filepath.Join(home, name), options, uidMaps, gidMaps)
|
||||
}
|
||||
logrus.Errorf("Failed to built-in GetDriver graph %s %s", name, home)
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
// New creates the driver and initializes it at the specified root.
|
||||
func New(root string, name string, options []string, uidMaps, gidMaps []idtools.IDMap) (driver Driver, err error) {
|
||||
if name != "" {
|
||||
logrus.Debugf("[graphdriver] trying provided driver %q", name) // so the logs show specified driver
|
||||
return GetDriver(name, root, options, uidMaps, gidMaps)
|
||||
}
|
||||
|
||||
// Guess for prior driver
|
||||
priorDrivers := scanPriorDrivers(root)
|
||||
for _, name := range priority {
|
||||
if name == "vfs" {
|
||||
// don't use vfs even if there is state present.
|
||||
continue
|
||||
}
|
||||
for _, prior := range priorDrivers {
|
||||
// of the state found from prior drivers, check in order of our priority
|
||||
// which we would prefer
|
||||
if prior == name {
|
||||
driver, err = getBuiltinDriver(name, root, options, uidMaps, gidMaps)
|
||||
if err != nil {
|
||||
// unlike below, we will return error here, because there is prior
|
||||
// state, and now it is no longer supported/prereq/compatible, so
|
||||
// something changed and needs attention. Otherwise the daemon's
|
||||
// images would just "disappear".
|
||||
logrus.Errorf("[graphdriver] prior storage driver %q failed: %s", name, err)
|
||||
return nil, err
|
||||
}
|
||||
if err := checkPriorDriver(name, root); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logrus.Infof("[graphdriver] using prior storage driver %q", name)
|
||||
return driver, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for priority drivers first
|
||||
for _, name := range priority {
|
||||
driver, err = getBuiltinDriver(name, root, options, uidMaps, gidMaps)
|
||||
if err != nil {
|
||||
if err == ErrNotSupported || err == ErrPrerequisites || err == ErrIncompatibleFS {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return driver, nil
|
||||
}
|
||||
|
||||
// Check all registered drivers if no priority driver is found
|
||||
for _, initFunc := range drivers {
|
||||
if driver, err = initFunc(root, options, uidMaps, gidMaps); err != nil {
|
||||
if err == ErrNotSupported || err == ErrPrerequisites || err == ErrIncompatibleFS {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return driver, nil
|
||||
}
|
||||
return nil, fmt.Errorf("No supported storage backend found")
|
||||
}
|
||||
|
||||
// scanPriorDrivers returns an un-ordered scan of directories of prior storage drivers
|
||||
func scanPriorDrivers(root string) []string {
|
||||
priorDrivers := []string{}
|
||||
for driver := range drivers {
|
||||
p := filepath.Join(root, driver)
|
||||
if _, err := os.Stat(p); err == nil && driver != "vfs" {
|
||||
priorDrivers = append(priorDrivers, driver)
|
||||
}
|
||||
}
|
||||
return priorDrivers
|
||||
}
|
||||
|
||||
func checkPriorDriver(name, root string) error {
|
||||
priorDrivers := []string{}
|
||||
for _, prior := range scanPriorDrivers(root) {
|
||||
if prior != name && prior != "vfs" {
|
||||
if _, err := os.Stat(filepath.Join(root, prior)); err == nil {
|
||||
priorDrivers = append(priorDrivers, prior)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(priorDrivers) > 0 {
|
||||
|
||||
return fmt.Errorf("%q contains other graphdrivers: %s; Please cleanup or explicitly choose storage driver (-s <DRIVER>)", root, strings.Join(priorDrivers, ","))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
8
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/driver_freebsd.go
generated
vendored
Normal file
8
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/driver_freebsd.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
package graphdriver
|
||||
|
||||
var (
|
||||
// Slice of drivers that should be used in an order
|
||||
priority = []string{
|
||||
"zfs",
|
||||
}
|
||||
)
|
||||
88
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/driver_linux.go
generated
vendored
Normal file
88
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/driver_linux.go
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
// +build linux
|
||||
|
||||
package graphdriver
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
// FsMagicAufs filesystem id for Aufs
|
||||
FsMagicAufs = FsMagic(0x61756673)
|
||||
// FsMagicBtrfs filesystem id for Btrfs
|
||||
FsMagicBtrfs = FsMagic(0x9123683E)
|
||||
// FsMagicCramfs filesystem id for Cramfs
|
||||
FsMagicCramfs = FsMagic(0x28cd3d45)
|
||||
// FsMagicExtfs filesystem id for Extfs
|
||||
FsMagicExtfs = FsMagic(0x0000EF53)
|
||||
// FsMagicF2fs filesystem id for F2fs
|
||||
FsMagicF2fs = FsMagic(0xF2F52010)
|
||||
// FsMagicGPFS filesystem id for GPFS
|
||||
FsMagicGPFS = FsMagic(0x47504653)
|
||||
// FsMagicJffs2Fs filesystem if for Jffs2Fs
|
||||
FsMagicJffs2Fs = FsMagic(0x000072b6)
|
||||
// FsMagicJfs filesystem id for Jfs
|
||||
FsMagicJfs = FsMagic(0x3153464a)
|
||||
// FsMagicNfsFs filesystem id for NfsFs
|
||||
FsMagicNfsFs = FsMagic(0x00006969)
|
||||
// FsMagicRAMFs filesystem id for RamFs
|
||||
FsMagicRAMFs = FsMagic(0x858458f6)
|
||||
// FsMagicReiserFs filesystem id for ReiserFs
|
||||
FsMagicReiserFs = FsMagic(0x52654973)
|
||||
// FsMagicSmbFs filesystem id for SmbFs
|
||||
FsMagicSmbFs = FsMagic(0x0000517B)
|
||||
// FsMagicSquashFs filesystem id for SquashFs
|
||||
FsMagicSquashFs = FsMagic(0x73717368)
|
||||
// FsMagicTmpFs filesystem id for TmpFs
|
||||
FsMagicTmpFs = FsMagic(0x01021994)
|
||||
// FsMagicVxFS filesystem id for VxFs
|
||||
FsMagicVxFS = FsMagic(0xa501fcf5)
|
||||
// FsMagicXfs filesystem id for Xfs
|
||||
FsMagicXfs = FsMagic(0x58465342)
|
||||
// FsMagicZfs filesystem id for Zfs
|
||||
FsMagicZfs = FsMagic(0x2fc12fc1)
|
||||
)
|
||||
|
||||
var (
|
||||
// Slice of drivers that should be used in an order
|
||||
priority = []string{
|
||||
"aufs",
|
||||
"btrfs",
|
||||
"zfs",
|
||||
"devicemapper",
|
||||
"overlay",
|
||||
"vfs",
|
||||
}
|
||||
|
||||
// FsNames maps filesystem id to name of the filesystem.
|
||||
FsNames = map[FsMagic]string{
|
||||
FsMagicAufs: "aufs",
|
||||
FsMagicBtrfs: "btrfs",
|
||||
FsMagicCramfs: "cramfs",
|
||||
FsMagicExtfs: "extfs",
|
||||
FsMagicF2fs: "f2fs",
|
||||
FsMagicGPFS: "gpfs",
|
||||
FsMagicJffs2Fs: "jffs2",
|
||||
FsMagicJfs: "jfs",
|
||||
FsMagicNfsFs: "nfs",
|
||||
FsMagicRAMFs: "ramfs",
|
||||
FsMagicReiserFs: "reiserfs",
|
||||
FsMagicSmbFs: "smb",
|
||||
FsMagicSquashFs: "squashfs",
|
||||
FsMagicTmpFs: "tmpfs",
|
||||
FsMagicUnsupported: "unsupported",
|
||||
FsMagicVxFS: "vxfs",
|
||||
FsMagicXfs: "xfs",
|
||||
FsMagicZfs: "zfs",
|
||||
}
|
||||
)
|
||||
|
||||
// GetFSMagic returns the filesystem id given the path.
|
||||
func GetFSMagic(rootpath string) (FsMagic, error) {
|
||||
var buf syscall.Statfs_t
|
||||
if err := syscall.Statfs(filepath.Dir(rootpath), &buf); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return FsMagic(buf.Type), nil
|
||||
}
|
||||
15
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/driver_unsupported.go
generated
vendored
Normal file
15
vendor/github.com/hyperhq/hypercli/daemon/graphdriver/driver_unsupported.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// +build !linux,!windows,!freebsd
|
||||
|
||||
package graphdriver
|
||||
|
||||
var (
|
||||
// Slice of drivers that should be used in an order
|
||||
priority = []string{
|
||||
"unsupported",
|
||||
}
|
||||
)
|
||||
|
||||
// GetFSMagic returns the filesystem id given the path.
|
||||
func GetFSMagic(rootpath string) (FsMagic, error) {
|
||||
return FsMagicUnsupported, nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user