Initial commit
This commit is contained in:
69
vendor/github.com/hyperhq/hypercli/container/archive.go
generated
vendored
Normal file
69
vendor/github.com/hyperhq/hypercli/container/archive.go
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/engine-api/types"
|
||||
)
|
||||
|
||||
// ResolvePath resolves the given path in the container to a resource on the
|
||||
// host. Returns a resolved path (absolute path to the resource on the host),
|
||||
// the absolute path to the resource relative to the container's rootfs, and
|
||||
// a error if the path points to outside the container's rootfs.
|
||||
func (container *Container) ResolvePath(path string) (resolvedPath, absPath string, err error) {
|
||||
// Consider the given path as an absolute path in the container.
|
||||
absPath = archive.PreserveTrailingDotOrSeparator(filepath.Join(string(filepath.Separator), path), path)
|
||||
|
||||
// Split the absPath into its Directory and Base components. We will
|
||||
// resolve the dir in the scope of the container then append the base.
|
||||
dirPath, basePath := filepath.Split(absPath)
|
||||
|
||||
resolvedDirPath, err := container.GetResourcePath(dirPath)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
// resolvedDirPath will have been cleaned (no trailing path separators) so
|
||||
// we can manually join it with the base path element.
|
||||
resolvedPath = resolvedDirPath + string(filepath.Separator) + basePath
|
||||
|
||||
return resolvedPath, absPath, nil
|
||||
}
|
||||
|
||||
// StatPath is the unexported version of StatPath. Locks and mounts should
|
||||
// be acquired before calling this method and the given path should be fully
|
||||
// resolved to a path on the host corresponding to the given absolute path
|
||||
// inside the container.
|
||||
func (container *Container) StatPath(resolvedPath, absPath string) (stat *types.ContainerPathStat, err error) {
|
||||
lstat, err := os.Lstat(resolvedPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var linkTarget string
|
||||
if lstat.Mode()&os.ModeSymlink != 0 {
|
||||
// Fully evaluate the symlink in the scope of the container rootfs.
|
||||
hostPath, err := container.GetResourcePath(absPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
linkTarget, err = filepath.Rel(container.BaseFS, hostPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Make it an absolute path.
|
||||
linkTarget = filepath.Join(string(filepath.Separator), linkTarget)
|
||||
}
|
||||
|
||||
return &types.ContainerPathStat{
|
||||
Name: filepath.Base(absPath),
|
||||
Size: lstat.Size(),
|
||||
Mode: lstat.Mode(),
|
||||
Mtime: lstat.ModTime(),
|
||||
LinkTarget: linkTarget,
|
||||
}, nil
|
||||
}
|
||||
570
vendor/github.com/hyperhq/hypercli/container/container.go
generated
vendored
Normal file
570
vendor/github.com/hyperhq/hypercli/container/container.go
generated
vendored
Normal file
@@ -0,0 +1,570 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/daemon/exec"
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
"github.com/docker/docker/daemon/logger"
|
||||
"github.com/docker/docker/daemon/logger/jsonfilelog"
|
||||
"github.com/docker/docker/daemon/network"
|
||||
derr "github.com/docker/docker/errors"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/layer"
|
||||
"github.com/docker/docker/pkg/promise"
|
||||
"github.com/docker/docker/pkg/signal"
|
||||
"github.com/docker/docker/pkg/symlink"
|
||||
"github.com/docker/docker/runconfig"
|
||||
"github.com/docker/docker/volume"
|
||||
containertypes "github.com/docker/engine-api/types/container"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/opencontainers/runc/libcontainer/label"
|
||||
)
|
||||
|
||||
const configFileName = "config.v2.json"
|
||||
|
||||
// CommonContainer holds the fields for a container which are
|
||||
// applicable across all platforms supported by the daemon.
|
||||
type CommonContainer struct {
|
||||
*runconfig.StreamConfig
|
||||
// embed for Container to support states directly.
|
||||
*State `json:"State"` // Needed for remote api version <= 1.11
|
||||
Root string `json:"-"` // Path to the "home" of the container, including metadata.
|
||||
BaseFS string `json:"-"` // Path to the graphdriver mountpoint
|
||||
RWLayer layer.RWLayer `json:"-"`
|
||||
ID string
|
||||
Created time.Time
|
||||
Path string
|
||||
Args []string
|
||||
Config *containertypes.Config
|
||||
ImageID image.ID `json:"Image"`
|
||||
NetworkSettings *network.Settings
|
||||
LogPath string
|
||||
Name string
|
||||
Driver string
|
||||
// MountLabel contains the options for the 'mount' command
|
||||
MountLabel string
|
||||
ProcessLabel string
|
||||
RestartCount int
|
||||
HasBeenStartedBefore bool
|
||||
HasBeenManuallyStopped bool // used for unless-stopped restart policy
|
||||
MountPoints map[string]*volume.MountPoint
|
||||
HostConfig *containertypes.HostConfig `json:"-"` // do not serialize the host config in the json, otherwise we'll make the container unportable
|
||||
Command *execdriver.Command `json:"-"`
|
||||
monitor *containerMonitor
|
||||
ExecCommands *exec.Store `json:"-"`
|
||||
// logDriver for closing
|
||||
LogDriver logger.Logger `json:"-"`
|
||||
LogCopier *logger.Copier `json:"-"`
|
||||
}
|
||||
|
||||
// NewBaseContainer creates a new container with its
|
||||
// basic configuration.
|
||||
func NewBaseContainer(id, root string) *Container {
|
||||
return &Container{
|
||||
CommonContainer: CommonContainer{
|
||||
ID: id,
|
||||
State: NewState(),
|
||||
ExecCommands: exec.NewStore(),
|
||||
Root: root,
|
||||
MountPoints: make(map[string]*volume.MountPoint),
|
||||
StreamConfig: runconfig.NewStreamConfig(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// FromDisk loads the container configuration stored in the host.
|
||||
func (container *Container) FromDisk() error {
|
||||
pth, err := container.ConfigPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonSource, err := os.Open(pth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer jsonSource.Close()
|
||||
|
||||
dec := json.NewDecoder(jsonSource)
|
||||
|
||||
// Load container settings
|
||||
if err := dec.Decode(container); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := label.ReserveLabel(container.ProcessLabel); err != nil {
|
||||
return err
|
||||
}
|
||||
return container.readHostConfig()
|
||||
}
|
||||
|
||||
// ToDisk saves the container configuration on disk.
|
||||
func (container *Container) ToDisk() error {
|
||||
pth, err := container.ConfigPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonSource, err := os.Create(pth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer jsonSource.Close()
|
||||
|
||||
enc := json.NewEncoder(jsonSource)
|
||||
|
||||
// Save container settings
|
||||
if err := enc.Encode(container); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return container.WriteHostConfig()
|
||||
}
|
||||
|
||||
// ToDiskLocking saves the container configuration on disk in a thread safe way.
|
||||
func (container *Container) ToDiskLocking() error {
|
||||
container.Lock()
|
||||
err := container.ToDisk()
|
||||
container.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
// readHostConfig reads the host configuration from disk for the container.
|
||||
func (container *Container) readHostConfig() error {
|
||||
container.HostConfig = &containertypes.HostConfig{}
|
||||
// If the hostconfig file does not exist, do not read it.
|
||||
// (We still have to initialize container.HostConfig,
|
||||
// but that's OK, since we just did that above.)
|
||||
pth, err := container.HostConfigPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := os.Open(pth)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := json.NewDecoder(f).Decode(&container.HostConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
container.InitDNSHostConfig()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteHostConfig saves the host configuration on disk for the container.
|
||||
func (container *Container) WriteHostConfig() error {
|
||||
pth, err := container.HostConfigPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := os.Create(pth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return json.NewEncoder(f).Encode(&container.HostConfig)
|
||||
}
|
||||
|
||||
// GetResourcePath evaluates `path` in the scope of the container's BaseFS, with proper path
|
||||
// sanitisation. Symlinks are all scoped to the BaseFS of the container, as
|
||||
// though the container's BaseFS was `/`.
|
||||
//
|
||||
// The BaseFS of a container is the host-facing path which is bind-mounted as
|
||||
// `/` inside the container. This method is essentially used to access a
|
||||
// particular path inside the container as though you were a process in that
|
||||
// container.
|
||||
//
|
||||
// NOTE: The returned path is *only* safely scoped inside the container's BaseFS
|
||||
// if no component of the returned path changes (such as a component
|
||||
// symlinking to a different path) between using this method and using the
|
||||
// path. See symlink.FollowSymlinkInScope for more details.
|
||||
func (container *Container) GetResourcePath(path string) (string, error) {
|
||||
// IMPORTANT - These are paths on the OS where the daemon is running, hence
|
||||
// any filepath operations must be done in an OS agnostic way.
|
||||
cleanPath := filepath.Join(string(os.PathSeparator), path)
|
||||
r, e := symlink.FollowSymlinkInScope(filepath.Join(container.BaseFS, cleanPath), container.BaseFS)
|
||||
return r, e
|
||||
}
|
||||
|
||||
// GetRootResourcePath evaluates `path` in the scope of the container's root, with proper path
|
||||
// sanitisation. Symlinks are all scoped to the root of the container, as
|
||||
// though the container's root was `/`.
|
||||
//
|
||||
// The root of a container is the host-facing configuration metadata directory.
|
||||
// Only use this method to safely access the container's `container.json` or
|
||||
// other metadata files. If in doubt, use container.GetResourcePath.
|
||||
//
|
||||
// NOTE: The returned path is *only* safely scoped inside the container's root
|
||||
// if no component of the returned path changes (such as a component
|
||||
// symlinking to a different path) between using this method and using the
|
||||
// path. See symlink.FollowSymlinkInScope for more details.
|
||||
func (container *Container) GetRootResourcePath(path string) (string, error) {
|
||||
// IMPORTANT - These are paths on the OS where the daemon is running, hence
|
||||
// any filepath operations must be done in an OS agnostic way.
|
||||
cleanPath := filepath.Join(string(os.PathSeparator), path)
|
||||
return symlink.FollowSymlinkInScope(filepath.Join(container.Root, cleanPath), container.Root)
|
||||
}
|
||||
|
||||
// ExitOnNext signals to the monitor that it should not restart the container
|
||||
// after we send the kill signal.
|
||||
func (container *Container) ExitOnNext() {
|
||||
container.monitor.ExitOnNext()
|
||||
}
|
||||
|
||||
// Resize changes the TTY of the process running inside the container
|
||||
// to the given height and width. The container must be running.
|
||||
func (container *Container) Resize(h, w int) error {
|
||||
if container.Command.ProcessConfig.Terminal == nil {
|
||||
return fmt.Errorf("Container %s does not have a terminal ready", container.ID)
|
||||
}
|
||||
if err := container.Command.ProcessConfig.Terminal.Resize(h, w); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HostConfigPath returns the path to the container's JSON hostconfig
|
||||
func (container *Container) HostConfigPath() (string, error) {
|
||||
return container.GetRootResourcePath("hostconfig.json")
|
||||
}
|
||||
|
||||
// ConfigPath returns the path to the container's JSON config
|
||||
func (container *Container) ConfigPath() (string, error) {
|
||||
return container.GetRootResourcePath(configFileName)
|
||||
}
|
||||
|
||||
func validateID(id string) error {
|
||||
if id == "" {
|
||||
return derr.ErrorCodeEmptyID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns true if the container exposes a certain port
|
||||
func (container *Container) exposes(p nat.Port) bool {
|
||||
_, exists := container.Config.ExposedPorts[p]
|
||||
return exists
|
||||
}
|
||||
|
||||
// GetLogConfig returns the log configuration for the container.
|
||||
func (container *Container) GetLogConfig(defaultConfig containertypes.LogConfig) containertypes.LogConfig {
|
||||
cfg := container.HostConfig.LogConfig
|
||||
if cfg.Type != "" || len(cfg.Config) > 0 { // container has log driver configured
|
||||
if cfg.Type == "" {
|
||||
cfg.Type = jsonfilelog.Name
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
// Use daemon's default log config for containers
|
||||
return defaultConfig
|
||||
}
|
||||
|
||||
// StartLogger starts a new logger driver for the container.
|
||||
func (container *Container) StartLogger(cfg containertypes.LogConfig) (logger.Logger, error) {
|
||||
c, err := logger.GetLogDriver(cfg.Type)
|
||||
if err != nil {
|
||||
return nil, derr.ErrorCodeLoggingFactory.WithArgs(err)
|
||||
}
|
||||
ctx := logger.Context{
|
||||
Config: cfg.Config,
|
||||
ContainerID: container.ID,
|
||||
ContainerName: container.Name,
|
||||
ContainerEntrypoint: container.Path,
|
||||
ContainerArgs: container.Args,
|
||||
ContainerImageID: container.ImageID.String(),
|
||||
ContainerImageName: container.Config.Image,
|
||||
ContainerCreated: container.Created,
|
||||
ContainerEnv: container.Config.Env,
|
||||
ContainerLabels: container.Config.Labels,
|
||||
}
|
||||
|
||||
// Set logging file for "json-logger"
|
||||
if cfg.Type == jsonfilelog.Name {
|
||||
ctx.LogPath, err = container.GetRootResourcePath(fmt.Sprintf("%s-json.log", container.ID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return c(ctx)
|
||||
}
|
||||
|
||||
// GetProcessLabel returns the process label for the container.
|
||||
func (container *Container) GetProcessLabel() string {
|
||||
// even if we have a process label return "" if we are running
|
||||
// in privileged mode
|
||||
if container.HostConfig.Privileged {
|
||||
return ""
|
||||
}
|
||||
return container.ProcessLabel
|
||||
}
|
||||
|
||||
// GetMountLabel returns the mounting label for the container.
|
||||
// This label is empty if the container is privileged.
|
||||
func (container *Container) GetMountLabel() string {
|
||||
if container.HostConfig.Privileged {
|
||||
return ""
|
||||
}
|
||||
return container.MountLabel
|
||||
}
|
||||
|
||||
// GetExecIDs returns the list of exec commands running on the container.
|
||||
func (container *Container) GetExecIDs() []string {
|
||||
return container.ExecCommands.List()
|
||||
}
|
||||
|
||||
// Attach connects to the container's TTY, delegating to standard
|
||||
// streams or websockets depending on the configuration.
|
||||
func (container *Container) Attach(stdin io.ReadCloser, stdout io.Writer, stderr io.Writer, keys []byte) chan error {
|
||||
return AttachStreams(container.StreamConfig, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, stdin, stdout, stderr, keys)
|
||||
}
|
||||
|
||||
// AttachStreams connects streams to a TTY.
|
||||
// Used by exec too. Should this move somewhere else?
|
||||
func AttachStreams(streamConfig *runconfig.StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer, keys []byte) chan error {
|
||||
var (
|
||||
cStdout, cStderr io.ReadCloser
|
||||
cStdin io.WriteCloser
|
||||
wg sync.WaitGroup
|
||||
errors = make(chan error, 3)
|
||||
)
|
||||
|
||||
if stdin != nil && openStdin {
|
||||
cStdin = streamConfig.StdinPipe()
|
||||
wg.Add(1)
|
||||
}
|
||||
|
||||
if stdout != nil {
|
||||
cStdout = streamConfig.StdoutPipe()
|
||||
wg.Add(1)
|
||||
}
|
||||
|
||||
if stderr != nil {
|
||||
cStderr = streamConfig.StderrPipe()
|
||||
wg.Add(1)
|
||||
}
|
||||
|
||||
// Connect stdin of container to the http conn.
|
||||
go func() {
|
||||
if stdin == nil || !openStdin {
|
||||
return
|
||||
}
|
||||
logrus.Debugf("attach: stdin: begin")
|
||||
defer func() {
|
||||
if stdinOnce && !tty {
|
||||
cStdin.Close()
|
||||
} else {
|
||||
// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
|
||||
if cStdout != nil {
|
||||
cStdout.Close()
|
||||
}
|
||||
if cStderr != nil {
|
||||
cStderr.Close()
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
logrus.Debugf("attach: stdin: end")
|
||||
}()
|
||||
|
||||
var err error
|
||||
if tty {
|
||||
_, err = copyEscapable(cStdin, stdin, keys)
|
||||
} else {
|
||||
_, err = io.Copy(cStdin, stdin)
|
||||
|
||||
}
|
||||
if err == io.ErrClosedPipe {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
logrus.Errorf("attach: stdin: %s", err)
|
||||
errors <- err
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
attachStream := func(name string, stream io.Writer, streamPipe io.ReadCloser) {
|
||||
if stream == nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
// Make sure stdin gets closed
|
||||
if stdin != nil {
|
||||
stdin.Close()
|
||||
}
|
||||
streamPipe.Close()
|
||||
wg.Done()
|
||||
logrus.Debugf("attach: %s: end", name)
|
||||
}()
|
||||
|
||||
logrus.Debugf("attach: %s: begin", name)
|
||||
_, err := io.Copy(stream, streamPipe)
|
||||
if err == io.ErrClosedPipe {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
logrus.Errorf("attach: %s: %v", name, err)
|
||||
errors <- err
|
||||
}
|
||||
}
|
||||
|
||||
go attachStream("stdout", stdout, cStdout)
|
||||
go attachStream("stderr", stderr, cStderr)
|
||||
|
||||
return promise.Go(func() error {
|
||||
wg.Wait()
|
||||
close(errors)
|
||||
for err := range errors {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Code c/c from io.Copy() modified to handle escape sequence
|
||||
func copyEscapable(dst io.Writer, src io.ReadCloser, keys []byte) (written int64, err error) {
|
||||
if len(keys) == 0 {
|
||||
// Default keys : ctrl-p ctrl-q
|
||||
keys = []byte{16, 17}
|
||||
}
|
||||
buf := make([]byte, 32*1024)
|
||||
for {
|
||||
nr, er := src.Read(buf)
|
||||
if nr > 0 {
|
||||
// ---- Docker addition
|
||||
for i, key := range keys {
|
||||
if nr != 1 || buf[0] != key {
|
||||
break
|
||||
}
|
||||
if i == len(keys)-1 {
|
||||
if err := src.Close(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
nr, er = src.Read(buf)
|
||||
}
|
||||
// ---- End of docker
|
||||
nw, ew := dst.Write(buf[0:nr])
|
||||
if nw > 0 {
|
||||
written += int64(nw)
|
||||
}
|
||||
if ew != nil {
|
||||
err = ew
|
||||
break
|
||||
}
|
||||
if nr != nw {
|
||||
err = io.ErrShortWrite
|
||||
break
|
||||
}
|
||||
}
|
||||
if er == io.EOF {
|
||||
break
|
||||
}
|
||||
if er != nil {
|
||||
err = er
|
||||
break
|
||||
}
|
||||
}
|
||||
return written, err
|
||||
}
|
||||
|
||||
// ShouldRestart decides whether the daemon should restart the container or not.
|
||||
// This is based on the container's restart policy.
|
||||
func (container *Container) ShouldRestart() bool {
|
||||
return container.HostConfig.RestartPolicy.Name == "always" ||
|
||||
(container.HostConfig.RestartPolicy.Name == "unless-stopped" && !container.HasBeenManuallyStopped) ||
|
||||
(container.HostConfig.RestartPolicy.Name == "on-failure" && container.ExitCode != 0)
|
||||
}
|
||||
|
||||
// AddBindMountPoint adds a new bind mount point configuration to the container.
|
||||
func (container *Container) AddBindMountPoint(name, source, destination string, rw bool) {
|
||||
container.MountPoints[destination] = &volume.MountPoint{
|
||||
Name: name,
|
||||
Source: source,
|
||||
Destination: destination,
|
||||
RW: rw,
|
||||
}
|
||||
}
|
||||
|
||||
// AddLocalMountPoint adds a new local mount point configuration to the container.
|
||||
func (container *Container) AddLocalMountPoint(name, destination string, rw bool) {
|
||||
container.MountPoints[destination] = &volume.MountPoint{
|
||||
Name: name,
|
||||
Driver: volume.DefaultDriverName,
|
||||
Destination: destination,
|
||||
RW: rw,
|
||||
}
|
||||
}
|
||||
|
||||
// AddMountPointWithVolume adds a new mount point configured with a volume to the container.
|
||||
func (container *Container) AddMountPointWithVolume(destination string, vol volume.Volume, rw bool) {
|
||||
container.MountPoints[destination] = &volume.MountPoint{
|
||||
Name: vol.Name(),
|
||||
Driver: vol.DriverName(),
|
||||
Destination: destination,
|
||||
RW: rw,
|
||||
Volume: vol,
|
||||
}
|
||||
}
|
||||
|
||||
// IsDestinationMounted checks whether a path is mounted on the container or not.
|
||||
func (container *Container) IsDestinationMounted(destination string) bool {
|
||||
return container.MountPoints[destination] != nil
|
||||
}
|
||||
|
||||
// StopSignal returns the signal used to stop the container.
|
||||
func (container *Container) StopSignal() int {
|
||||
var stopSignal syscall.Signal
|
||||
if container.Config.StopSignal != "" {
|
||||
stopSignal, _ = signal.ParseSignal(container.Config.StopSignal)
|
||||
}
|
||||
|
||||
if int(stopSignal) == 0 {
|
||||
stopSignal, _ = signal.ParseSignal(signal.DefaultStopSignal)
|
||||
}
|
||||
return int(stopSignal)
|
||||
}
|
||||
|
||||
// InitDNSHostConfig ensures that the dns fields are never nil.
|
||||
// New containers don't ever have those fields nil,
|
||||
// but pre created containers can still have those nil values.
|
||||
// The non-recommended host configuration in the start api can
|
||||
// make these fields nil again, this corrects that issue until
|
||||
// we remove that behavior for good.
|
||||
// See https://github.com/docker/docker/pull/17779
|
||||
// for a more detailed explanation on why we don't want that.
|
||||
func (container *Container) InitDNSHostConfig() {
|
||||
container.Lock()
|
||||
defer container.Unlock()
|
||||
if container.HostConfig.DNS == nil {
|
||||
container.HostConfig.DNS = make([]string, 0)
|
||||
}
|
||||
|
||||
if container.HostConfig.DNSSearch == nil {
|
||||
container.HostConfig.DNSSearch = make([]string, 0)
|
||||
}
|
||||
|
||||
if container.HostConfig.DNSOptions == nil {
|
||||
container.HostConfig.DNSOptions = make([]string, 0)
|
||||
}
|
||||
}
|
||||
36
vendor/github.com/hyperhq/hypercli/container/container_unit_test.go
generated
vendored
Normal file
36
vendor/github.com/hyperhq/hypercli/container/container_unit_test.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/signal"
|
||||
"github.com/docker/engine-api/types/container"
|
||||
)
|
||||
|
||||
func TestContainerStopSignal(t *testing.T) {
|
||||
c := &Container{
|
||||
CommonContainer: CommonContainer{
|
||||
Config: &container.Config{},
|
||||
},
|
||||
}
|
||||
|
||||
def, err := signal.ParseSignal(signal.DefaultStopSignal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s := c.StopSignal()
|
||||
if s != int(def) {
|
||||
t.Fatalf("Expected %v, got %v", def, s)
|
||||
}
|
||||
|
||||
c = &Container{
|
||||
CommonContainer: CommonContainer{
|
||||
Config: &container.Config{StopSignal: "SIGKILL"},
|
||||
},
|
||||
}
|
||||
s = c.StopSignal()
|
||||
if s != 9 {
|
||||
t.Fatalf("Expected 9, got %v", s)
|
||||
}
|
||||
}
|
||||
770
vendor/github.com/hyperhq/hypercli/container/container_unix.go
generated
vendored
Normal file
770
vendor/github.com/hyperhq/hypercli/container/container_unix.go
generated
vendored
Normal file
@@ -0,0 +1,770 @@
|
||||
// +build linux freebsd
|
||||
|
||||
package container
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
derr "github.com/docker/docker/errors"
|
||||
"github.com/docker/docker/pkg/chrootarchive"
|
||||
"github.com/docker/docker/pkg/symlink"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
runconfigopts "github.com/docker/docker/runconfig/opts"
|
||||
"github.com/docker/docker/utils"
|
||||
"github.com/docker/docker/volume"
|
||||
containertypes "github.com/docker/engine-api/types/container"
|
||||
"github.com/docker/engine-api/types/network"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/docker/libnetwork"
|
||||
"github.com/docker/libnetwork/netlabel"
|
||||
"github.com/docker/libnetwork/options"
|
||||
"github.com/docker/libnetwork/types"
|
||||
"github.com/opencontainers/runc/libcontainer/label"
|
||||
)
|
||||
|
||||
// DefaultSHMSize is the default size (64MB) of the SHM which will be mounted in the container
|
||||
const DefaultSHMSize int64 = 67108864
|
||||
|
||||
// Container holds the fields specific to unixen implementations.
|
||||
// See CommonContainer for standard fields common to all containers.
|
||||
type Container struct {
|
||||
CommonContainer
|
||||
|
||||
// Fields below here are platform specific.
|
||||
AppArmorProfile string
|
||||
HostnamePath string
|
||||
HostsPath string
|
||||
ShmPath string
|
||||
MqueuePath string
|
||||
ResolvConfPath string
|
||||
SeccompProfile string
|
||||
}
|
||||
|
||||
// CreateDaemonEnvironment returns the list of all environment variables given the list of
|
||||
// environment variables related to links.
|
||||
// Sets PATH, HOSTNAME and if container.Config.Tty is set: TERM.
|
||||
// The defaults set here do not override the values in container.Config.Env
|
||||
func (container *Container) CreateDaemonEnvironment(linkedEnv []string) []string {
|
||||
// if a domain name was specified, append it to the hostname (see #7851)
|
||||
fullHostname := container.Config.Hostname
|
||||
if container.Config.Domainname != "" {
|
||||
fullHostname = fmt.Sprintf("%s.%s", fullHostname, container.Config.Domainname)
|
||||
}
|
||||
// Setup environment
|
||||
env := []string{
|
||||
"PATH=" + system.DefaultPathEnv,
|
||||
"HOSTNAME=" + fullHostname,
|
||||
}
|
||||
if container.Config.Tty {
|
||||
env = append(env, "TERM=xterm")
|
||||
}
|
||||
env = append(env, linkedEnv...)
|
||||
// because the env on the container can override certain default values
|
||||
// we need to replace the 'env' keys where they match and append anything
|
||||
// else.
|
||||
env = utils.ReplaceOrAppendEnvValues(env, container.Config.Env)
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
// TrySetNetworkMount attempts to set the network mounts given a provided destination and
|
||||
// the path to use for it; return true if the given destination was a network mount file
|
||||
func (container *Container) TrySetNetworkMount(destination string, path string) bool {
|
||||
if destination == "/etc/resolv.conf" {
|
||||
container.ResolvConfPath = path
|
||||
return true
|
||||
}
|
||||
if destination == "/etc/hostname" {
|
||||
container.HostnamePath = path
|
||||
return true
|
||||
}
|
||||
if destination == "/etc/hosts" {
|
||||
container.HostsPath = path
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// BuildHostnameFile writes the container's hostname file.
|
||||
func (container *Container) BuildHostnameFile() error {
|
||||
hostnamePath, err := container.GetRootResourcePath("hostname")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.HostnamePath = hostnamePath
|
||||
|
||||
if container.Config.Domainname != "" {
|
||||
return ioutil.WriteFile(container.HostnamePath, []byte(fmt.Sprintf("%s.%s\n", container.Config.Hostname, container.Config.Domainname)), 0644)
|
||||
}
|
||||
return ioutil.WriteFile(container.HostnamePath, []byte(container.Config.Hostname+"\n"), 0644)
|
||||
}
|
||||
|
||||
// GetEndpointInNetwork returns the container's endpoint to the provided network.
|
||||
func (container *Container) GetEndpointInNetwork(n libnetwork.Network) (libnetwork.Endpoint, error) {
|
||||
endpointName := strings.TrimPrefix(container.Name, "/")
|
||||
return n.EndpointByName(endpointName)
|
||||
}
|
||||
|
||||
func (container *Container) buildPortMapInfo(ep libnetwork.Endpoint) error {
|
||||
if ep == nil {
|
||||
return derr.ErrorCodeEmptyEndpoint
|
||||
}
|
||||
|
||||
networkSettings := container.NetworkSettings
|
||||
if networkSettings == nil {
|
||||
return derr.ErrorCodeEmptyNetwork
|
||||
}
|
||||
|
||||
if len(networkSettings.Ports) == 0 {
|
||||
pm, err := getEndpointPortMapInfo(ep)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
networkSettings.Ports = pm
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getEndpointPortMapInfo(ep libnetwork.Endpoint) (nat.PortMap, error) {
|
||||
pm := nat.PortMap{}
|
||||
driverInfo, err := ep.DriverInfo()
|
||||
if err != nil {
|
||||
return pm, err
|
||||
}
|
||||
|
||||
if driverInfo == nil {
|
||||
// It is not an error for epInfo to be nil
|
||||
return pm, nil
|
||||
}
|
||||
|
||||
if expData, ok := driverInfo[netlabel.ExposedPorts]; ok {
|
||||
if exposedPorts, ok := expData.([]types.TransportPort); ok {
|
||||
for _, tp := range exposedPorts {
|
||||
natPort, err := nat.NewPort(tp.Proto.String(), strconv.Itoa(int(tp.Port)))
|
||||
if err != nil {
|
||||
return pm, derr.ErrorCodeParsingPort.WithArgs(tp.Port, err)
|
||||
}
|
||||
pm[natPort] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mapData, ok := driverInfo[netlabel.PortMap]
|
||||
if !ok {
|
||||
return pm, nil
|
||||
}
|
||||
|
||||
if portMapping, ok := mapData.([]types.PortBinding); ok {
|
||||
for _, pp := range portMapping {
|
||||
natPort, err := nat.NewPort(pp.Proto.String(), strconv.Itoa(int(pp.Port)))
|
||||
if err != nil {
|
||||
return pm, err
|
||||
}
|
||||
natBndg := nat.PortBinding{HostIP: pp.HostIP.String(), HostPort: strconv.Itoa(int(pp.HostPort))}
|
||||
pm[natPort] = append(pm[natPort], natBndg)
|
||||
}
|
||||
}
|
||||
|
||||
return pm, nil
|
||||
}
|
||||
|
||||
func getSandboxPortMapInfo(sb libnetwork.Sandbox) nat.PortMap {
|
||||
pm := nat.PortMap{}
|
||||
if sb == nil {
|
||||
return pm
|
||||
}
|
||||
|
||||
for _, ep := range sb.Endpoints() {
|
||||
pm, _ = getEndpointPortMapInfo(ep)
|
||||
if len(pm) > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return pm
|
||||
}
|
||||
|
||||
// BuildEndpointInfo sets endpoint-related fields on container.NetworkSettings based on the provided network and endpoint.
|
||||
func (container *Container) BuildEndpointInfo(n libnetwork.Network, ep libnetwork.Endpoint) error {
|
||||
if ep == nil {
|
||||
return derr.ErrorCodeEmptyEndpoint
|
||||
}
|
||||
|
||||
networkSettings := container.NetworkSettings
|
||||
if networkSettings == nil {
|
||||
return derr.ErrorCodeEmptyNetwork
|
||||
}
|
||||
|
||||
epInfo := ep.Info()
|
||||
if epInfo == nil {
|
||||
// It is not an error to get an empty endpoint info
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, ok := networkSettings.Networks[n.Name()]; !ok {
|
||||
networkSettings.Networks[n.Name()] = new(network.EndpointSettings)
|
||||
}
|
||||
networkSettings.Networks[n.Name()].NetworkID = n.ID()
|
||||
networkSettings.Networks[n.Name()].EndpointID = ep.ID()
|
||||
|
||||
iface := epInfo.Iface()
|
||||
if iface == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if iface.MacAddress() != nil {
|
||||
networkSettings.Networks[n.Name()].MacAddress = iface.MacAddress().String()
|
||||
}
|
||||
|
||||
if iface.Address() != nil {
|
||||
ones, _ := iface.Address().Mask.Size()
|
||||
networkSettings.Networks[n.Name()].IPAddress = iface.Address().IP.String()
|
||||
networkSettings.Networks[n.Name()].IPPrefixLen = ones
|
||||
}
|
||||
|
||||
if iface.AddressIPv6() != nil && iface.AddressIPv6().IP.To16() != nil {
|
||||
onesv6, _ := iface.AddressIPv6().Mask.Size()
|
||||
networkSettings.Networks[n.Name()].GlobalIPv6Address = iface.AddressIPv6().IP.String()
|
||||
networkSettings.Networks[n.Name()].GlobalIPv6PrefixLen = onesv6
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateJoinInfo updates network settings when container joins network n with endpoint ep.
|
||||
func (container *Container) UpdateJoinInfo(n libnetwork.Network, ep libnetwork.Endpoint) error {
|
||||
if err := container.buildPortMapInfo(ep); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
epInfo := ep.Info()
|
||||
if epInfo == nil {
|
||||
// It is not an error to get an empty endpoint info
|
||||
return nil
|
||||
}
|
||||
if epInfo.Gateway() != nil {
|
||||
container.NetworkSettings.Networks[n.Name()].Gateway = epInfo.Gateway().String()
|
||||
}
|
||||
if epInfo.GatewayIPv6().To16() != nil {
|
||||
container.NetworkSettings.Networks[n.Name()].IPv6Gateway = epInfo.GatewayIPv6().String()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateSandboxNetworkSettings updates the sandbox ID and Key.
|
||||
func (container *Container) UpdateSandboxNetworkSettings(sb libnetwork.Sandbox) error {
|
||||
container.NetworkSettings.SandboxID = sb.ID()
|
||||
container.NetworkSettings.SandboxKey = sb.Key()
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildJoinOptions builds endpoint Join options from a given network.
|
||||
func (container *Container) BuildJoinOptions(n libnetwork.Network) ([]libnetwork.EndpointOption, error) {
|
||||
var joinOptions []libnetwork.EndpointOption
|
||||
if epConfig, ok := container.NetworkSettings.Networks[n.Name()]; ok {
|
||||
for _, str := range epConfig.Links {
|
||||
name, alias, err := runconfigopts.ParseLink(str)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
joinOptions = append(joinOptions, libnetwork.CreateOptionAlias(name, alias))
|
||||
}
|
||||
}
|
||||
return joinOptions, nil
|
||||
}
|
||||
|
||||
// BuildCreateEndpointOptions builds endpoint options from a given network.
|
||||
func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network, epConfig *network.EndpointSettings, sb libnetwork.Sandbox) ([]libnetwork.EndpointOption, error) {
|
||||
var (
|
||||
portSpecs = make(nat.PortSet)
|
||||
bindings = make(nat.PortMap)
|
||||
pbList []types.PortBinding
|
||||
exposeList []types.TransportPort
|
||||
createOptions []libnetwork.EndpointOption
|
||||
)
|
||||
|
||||
if n.Name() == "bridge" || container.NetworkSettings.IsAnonymousEndpoint {
|
||||
createOptions = append(createOptions, libnetwork.CreateOptionAnonymous())
|
||||
}
|
||||
|
||||
if epConfig != nil {
|
||||
ipam := epConfig.IPAMConfig
|
||||
if ipam != nil && (ipam.IPv4Address != "" || ipam.IPv6Address != "") {
|
||||
createOptions = append(createOptions,
|
||||
libnetwork.CreateOptionIpam(net.ParseIP(ipam.IPv4Address), net.ParseIP(ipam.IPv6Address), nil))
|
||||
}
|
||||
|
||||
for _, alias := range epConfig.Aliases {
|
||||
createOptions = append(createOptions, libnetwork.CreateOptionMyAlias(alias))
|
||||
}
|
||||
}
|
||||
|
||||
if !containertypes.NetworkMode(n.Name()).IsUserDefined() {
|
||||
createOptions = append(createOptions, libnetwork.CreateOptionDisableResolution())
|
||||
}
|
||||
|
||||
// configs that are applicable only for the endpoint in the network
|
||||
// to which container was connected to on docker run.
|
||||
// Ideally all these network-specific endpoint configurations must be moved under
|
||||
// container.NetworkSettings.Networks[n.Name()]
|
||||
if n.Name() == container.HostConfig.NetworkMode.NetworkName() ||
|
||||
(n.Name() == "bridge" && container.HostConfig.NetworkMode.IsDefault()) {
|
||||
if container.Config.MacAddress != "" {
|
||||
mac, err := net.ParseMAC(container.Config.MacAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
genericOption := options.Generic{
|
||||
netlabel.MacAddress: mac,
|
||||
}
|
||||
|
||||
createOptions = append(createOptions, libnetwork.EndpointOptionGeneric(genericOption))
|
||||
}
|
||||
}
|
||||
|
||||
// Port-mapping rules belong to the container & applicable only to non-internal networks
|
||||
portmaps := getSandboxPortMapInfo(sb)
|
||||
if n.Info().Internal() || len(portmaps) > 0 {
|
||||
return createOptions, nil
|
||||
}
|
||||
|
||||
if container.Config.ExposedPorts != nil {
|
||||
portSpecs = container.Config.ExposedPorts
|
||||
}
|
||||
|
||||
if container.HostConfig.PortBindings != nil {
|
||||
for p, b := range container.HostConfig.PortBindings {
|
||||
bindings[p] = []nat.PortBinding{}
|
||||
for _, bb := range b {
|
||||
bindings[p] = append(bindings[p], nat.PortBinding{
|
||||
HostIP: bb.HostIP,
|
||||
HostPort: bb.HostPort,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ports := make([]nat.Port, len(portSpecs))
|
||||
var i int
|
||||
for p := range portSpecs {
|
||||
ports[i] = p
|
||||
i++
|
||||
}
|
||||
nat.SortPortMap(ports, bindings)
|
||||
for _, port := range ports {
|
||||
expose := types.TransportPort{}
|
||||
expose.Proto = types.ParseProtocol(port.Proto())
|
||||
expose.Port = uint16(port.Int())
|
||||
exposeList = append(exposeList, expose)
|
||||
|
||||
pb := types.PortBinding{Port: expose.Port, Proto: expose.Proto}
|
||||
binding := bindings[port]
|
||||
for i := 0; i < len(binding); i++ {
|
||||
pbCopy := pb.GetCopy()
|
||||
newP, err := nat.NewPort(nat.SplitProtoPort(binding[i].HostPort))
|
||||
var portStart, portEnd int
|
||||
if err == nil {
|
||||
portStart, portEnd, err = newP.Range()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, derr.ErrorCodeHostPort.WithArgs(binding[i].HostPort, err)
|
||||
}
|
||||
pbCopy.HostPort = uint16(portStart)
|
||||
pbCopy.HostPortEnd = uint16(portEnd)
|
||||
pbCopy.HostIP = net.ParseIP(binding[i].HostIP)
|
||||
pbList = append(pbList, pbCopy)
|
||||
}
|
||||
|
||||
if container.HostConfig.PublishAllPorts && len(binding) == 0 {
|
||||
pbList = append(pbList, pb)
|
||||
}
|
||||
}
|
||||
|
||||
createOptions = append(createOptions,
|
||||
libnetwork.CreateOptionPortMapping(pbList),
|
||||
libnetwork.CreateOptionExposedPorts(exposeList))
|
||||
|
||||
return createOptions, nil
|
||||
}
|
||||
|
||||
// SetupWorkingDirectory sets up the container's working directory as set in container.Config.WorkingDir
|
||||
func (container *Container) SetupWorkingDirectory() error {
|
||||
if container.Config.WorkingDir == "" {
|
||||
return nil
|
||||
}
|
||||
container.Config.WorkingDir = filepath.Clean(container.Config.WorkingDir)
|
||||
|
||||
pth, err := container.GetResourcePath(container.Config.WorkingDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pthInfo, err := os.Stat(pth)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := system.MkdirAll(pth, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if pthInfo != nil && !pthInfo.IsDir() {
|
||||
return derr.ErrorCodeNotADir.WithArgs(container.Config.WorkingDir)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// appendNetworkMounts appends any network mounts to the array of mount points passed in
|
||||
func appendNetworkMounts(container *Container, volumeMounts []volume.MountPoint) ([]volume.MountPoint, error) {
|
||||
for _, mnt := range container.NetworkMounts() {
|
||||
dest, err := container.GetResourcePath(mnt.Destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
volumeMounts = append(volumeMounts, volume.MountPoint{Destination: dest})
|
||||
}
|
||||
return volumeMounts, nil
|
||||
}
|
||||
|
||||
// NetworkMounts returns the list of network mounts.
|
||||
func (container *Container) NetworkMounts() []execdriver.Mount {
|
||||
var mounts []execdriver.Mount
|
||||
shared := container.HostConfig.NetworkMode.IsContainer()
|
||||
if container.ResolvConfPath != "" {
|
||||
if _, err := os.Stat(container.ResolvConfPath); err != nil {
|
||||
logrus.Warnf("ResolvConfPath set to %q, but can't stat this filename (err = %v); skipping", container.ResolvConfPath, err)
|
||||
} else {
|
||||
label.Relabel(container.ResolvConfPath, container.MountLabel, shared)
|
||||
writable := !container.HostConfig.ReadonlyRootfs
|
||||
if m, exists := container.MountPoints["/etc/resolv.conf"]; exists {
|
||||
writable = m.RW
|
||||
}
|
||||
mounts = append(mounts, execdriver.Mount{
|
||||
Source: container.ResolvConfPath,
|
||||
Destination: "/etc/resolv.conf",
|
||||
Writable: writable,
|
||||
Propagation: volume.DefaultPropagationMode,
|
||||
})
|
||||
}
|
||||
}
|
||||
if container.HostnamePath != "" {
|
||||
if _, err := os.Stat(container.HostnamePath); err != nil {
|
||||
logrus.Warnf("HostnamePath set to %q, but can't stat this filename (err = %v); skipping", container.HostnamePath, err)
|
||||
} else {
|
||||
label.Relabel(container.HostnamePath, container.MountLabel, shared)
|
||||
writable := !container.HostConfig.ReadonlyRootfs
|
||||
if m, exists := container.MountPoints["/etc/hostname"]; exists {
|
||||
writable = m.RW
|
||||
}
|
||||
mounts = append(mounts, execdriver.Mount{
|
||||
Source: container.HostnamePath,
|
||||
Destination: "/etc/hostname",
|
||||
Writable: writable,
|
||||
Propagation: volume.DefaultPropagationMode,
|
||||
})
|
||||
}
|
||||
}
|
||||
if container.HostsPath != "" {
|
||||
if _, err := os.Stat(container.HostsPath); err != nil {
|
||||
logrus.Warnf("HostsPath set to %q, but can't stat this filename (err = %v); skipping", container.HostsPath, err)
|
||||
} else {
|
||||
label.Relabel(container.HostsPath, container.MountLabel, shared)
|
||||
writable := !container.HostConfig.ReadonlyRootfs
|
||||
if m, exists := container.MountPoints["/etc/hosts"]; exists {
|
||||
writable = m.RW
|
||||
}
|
||||
mounts = append(mounts, execdriver.Mount{
|
||||
Source: container.HostsPath,
|
||||
Destination: "/etc/hosts",
|
||||
Writable: writable,
|
||||
Propagation: volume.DefaultPropagationMode,
|
||||
})
|
||||
}
|
||||
}
|
||||
return mounts
|
||||
}
|
||||
|
||||
// CopyImagePathContent copies files in destination to the volume.
|
||||
func (container *Container) CopyImagePathContent(v volume.Volume, destination string) error {
|
||||
rootfs, err := symlink.FollowSymlinkInScope(filepath.Join(container.BaseFS, destination), container.BaseFS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = ioutil.ReadDir(rootfs); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
path, err := v.Mount()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := copyExistingContents(rootfs, path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return v.Unmount()
|
||||
}
|
||||
|
||||
// ShmResourcePath returns path to shm
|
||||
func (container *Container) ShmResourcePath() (string, error) {
|
||||
return container.GetRootResourcePath("shm")
|
||||
}
|
||||
|
||||
// MqueueResourcePath returns path to mqueue
|
||||
func (container *Container) MqueueResourcePath() (string, error) {
|
||||
return container.GetRootResourcePath("mqueue")
|
||||
}
|
||||
|
||||
// HasMountFor checks if path is a mountpoint
|
||||
func (container *Container) HasMountFor(path string) bool {
|
||||
_, exists := container.MountPoints[path]
|
||||
return exists
|
||||
}
|
||||
|
||||
// UnmountIpcMounts uses the provided unmount function to unmount shm and mqueue if they were mounted
|
||||
func (container *Container) UnmountIpcMounts(unmount func(pth string) error) {
|
||||
if container.HostConfig.IpcMode.IsContainer() || container.HostConfig.IpcMode.IsHost() {
|
||||
return
|
||||
}
|
||||
|
||||
var warnings []string
|
||||
|
||||
if !container.HasMountFor("/dev/shm") {
|
||||
shmPath, err := container.ShmResourcePath()
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
warnings = append(warnings, err.Error())
|
||||
} else if shmPath != "" {
|
||||
if err := unmount(shmPath); err != nil {
|
||||
warnings = append(warnings, fmt.Sprintf("failed to umount %s: %v", shmPath, err))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if !container.HasMountFor("/dev/mqueue") {
|
||||
mqueuePath, err := container.MqueueResourcePath()
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
warnings = append(warnings, err.Error())
|
||||
} else if mqueuePath != "" {
|
||||
if err := unmount(mqueuePath); err != nil {
|
||||
warnings = append(warnings, fmt.Sprintf("failed to umount %s: %v", mqueuePath, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(warnings) > 0 {
|
||||
logrus.Warnf("failed to cleanup ipc mounts:\n%v", strings.Join(warnings, "\n"))
|
||||
}
|
||||
}
|
||||
|
||||
// IpcMounts returns the list of IPC mounts
|
||||
func (container *Container) IpcMounts() []execdriver.Mount {
|
||||
var mounts []execdriver.Mount
|
||||
|
||||
if !container.HasMountFor("/dev/shm") {
|
||||
label.SetFileLabel(container.ShmPath, container.MountLabel)
|
||||
mounts = append(mounts, execdriver.Mount{
|
||||
Source: container.ShmPath,
|
||||
Destination: "/dev/shm",
|
||||
Writable: true,
|
||||
Propagation: volume.DefaultPropagationMode,
|
||||
})
|
||||
}
|
||||
|
||||
if !container.HasMountFor("/dev/mqueue") {
|
||||
label.SetFileLabel(container.MqueuePath, container.MountLabel)
|
||||
mounts = append(mounts, execdriver.Mount{
|
||||
Source: container.MqueuePath,
|
||||
Destination: "/dev/mqueue",
|
||||
Writable: true,
|
||||
Propagation: volume.DefaultPropagationMode,
|
||||
})
|
||||
}
|
||||
return mounts
|
||||
}
|
||||
|
||||
func updateCommand(c *execdriver.Command, resources containertypes.Resources) {
|
||||
c.Resources.BlkioWeight = resources.BlkioWeight
|
||||
c.Resources.CPUShares = resources.CPUShares
|
||||
c.Resources.CPUPeriod = resources.CPUPeriod
|
||||
c.Resources.CPUQuota = resources.CPUQuota
|
||||
c.Resources.CpusetCpus = resources.CpusetCpus
|
||||
c.Resources.CpusetMems = resources.CpusetMems
|
||||
c.Resources.Memory = resources.Memory
|
||||
c.Resources.MemorySwap = resources.MemorySwap
|
||||
c.Resources.MemoryReservation = resources.MemoryReservation
|
||||
c.Resources.KernelMemory = resources.KernelMemory
|
||||
}
|
||||
|
||||
// UpdateContainer updates resources of a container.
|
||||
func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfig) error {
|
||||
container.Lock()
|
||||
|
||||
resources := hostConfig.Resources
|
||||
cResources := &container.HostConfig.Resources
|
||||
if resources.BlkioWeight != 0 {
|
||||
cResources.BlkioWeight = resources.BlkioWeight
|
||||
}
|
||||
if resources.CPUShares != 0 {
|
||||
cResources.CPUShares = resources.CPUShares
|
||||
}
|
||||
if resources.CPUPeriod != 0 {
|
||||
cResources.CPUPeriod = resources.CPUPeriod
|
||||
}
|
||||
if resources.CPUQuota != 0 {
|
||||
cResources.CPUQuota = resources.CPUQuota
|
||||
}
|
||||
if resources.CpusetCpus != "" {
|
||||
cResources.CpusetCpus = resources.CpusetCpus
|
||||
}
|
||||
if resources.CpusetMems != "" {
|
||||
cResources.CpusetMems = resources.CpusetMems
|
||||
}
|
||||
if resources.Memory != 0 {
|
||||
cResources.Memory = resources.Memory
|
||||
}
|
||||
if resources.MemorySwap != 0 {
|
||||
cResources.MemorySwap = resources.MemorySwap
|
||||
}
|
||||
if resources.MemoryReservation != 0 {
|
||||
cResources.MemoryReservation = resources.MemoryReservation
|
||||
}
|
||||
if resources.KernelMemory != 0 {
|
||||
cResources.KernelMemory = resources.KernelMemory
|
||||
}
|
||||
container.Unlock()
|
||||
|
||||
// If container is not running, update hostConfig struct is enough,
|
||||
// resources will be updated when the container is started again.
|
||||
// If container is running (including paused), we need to update
|
||||
// the command so we can update configs to the real world.
|
||||
if container.IsRunning() {
|
||||
container.Lock()
|
||||
updateCommand(container.Command, *cResources)
|
||||
container.Unlock()
|
||||
}
|
||||
|
||||
if err := container.ToDiskLocking(); err != nil {
|
||||
logrus.Errorf("Error saving updated container: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func detachMounted(path string) error {
|
||||
return syscall.Unmount(path, syscall.MNT_DETACH)
|
||||
}
|
||||
|
||||
// UnmountVolumes unmounts all volumes
|
||||
func (container *Container) UnmountVolumes(forceSyscall bool, volumeEventLog func(name, action string, attributes map[string]string)) error {
|
||||
var (
|
||||
volumeMounts []volume.MountPoint
|
||||
err error
|
||||
)
|
||||
|
||||
for _, mntPoint := range container.MountPoints {
|
||||
dest, err := container.GetResourcePath(mntPoint.Destination)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
volumeMounts = append(volumeMounts, volume.MountPoint{Destination: dest, Volume: mntPoint.Volume})
|
||||
}
|
||||
|
||||
// Append any network mounts to the list (this is a no-op on Windows)
|
||||
if volumeMounts, err = appendNetworkMounts(container, volumeMounts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, volumeMount := range volumeMounts {
|
||||
if forceSyscall {
|
||||
if err := detachMounted(volumeMount.Destination); err != nil {
|
||||
logrus.Warnf("%s unmountVolumes: Failed to do lazy umount %v", container.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
if volumeMount.Volume != nil {
|
||||
if err := volumeMount.Volume.Unmount(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attributes := map[string]string{
|
||||
"driver": volumeMount.Volume.DriverName(),
|
||||
"container": container.ID,
|
||||
}
|
||||
volumeEventLog(volumeMount.Volume.Name(), "unmount", attributes)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// copyExistingContents copies from the source to the destination and
|
||||
// ensures the ownership is appropriately set.
|
||||
func copyExistingContents(source, destination string) error {
|
||||
volList, err := ioutil.ReadDir(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(volList) > 0 {
|
||||
srcList, err := ioutil.ReadDir(destination)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(srcList) == 0 {
|
||||
// If the source volume is empty, copies files from the root into the volume
|
||||
if err := chrootarchive.CopyWithTar(source, destination); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return copyOwnership(source, destination)
|
||||
}
|
||||
|
||||
// copyOwnership copies the permissions and uid:gid of the source file
|
||||
// to the destination file
|
||||
func copyOwnership(source, destination string) error {
|
||||
stat, err := system.Stat(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Chown(destination, int(stat.UID()), int(stat.GID())); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Chmod(destination, os.FileMode(stat.Mode()))
|
||||
}
|
||||
|
||||
// TmpfsMounts returns the list of tmpfs mounts
|
||||
func (container *Container) TmpfsMounts() []execdriver.Mount {
|
||||
var mounts []execdriver.Mount
|
||||
for dest, data := range container.HostConfig.Tmpfs {
|
||||
mounts = append(mounts, execdriver.Mount{
|
||||
Source: "tmpfs",
|
||||
Destination: dest,
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
return mounts
|
||||
}
|
||||
61
vendor/github.com/hyperhq/hypercli/container/container_windows.go
generated
vendored
Normal file
61
vendor/github.com/hyperhq/hypercli/container/container_windows.go
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
// +build windows
|
||||
|
||||
package container
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/docker/engine-api/types/container"
|
||||
)
|
||||
|
||||
// Container holds fields specific to the Windows implementation. See
|
||||
// CommonContainer for standard fields common to all containers.
|
||||
type Container struct {
|
||||
CommonContainer
|
||||
|
||||
// Fields below here are platform specific.
|
||||
}
|
||||
|
||||
// CreateDaemonEnvironment creates a new environment variable slice for this container.
|
||||
func (container *Container) CreateDaemonEnvironment(linkedEnv []string) []string {
|
||||
// On Windows, nothing to link. Just return the container environment.
|
||||
return container.Config.Env
|
||||
}
|
||||
|
||||
// SetupWorkingDirectory initializes the container working directory.
|
||||
// This is a NOOP In windows.
|
||||
func (container *Container) SetupWorkingDirectory() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmountIpcMounts unmount Ipc related mounts.
|
||||
// This is a NOOP on windows.
|
||||
func (container *Container) UnmountIpcMounts(unmount func(pth string) error) {
|
||||
}
|
||||
|
||||
// IpcMounts returns the list of Ipc related mounts.
|
||||
func (container *Container) IpcMounts() []execdriver.Mount {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmountVolumes explicitly unmounts volumes from the container.
|
||||
func (container *Container) UnmountVolumes(forceSyscall bool, volumeEventLog func(name, action string, attributes map[string]string)) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TmpfsMounts returns the list of tmpfs mounts
|
||||
func (container *Container) TmpfsMounts() []execdriver.Mount {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateContainer updates resources of a container
|
||||
func (container *Container) UpdateContainer(hostConfig *container.HostConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// appendNetworkMounts appends any network mounts to the array of mount points passed in.
|
||||
// Windows does not support network mounts (not to be confused with SMB network mounts), so
|
||||
// this is a no-op.
|
||||
func appendNetworkMounts(container *Container, volumeMounts []volume.MountPoint) ([]volume.MountPoint, error) {
|
||||
return volumeMounts, nil
|
||||
}
|
||||
35
vendor/github.com/hyperhq/hypercli/container/history.go
generated
vendored
Normal file
35
vendor/github.com/hyperhq/hypercli/container/history.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
package container
|
||||
|
||||
import "sort"
|
||||
|
||||
// History is a convenience type for storing a list of containers,
|
||||
// sorted by creation date in descendant order.
|
||||
type History []*Container
|
||||
|
||||
// Len returns the number of containers in the history.
|
||||
func (history *History) Len() int {
|
||||
return len(*history)
|
||||
}
|
||||
|
||||
// Less compares two containers and returns true if the second one
|
||||
// was created before the first one.
|
||||
func (history *History) Less(i, j int) bool {
|
||||
containers := *history
|
||||
return containers[j].Created.Before(containers[i].Created)
|
||||
}
|
||||
|
||||
// Swap switches containers i and j positions in the history.
|
||||
func (history *History) Swap(i, j int) {
|
||||
containers := *history
|
||||
containers[i], containers[j] = containers[j], containers[i]
|
||||
}
|
||||
|
||||
// Add the given container to history.
|
||||
func (history *History) Add(container *Container) {
|
||||
*history = append(*history, container)
|
||||
}
|
||||
|
||||
// sort orders the history by creation date in descendant order.
|
||||
func (history *History) sort() {
|
||||
sort.Sort(history)
|
||||
}
|
||||
91
vendor/github.com/hyperhq/hypercli/container/memory_store.go
generated
vendored
Normal file
91
vendor/github.com/hyperhq/hypercli/container/memory_store.go
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
package container
|
||||
|
||||
import "sync"
|
||||
|
||||
// memoryStore implements a Store in memory.
|
||||
type memoryStore struct {
|
||||
s map[string]*Container
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewMemoryStore initializes a new memory store.
|
||||
func NewMemoryStore() Store {
|
||||
return &memoryStore{
|
||||
s: make(map[string]*Container),
|
||||
}
|
||||
}
|
||||
|
||||
// Add appends a new container to the memory store.
|
||||
// It overrides the id if it existed before.
|
||||
func (c *memoryStore) Add(id string, cont *Container) {
|
||||
c.Lock()
|
||||
c.s[id] = cont
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
// Get returns a container from the store by id.
|
||||
func (c *memoryStore) Get(id string) *Container {
|
||||
c.Lock()
|
||||
res := c.s[id]
|
||||
c.Unlock()
|
||||
return res
|
||||
}
|
||||
|
||||
// Delete removes a container from the store by id.
|
||||
func (c *memoryStore) Delete(id string) {
|
||||
c.Lock()
|
||||
delete(c.s, id)
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
// List returns a sorted list of containers from the store.
|
||||
// The containers are ordered by creation date.
|
||||
func (c *memoryStore) List() []*Container {
|
||||
containers := new(History)
|
||||
c.Lock()
|
||||
for _, cont := range c.s {
|
||||
containers.Add(cont)
|
||||
}
|
||||
c.Unlock()
|
||||
containers.sort()
|
||||
return *containers
|
||||
}
|
||||
|
||||
// Size returns the number of containers in the store.
|
||||
func (c *memoryStore) Size() int {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
return len(c.s)
|
||||
}
|
||||
|
||||
// First returns the first container found in the store by a given filter.
|
||||
func (c *memoryStore) First(filter StoreFilter) *Container {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
for _, cont := range c.s {
|
||||
if filter(cont) {
|
||||
return cont
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyAll calls the reducer function with every container in the store.
|
||||
// This operation is asyncronous in the memory store.
|
||||
func (c *memoryStore) ApplyAll(apply StoreReducer) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
for _, cont := range c.s {
|
||||
wg.Add(1)
|
||||
go func(container *Container) {
|
||||
apply(container)
|
||||
wg.Done()
|
||||
}(cont)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
var _ Store = &memoryStore{}
|
||||
106
vendor/github.com/hyperhq/hypercli/container/memory_store_test.go
generated
vendored
Normal file
106
vendor/github.com/hyperhq/hypercli/container/memory_store_test.go
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewMemoryStore(t *testing.T) {
|
||||
s := NewMemoryStore()
|
||||
m, ok := s.(*memoryStore)
|
||||
if !ok {
|
||||
t.Fatalf("store is not a memory store %v", s)
|
||||
}
|
||||
if m.s == nil {
|
||||
t.Fatal("expected store map to not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddContainers(t *testing.T) {
|
||||
s := NewMemoryStore()
|
||||
s.Add("id", NewBaseContainer("id", "root"))
|
||||
if s.Size() != 1 {
|
||||
t.Fatalf("expected store size 1, got %v", s.Size())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetContainer(t *testing.T) {
|
||||
s := NewMemoryStore()
|
||||
s.Add("id", NewBaseContainer("id", "root"))
|
||||
c := s.Get("id")
|
||||
if c == nil {
|
||||
t.Fatal("expected container to not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteContainer(t *testing.T) {
|
||||
s := NewMemoryStore()
|
||||
s.Add("id", NewBaseContainer("id", "root"))
|
||||
s.Delete("id")
|
||||
if c := s.Get("id"); c != nil {
|
||||
t.Fatalf("expected container to be nil after removal, got %v", c)
|
||||
}
|
||||
|
||||
if s.Size() != 0 {
|
||||
t.Fatalf("expected store size to be 0, got %v", s.Size())
|
||||
}
|
||||
}
|
||||
|
||||
func TestListContainers(t *testing.T) {
|
||||
s := NewMemoryStore()
|
||||
|
||||
cont := NewBaseContainer("id", "root")
|
||||
cont.Created = time.Now()
|
||||
cont2 := NewBaseContainer("id2", "root")
|
||||
cont2.Created = time.Now().Add(24 * time.Hour)
|
||||
|
||||
s.Add("id", cont)
|
||||
s.Add("id2", cont2)
|
||||
|
||||
list := s.List()
|
||||
if len(list) != 2 {
|
||||
t.Fatalf("expected list size 2, got %v", len(list))
|
||||
}
|
||||
if list[0].ID != "id2" {
|
||||
t.Fatalf("expected older container to be first, got %v", list[0].ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFirstContainer(t *testing.T) {
|
||||
s := NewMemoryStore()
|
||||
|
||||
s.Add("id", NewBaseContainer("id", "root"))
|
||||
s.Add("id2", NewBaseContainer("id2", "root"))
|
||||
|
||||
first := s.First(func(cont *Container) bool {
|
||||
return cont.ID == "id2"
|
||||
})
|
||||
|
||||
if first == nil {
|
||||
t.Fatal("expected container to not be nil")
|
||||
}
|
||||
if first.ID != "id2" {
|
||||
t.Fatalf("expected id2, got %v", first)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyAllContainer(t *testing.T) {
|
||||
s := NewMemoryStore()
|
||||
|
||||
s.Add("id", NewBaseContainer("id", "root"))
|
||||
s.Add("id2", NewBaseContainer("id2", "root"))
|
||||
|
||||
s.ApplyAll(func(cont *Container) {
|
||||
if cont.ID == "id2" {
|
||||
cont.ID = "newID"
|
||||
}
|
||||
})
|
||||
|
||||
cont := s.Get("id2")
|
||||
if cont == nil {
|
||||
t.Fatal("expected container to not be nil")
|
||||
}
|
||||
if cont.ID != "newID" {
|
||||
t.Fatalf("expected newID, got %v", cont)
|
||||
}
|
||||
}
|
||||
400
vendor/github.com/hyperhq/hypercli/container/monitor.go
generated
vendored
Normal file
400
vendor/github.com/hyperhq/hypercli/container/monitor.go
generated
vendored
Normal file
@@ -0,0 +1,400 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
derr "github.com/docker/docker/errors"
|
||||
"github.com/docker/docker/pkg/promise"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/utils"
|
||||
"github.com/docker/engine-api/types/container"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTimeIncrement = 100
|
||||
loggerCloseTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
// supervisor defines the interface that a supervisor must implement
|
||||
type supervisor interface {
|
||||
// LogContainerEvent generates events related to a given container
|
||||
LogContainerEvent(*Container, string)
|
||||
// Cleanup ensures that the container is properly unmounted
|
||||
Cleanup(*Container)
|
||||
// StartLogging starts the logging driver for the container
|
||||
StartLogging(*Container) error
|
||||
// Run starts a container
|
||||
Run(c *Container, pipes *execdriver.Pipes, startCallback execdriver.DriverCallback) (execdriver.ExitStatus, error)
|
||||
// IsShuttingDown tells whether the supervisor is shutting down or not
|
||||
IsShuttingDown() bool
|
||||
}
|
||||
|
||||
// containerMonitor monitors the execution of a container's main process.
|
||||
// If a restart policy is specified for the container the monitor will ensure that the
|
||||
// process is restarted based on the rules of the policy. When the container is finally stopped
|
||||
// the monitor will reset and cleanup any of the container resources such as networking allocations
|
||||
// and the rootfs
|
||||
type containerMonitor struct {
|
||||
mux sync.Mutex
|
||||
|
||||
// supervisor keeps track of the container and the events it generates
|
||||
supervisor supervisor
|
||||
|
||||
// container is the container being monitored
|
||||
container *Container
|
||||
|
||||
// restartPolicy is the current policy being applied to the container monitor
|
||||
restartPolicy container.RestartPolicy
|
||||
|
||||
// failureCount is the number of times the container has failed to
|
||||
// start in a row
|
||||
failureCount int
|
||||
|
||||
// shouldStop signals the monitor that the next time the container exits it is
|
||||
// either because docker or the user asked for the container to be stopped
|
||||
shouldStop bool
|
||||
|
||||
// startSignal is a channel that is closes after the container initially starts
|
||||
startSignal chan struct{}
|
||||
|
||||
// stopChan is used to signal to the monitor whenever there is a wait for the
|
||||
// next restart so that the timeIncrement is not honored and the user is not
|
||||
// left waiting for nothing to happen during this time
|
||||
stopChan chan struct{}
|
||||
|
||||
// timeIncrement is the amount of time to wait between restarts
|
||||
// this is in milliseconds
|
||||
timeIncrement int
|
||||
|
||||
// lastStartTime is the time which the monitor last exec'd the container's process
|
||||
lastStartTime time.Time
|
||||
}
|
||||
|
||||
// StartMonitor initializes a containerMonitor for this container with the provided supervisor and restart policy
|
||||
// and starts the container's process.
|
||||
func (container *Container) StartMonitor(s supervisor, policy container.RestartPolicy) error {
|
||||
container.monitor = &containerMonitor{
|
||||
supervisor: s,
|
||||
container: container,
|
||||
restartPolicy: policy,
|
||||
timeIncrement: defaultTimeIncrement,
|
||||
stopChan: make(chan struct{}),
|
||||
startSignal: make(chan struct{}),
|
||||
}
|
||||
|
||||
return container.monitor.wait()
|
||||
}
|
||||
|
||||
// wait starts the container and wait until
|
||||
// we either receive an error from the initial start of the container's
|
||||
// process or until the process is running in the container
|
||||
func (m *containerMonitor) wait() error {
|
||||
select {
|
||||
case <-m.startSignal:
|
||||
case err := <-promise.Go(m.start):
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop signals to the container monitor that it should stop monitoring the container
|
||||
// for exits the next time the process dies
|
||||
func (m *containerMonitor) ExitOnNext() {
|
||||
m.mux.Lock()
|
||||
|
||||
// we need to protect having a double close of the channel when stop is called
|
||||
// twice or else we will get a panic
|
||||
if !m.shouldStop {
|
||||
m.shouldStop = true
|
||||
close(m.stopChan)
|
||||
}
|
||||
|
||||
m.mux.Unlock()
|
||||
}
|
||||
|
||||
// Close closes the container's resources such as networking allocations and
|
||||
// unmounts the container's root filesystem
|
||||
func (m *containerMonitor) Close() error {
|
||||
// Cleanup networking and mounts
|
||||
m.supervisor.Cleanup(m.container)
|
||||
|
||||
// FIXME: here is race condition between two RUN instructions in Dockerfile
|
||||
// because they share same runconfig and change image. Must be fixed
|
||||
// in builder/builder.go
|
||||
if err := m.container.ToDisk(); err != nil {
|
||||
logrus.Errorf("Error dumping container %s state to disk: %s", m.container.ID, err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start starts the containers process and monitors it according to the restart policy
|
||||
func (m *containerMonitor) start() error {
|
||||
var (
|
||||
err error
|
||||
exitStatus execdriver.ExitStatus
|
||||
// this variable indicates where we in execution flow:
|
||||
// before Run or after
|
||||
afterRun bool
|
||||
)
|
||||
|
||||
// ensure that when the monitor finally exits we release the networking and unmount the rootfs
|
||||
defer func() {
|
||||
if afterRun {
|
||||
m.container.Lock()
|
||||
defer m.container.Unlock()
|
||||
m.container.SetStopped(&exitStatus)
|
||||
}
|
||||
m.Close()
|
||||
}()
|
||||
// reset stopped flag
|
||||
if m.container.HasBeenManuallyStopped {
|
||||
m.container.HasBeenManuallyStopped = false
|
||||
}
|
||||
|
||||
// reset the restart count
|
||||
m.container.RestartCount = -1
|
||||
|
||||
for {
|
||||
m.container.RestartCount++
|
||||
|
||||
if err := m.supervisor.StartLogging(m.container); err != nil {
|
||||
m.resetContainer(false)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
pipes := execdriver.NewPipes(m.container.Stdin(), m.container.Stdout(), m.container.Stderr(), m.container.Config.OpenStdin)
|
||||
|
||||
m.logEvent("start")
|
||||
|
||||
m.lastStartTime = time.Now()
|
||||
|
||||
if exitStatus, err = m.supervisor.Run(m.container, pipes, m.callback); err != nil {
|
||||
// if we receive an internal error from the initial start of a container then lets
|
||||
// return it instead of entering the restart loop
|
||||
// set to 127 for container cmd not found/does not exist)
|
||||
if strings.Contains(err.Error(), "executable file not found") ||
|
||||
strings.Contains(err.Error(), "no such file or directory") ||
|
||||
strings.Contains(err.Error(), "system cannot find the file specified") {
|
||||
if m.container.RestartCount == 0 {
|
||||
m.container.ExitCode = 127
|
||||
m.resetContainer(false)
|
||||
return derr.ErrorCodeCmdNotFound
|
||||
}
|
||||
}
|
||||
// set to 126 for container cmd can't be invoked errors
|
||||
if strings.Contains(err.Error(), syscall.EACCES.Error()) {
|
||||
if m.container.RestartCount == 0 {
|
||||
m.container.ExitCode = 126
|
||||
m.resetContainer(false)
|
||||
return derr.ErrorCodeCmdCouldNotBeInvoked
|
||||
}
|
||||
}
|
||||
|
||||
if m.container.RestartCount == 0 {
|
||||
m.container.ExitCode = -1
|
||||
m.resetContainer(false)
|
||||
|
||||
return derr.ErrorCodeCantStart.WithArgs(m.container.ID, utils.GetErrorMessage(err))
|
||||
}
|
||||
|
||||
logrus.Errorf("Error running container: %s", err)
|
||||
}
|
||||
|
||||
// here container.Lock is already lost
|
||||
afterRun = true
|
||||
|
||||
m.resetMonitor(err == nil && exitStatus.ExitCode == 0)
|
||||
|
||||
if m.shouldRestart(exitStatus.ExitCode) {
|
||||
m.container.SetRestartingLocking(&exitStatus)
|
||||
m.logEvent("die")
|
||||
m.resetContainer(true)
|
||||
|
||||
// sleep with a small time increment between each restart to help avoid issues cased by quickly
|
||||
// restarting the container because of some types of errors ( networking cut out, etc... )
|
||||
m.waitForNextRestart()
|
||||
|
||||
// we need to check this before reentering the loop because the waitForNextRestart could have
|
||||
// been terminated by a request from a user
|
||||
if m.shouldStop {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
m.logEvent("die")
|
||||
m.resetContainer(true)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// resetMonitor resets the stateful fields on the containerMonitor based on the
|
||||
// previous runs success or failure. Regardless of success, if the container had
|
||||
// an execution time of more than 10s then reset the timer back to the default
|
||||
func (m *containerMonitor) resetMonitor(successful bool) {
|
||||
executionTime := time.Now().Sub(m.lastStartTime).Seconds()
|
||||
|
||||
if executionTime > 10 {
|
||||
m.timeIncrement = defaultTimeIncrement
|
||||
} else {
|
||||
// otherwise we need to increment the amount of time we wait before restarting
|
||||
// the process. We will build up by multiplying the increment by 2
|
||||
m.timeIncrement *= 2
|
||||
}
|
||||
|
||||
// the container exited successfully so we need to reset the failure counter
|
||||
if successful {
|
||||
m.failureCount = 0
|
||||
} else {
|
||||
m.failureCount++
|
||||
}
|
||||
}
|
||||
|
||||
// waitForNextRestart waits with the default time increment to restart the container unless
|
||||
// a user or docker asks for the container to be stopped
|
||||
func (m *containerMonitor) waitForNextRestart() {
|
||||
select {
|
||||
case <-time.After(time.Duration(m.timeIncrement) * time.Millisecond):
|
||||
case <-m.stopChan:
|
||||
}
|
||||
}
|
||||
|
||||
// shouldRestart checks the restart policy and applies the rules to determine if
|
||||
// the container's process should be restarted
|
||||
func (m *containerMonitor) shouldRestart(exitCode int) bool {
|
||||
m.mux.Lock()
|
||||
defer m.mux.Unlock()
|
||||
|
||||
// do not restart if the user or docker has requested that this container be stopped
|
||||
if m.shouldStop {
|
||||
m.container.HasBeenManuallyStopped = !m.supervisor.IsShuttingDown()
|
||||
return false
|
||||
}
|
||||
|
||||
switch {
|
||||
case m.restartPolicy.IsAlways(), m.restartPolicy.IsUnlessStopped():
|
||||
return true
|
||||
case m.restartPolicy.IsOnFailure():
|
||||
// the default value of 0 for MaximumRetryCount means that we will not enforce a maximum count
|
||||
if max := m.restartPolicy.MaximumRetryCount; max != 0 && m.failureCount > max {
|
||||
logrus.Debugf("stopping restart of container %s because maximum failure could of %d has been reached",
|
||||
stringid.TruncateID(m.container.ID), max)
|
||||
return false
|
||||
}
|
||||
|
||||
return exitCode != 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// callback ensures that the container's state is properly updated after we
|
||||
// received ack from the execution drivers
|
||||
func (m *containerMonitor) callback(processConfig *execdriver.ProcessConfig, pid int, chOOM <-chan struct{}) error {
|
||||
go func() {
|
||||
for range chOOM {
|
||||
m.logEvent("oom")
|
||||
}
|
||||
}()
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
m.container.SetRunning(pid)
|
||||
|
||||
// signal that the process has started
|
||||
// close channel only if not closed
|
||||
select {
|
||||
case <-m.startSignal:
|
||||
default:
|
||||
close(m.startSignal)
|
||||
}
|
||||
|
||||
if err := m.container.ToDiskLocking(); err != nil {
|
||||
logrus.Errorf("Error saving container to disk: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// resetContainer resets the container's IO and ensures that the command is able to be executed again
|
||||
// by copying the data into a new struct
|
||||
// if lock is true, then container locked during reset
|
||||
func (m *containerMonitor) resetContainer(lock bool) {
|
||||
container := m.container
|
||||
if lock {
|
||||
container.Lock()
|
||||
defer container.Unlock()
|
||||
}
|
||||
|
||||
if err := container.CloseStreams(); err != nil {
|
||||
logrus.Errorf("%s: %s", container.ID, err)
|
||||
}
|
||||
|
||||
if container.Command != nil && container.Command.ProcessConfig.Terminal != nil {
|
||||
if err := container.Command.ProcessConfig.Terminal.Close(); err != nil {
|
||||
logrus.Errorf("%s: Error closing terminal: %s", container.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Re-create a brand new stdin pipe once the container exited
|
||||
if container.Config.OpenStdin {
|
||||
container.NewInputPipes()
|
||||
}
|
||||
|
||||
if container.LogDriver != nil {
|
||||
if container.LogCopier != nil {
|
||||
exit := make(chan struct{})
|
||||
go func() {
|
||||
container.LogCopier.Wait()
|
||||
close(exit)
|
||||
}()
|
||||
select {
|
||||
case <-time.After(loggerCloseTimeout):
|
||||
logrus.Warnf("Logger didn't exit in time: logs may be truncated")
|
||||
container.LogCopier.Close()
|
||||
// always waits for the LogCopier to finished before closing
|
||||
<-exit
|
||||
case <-exit:
|
||||
}
|
||||
}
|
||||
container.LogDriver.Close()
|
||||
container.LogCopier = nil
|
||||
container.LogDriver = nil
|
||||
}
|
||||
|
||||
c := container.Command.ProcessConfig.Cmd
|
||||
|
||||
container.Command.ProcessConfig.Cmd = exec.Cmd{
|
||||
Stdin: c.Stdin,
|
||||
Stdout: c.Stdout,
|
||||
Stderr: c.Stderr,
|
||||
Path: c.Path,
|
||||
Env: c.Env,
|
||||
ExtraFiles: c.ExtraFiles,
|
||||
Args: c.Args,
|
||||
Dir: c.Dir,
|
||||
SysProcAttr: c.SysProcAttr,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *containerMonitor) logEvent(action string) {
|
||||
m.supervisor.LogContainerEvent(m.container, action)
|
||||
}
|
||||
281
vendor/github.com/hyperhq/hypercli/container/state.go
generated
vendored
Normal file
281
vendor/github.com/hyperhq/hypercli/container/state.go
generated
vendored
Normal file
@@ -0,0 +1,281 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
derr "github.com/docker/docker/errors"
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
|
||||
// State holds the current container state, and has methods to get and
|
||||
// set the state. Container has an embed, which allows all of the
|
||||
// functions defined against State to run against Container.
|
||||
type State struct {
|
||||
sync.Mutex
|
||||
// FIXME: Why do we have both paused and running if a
|
||||
// container cannot be paused and running at the same time?
|
||||
Running bool
|
||||
Paused bool
|
||||
Restarting bool
|
||||
OOMKilled bool
|
||||
RemovalInProgress bool // Not need for this to be persistent on disk.
|
||||
Dead bool
|
||||
Pid int
|
||||
ExitCode int
|
||||
Error string // contains last known error when starting the container
|
||||
StartedAt time.Time
|
||||
FinishedAt time.Time
|
||||
waitChan chan struct{}
|
||||
}
|
||||
|
||||
// NewState creates a default state object with a fresh channel for state changes.
|
||||
func NewState() *State {
|
||||
return &State{
|
||||
waitChan: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a human-readable description of the state
|
||||
func (s *State) String() string {
|
||||
if s.Running {
|
||||
if s.Paused {
|
||||
return fmt.Sprintf("Up %s (Paused)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
|
||||
}
|
||||
if s.Restarting {
|
||||
return fmt.Sprintf("Restarting (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt)))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Up %s", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
|
||||
}
|
||||
|
||||
if s.RemovalInProgress {
|
||||
return "Removal In Progress"
|
||||
}
|
||||
|
||||
if s.Dead {
|
||||
return "Dead"
|
||||
}
|
||||
|
||||
if s.StartedAt.IsZero() {
|
||||
return "Created"
|
||||
}
|
||||
|
||||
if s.FinishedAt.IsZero() {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Exited (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt)))
|
||||
}
|
||||
|
||||
// StateString returns a single string to describe state
|
||||
func (s *State) StateString() string {
|
||||
if s.Running {
|
||||
if s.Paused {
|
||||
return "paused"
|
||||
}
|
||||
if s.Restarting {
|
||||
return "restarting"
|
||||
}
|
||||
return "running"
|
||||
}
|
||||
|
||||
if s.Dead {
|
||||
return "dead"
|
||||
}
|
||||
|
||||
if s.StartedAt.IsZero() {
|
||||
return "created"
|
||||
}
|
||||
|
||||
return "exited"
|
||||
}
|
||||
|
||||
// IsValidStateString checks if the provided string is a valid container state or not.
|
||||
func IsValidStateString(s string) bool {
|
||||
if s != "paused" &&
|
||||
s != "restarting" &&
|
||||
s != "running" &&
|
||||
s != "dead" &&
|
||||
s != "created" &&
|
||||
s != "exited" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func wait(waitChan <-chan struct{}, timeout time.Duration) error {
|
||||
if timeout < 0 {
|
||||
<-waitChan
|
||||
return nil
|
||||
}
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
return derr.ErrorCodeTimedOut.WithArgs(timeout)
|
||||
case <-waitChan:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// waitRunning waits until state is running. If state is already
|
||||
// running it returns immediately. If you want wait forever you must
|
||||
// supply negative timeout. Returns pid, that was passed to
|
||||
// SetRunning.
|
||||
func (s *State) waitRunning(timeout time.Duration) (int, error) {
|
||||
s.Lock()
|
||||
if s.Running {
|
||||
pid := s.Pid
|
||||
s.Unlock()
|
||||
return pid, nil
|
||||
}
|
||||
waitChan := s.waitChan
|
||||
s.Unlock()
|
||||
if err := wait(waitChan, timeout); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return s.GetPID(), nil
|
||||
}
|
||||
|
||||
// WaitStop waits until state is stopped. If state already stopped it returns
|
||||
// immediately. If you want wait forever you must supply negative timeout.
|
||||
// Returns exit code, that was passed to SetStoppedLocking
|
||||
func (s *State) WaitStop(timeout time.Duration) (int, error) {
|
||||
s.Lock()
|
||||
if !s.Running {
|
||||
exitCode := s.ExitCode
|
||||
s.Unlock()
|
||||
return exitCode, nil
|
||||
}
|
||||
waitChan := s.waitChan
|
||||
s.Unlock()
|
||||
if err := wait(waitChan, timeout); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return s.getExitCode(), nil
|
||||
}
|
||||
|
||||
// IsRunning returns whether the running flag is set. Used by Container to check whether a container is running.
|
||||
func (s *State) IsRunning() bool {
|
||||
s.Lock()
|
||||
res := s.Running
|
||||
s.Unlock()
|
||||
return res
|
||||
}
|
||||
|
||||
// GetPID holds the process id of a container.
|
||||
func (s *State) GetPID() int {
|
||||
s.Lock()
|
||||
res := s.Pid
|
||||
s.Unlock()
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *State) getExitCode() int {
|
||||
s.Lock()
|
||||
res := s.ExitCode
|
||||
s.Unlock()
|
||||
return res
|
||||
}
|
||||
|
||||
// SetRunning sets the state of the container to "running".
|
||||
func (s *State) SetRunning(pid int) {
|
||||
s.Error = ""
|
||||
s.Running = true
|
||||
s.Paused = false
|
||||
s.Restarting = false
|
||||
s.ExitCode = 0
|
||||
s.Pid = pid
|
||||
s.StartedAt = time.Now().UTC()
|
||||
close(s.waitChan) // fire waiters for start
|
||||
s.waitChan = make(chan struct{})
|
||||
}
|
||||
|
||||
// SetStoppedLocking locks the container state is sets it to "stopped".
|
||||
func (s *State) SetStoppedLocking(exitStatus *execdriver.ExitStatus) {
|
||||
s.Lock()
|
||||
s.SetStopped(exitStatus)
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
// SetStopped sets the container state to "stopped" without locking.
|
||||
func (s *State) SetStopped(exitStatus *execdriver.ExitStatus) {
|
||||
s.Running = false
|
||||
s.Restarting = false
|
||||
s.Pid = 0
|
||||
s.FinishedAt = time.Now().UTC()
|
||||
s.setFromExitStatus(exitStatus)
|
||||
close(s.waitChan) // fire waiters for stop
|
||||
s.waitChan = make(chan struct{})
|
||||
}
|
||||
|
||||
// SetRestartingLocking is when docker handles the auto restart of containers when they are
|
||||
// in the middle of a stop and being restarted again
|
||||
func (s *State) SetRestartingLocking(exitStatus *execdriver.ExitStatus) {
|
||||
s.Lock()
|
||||
s.SetRestarting(exitStatus)
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
// SetRestarting sets the container state to "restarting".
|
||||
// It also sets the container PID to 0.
|
||||
func (s *State) SetRestarting(exitStatus *execdriver.ExitStatus) {
|
||||
// we should consider the container running when it is restarting because of
|
||||
// all the checks in docker around rm/stop/etc
|
||||
s.Running = true
|
||||
s.Restarting = true
|
||||
s.Pid = 0
|
||||
s.FinishedAt = time.Now().UTC()
|
||||
s.setFromExitStatus(exitStatus)
|
||||
close(s.waitChan) // fire waiters for stop
|
||||
s.waitChan = make(chan struct{})
|
||||
}
|
||||
|
||||
// SetError sets the container's error state. This is useful when we want to
|
||||
// know the error that occurred when container transits to another state
|
||||
// when inspecting it
|
||||
func (s *State) SetError(err error) {
|
||||
s.Error = err.Error()
|
||||
}
|
||||
|
||||
// IsPaused returns whether the container is paused or not.
|
||||
func (s *State) IsPaused() bool {
|
||||
s.Lock()
|
||||
res := s.Paused
|
||||
s.Unlock()
|
||||
return res
|
||||
}
|
||||
|
||||
// IsRestarting returns whether the container is restarting or not.
|
||||
func (s *State) IsRestarting() bool {
|
||||
s.Lock()
|
||||
res := s.Restarting
|
||||
s.Unlock()
|
||||
return res
|
||||
}
|
||||
|
||||
// SetRemovalInProgress sets the container state as being removed.
|
||||
func (s *State) SetRemovalInProgress() error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
if s.RemovalInProgress {
|
||||
return derr.ErrorCodeAlreadyRemoving
|
||||
}
|
||||
s.RemovalInProgress = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResetRemovalInProgress make the RemovalInProgress state to false.
|
||||
func (s *State) ResetRemovalInProgress() {
|
||||
s.Lock()
|
||||
s.RemovalInProgress = false
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
// SetDead sets the container state to "dead"
|
||||
func (s *State) SetDead() {
|
||||
s.Lock()
|
||||
s.Dead = true
|
||||
s.Unlock()
|
||||
}
|
||||
111
vendor/github.com/hyperhq/hypercli/container/state_test.go
generated
vendored
Normal file
111
vendor/github.com/hyperhq/hypercli/container/state_test.go
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
)
|
||||
|
||||
func TestStateRunStop(t *testing.T) {
|
||||
s := NewState()
|
||||
for i := 1; i < 3; i++ { // full lifecycle two times
|
||||
started := make(chan struct{})
|
||||
var pid int64
|
||||
go func() {
|
||||
runPid, _ := s.waitRunning(-1 * time.Second)
|
||||
atomic.StoreInt64(&pid, int64(runPid))
|
||||
close(started)
|
||||
}()
|
||||
s.Lock()
|
||||
s.SetRunning(i + 100)
|
||||
s.Unlock()
|
||||
|
||||
if !s.IsRunning() {
|
||||
t.Fatal("State not running")
|
||||
}
|
||||
if s.Pid != i+100 {
|
||||
t.Fatalf("Pid %v, expected %v", s.Pid, i+100)
|
||||
}
|
||||
if s.ExitCode != 0 {
|
||||
t.Fatalf("ExitCode %v, expected 0", s.ExitCode)
|
||||
}
|
||||
select {
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
t.Fatal("Start callback doesn't fire in 100 milliseconds")
|
||||
case <-started:
|
||||
t.Log("Start callback fired")
|
||||
}
|
||||
runPid := int(atomic.LoadInt64(&pid))
|
||||
if runPid != i+100 {
|
||||
t.Fatalf("Pid %v, expected %v", runPid, i+100)
|
||||
}
|
||||
if pid, err := s.waitRunning(-1 * time.Second); err != nil || pid != i+100 {
|
||||
t.Fatalf("waitRunning returned pid: %v, err: %v, expected pid: %v, err: %v", pid, err, i+100, nil)
|
||||
}
|
||||
|
||||
stopped := make(chan struct{})
|
||||
var exit int64
|
||||
go func() {
|
||||
exitCode, _ := s.WaitStop(-1 * time.Second)
|
||||
atomic.StoreInt64(&exit, int64(exitCode))
|
||||
close(stopped)
|
||||
}()
|
||||
s.SetStoppedLocking(&execdriver.ExitStatus{ExitCode: i})
|
||||
if s.IsRunning() {
|
||||
t.Fatal("State is running")
|
||||
}
|
||||
if s.ExitCode != i {
|
||||
t.Fatalf("ExitCode %v, expected %v", s.ExitCode, i)
|
||||
}
|
||||
if s.Pid != 0 {
|
||||
t.Fatalf("Pid %v, expected 0", s.Pid)
|
||||
}
|
||||
select {
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
t.Fatal("Stop callback doesn't fire in 100 milliseconds")
|
||||
case <-stopped:
|
||||
t.Log("Stop callback fired")
|
||||
}
|
||||
exitCode := int(atomic.LoadInt64(&exit))
|
||||
if exitCode != i {
|
||||
t.Fatalf("ExitCode %v, expected %v", exitCode, i)
|
||||
}
|
||||
if exitCode, err := s.WaitStop(-1 * time.Second); err != nil || exitCode != i {
|
||||
t.Fatalf("WaitStop returned exitCode: %v, err: %v, expected exitCode: %v, err: %v", exitCode, err, i, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStateTimeoutWait(t *testing.T) {
|
||||
s := NewState()
|
||||
started := make(chan struct{})
|
||||
go func() {
|
||||
s.waitRunning(100 * time.Millisecond)
|
||||
close(started)
|
||||
}()
|
||||
select {
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
t.Fatal("Start callback doesn't fire in 100 milliseconds")
|
||||
case <-started:
|
||||
t.Log("Start callback fired")
|
||||
}
|
||||
|
||||
s.Lock()
|
||||
s.SetRunning(49)
|
||||
s.Unlock()
|
||||
|
||||
stopped := make(chan struct{})
|
||||
go func() {
|
||||
s.waitRunning(100 * time.Millisecond)
|
||||
close(stopped)
|
||||
}()
|
||||
select {
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
t.Fatal("Start callback doesn't fire in 100 milliseconds")
|
||||
case <-stopped:
|
||||
t.Log("Start callback fired")
|
||||
}
|
||||
|
||||
}
|
||||
12
vendor/github.com/hyperhq/hypercli/container/state_unix.go
generated
vendored
Normal file
12
vendor/github.com/hyperhq/hypercli/container/state_unix.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// +build linux freebsd
|
||||
|
||||
package container
|
||||
|
||||
import "github.com/docker/docker/daemon/execdriver"
|
||||
|
||||
// setFromExitStatus is a platform specific helper function to set the state
|
||||
// based on the ExitStatus structure.
|
||||
func (s *State) setFromExitStatus(exitStatus *execdriver.ExitStatus) {
|
||||
s.ExitCode = exitStatus.ExitCode
|
||||
s.OOMKilled = exitStatus.OOMKilled
|
||||
}
|
||||
9
vendor/github.com/hyperhq/hypercli/container/state_windows.go
generated
vendored
Normal file
9
vendor/github.com/hyperhq/hypercli/container/state_windows.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
package container
|
||||
|
||||
import "github.com/docker/docker/daemon/execdriver"
|
||||
|
||||
// setFromExitStatus is a platform specific helper function to set the state
|
||||
// based on the ExitStatus structure.
|
||||
func (s *State) setFromExitStatus(exitStatus *execdriver.ExitStatus) {
|
||||
s.ExitCode = exitStatus.ExitCode
|
||||
}
|
||||
28
vendor/github.com/hyperhq/hypercli/container/store.go
generated
vendored
Normal file
28
vendor/github.com/hyperhq/hypercli/container/store.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
package container
|
||||
|
||||
// StoreFilter defines a function to filter
|
||||
// container in the store.
|
||||
type StoreFilter func(*Container) bool
|
||||
|
||||
// StoreReducer defines a function to
|
||||
// manipulate containers in the store
|
||||
type StoreReducer func(*Container)
|
||||
|
||||
// Store defines an interface that
|
||||
// any container store must implement.
|
||||
type Store interface {
|
||||
// Add appends a new container to the store.
|
||||
Add(string, *Container)
|
||||
// Get returns a container from the store by the identifier it was stored with.
|
||||
Get(string) *Container
|
||||
// Delete removes a container from the store by the identifier it was stored with.
|
||||
Delete(string)
|
||||
// List returns a list of containers from the store.
|
||||
List() []*Container
|
||||
// Size returns the number of containers in the store.
|
||||
Size() int
|
||||
// First returns the first container found in the store by a given filter.
|
||||
First(StoreFilter) *Container
|
||||
// ApplyAll calls the reducer function with every container in the store.
|
||||
ApplyAll(StoreReducer)
|
||||
}
|
||||
Reference in New Issue
Block a user