diff --git a/components/engine/Dockerfile b/components/engine/Dockerfile index 1bcbca9924..aba9c9eda4 100644 --- a/components/engine/Dockerfile +++ b/components/engine/Dockerfile @@ -249,6 +249,24 @@ RUN set -x \ && go build -v -o /usr/local/bin/rsrc github.com/akavel/rsrc \ && rm -rf "$GOPATH" +# Install runc +ENV RUNC_COMMIT bbde9c426ff363d813b8722f0744115c13b408b6 +RUN set -x \ + && export GOPATH="$(mktemp -d)" \ + && git clone git://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \ + && cd "$GOPATH/src/github.com/opencontainers/runc" \ + && git checkout -q "$RUNC_COMMIT" \ + && make BUILDTAGS="seccomp apparmor selinux" && make install + +# Install containerd +ENV CONTAINERD_COMMIT 7146b01a3d7aaa146414cdfb0a6c96cfba5d9091 +RUN set -x \ + && export GOPATH="$(mktemp -d)" \ + && git clone git://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \ + && cd "$GOPATH/src/github.com/docker/containerd" \ + && git checkout -q "$CONTAINERD_COMMIT" \ + && make && make install + # Wrap all commands in the "docker-in-docker" script to allow nested containers ENTRYPOINT ["hack/dind"] diff --git a/components/engine/Dockerfile.aarch64 b/components/engine/Dockerfile.aarch64 index f7a43da939..88cd6c2a69 100644 --- a/components/engine/Dockerfile.aarch64 +++ b/components/engine/Dockerfile.aarch64 @@ -186,6 +186,24 @@ RUN set -x \ && go build -v -o /usr/local/bin/tomlv github.com/BurntSushi/toml/cmd/tomlv \ && rm -rf "$GOPATH" +# Install runc +ENV RUNC_COMMIT bbde9c426ff363d813b8722f0744115c13b408b6 +RUN set -x \ + && export GOPATH="$(mktemp -d)" \ + && git clone git://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \ + && cd "$GOPATH/src/github.com/opencontainers/runc" \ + && git checkout -q "$RUNC_COMMIT" \ + && make BUILDTAGS="seccomp apparmor selinux" && make install + +# Install containerd +ENV CONTAINERD_COMMIT 7146b01a3d7aaa146414cdfb0a6c96cfba5d9091 +RUN set -x \ + && export GOPATH="$(mktemp -d)" \ + && git clone git://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \ + && cd "$GOPATH/src/github.com/docker/containerd" \ + && git checkout -q "$CONTAINERD_COMMIT" \ + && make && make install + # Wrap all commands in the "docker-in-docker" script to allow nested containers ENTRYPOINT ["hack/dind"] diff --git a/components/engine/Dockerfile.armhf b/components/engine/Dockerfile.armhf index f5b3420894..97dd14fba8 100644 --- a/components/engine/Dockerfile.armhf +++ b/components/engine/Dockerfile.armhf @@ -205,6 +205,24 @@ RUN set -x \ && go build -v -o /usr/local/bin/rsrc github.com/akavel/rsrc \ && rm -rf "$GOPATH" +# Install runc +ENV RUNC_COMMIT bbde9c426ff363d813b8722f0744115c13b408b6 +RUN set -x \ + && export GOPATH="$(mktemp -d)" \ + && git clone git://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \ + && cd "$GOPATH/src/github.com/opencontainers/runc" \ + && git checkout -q "$RUNC_COMMIT" \ + && make BUILDTAGS="seccomp apparmor selinux" && make install + +# Install containerd +ENV CONTAINERD_COMMIT 7146b01a3d7aaa146414cdfb0a6c96cfba5d9091 +RUN set -x \ + && export GOPATH="$(mktemp -d)" \ + && git clone git://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \ + && cd "$GOPATH/src/github.com/docker/containerd" \ + && git checkout -q "$CONTAINERD_COMMIT" \ + && make && make install + # Wrap all commands in the "docker-in-docker" script to allow nested containers ENTRYPOINT ["hack/dind"] diff --git a/components/engine/Dockerfile.gccgo b/components/engine/Dockerfile.gccgo index 85427b7a1e..337a22da07 100644 --- a/components/engine/Dockerfile.gccgo +++ b/components/engine/Dockerfile.gccgo @@ -73,6 +73,24 @@ VOLUME /var/lib/docker WORKDIR /go/src/github.com/docker/docker ENV DOCKER_BUILDTAGS apparmor seccomp selinux +# Install runc +ENV RUNC_COMMIT bbde9c426ff363d813b8722f0744115c13b408b6 +RUN set -x \ + && export GOPATH="$(mktemp -d)" \ + && git clone git://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \ + && cd "$GOPATH/src/github.com/opencontainers/runc" \ + && git checkout -q "$RUNC_COMMIT" \ + && make BUILDTAGS="seccomp apparmor selinux" && make install + +# Install containerd +ENV CONTAINERD_COMMIT 7146b01a3d7aaa146414cdfb0a6c96cfba5d9091 +RUN set -x \ + && export GOPATH="$(mktemp -d)" \ + && git clone git://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \ + && cd "$GOPATH/src/github.com/docker/containerd" \ + && git checkout -q "$CONTAINERD_COMMIT" \ + && make && make install + # Wrap all commands in the "docker-in-docker" script to allow nested containers ENTRYPOINT ["hack/dind"] diff --git a/components/engine/Dockerfile.ppc64le b/components/engine/Dockerfile.ppc64le index 7b202ce568..3abf527bd9 100644 --- a/components/engine/Dockerfile.ppc64le +++ b/components/engine/Dockerfile.ppc64le @@ -197,6 +197,24 @@ RUN set -x \ && go build -v -o /usr/local/bin/rsrc github.com/akavel/rsrc \ && rm -rf "$GOPATH" +# Install runc +ENV RUNC_COMMIT bbde9c426ff363d813b8722f0744115c13b408b6 +RUN set -x \ + && export GOPATH="$(mktemp -d)" \ + && git clone git://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \ + && cd "$GOPATH/src/github.com/opencontainers/runc" \ + && git checkout -q "$RUNC_COMMIT" \ + && make BUILDTAGS="seccomp apparmor selinux" && make install + +# Install containerd +ENV CONTAINERD_COMMIT 7146b01a3d7aaa146414cdfb0a6c96cfba5d9091 +RUN set -x \ + && export GOPATH="$(mktemp -d)" \ + && git clone git://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \ + && cd "$GOPATH/src/github.com/docker/containerd" \ + && git checkout -q "$CONTAINERD_COMMIT" \ + && make && make install + # Wrap all commands in the "docker-in-docker" script to allow nested containers ENTRYPOINT ["hack/dind"] diff --git a/components/engine/Dockerfile.s390x b/components/engine/Dockerfile.s390x index 03db594f15..460a235345 100644 --- a/components/engine/Dockerfile.s390x +++ b/components/engine/Dockerfile.s390x @@ -176,6 +176,24 @@ RUN set -x \ && go build -v -o /usr/local/bin/rsrc github.com/akavel/rsrc \ && rm -rf "$GOPATH" +# Install runc +ENV RUNC_COMMIT bbde9c426ff363d813b8722f0744115c13b408b6 +RUN set -x \ + && export GOPATH="$(mktemp -d)" \ + && git clone git://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \ + && cd "$GOPATH/src/github.com/opencontainers/runc" \ + && git checkout -q "$RUNC_COMMIT" \ + && make BUILDTAGS="seccomp apparmor selinux" && make install + +# Install containerd +ENV CONTAINERD_COMMIT 7146b01a3d7aaa146414cdfb0a6c96cfba5d9091 +RUN set -x \ + && export GOPATH="$(mktemp -d)" \ + && git clone git://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \ + && cd "$GOPATH/src/github.com/docker/containerd" \ + && git checkout -q "$CONTAINERD_COMMIT" \ + && make && make install + # Wrap all commands in the "docker-in-docker" script to allow nested containers ENTRYPOINT ["hack/dind"] diff --git a/components/engine/Dockerfile.simple b/components/engine/Dockerfile.simple index 427a0717fb..67fbdbb1ed 100644 --- a/components/engine/Dockerfile.simple +++ b/components/engine/Dockerfile.simple @@ -29,6 +29,24 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ aufs-tools \ && rm -rf /var/lib/apt/lists/* +# Install runc +ENV RUNC_COMMIT bbde9c426ff363d813b8722f0744115c13b408b6 +RUN set -x \ + && export GOPATH="$(mktemp -d)" \ + && git clone git://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \ + && cd "$GOPATH/src/github.com/opencontainers/runc" \ + && git checkout -q "$RUNC_COMMIT" \ + && make BUILDTAGS="seccomp apparmor selinux" && make install + +# Install containerd +ENV CONTAINERD_COMMIT 7146b01a3d7aaa146414cdfb0a6c96cfba5d9091 +RUN set -x \ + && export GOPATH="$(mktemp -d)" \ + && git clone git://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \ + && cd "$GOPATH/src/github.com/docker/containerd" \ + && git checkout -q "$CONTAINERD_COMMIT" \ + && make && make install + ENV AUTO_GOPATH 1 WORKDIR /usr/src/docker COPY . /usr/src/docker diff --git a/components/engine/api/client/run.go b/components/engine/api/client/run.go index c75441dcc2..503cfdcd48 100644 --- a/components/engine/api/client/run.go +++ b/components/engine/api/client/run.go @@ -14,7 +14,6 @@ import ( "github.com/docker/docker/opts" "github.com/docker/docker/pkg/promise" "github.com/docker/docker/pkg/signal" - "github.com/docker/docker/pkg/stringid" runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/docker/engine-api/types" "github.com/docker/libnetwork/resolvconf/dns" @@ -256,16 +255,6 @@ func (cli *DockerCli) CmdRun(args ...string) error { // Attached mode if *flAutoRemove { - // Warn user if they detached us - js, err := cli.client.ContainerInspect(context.Background(), createResponse.ID) - if err != nil { - return runStartContainerErr(err) - } - if js.State.Running == true || js.State.Paused == true { - fmt.Fprintf(cli.out, "Detached from %s, awaiting its termination in order to uphold \"--rm\".\n", - stringid.TruncateID(createResponse.ID)) - } - // Autoremove: wait for the container to finish, retrieve // the exit code and remove the container if status, err = cli.client.ContainerWait(context.Background(), createResponse.ID); err != nil { diff --git a/components/engine/api/server/router/container/exec.go b/components/engine/api/server/router/container/exec.go index bc336f6039..ee0d855ce4 100644 --- a/components/engine/api/server/router/container/exec.go +++ b/components/engine/api/server/router/container/exec.go @@ -112,7 +112,9 @@ func (s *containerRouter) postContainerExecStart(ctx context.Context, w http.Res if execStartCheck.Detach { return err } + stdout.Write([]byte(err.Error())) logrus.Errorf("Error running exec in container: %v\n", err) + return err } return nil } diff --git a/components/engine/container/container.go b/components/engine/container/container.go index 39336ab168..2c407d19de 100644 --- a/components/engine/container/container.go +++ b/components/engine/container/container.go @@ -17,7 +17,6 @@ import ( "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" @@ -27,6 +26,7 @@ import ( "github.com/docker/docker/pkg/promise" "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/symlink" + "github.com/docker/docker/restartmanager" "github.com/docker/docker/runconfig" runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/docker/docker/volume" @@ -74,13 +74,12 @@ type CommonContainer struct { 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:"-"` + ExecCommands *exec.Store `json:"-"` // logDriver for closing - LogDriver logger.Logger `json:"-"` - LogCopier *logger.Copier `json:"-"` - attachContext *attachContext + LogDriver logger.Logger `json:"-"` + LogCopier *logger.Copier `json:"-"` + restartManager restartmanager.RestartManager + attachContext *attachContext } // NewBaseContainer creates a new container with its @@ -276,19 +275,9 @@ func (container *Container) GetRootResourcePath(path string) (string, error) { // 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 container.restartManager != nil { + container.restartManager.Cancel() } - 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 @@ -897,19 +886,33 @@ func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network, epC // UpdateMonitor updates monitor configure for running container func (container *Container) UpdateMonitor(restartPolicy containertypes.RestartPolicy) { - monitor := container.monitor - // No need to update monitor if container hasn't got one - // monitor will be generated correctly according to container - if monitor == nil { - return + type policySetter interface { + SetPolicy(containertypes.RestartPolicy) } - monitor.mux.Lock() - // to check whether restart policy has changed. - if restartPolicy.Name != "" && !monitor.restartPolicy.IsSame(&restartPolicy) { - monitor.restartPolicy = restartPolicy + if rm, ok := container.RestartManager(false).(policySetter); ok { + rm.SetPolicy(restartPolicy) } - monitor.mux.Unlock() +} + +// FullHostname returns hostname and optional domain appended to it. +func (container *Container) FullHostname() string { + fullHostname := container.Config.Hostname + if container.Config.Domainname != "" { + fullHostname = fmt.Sprintf("%s.%s", fullHostname, container.Config.Domainname) + } + return fullHostname +} + +// RestartManager returns the current restartmanager instace connected to container. +func (container *Container) RestartManager(reset bool) restartmanager.RestartManager { + if reset { + container.RestartCount = 0 + } + if container.restartManager == nil { + container.restartManager = restartmanager.New(container.HostConfig.RestartPolicy) + } + return container.restartManager } type attachContext struct { diff --git a/components/engine/container/container_unix.go b/components/engine/container/container_unix.go index 64ff4ee1f7..4f86a45581 100644 --- a/components/engine/container/container_unix.go +++ b/components/engine/container/container_unix.go @@ -11,7 +11,6 @@ import ( "syscall" "github.com/Sirupsen/logrus" - "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/pkg/chrootarchive" "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/pkg/system" @@ -39,6 +38,15 @@ type Container struct { NoNewPrivileges bool } +// 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 +} + // 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. @@ -57,7 +65,6 @@ func (container *Container) CreateDaemonEnvironment(linkedEnv []string) []string // we need to replace the 'env' keys where they match and append anything // else. env = utils.ReplaceOrAppendEnvValues(env, container.Config.Env) - return env } @@ -103,8 +110,8 @@ func appendNetworkMounts(container *Container, volumeMounts []volume.MountPoint) } // NetworkMounts returns the list of network mounts. -func (container *Container) NetworkMounts() []execdriver.Mount { - var mounts []execdriver.Mount +func (container *Container) NetworkMounts() []Mount { + var mounts []Mount shared := container.HostConfig.NetworkMode.IsContainer() if container.ResolvConfPath != "" { if _, err := os.Stat(container.ResolvConfPath); err != nil { @@ -115,7 +122,7 @@ func (container *Container) NetworkMounts() []execdriver.Mount { if m, exists := container.MountPoints["/etc/resolv.conf"]; exists { writable = m.RW } - mounts = append(mounts, execdriver.Mount{ + mounts = append(mounts, Mount{ Source: container.ResolvConfPath, Destination: "/etc/resolv.conf", Writable: writable, @@ -132,7 +139,7 @@ func (container *Container) NetworkMounts() []execdriver.Mount { if m, exists := container.MountPoints["/etc/hostname"]; exists { writable = m.RW } - mounts = append(mounts, execdriver.Mount{ + mounts = append(mounts, Mount{ Source: container.HostnamePath, Destination: "/etc/hostname", Writable: writable, @@ -149,7 +156,7 @@ func (container *Container) NetworkMounts() []execdriver.Mount { if m, exists := container.MountPoints["/etc/hosts"]; exists { writable = m.RW } - mounts = append(mounts, execdriver.Mount{ + mounts = append(mounts, Mount{ Source: container.HostsPath, Destination: "/etc/hosts", Writable: writable, @@ -224,37 +231,26 @@ func (container *Container) UnmountIpcMounts(unmount func(pth string) error) { } // IpcMounts returns the list of IPC mounts -func (container *Container) IpcMounts() []execdriver.Mount { - var mounts []execdriver.Mount +func (container *Container) IpcMounts() []Mount { + var mounts []Mount if !container.HasMountFor("/dev/shm") { label.SetFileLabel(container.ShmPath, container.MountLabel) - mounts = append(mounts, execdriver.Mount{ + mounts = append(mounts, Mount{ Source: container.ShmPath, Destination: "/dev/shm", 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 + return mounts } // UpdateContainer updates configuration of a container. func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfig) error { container.Lock() + defer container.Unlock() // update resources of container resources := hostConfig.Resources @@ -294,19 +290,8 @@ func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfi if hostConfig.RestartPolicy.Name != "" { container.HostConfig.RestartPolicy = hostConfig.RestartPolicy } - 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 { + if err := container.ToDisk(); err != nil { logrus.Errorf("Error saving updated container: %v", err) return err } @@ -400,10 +385,10 @@ func copyOwnership(source, destination string) error { } // TmpfsMounts returns the list of tmpfs mounts -func (container *Container) TmpfsMounts() []execdriver.Mount { - var mounts []execdriver.Mount +func (container *Container) TmpfsMounts() []Mount { + var mounts []Mount for dest, data := range container.HostConfig.Tmpfs { - mounts = append(mounts, execdriver.Mount{ + mounts = append(mounts, Mount{ Source: "tmpfs", Destination: dest, Data: data, diff --git a/components/engine/container/container_windows.go b/components/engine/container/container_windows.go index fb24ebb968..5c923960bb 100644 --- a/components/engine/container/container_windows.go +++ b/components/engine/container/container_windows.go @@ -7,7 +7,6 @@ import ( "os" "path/filepath" - "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/volume" containertypes "github.com/docker/engine-api/types/container" ) @@ -23,6 +22,12 @@ type Container struct { // Fields below here are platform specific. } +// ExitStatus provides exit reasons for a container. +type ExitStatus struct { + // The exit code with which the container exited. + ExitCode int +} + // 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. @@ -35,7 +40,7 @@ func (container *Container) UnmountIpcMounts(unmount func(pth string) error) { } // IpcMounts returns the list of Ipc related mounts. -func (container *Container) IpcMounts() []execdriver.Mount { +func (container *Container) IpcMounts() []Mount { return nil } @@ -45,7 +50,7 @@ func (container *Container) UnmountVolumes(forceSyscall bool, volumeEventLog fun } // TmpfsMounts returns the list of tmpfs mounts -func (container *Container) TmpfsMounts() []execdriver.Mount { +func (container *Container) TmpfsMounts() []Mount { return nil } diff --git a/components/engine/container/memory_store.go b/components/engine/container/memory_store.go index 153242fdb4..30c1f7add7 100644 --- a/components/engine/container/memory_store.go +++ b/components/engine/container/memory_store.go @@ -5,7 +5,7 @@ import "sync" // memoryStore implements a Store in memory. type memoryStore struct { s map[string]*Container - sync.Mutex + sync.RWMutex } // NewMemoryStore initializes a new memory store. @@ -25,9 +25,9 @@ func (c *memoryStore) Add(id string, cont *Container) { // Get returns a container from the store by id. func (c *memoryStore) Get(id string) *Container { - c.Lock() + c.RLock() res := c.s[id] - c.Unlock() + c.RUnlock() return res } @@ -42,26 +42,26 @@ func (c *memoryStore) Delete(id string) { // The containers are ordered by creation date. func (c *memoryStore) List() []*Container { containers := new(History) - c.Lock() + c.RLock() for _, cont := range c.s { containers.Add(cont) } - c.Unlock() + c.RUnlock() containers.sort() return *containers } // Size returns the number of containers in the store. func (c *memoryStore) Size() int { - c.Lock() - defer c.Unlock() + c.RLock() + defer c.RUnlock() 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() + c.RLock() + defer c.RUnlock() for _, cont := range c.s { if filter(cont) { return cont @@ -72,9 +72,10 @@ func (c *memoryStore) First(filter StoreFilter) *Container { // ApplyAll calls the reducer function with every container in the store. // This operation is asyncronous in the memory store. +// NOTE: Modifications to the store MUST NOT be done by the StoreReducer. func (c *memoryStore) ApplyAll(apply StoreReducer) { - c.Lock() - defer c.Unlock() + c.RLock() + defer c.RUnlock() wg := new(sync.WaitGroup) for _, cont := range c.s { diff --git a/components/engine/container/monitor.go b/components/engine/container/monitor.go index afea01fcc9..ba82d875b7 100644 --- a/components/engine/container/monitor.go +++ b/components/engine/container/monitor.go @@ -1,24 +1,13 @@ package container import ( - "fmt" - "io" - "os/exec" - "strings" - "sync" - "syscall" "time" "github.com/Sirupsen/logrus" - "github.com/docker/docker/daemon/execdriver" - "github.com/docker/docker/pkg/promise" - "github.com/docker/docker/pkg/stringid" - "github.com/docker/engine-api/types/container" ) const ( - defaultTimeIncrement = 100 - loggerCloseTimeout = 10 * time.Second + loggerCloseTimeout = 10 * time.Second ) // supervisor defines the interface that a supervisor must implement @@ -30,311 +19,13 @@ type supervisor interface { // 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) + Run(c *Container) 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) error { - container.monitor = &containerMonitor{ - supervisor: s, - container: container, - restartPolicy: container.HostConfig.RestartPolicy, - 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) - - 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 fmt.Errorf("Container command not found or does not exist.") - } - } - // 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 fmt.Errorf("Container command could not be invoked.") - } - } - - if m.container.RestartCount == 0 { - m.container.ExitCode = -1 - m.resetContainer(false) - - return fmt.Errorf("Cannot start container %s: %v", m.container.ID, 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 +// Reset puts a container into a state where it can be restarted again. +func (container *Container) Reset(lock bool) { if lock { container.Lock() defer container.Unlock() @@ -344,12 +35,6 @@ func (m *containerMonitor) resetContainer(lock bool) { 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() @@ -365,9 +50,6 @@ func (m *containerMonitor) resetContainer(lock bool) { 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: } } @@ -375,22 +57,4 @@ func (m *containerMonitor) resetContainer(lock bool) { 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) } diff --git a/components/engine/container/mounts_unix.go b/components/engine/container/mounts_unix.go new file mode 100644 index 0000000000..c52abed2dc --- /dev/null +++ b/components/engine/container/mounts_unix.go @@ -0,0 +1,12 @@ +// +build !windows + +package container + +// 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"` +} diff --git a/components/engine/container/mounts_windows.go b/components/engine/container/mounts_windows.go new file mode 100644 index 0000000000..01b327f788 --- /dev/null +++ b/components/engine/container/mounts_windows.go @@ -0,0 +1,8 @@ +package container + +// Mount contains information for a mount operation. +type Mount struct { + Source string `json:"source"` + Destination string `json:"destination"` + Writable bool `json:"writable"` +} diff --git a/components/engine/container/state.go b/components/engine/container/state.go index 7173c7632f..a12a193e32 100644 --- a/components/engine/container/state.go +++ b/components/engine/container/state.go @@ -5,7 +5,6 @@ import ( "sync" "time" - "github.com/docker/docker/daemon/execdriver" "github.com/docker/go-units" ) @@ -179,28 +178,31 @@ func (s *State) getExitCode() int { } // SetRunning sets the state of the container to "running". -func (s *State) SetRunning(pid int) { +func (s *State) SetRunning(pid int, initial bool) { s.Error = "" s.Running = true s.Paused = false s.Restarting = false s.ExitCode = 0 s.Pid = pid - s.StartedAt = time.Now().UTC() + if initial { + 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) { +func (s *State) SetStoppedLocking(exitStatus *ExitStatus) { s.Lock() s.SetStopped(exitStatus) s.Unlock() } // SetStopped sets the container state to "stopped" without locking. -func (s *State) SetStopped(exitStatus *execdriver.ExitStatus) { +func (s *State) SetStopped(exitStatus *ExitStatus) { s.Running = false + s.Paused = false s.Restarting = false s.Pid = 0 s.FinishedAt = time.Now().UTC() @@ -211,7 +213,7 @@ func (s *State) SetStopped(exitStatus *execdriver.ExitStatus) { // 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) { +func (s *State) SetRestartingLocking(exitStatus *ExitStatus) { s.Lock() s.SetRestarting(exitStatus) s.Unlock() @@ -219,7 +221,7 @@ func (s *State) SetRestartingLocking(exitStatus *execdriver.ExitStatus) { // SetRestarting sets the container state to "restarting". // It also sets the container PID to 0. -func (s *State) SetRestarting(exitStatus *execdriver.ExitStatus) { +func (s *State) SetRestarting(exitStatus *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 diff --git a/components/engine/container/state_test.go b/components/engine/container/state_test.go index 75028168d4..7b35b17820 100644 --- a/components/engine/container/state_test.go +++ b/components/engine/container/state_test.go @@ -4,8 +4,6 @@ import ( "sync/atomic" "testing" "time" - - "github.com/docker/docker/daemon/execdriver" ) func TestStateRunStop(t *testing.T) { @@ -19,7 +17,7 @@ func TestStateRunStop(t *testing.T) { close(started) }() s.Lock() - s.SetRunning(i + 100) + s.SetRunning(i+100, false) s.Unlock() if !s.IsRunning() { @@ -52,7 +50,7 @@ func TestStateRunStop(t *testing.T) { atomic.StoreInt64(&exit, int64(exitCode)) close(stopped) }() - s.SetStoppedLocking(&execdriver.ExitStatus{ExitCode: i}) + s.SetStoppedLocking(&ExitStatus{ExitCode: i}) if s.IsRunning() { t.Fatal("State is running") } @@ -93,7 +91,7 @@ func TestStateTimeoutWait(t *testing.T) { } s.Lock() - s.SetRunning(49) + s.SetRunning(49, false) s.Unlock() stopped := make(chan struct{}) diff --git a/components/engine/container/state_unix.go b/components/engine/container/state_unix.go index 204b968b24..8d25a23790 100644 --- a/components/engine/container/state_unix.go +++ b/components/engine/container/state_unix.go @@ -2,11 +2,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) { +func (s *State) setFromExitStatus(exitStatus *ExitStatus) { s.ExitCode = exitStatus.ExitCode s.OOMKilled = exitStatus.OOMKilled } diff --git a/components/engine/container/state_windows.go b/components/engine/container/state_windows.go index 645c9348c3..02802a02a4 100644 --- a/components/engine/container/state_windows.go +++ b/components/engine/container/state_windows.go @@ -1,9 +1,7 @@ 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) { +func (s *State) setFromExitStatus(exitStatus *ExitStatus) { s.ExitCode = exitStatus.ExitCode } diff --git a/components/engine/daemon/README.md b/components/engine/daemon/README.md deleted file mode 100644 index 1778983fb3..0000000000 --- a/components/engine/daemon/README.md +++ /dev/null @@ -1,9 +0,0 @@ -This directory contains code pertaining to running containers and storing images - -Code pertaining to running containers: - - - execdriver - -Code pertaining to storing images: - - - graphdriver diff --git a/components/engine/daemon/apparmor_default.go b/components/engine/daemon/apparmor_default.go new file mode 100644 index 0000000000..e4065b4ad9 --- /dev/null +++ b/components/engine/daemon/apparmor_default.go @@ -0,0 +1,30 @@ +// +build linux + +package daemon + +import ( + "github.com/Sirupsen/logrus" + aaprofile "github.com/docker/docker/profiles/apparmor" + "github.com/opencontainers/runc/libcontainer/apparmor" +) + +// Define constants for native driver +const ( + defaultApparmorProfile = "docker-default" +) + +func installDefaultAppArmorProfile() { + 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 { + logrus.Errorf("AppArmor enabled on system but the %s profile could not be loaded.", policy) + } + } + } + } +} diff --git a/components/engine/daemon/apparmor_default_unsupported.go b/components/engine/daemon/apparmor_default_unsupported.go new file mode 100644 index 0000000000..f186a68af9 --- /dev/null +++ b/components/engine/daemon/apparmor_default_unsupported.go @@ -0,0 +1,6 @@ +// +build !linux + +package daemon + +func installDefaultAppArmorProfile() { +} diff --git a/components/engine/daemon/execdriver/utils_unix.go b/components/engine/daemon/caps/utils_unix.go similarity index 89% rename from components/engine/daemon/execdriver/utils_unix.go rename to components/engine/daemon/caps/utils_unix.go index e9f48e5c69..c99485f51d 100644 --- a/components/engine/daemon/execdriver/utils_unix.go +++ b/components/engine/daemon/caps/utils_unix.go @@ -1,6 +1,6 @@ // +build !windows -package execdriver +package caps import ( "fmt" @@ -24,7 +24,7 @@ func init() { } capabilityList = append(capabilityList, &CapabilityMapping{ - Key: strings.ToUpper(cap.String()), + Key: "CAP_" + strings.ToUpper(cap.String()), Value: cap, }, ) @@ -77,12 +77,16 @@ func TweakCapabilities(basics, adds, drops []string) ([]string, error) { allCaps = GetAllCapabilities() ) + // FIXME(tonistiigi): docker format is without CAP_ prefix, oci is with prefix + // Currently they are mixed in here. We should do conversion in one place. + // look for invalid cap in the drop list for _, cap := range drops { if strings.ToLower(cap) == "all" { continue } - if !stringutils.InSlice(allCaps, cap) { + + if !stringutils.InSlice(allCaps, "CAP_"+cap) { return nil, fmt.Errorf("Unknown capability drop: %q", cap) } } @@ -100,7 +104,7 @@ func TweakCapabilities(basics, adds, drops []string) ([]string, error) { } // if we don't drop `all`, add back all the non-dropped caps - if !stringutils.InSlice(drops, cap) { + if !stringutils.InSlice(drops, cap[4:]) { newCaps = append(newCaps, strings.ToUpper(cap)) } } @@ -112,6 +116,8 @@ func TweakCapabilities(basics, adds, drops []string) ([]string, error) { continue } + cap = "CAP_" + cap + if !stringutils.InSlice(allCaps, cap) { return nil, fmt.Errorf("Unknown capability to add: %q", cap) } diff --git a/components/engine/daemon/config.go b/components/engine/daemon/config.go index d37f6488f3..a1a5f48c53 100644 --- a/components/engine/daemon/config.go +++ b/components/engine/daemon/config.go @@ -115,7 +115,7 @@ func (config *Config) InstallCommonFlags(cmd *flag.FlagSet, usageFn func(string) 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.StringVar(&config.ExecRoot, []string{"-exec-root"}, defaultExecRoot, 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")) diff --git a/components/engine/daemon/config_unix.go b/components/engine/daemon/config_unix.go index 1047f00f1e..866923e74f 100644 --- a/components/engine/daemon/config_unix.go +++ b/components/engine/daemon/config_unix.go @@ -12,8 +12,9 @@ import ( ) var ( - defaultPidFile = "/var/run/docker.pid" - defaultGraph = "/var/lib/docker" + defaultPidFile = "/var/run/docker.pid" + defaultGraph = "/var/lib/docker" + defaultExecRoot = "/var/run/docker" ) // Config defines the configuration of a docker daemon. @@ -30,6 +31,7 @@ type Config struct { RemappedRoot string `json:"userns-remap,omitempty"` CgroupParent string `json:"cgroup-parent,omitempty"` Ulimits map[string]*units.Ulimit `json:"default-ulimits,omitempty"` + ContainerdAddr string `json:"containerd,omitempty"` } // bridgeConfig stores all the bridge driver specific @@ -80,6 +82,7 @@ func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) strin 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")) + cmd.StringVar(&config.ContainerdAddr, []string{"-containerd"}, "", usageFn("Path to containerD socket")) config.attachExperimentalFlags(cmd, usageFn) } diff --git a/components/engine/daemon/config_windows.go b/components/engine/daemon/config_windows.go index 81480ad80b..ca141b986c 100644 --- a/components/engine/daemon/config_windows.go +++ b/components/engine/daemon/config_windows.go @@ -7,8 +7,9 @@ import ( ) var ( - defaultPidFile = os.Getenv("programdata") + string(os.PathSeparator) + "docker.pid" - defaultGraph = os.Getenv("programdata") + string(os.PathSeparator) + "docker" + defaultPidFile = os.Getenv("programdata") + string(os.PathSeparator) + "docker.pid" + defaultGraph = os.Getenv("programdata") + string(os.PathSeparator) + "docker" + defaultExecRoot = defaultGraph ) // bridgeConfig stores all the bridge driver specific diff --git a/components/engine/daemon/container_operations.go b/components/engine/daemon/container_operations.go index 3df74032b8..45c6c2acd6 100644 --- a/components/engine/daemon/container_operations.go +++ b/components/engine/daemon/container_operations.go @@ -48,11 +48,10 @@ func (daemon *Daemon) buildSandboxOptions(container *container.Container, n libn sboxOptions = append(sboxOptions, libnetwork.OptionUseDefaultSandbox()) sboxOptions = append(sboxOptions, libnetwork.OptionOriginHostsPath("/etc/hosts")) sboxOptions = append(sboxOptions, libnetwork.OptionOriginResolvConfPath("/etc/resolv.conf")) - } else if daemon.execDriver.SupportsHooks() { - // OptionUseExternalKey is mandatory for userns support. - // But optional for non-userns support - sboxOptions = append(sboxOptions, libnetwork.OptionUseExternalKey()) } + // OptionUseExternalKey is mandatory for userns support. + // But optional for non-userns support + sboxOptions = append(sboxOptions, libnetwork.OptionUseExternalKey()) container.HostsPath, err = container.GetRootResourcePath("hosts") if err != nil { diff --git a/components/engine/daemon/container_operations_unix.go b/components/engine/daemon/container_operations_unix.go index 44454462c2..dd637f5a04 100644 --- a/components/engine/daemon/container_operations_unix.go +++ b/components/engine/daemon/container_operations_unix.go @@ -13,7 +13,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/container" - "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/daemon/links" "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/idtools" @@ -22,13 +21,16 @@ import ( "github.com/docker/docker/runconfig" containertypes "github.com/docker/engine-api/types/container" networktypes "github.com/docker/engine-api/types/network" - "github.com/docker/go-units" "github.com/docker/libnetwork" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/devices" "github.com/opencontainers/runc/libcontainer/label" + "github.com/opencontainers/specs/specs-go" ) +func u32Ptr(i int64) *uint32 { u := uint32(i); return &u } +func fmPtr(i int64) *os.FileMode { fm := os.FileMode(i); return &fm } + func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]string, error) { var env []string children := daemon.children(container) @@ -64,220 +66,6 @@ func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]s return env, nil } -func (daemon *Daemon) populateCommand(c *container.Container, env []string) error { - var en *execdriver.Network - if !c.Config.NetworkDisabled { - en = &execdriver.Network{} - if !daemon.execDriver.SupportsHooks() || c.HostConfig.NetworkMode.IsHost() { - en.NamespacePath = c.NetworkSettings.SandboxKey - } - - if c.HostConfig.NetworkMode.IsContainer() { - nc, err := daemon.getNetworkedContainer(c.ID, c.HostConfig.NetworkMode.ConnectedContainer()) - if err != nil { - return err - } - en.ContainerID = nc.ID - } - } - - ipc := &execdriver.Ipc{} - var err error - c.ShmPath, err = c.ShmResourcePath() - if err != nil { - return err - } - - if c.HostConfig.IpcMode.IsContainer() { - ic, err := daemon.getIpcContainer(c) - if err != nil { - return err - } - ipc.ContainerID = ic.ID - c.ShmPath = ic.ShmPath - } else { - ipc.HostIpc = c.HostConfig.IpcMode.IsHost() - if ipc.HostIpc { - if _, err := os.Stat("/dev/shm"); err != nil { - return fmt.Errorf("/dev/shm is not mounted, but must be for --ipc=host") - } - c.ShmPath = "/dev/shm" - } - } - - pid := &execdriver.Pid{} - pid.HostPid = c.HostConfig.PidMode.IsHost() - - uts := &execdriver.UTS{ - HostUTS: c.HostConfig.UTSMode.IsHost(), - } - - // Build lists of devices allowed and created within the container. - var userSpecifiedDevices []*configs.Device - for _, deviceMapping := range c.HostConfig.Devices { - devs, err := getDevicesFromPath(deviceMapping) - if err != nil { - return err - } - - userSpecifiedDevices = append(userSpecifiedDevices, devs...) - } - - allowedDevices := mergeDevices(configs.DefaultAllowedDevices, userSpecifiedDevices) - - autoCreatedDevices := mergeDevices(configs.DefaultAutoCreatedDevices, userSpecifiedDevices) - - var rlimits []*units.Rlimit - ulimits := c.HostConfig.Ulimits - - // Merge ulimits with daemon defaults - ulIdx := make(map[string]*units.Ulimit) - for _, ul := range ulimits { - ulIdx[ul.Name] = ul - } - for name, ul := range daemon.configStore.Ulimits { - if _, exists := ulIdx[name]; !exists { - ulimits = append(ulimits, ul) - } - } - - weightDevices, err := getBlkioWeightDevices(c.HostConfig) - if err != nil { - return err - } - - readBpsDevice, err := getBlkioReadBpsDevices(c.HostConfig) - if err != nil { - return err - } - - writeBpsDevice, err := getBlkioWriteBpsDevices(c.HostConfig) - if err != nil { - return err - } - - readIOpsDevice, err := getBlkioReadIOpsDevices(c.HostConfig) - if err != nil { - return err - } - - writeIOpsDevice, err := getBlkioWriteIOpsDevices(c.HostConfig) - if err != nil { - return err - } - - for _, limit := range ulimits { - rl, err := limit.GetRlimit() - if err != nil { - return err - } - rlimits = append(rlimits, rl) - } - - resources := &execdriver.Resources{ - CommonResources: execdriver.CommonResources{ - Memory: c.HostConfig.Memory, - MemoryReservation: c.HostConfig.MemoryReservation, - CPUShares: c.HostConfig.CPUShares, - BlkioWeight: c.HostConfig.BlkioWeight, - }, - MemorySwap: c.HostConfig.MemorySwap, - KernelMemory: c.HostConfig.KernelMemory, - CpusetCpus: c.HostConfig.CpusetCpus, - CpusetMems: c.HostConfig.CpusetMems, - CPUPeriod: c.HostConfig.CPUPeriod, - CPUQuota: c.HostConfig.CPUQuota, - Rlimits: rlimits, - BlkioWeightDevice: weightDevices, - BlkioThrottleReadBpsDevice: readBpsDevice, - BlkioThrottleWriteBpsDevice: writeBpsDevice, - BlkioThrottleReadIOpsDevice: readIOpsDevice, - BlkioThrottleWriteIOpsDevice: writeIOpsDevice, - PidsLimit: c.HostConfig.PidsLimit, - MemorySwappiness: -1, - } - - if c.HostConfig.OomKillDisable != nil { - resources.OomKillDisable = *c.HostConfig.OomKillDisable - } - if c.HostConfig.MemorySwappiness != nil { - resources.MemorySwappiness = *c.HostConfig.MemorySwappiness - } - - processConfig := execdriver.ProcessConfig{ - CommonProcessConfig: execdriver.CommonProcessConfig{ - Entrypoint: c.Path, - Arguments: c.Args, - Tty: c.Config.Tty, - }, - Privileged: c.HostConfig.Privileged, - User: c.Config.User, - } - - processConfig.SysProcAttr = &syscall.SysProcAttr{Setsid: true} - processConfig.Env = env - - remappedRoot := &execdriver.User{} - if c.HostConfig.UsernsMode.IsPrivate() { - rootUID, rootGID := daemon.GetRemappedUIDGID() - if rootUID != 0 { - remappedRoot.UID = rootUID - remappedRoot.GID = rootGID - } - } - - uidMap, gidMap := daemon.GetUIDGIDMaps() - - if !daemon.seccompEnabled { - if c.SeccompProfile != "" && c.SeccompProfile != "unconfined" { - return fmt.Errorf("Seccomp is not enabled in your kernel, cannot run a custom seccomp profile.") - } - logrus.Warn("Seccomp is not enabled in your kernel, running container without default profile.") - c.SeccompProfile = "unconfined" - } - - defaultCgroupParent := "/docker" - if daemon.configStore.CgroupParent != "" { - defaultCgroupParent = daemon.configStore.CgroupParent - } else if daemon.usingSystemd() { - defaultCgroupParent = "system.slice" - } - c.Command = &execdriver.Command{ - CommonCommand: execdriver.CommonCommand{ - ID: c.ID, - MountLabel: c.GetMountLabel(), - Network: en, - ProcessConfig: processConfig, - ProcessLabel: c.GetProcessLabel(), - Rootfs: c.BaseFS, - Resources: resources, - WorkingDir: c.Config.WorkingDir, - }, - AllowedDevices: allowedDevices, - AppArmorProfile: c.AppArmorProfile, - AutoCreatedDevices: autoCreatedDevices, - CapAdd: c.HostConfig.CapAdd, - CapDrop: c.HostConfig.CapDrop, - CgroupParent: defaultCgroupParent, - GIDMapping: gidMap, - GroupAdd: c.HostConfig.GroupAdd, - Ipc: ipc, - OomScoreAdj: c.HostConfig.OomScoreAdj, - Pid: pid, - ReadonlyRootfs: c.HostConfig.ReadonlyRootfs, - RemappedRoot: remappedRoot, - SeccompProfile: c.SeccompProfile, - UIDMapping: uidMap, - UTS: uts, - NoNewPrivileges: c.NoNewPrivileges, - } - if c.HostConfig.CgroupParent != "" { - c.Command.CgroupParent = c.HostConfig.CgroupParent - } - - return nil -} - // getSize returns the real size & virtual size of the container. func (daemon *Daemon) getSize(container *container.Container) (int64, int64) { var ( @@ -395,28 +183,49 @@ func (daemon *Daemon) getIpcContainer(container *container.Container) (*containe } func (daemon *Daemon) setupIpcDirs(c *container.Container) error { - rootUID, rootGID := daemon.GetRemappedUIDGID() - if !c.HasMountFor("/dev/shm") { - shmPath, err := c.ShmResourcePath() + var err error + + c.ShmPath, err = c.ShmResourcePath() + if err != nil { + return err + } + + if c.HostConfig.IpcMode.IsContainer() { + ic, err := daemon.getIpcContainer(c) if err != nil { return err } + c.ShmPath = ic.ShmPath + } else if c.HostConfig.IpcMode.IsHost() { + if _, err := os.Stat("/dev/shm"); err != nil { + return fmt.Errorf("/dev/shm is not mounted, but must be for --ipc=host") + } + c.ShmPath = "/dev/shm" + } else { + rootUID, rootGID := daemon.GetRemappedUIDGID() + if !c.HasMountFor("/dev/shm") { + shmPath, err := c.ShmResourcePath() + if err != nil { + return err + } - if err := idtools.MkdirAllAs(shmPath, 0700, rootUID, rootGID); err != nil { - return err + if err := idtools.MkdirAllAs(shmPath, 0700, rootUID, rootGID); err != nil { + return err + } + + shmSize := container.DefaultSHMSize + if c.HostConfig.ShmSize != 0 { + shmSize = c.HostConfig.ShmSize + } + shmproperty := "mode=1777,size=" + strconv.FormatInt(shmSize, 10) + if err := syscall.Mount("shm", shmPath, "tmpfs", uintptr(syscall.MS_NOEXEC|syscall.MS_NOSUID|syscall.MS_NODEV), label.FormatMountLabel(shmproperty, c.GetMountLabel())); err != nil { + return fmt.Errorf("mounting shm tmpfs: %s", err) + } + if err := os.Chown(shmPath, rootUID, rootGID); err != nil { + return err + } } - shmSize := container.DefaultSHMSize - if c.HostConfig.ShmSize != 0 { - shmSize = c.HostConfig.ShmSize - } - shmproperty := "mode=1777,size=" + strconv.FormatInt(shmSize, 10) - if err := syscall.Mount("shm", shmPath, "tmpfs", uintptr(syscall.MS_NOEXEC|syscall.MS_NOSUID|syscall.MS_NODEV), label.FormatMountLabel(shmproperty, c.GetMountLabel())); err != nil { - return fmt.Errorf("mounting shm tmpfs: %s", err) - } - if err := os.Chown(shmPath, rootUID, rootGID); err != nil { - return err - } } return nil @@ -474,7 +283,19 @@ func killProcessDirectly(container *container.Container) error { return nil } -func getDevicesFromPath(deviceMapping containertypes.DeviceMapping) (devs []*configs.Device, err error) { +func specDevice(d *configs.Device) specs.Device { + return specs.Device{ + Type: string(d.Type), + Path: d.Path, + Major: d.Major, + Minor: d.Minor, + FileMode: fmPtr(int64(d.FileMode)), + UID: u32Ptr(int64(d.Uid)), + GID: u32Ptr(int64(d.Gid)), + } +} + +func getDevicesFromPath(deviceMapping containertypes.DeviceMapping) (devs []specs.Device, err error) { resolvedPathOnHost := deviceMapping.PathOnHost // check if it is a symbolic link @@ -488,7 +309,7 @@ func getDevicesFromPath(deviceMapping containertypes.DeviceMapping) (devs []*con // if there was no error, return the device if err == nil { device.Path = deviceMapping.PathInContainer - return append(devs, device), nil + return append(devs, specDevice(device)), nil } // if the device is not a device node @@ -508,7 +329,7 @@ func getDevicesFromPath(deviceMapping containertypes.DeviceMapping) (devs []*con // add the device to userSpecified devices childDevice.Path = strings.Replace(dpath, resolvedPathOnHost, deviceMapping.PathInContainer, 1) - devs = append(devs, childDevice) + devs = append(devs, specDevice(childDevice)) return nil }) diff --git a/components/engine/daemon/container_operations_windows.go b/components/engine/daemon/container_operations_windows.go index 56e95abf25..701bfd8c90 100644 --- a/components/engine/daemon/container_operations_windows.go +++ b/components/engine/daemon/container_operations_windows.go @@ -4,14 +4,9 @@ package daemon import ( "fmt" - "strings" - - networktypes "github.com/docker/engine-api/types/network" "github.com/docker/docker/container" - "github.com/docker/docker/daemon/execdriver" - "github.com/docker/docker/daemon/execdriver/windows" - "github.com/docker/docker/layer" + networktypes "github.com/docker/engine-api/types/network" "github.com/docker/libnetwork" ) @@ -29,135 +24,6 @@ func (daemon *Daemon) DisconnectFromNetwork(container *container.Container, n li return fmt.Errorf("Windows does not support disconnecting a running container from a network") } -func (daemon *Daemon) populateCommand(c *container.Container, env []string) error { - en := &execdriver.Network{ - Interface: nil, - } - - var epList []string - - // Connect all the libnetwork allocated networks to the container - if c.NetworkSettings != nil { - for n := range c.NetworkSettings.Networks { - sn, err := daemon.FindNetwork(n) - if err != nil { - continue - } - - ep, err := c.GetEndpointInNetwork(sn) - if err != nil { - continue - } - - data, err := ep.DriverInfo() - if err != nil { - continue - } - if data["hnsid"] != nil { - epList = append(epList, data["hnsid"].(string)) - } - } - } - - if daemon.netController == 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.Iface, - 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 fmt.Errorf("invalid network mode: %s", 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 fmt.Errorf("Failed to graph.Get on ImageID %s - %s", 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 fmt.Errorf("Failed to get layer path from graphdriver %s for ImageID %s - %s", daemon.layerStore, img.RootFS.ChainID(), err) - } - // Reverse order, expecting parent most first - layerPaths = append([]string{path}, layerPaths...) - } - } - - m, err := c.RWLayer.Metadata() - if err != nil { - return fmt.Errorf("Failed to get layer metadata - %s", 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, - EpList: epList, - } - - return nil -} - // getSize returns real size & virtual size func (daemon *Daemon) getSize(container *container.Container) (int64, int64) { // TODO Windows diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go index 8fd8edcc37..a01a8b4d9b 100644 --- a/components/engine/daemon/daemon.go +++ b/components/engine/daemon/daemon.go @@ -20,13 +20,12 @@ import ( "time" "github.com/Sirupsen/logrus" + containerd "github.com/docker/containerd/api/grpc/types" "github.com/docker/docker/api" "github.com/docker/docker/builder" "github.com/docker/docker/container" "github.com/docker/docker/daemon/events" "github.com/docker/docker/daemon/exec" - "github.com/docker/docker/daemon/execdriver" - "github.com/docker/docker/daemon/execdriver/execdrivers" "github.com/docker/docker/errors" "github.com/docker/engine-api/types" containertypes "github.com/docker/engine-api/types/container" @@ -46,12 +45,12 @@ import ( "github.com/docker/docker/image" "github.com/docker/docker/image/tarexport" "github.com/docker/docker/layer" + "github.com/docker/docker/libcontainerd" "github.com/docker/docker/migrate/v1" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/graphdb" "github.com/docker/docker/pkg/idtools" - "github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/namesgenerator" "github.com/docker/docker/pkg/progress" "github.com/docker/docker/pkg/registrar" @@ -115,7 +114,6 @@ type Daemon struct { trustKey libtrust.PrivateKey idIndex *truncindex.TruncIndex configStore *Config - execDriver execdriver.Driver statsCollector *statsCollector defaultLogConfig containertypes.LogConfig RegistryService *registry.Service @@ -132,6 +130,8 @@ type Daemon struct { imageStore image.Store nameIndex *registrar.Registrar linkIndex *linkIndex + containerd libcontainerd.Client + defaultIsolation containertypes.Isolation // Default isolation mode on Windows } // GetContainer looks for a container using the provided information, which could be @@ -220,36 +220,16 @@ func (daemon *Daemon) registerName(container *container.Container) error { } // Register makes a container object usable by the daemon as -func (daemon *Daemon) Register(container *container.Container) error { +func (daemon *Daemon) Register(c *container.Container) error { // Attach to stdout and stderr - if container.Config.OpenStdin { - container.NewInputPipes() + if c.Config.OpenStdin { + c.NewInputPipes() } else { - container.NewNopInputPipe() + c.NewNopInputPipe() } - daemon.containers.Add(container.ID, container) - daemon.idIndex.Add(container.ID) - - if container.IsRunning() { - logrus.Debugf("killing old running container %s", container.ID) - // Set exit code to 128 + SIGKILL (9) to properly represent unsuccessful exit - container.SetStoppedLocking(&execdriver.ExitStatus{ExitCode: 137}) - // use the current driver and ensure that the container is dead x.x - cmd := &execdriver.Command{ - CommonCommand: execdriver.CommonCommand{ - ID: container.ID, - }, - } - daemon.execDriver.Terminate(cmd) - - container.UnmountIpcMounts(mount.Unmount) - - daemon.Unmount(container) - if err := container.ToDiskLocking(); err != nil { - logrus.Errorf("Error saving stopped state to disk: %v", err) - } - } + daemon.containers.Add(c.ID, c) + daemon.idIndex.Add(c.ID) return nil } @@ -307,17 +287,38 @@ func (daemon *Daemon) restore() error { logrus.Errorf("Failed to register container %s: %s", c.ID, err) continue } - - // get list of containers we need to restart - if daemon.configStore.AutoRestart && c.ShouldRestart() { - restartContainers[c] = make(chan struct{}) - } - - // if c.hostConfig.Links is nil (not just empty), then it is using the old sqlite links and needs to be migrated - if c.HostConfig != nil && c.HostConfig.Links == nil { - migrateLegacyLinks = true - } } + var wg sync.WaitGroup + var mapLock sync.Mutex + for _, c := range containers { + wg.Add(1) + go func(c *container.Container) { + defer wg.Done() + if c.IsRunning() || c.IsPaused() { + if err := daemon.containerd.Restore(c.ID, libcontainerd.WithRestartManager(c.RestartManager(true))); err != nil { + logrus.Errorf("Failed to restore with containerd: %q", err) + return + } + } + // fixme: only if not running + // get list of containers we need to restart + if daemon.configStore.AutoRestart && !c.IsRunning() && !c.IsPaused() && c.ShouldRestart() { + mapLock.Lock() + restartContainers[c] = make(chan struct{}) + mapLock.Unlock() + } else if !c.IsRunning() && !c.IsPaused() { + if mountid, err := daemon.layerStore.GetMountID(c.ID); err == nil { + daemon.cleanupMountsByID(mountid) + } + } + + // if c.hostConfig.Links is nil (not just empty), then it is using the old sqlite links and needs to be migrated + if c.HostConfig != nil && c.HostConfig.Links == nil { + migrateLegacyLinks = true + } + }(c) + } + wg.Wait() // migrate any legacy links from sqlite linkdbFile := filepath.Join(daemon.root, "linkgraph.db") @@ -599,7 +600,7 @@ func (daemon *Daemon) registerLink(parent, child *container.Container, alias str // NewDaemon sets up everything for the daemon to be able to service // requests from the webserver. -func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemon, err error) { +func NewDaemon(config *Config, registryService *registry.Service, containerdRemote libcontainerd.Remote) (daemon *Daemon, err error) { setDefaultMtu(config) // Ensure we have compatible and valid configuration options @@ -659,7 +660,7 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo } os.Setenv("TMPDIR", realTmp) - d := &Daemon{} + d := &Daemon{configStore: config} // Ensure the daemon is properly shutdown if there is a failure during // initialization defer func() { @@ -670,6 +671,11 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo } }() + // Set the default isolation mode (only applicable on Windows) + if err := d.setDefaultIsolation(); err != nil { + return nil, fmt.Errorf("error setting default isolation mode: %v", err) + } + // Verify logging driver type if config.LogConfig.Type != "none" { if _, err := logger.GetLogDriver(config.LogConfig.Type); err != nil { @@ -682,6 +688,7 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo logrus.Warnf("Failed to configure golang's threads limit: %v", err) } + installDefaultAppArmorProfile() daemonRepo := filepath.Join(config.Root, "containers") if err := idtools.MkdirAllAs(daemonRepo, 0700, rootUID, rootGID); err != nil && !os.IsExist(err) { return nil, err @@ -781,11 +788,6 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo return nil, fmt.Errorf("Devices cgroup isn't mounted") } - ed, err := execdrivers.NewDriver(config.ExecOptions, config.ExecRoot, config.Root, sysInfo) - if err != nil { - return nil, err - } - d.ID = trustKey.PublicKey().KeyID() d.repository = daemonRepo d.containers = container.NewMemoryStore() @@ -794,8 +796,6 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo d.distributionMetadataStore = distributionMetadataStore d.trustKey = trustKey d.idIndex = truncindex.NewTruncIndex([]string{}) - d.configStore = config - d.execDriver = ed d.statsCollector = d.newStatsCollector(1 * time.Second) d.defaultLogConfig = containertypes.LogConfig{ Type: config.LogConfig.Type, @@ -812,10 +812,12 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo d.nameIndex = registrar.NewRegistrar() d.linkIndex = newLinkIndex() - if err := d.cleanupMounts(); err != nil { + go d.execCommandGC() + + d.containerd, err = containerdRemote.Client(d) + if err != nil { return nil, err } - go d.execCommandGC() if err := d.restore(); err != nil { return nil, err @@ -877,6 +879,9 @@ func (daemon *Daemon) Shutdown() error { logrus.Errorf("Stop container error: %v", err) return } + if mountid, err := daemon.layerStore.GetMountID(c.ID); err == nil { + daemon.cleanupMountsByID(mountid) + } logrus.Debugf("container stopped %s", c.ID) }) } @@ -923,29 +928,16 @@ func (daemon *Daemon) Mount(container *container.Container) error { } // Unmount unsets the container base filesystem -func (daemon *Daemon) Unmount(container *container.Container) { +func (daemon *Daemon) Unmount(container *container.Container) error { if err := container.RWLayer.Unmount(); err != nil { logrus.Errorf("Error unmounting container %s: %s", container.ID, err) + return err } -} - -// Run uses the execution driver to run a given container -func (daemon *Daemon) Run(c *container.Container, pipes *execdriver.Pipes, startCallback execdriver.DriverCallback) (execdriver.ExitStatus, error) { - hooks := execdriver.Hooks{ - Start: startCallback, - } - hooks.PreStart = append(hooks.PreStart, func(processConfig *execdriver.ProcessConfig, pid int, chOOM <-chan struct{}) error { - return daemon.setNetworkNamespaceKey(c.ID, pid) - }) - return daemon.execDriver.Run(c.Command, pipes, hooks) + return nil } func (daemon *Daemon) kill(c *container.Container, sig int) error { - return daemon.execDriver.Kill(c.Command, sig) -} - -func (daemon *Daemon) stats(c *container.Container) (*execdriver.ResourceStats, error) { - return daemon.execDriver.Stats(c.ID) + return daemon.containerd.Signal(c.ID, sig) } func (daemon *Daemon) subscribeToContainerStats(c *container.Container) chan interface{} { @@ -1322,12 +1314,6 @@ func (daemon *Daemon) GraphDriverName() string { return daemon.layerStore.DriverName() } -// ExecutionDriver returns the currently used driver for creating and -// starting execs in a container. -func (daemon *Daemon) ExecutionDriver() execdriver.Driver { - return daemon.execDriver -} - // GetUIDGIDMaps returns the current daemon's user namespace settings // for the full uid and gid maps which will be applied to containers // started in this instance. @@ -1536,7 +1522,7 @@ func (daemon *Daemon) IsShuttingDown() bool { } // GetContainerStats collects all the stats published by a container -func (daemon *Daemon) GetContainerStats(container *container.Container) (*execdriver.ResourceStats, error) { +func (daemon *Daemon) GetContainerStats(container *container.Container) (*types.StatsJSON, error) { stats, err := daemon.stats(container) if err != nil { return nil, err @@ -1547,7 +1533,22 @@ func (daemon *Daemon) GetContainerStats(container *container.Container) (*execdr if nwStats, err = daemon.getNetworkStats(container); err != nil { return nil, err } - stats.Interfaces = nwStats + + stats.Networks = make(map[string]types.NetworkStats) + for _, iface := range nwStats { + // For API Version >= 1.21, the original data of network will + // be returned. + stats.Networks[iface.Name] = types.NetworkStats{ + RxBytes: iface.RxBytes, + RxPackets: iface.RxPackets, + RxErrors: iface.RxErrors, + RxDropped: iface.RxDropped, + TxBytes: iface.TxBytes, + TxPackets: iface.TxPackets, + TxErrors: iface.TxErrors, + TxDropped: iface.TxDropped, + } + } return stats, nil } @@ -1735,3 +1736,16 @@ func (daemon *Daemon) networkOptions(dconfig *Config) ([]nwconfig.Option, error) options = append(options, driverOptions(dconfig)...) return options, nil } + +func copyBlkioEntry(entries []*containerd.BlkioStatsEntry) []types.BlkioStatEntry { + out := make([]types.BlkioStatEntry, len(entries)) + for i, re := range entries { + out[i] = types.BlkioStatEntry{ + Major: re.Major, + Minor: re.Minor, + Op: re.Op, + Value: re.Value, + } + } + return out +} diff --git a/components/engine/daemon/daemon_linux.go b/components/engine/daemon/daemon_linux.go index 22973069ca..deb3291155 100644 --- a/components/engine/daemon/daemon_linux.go +++ b/components/engine/daemon/daemon_linux.go @@ -12,6 +12,64 @@ import ( "github.com/docker/docker/pkg/mount" ) +func (daemon *Daemon) cleanupMountsByID(id string) error { + logrus.Debugf("Cleaning up old mountid %s: start.", id) + f, err := os.Open("/proc/self/mountinfo") + if err != nil { + return err + } + defer f.Close() + + return daemon.cleanupMountsFromReaderByID(f, id, mount.Unmount) +} + +func (daemon *Daemon) cleanupMountsFromReaderByID(reader io.Reader, id string, unmount func(target string) error) error { + if daemon.root == "" { + return nil + } + var errors []string + mountRoot := "" + shmSuffix := "/" + id + "/shm" + mergedSuffix := "/" + id + "/merged" + sc := bufio.NewScanner(reader) + for sc.Scan() { + line := sc.Text() + fields := strings.Fields(line) + if strings.HasPrefix(fields[4], daemon.root) { + logrus.Debugf("Mount base: %v", fields[4]) + mnt := fields[4] + if strings.HasSuffix(mnt, shmSuffix) || strings.HasSuffix(mnt, mergedSuffix) { + logrus.Debugf("Unmounting %v", mnt) + if err := unmount(mnt); err != nil { + logrus.Error(err) + errors = append(errors, err.Error()) + } + } else if mountBase := filepath.Base(mnt); mountBase == id { + mountRoot = mnt + } + } + } + + if mountRoot != "" { + logrus.Debugf("Unmounting %v", mountRoot) + if err := unmount(mountRoot); 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 container shm/mqueue/rootfs mounts: done.") + return nil +} + // cleanupMounts umounts shm/mqueue mounts for old containers func (daemon *Daemon) cleanupMounts() error { logrus.Debugf("Cleaning up old container shm/mqueue/rootfs mounts: start.") @@ -25,7 +83,7 @@ func (daemon *Daemon) cleanupMounts() error { } func (daemon *Daemon) cleanupMountsFromReader(reader io.Reader, unmount func(target string) error) error { - if daemon.repository == "" { + if daemon.root == "" { return nil } sc := bufio.NewScanner(reader) @@ -37,7 +95,7 @@ func (daemon *Daemon) cleanupMountsFromReader(reader io.Reader, unmount func(tar logrus.Debugf("Mount base: %v", fields[4]) mnt := fields[4] mountBase := filepath.Base(mnt) - if mountBase == "mqueue" || mountBase == "shm" || mountBase == "merged" { + if mountBase == "shm" || mountBase == "merged" { logrus.Debugf("Unmounting %v", mnt) if err := unmount(mnt); err != nil { logrus.Error(err) diff --git a/components/engine/daemon/daemon_linux_test.go b/components/engine/daemon/daemon_linux_test.go index 0439d0bcd7..672d8fc72e 100644 --- a/components/engine/daemon/daemon_linux_test.go +++ b/components/engine/daemon/daemon_linux_test.go @@ -7,53 +7,83 @@ import ( "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/docker/docker/bundles/1.9.0-dev/test-integration-cli/d45526097/graph/containers/47903e2e67014246eba27607809d5f5c2437c3bf84c2986393448f84093cc40b/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw` +const mountsFixture = `142 78 0:38 / / rw,relatime - aufs none rw,si=573b861da0b3a05b,dio +143 142 0:60 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw +144 142 0:67 / /dev rw,nosuid - tmpfs tmpfs rw,mode=755 +145 144 0:78 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 +146 144 0:49 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw +147 142 0:84 / /sys rw,nosuid,nodev,noexec,relatime - sysfs sysfs rw +148 147 0:86 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755 +149 148 0:22 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpuset +150 148 0:25 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/cpu rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpu +151 148 0:27 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/cpuacct rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpuacct +152 148 0:28 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,memory +153 148 0:29 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,devices +154 148 0:30 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,freezer +155 148 0:31 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,blkio +156 148 0:32 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,perf_event +157 148 0:33 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,hugetlb +158 148 0:35 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime - cgroup systemd rw,name=systemd +159 142 8:4 /home/mlaventure/gopath /home/mlaventure/gopath rw,relatime - ext4 /dev/disk/by-uuid/d99e196c-1fc4-4b4f-bab9-9962b2b34e99 rw,errors=remount-ro,data=ordered +160 142 8:4 /var/lib/docker/volumes/9a428b651ee4c538130143cad8d87f603a4bf31b928afe7ff3ecd65480692b35/_data /var/lib/docker rw,relatime - ext4 /dev/disk/by-uuid/d99e196c-1fc4-4b4f-bab9-9962b2b34e99 rw,errors=remount-ro,data=ordered +164 142 8:4 /home/mlaventure/gopath/src/github.com/docker/docker /go/src/github.com/docker/docker rw,relatime - ext4 /dev/disk/by-uuid/d99e196c-1fc4-4b4f-bab9-9962b2b34e99 rw,errors=remount-ro,data=ordered +165 142 8:4 /var/lib/docker/containers/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a/resolv.conf /etc/resolv.conf rw,relatime - ext4 /dev/disk/by-uuid/d99e196c-1fc4-4b4f-bab9-9962b2b34e99 rw,errors=remount-ro,data=ordered +166 142 8:4 /var/lib/docker/containers/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a/hostname /etc/hostname rw,relatime - ext4 /dev/disk/by-uuid/d99e196c-1fc4-4b4f-bab9-9962b2b34e99 rw,errors=remount-ro,data=ordered +167 142 8:4 /var/lib/docker/containers/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a/hosts /etc/hosts rw,relatime - ext4 /dev/disk/by-uuid/d99e196c-1fc4-4b4f-bab9-9962b2b34e99 rw,errors=remount-ro,data=ordered +168 144 0:39 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k +169 144 0:12 /14 /dev/console rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=000 +83 147 0:10 / /sys/kernel/security rw,relatime - securityfs none rw +89 142 0:87 / /tmp rw,relatime - tmpfs none rw +97 142 0:60 / /run/docker/netns/default rw,nosuid,nodev,noexec,relatime - proc proc rw +100 160 8:4 /var/lib/docker/volumes/9a428b651ee4c538130143cad8d87f603a4bf31b928afe7ff3ecd65480692b35/_data/aufs /var/lib/docker/aufs rw,relatime - ext4 /dev/disk/by-uuid/d99e196c-1fc4-4b4f-bab9-9962b2b34e99 rw,errors=remount-ro,data=ordered +115 100 0:102 / /var/lib/docker/aufs/mnt/0ecda1c63e5b58b3d89ff380bf646c95cc980252cf0b52466d43619aec7c8432 rw,relatime - aufs none rw,si=573b861dbc01905b,dio +116 160 0:107 / /var/lib/docker/containers/d045dc441d2e2e1d5b3e328d47e5943811a40819fb47497c5f5a5df2d6d13c37/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k +118 142 0:102 / /run/docker/libcontainerd/d045dc441d2e2e1d5b3e328d47e5943811a40819fb47497c5f5a5df2d6d13c37/rootfs rw,relatime - aufs none rw,si=573b861dbc01905b,dio +242 142 0:60 / /run/docker/netns/c3664df2a0f7 rw,nosuid,nodev,noexec,relatime - proc proc rw +120 100 0:122 / /var/lib/docker/aufs/mnt/03ca4b49e71f1e49a41108829f4d5c70ac95934526e2af8984a1f65f1de0715d rw,relatime - aufs none rw,si=573b861eb147805b,dio +171 142 0:122 / /run/docker/libcontainerd/e406ff6f3e18516d50e03dbca4de54767a69a403a6f7ec1edc2762812824521e/rootfs rw,relatime - aufs none rw,si=573b861eb147805b,dio +310 142 0:60 / /run/docker/netns/71a18572176b rw,nosuid,nodev,noexec,relatime - proc proc rw +` +func TestCleanupMounts(t *testing.T) { d := &Daemon{ - repository: "/go/src/github.com/docker/docker/bundles/1.9.0-dev/test-integration-cli/d45526097/graph/containers/", + root: "/var/lib/docker/", } - expected := "/go/src/github.com/docker/docker/bundles/1.9.0-dev/test-integration-cli/d45526097/graph/containers/47903e2e67014246eba27607809d5f5c2437c3bf84c2986393448f84093cc40b/mqueue" - var unmounted bool + expected := "/var/lib/docker/containers/d045dc441d2e2e1d5b3e328d47e5943811a40819fb47497c5f5a5df2d6d13c37/shm" + var unmounted int unmount := func(target string) error { if target == expected { - unmounted = true + unmounted++ } return nil } - d.cleanupMountsFromReader(strings.NewReader(fixture), unmount) + d.cleanupMountsFromReader(strings.NewReader(mountsFixture), unmount) - if !unmounted { - t.Fatalf("Expected to unmount the mqueue") + if unmounted != 1 { + t.Fatalf("Expected to unmount the shm (and the shm only)") + } +} + +func TestCleanupMountsByID(t *testing.T) { + d := &Daemon{ + root: "/var/lib/docker/", + } + + expected := "/var/lib/docker/aufs/mnt/03ca4b49e71f1e49a41108829f4d5c70ac95934526e2af8984a1f65f1de0715d" + var unmounted int + unmount := func(target string) error { + if target == expected { + unmounted++ + } + return nil + } + + d.cleanupMountsFromReaderByID(strings.NewReader(mountsFixture), "03ca4b49e71f1e49a41108829f4d5c70ac95934526e2af8984a1f65f1de0715d", unmount) + + if unmounted != 1 { + t.Fatalf("Expected to unmount the auf root (and that only)") } } diff --git a/components/engine/daemon/daemon_unix.go b/components/engine/daemon/daemon_unix.go index 77fe6dec3a..84b52a6c46 100644 --- a/components/engine/daemon/daemon_unix.go +++ b/components/engine/daemon/daemon_unix.go @@ -13,6 +13,7 @@ import ( "strconv" "strings" "syscall" + "time" "github.com/Sirupsen/logrus" "github.com/docker/docker/container" @@ -25,6 +26,7 @@ import ( "github.com/docker/docker/reference" "github.com/docker/docker/runconfig" runconfigopts "github.com/docker/docker/runconfig/opts" + "github.com/docker/engine-api/types" pblkiodev "github.com/docker/engine-api/types/blkiodev" containertypes "github.com/docker/engine-api/types/container" "github.com/docker/libnetwork" @@ -33,10 +35,10 @@ import ( "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" + lntypes "github.com/docker/libnetwork/types" "github.com/opencontainers/runc/libcontainer/label" "github.com/opencontainers/runc/libcontainer/user" + "github.com/opencontainers/specs/specs-go" ) const ( @@ -51,16 +53,81 @@ const ( defaultRemappedID string = "dockremap" ) -func getBlkioWeightDevices(config *containertypes.HostConfig) ([]*blkiodev.WeightDevice, error) { +func getMemoryResources(config containertypes.Resources) *specs.Memory { + memory := specs.Memory{} + + if config.Memory > 0 { + limit := uint64(config.Memory) + memory.Limit = &limit + } + + if config.MemoryReservation > 0 { + reservation := uint64(config.MemoryReservation) + memory.Reservation = &reservation + } + + if config.MemorySwap != 0 { + swap := uint64(config.MemorySwap) + memory.Swap = &swap + } + + if config.MemorySwappiness != nil { + swappiness := uint64(*config.MemorySwappiness) + memory.Swappiness = &swappiness + } + + if config.KernelMemory != 0 { + kernelMemory := uint64(config.KernelMemory) + memory.Kernel = &kernelMemory + } + + return &memory +} + +func getCPUResources(config containertypes.Resources) *specs.CPU { + cpu := specs.CPU{} + + if config.CPUShares != 0 { + shares := uint64(config.CPUShares) + cpu.Shares = &shares + } + + if config.CpusetCpus != "" { + cpuset := config.CpusetCpus + cpu.Cpus = &cpuset + } + + if config.CpusetMems != "" { + cpuset := config.CpusetMems + cpu.Mems = &cpuset + } + + if config.CPUPeriod != 0 { + period := uint64(config.CPUPeriod) + cpu.Period = &period + } + + if config.CPUQuota != 0 { + quota := uint64(config.CPUQuota) + cpu.Quota = "a + } + + return &cpu +} + +func getBlkioWeightDevices(config containertypes.Resources) ([]specs.WeightDevice, error) { var stat syscall.Stat_t - var blkioWeightDevices []*blkiodev.WeightDevice + var blkioWeightDevices []specs.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) + weight := weightDevice.Weight + d := specs.WeightDevice{Weight: &weight} + d.Major = int64(stat.Rdev / 256) + d.Major = int64(stat.Rdev % 256) + blkioWeightDevices = append(blkioWeightDevices, d) } return blkioWeightDevices, nil @@ -105,61 +172,73 @@ func parseSecurityOpt(container *container.Container, config *containertypes.Hos return err } -func getBlkioReadIOpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) { - var blkioReadIOpsDevice []*blkiodev.ThrottleDevice +func getBlkioReadIOpsDevices(config containertypes.Resources) ([]specs.ThrottleDevice, error) { + var blkioReadIOpsDevice []specs.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) + rate := iopsDevice.Rate + d := specs.ThrottleDevice{Rate: &rate} + d.Major = int64(stat.Rdev / 256) + d.Major = int64(stat.Rdev % 256) + blkioReadIOpsDevice = append(blkioReadIOpsDevice, d) } return blkioReadIOpsDevice, nil } -func getBlkioWriteIOpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) { - var blkioWriteIOpsDevice []*blkiodev.ThrottleDevice +func getBlkioWriteIOpsDevices(config containertypes.Resources) ([]specs.ThrottleDevice, error) { + var blkioWriteIOpsDevice []specs.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) + rate := iopsDevice.Rate + d := specs.ThrottleDevice{Rate: &rate} + d.Major = int64(stat.Rdev / 256) + d.Major = int64(stat.Rdev % 256) + blkioWriteIOpsDevice = append(blkioWriteIOpsDevice, d) } return blkioWriteIOpsDevice, nil } -func getBlkioReadBpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) { - var blkioReadBpsDevice []*blkiodev.ThrottleDevice +func getBlkioReadBpsDevices(config containertypes.Resources) ([]specs.ThrottleDevice, error) { + var blkioReadBpsDevice []specs.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) + rate := bpsDevice.Rate + d := specs.ThrottleDevice{Rate: &rate} + d.Major = int64(stat.Rdev / 256) + d.Major = int64(stat.Rdev % 256) + blkioReadBpsDevice = append(blkioReadBpsDevice, d) } return blkioReadBpsDevice, nil } -func getBlkioWriteBpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) { - var blkioWriteBpsDevice []*blkiodev.ThrottleDevice +func getBlkioWriteBpsDevices(config containertypes.Resources) ([]specs.ThrottleDevice, error) { + var blkioWriteBpsDevice []specs.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) + rate := bpsDevice.Rate + d := specs.ThrottleDevice{Rate: &rate} + d.Major = int64(stat.Rdev / 256) + d.Major = int64(stat.Rdev % 256) + blkioWriteBpsDevice = append(blkioWriteBpsDevice, d) } return blkioWriteBpsDevice, nil @@ -600,8 +679,8 @@ func initBridgeDriver(controller libnetwork.NetworkController, config *Config) e nw, nw6List, err := ipamutils.ElectInterfaceAddresses(bridgeName) if err == nil { - ipamV4Conf.PreferredPool = types.GetIPNetCanonical(nw).String() - hip, _ := types.GetHostPartIP(nw.IP, nw.Mask) + ipamV4Conf.PreferredPool = lntypes.GetIPNetCanonical(nw).String() + hip, _ := lntypes.GetHostPartIP(nw.IP, nw.Mask) if hip.IsGlobalUnicast() { ipamV4Conf.Gateway = nw.IP.String() } @@ -953,11 +1032,69 @@ func (daemon *Daemon) conditionalMountOnStart(container *container.Container) er // 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 (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) error { + return daemon.Unmount(container) } func restoreCustomImage(is image.Store, ls layer.Store, rs reference.Store) error { // Unix has no custom images to register return nil } + +func (daemon *Daemon) stats(c *container.Container) (*types.StatsJSON, error) { + if !c.IsRunning() { + return nil, errNotRunning{c.ID} + } + stats, err := daemon.containerd.Stats(c.ID) + if err != nil { + return nil, err + } + s := &types.StatsJSON{} + cgs := stats.CgroupStats + if cgs != nil { + s.BlkioStats = types.BlkioStats{ + IoServiceBytesRecursive: copyBlkioEntry(cgs.BlkioStats.IoServiceBytesRecursive), + IoServicedRecursive: copyBlkioEntry(cgs.BlkioStats.IoServicedRecursive), + IoQueuedRecursive: copyBlkioEntry(cgs.BlkioStats.IoQueuedRecursive), + IoServiceTimeRecursive: copyBlkioEntry(cgs.BlkioStats.IoServiceTimeRecursive), + IoWaitTimeRecursive: copyBlkioEntry(cgs.BlkioStats.IoWaitTimeRecursive), + IoMergedRecursive: copyBlkioEntry(cgs.BlkioStats.IoMergedRecursive), + IoTimeRecursive: copyBlkioEntry(cgs.BlkioStats.IoTimeRecursive), + SectorsRecursive: copyBlkioEntry(cgs.BlkioStats.SectorsRecursive), + } + cpu := cgs.CpuStats + s.CPUStats = types.CPUStats{ + CPUUsage: types.CPUUsage{ + TotalUsage: cpu.CpuUsage.TotalUsage, + PercpuUsage: cpu.CpuUsage.PercpuUsage, + UsageInKernelmode: cpu.CpuUsage.UsageInKernelmode, + UsageInUsermode: cpu.CpuUsage.UsageInUsermode, + }, + ThrottlingData: types.ThrottlingData{ + Periods: cpu.ThrottlingData.Periods, + ThrottledPeriods: cpu.ThrottlingData.ThrottledPeriods, + ThrottledTime: cpu.ThrottlingData.ThrottledTime, + }, + } + mem := cgs.MemoryStats.Usage + s.MemoryStats = types.MemoryStats{ + Usage: mem.Usage, + MaxUsage: mem.MaxUsage, + Stats: cgs.MemoryStats.Stats, + Failcnt: mem.Failcnt, + } + if cgs.PidsStats != nil { + s.PidsStats = types.PidsStats{ + Current: cgs.PidsStats.Current, + } + } + } + s.Read = time.Unix(int64(stats.Timestamp), 0) + return s, nil +} + +// setDefaultIsolation determine the default isolation mode for the +// daemon to run in. This is only applicable on Windows +func (daemon *Daemon) setDefaultIsolation() error { + return nil +} diff --git a/components/engine/daemon/daemon_windows.go b/components/engine/daemon/daemon_windows.go index 27f19be50d..8f1e8f337f 100644 --- a/components/engine/daemon/daemon_windows.go +++ b/components/engine/daemon/daemon_windows.go @@ -18,11 +18,13 @@ import ( "github.com/docker/docker/layer" "github.com/docker/docker/reference" "github.com/docker/docker/runconfig" - containertypes "github.com/docker/engine-api/types/container" // register the windows graph driver "github.com/docker/docker/daemon/graphdriver/windows" "github.com/docker/docker/pkg/idtools" + "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/system" + "github.com/docker/engine-api/types" + containertypes "github.com/docker/engine-api/types/container" "github.com/docker/libnetwork" nwconfig "github.com/docker/libnetwork/config" winlibnetwork "github.com/docker/libnetwork/drivers/windows" @@ -39,7 +41,7 @@ const ( windowsMaxCPUShares = 10000 ) -func getBlkioWeightDevices(config *containertypes.HostConfig) ([]*blkiodev.WeightDevice, error) { +func getBlkioWeightDevices(config *containertypes.HostConfig) ([]blkiodev.WeightDevice, error) { return nil, nil } @@ -47,19 +49,19 @@ func parseSecurityOpt(container *container.Container, config *containertypes.Hos return nil } -func getBlkioReadIOpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) { +func getBlkioReadIOpsDevices(config *containertypes.HostConfig) ([]blkiodev.ThrottleDevice, error) { return nil, nil } -func getBlkioWriteIOpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) { +func getBlkioWriteIOpsDevices(config *containertypes.HostConfig) ([]blkiodev.ThrottleDevice, error) { return nil, nil } -func getBlkioReadBpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) { +func getBlkioReadBpsDevices(config *containertypes.HostConfig) ([]blkiodev.ThrottleDevice, error) { return nil, nil } -func getBlkioWriteBpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) { +func getBlkioWriteBpsDevices(config *containertypes.HostConfig) ([]blkiodev.ThrottleDevice, error) { return nil, nil } @@ -287,6 +289,10 @@ func (daemon *Daemon) registerLinks(container *container.Container, hostConfig * return nil } +func (daemon *Daemon) cleanupMountsByID(in string) error { + return nil +} + func (daemon *Daemon) cleanupMounts() error { return nil } @@ -307,8 +313,19 @@ func setupDaemonRoot(config *Config, rootDir string, rootUID, rootGID int) error // conditionalMountOnStart is a platform specific helper function during the // container start to call mount. func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error { + + // Are we going to run as a Hyper-V container? + hv := false + if container.HostConfig.Isolation.IsDefault() { + // Container is set to use the default, so take the default from the daemon configuration + hv = daemon.defaultIsolation.IsHyperV() + } else { + // Container is requesting an isolation mode. Honour it. + hv = container.HostConfig.Isolation.IsHyperV() + } + // We do not mount if a Hyper-V container - if !container.HostConfig.Isolation.IsHyperV() { + if !hv { if err := daemon.Mount(container); err != nil { return err } @@ -318,11 +335,12 @@ func (daemon *Daemon) conditionalMountOnStart(container *container.Container) er // conditionalUnmountOnCleanup is a platform specific helper function called // during the cleanup of a container to unmount. -func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) { +func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) error { // We do not unmount if a Hyper-V container if !container.HostConfig.Isolation.IsHyperV() { - daemon.Unmount(container) + return daemon.Unmount(container) } + return nil } func restoreCustomImage(is image.Store, ls layer.Store, rs reference.Store) error { @@ -404,3 +422,35 @@ func restoreCustomImage(is image.Store, ls layer.Store, rs reference.Store) erro func driverOptions(config *Config) []nwconfig.Option { return []nwconfig.Option{} } + +func (daemon *Daemon) stats(c *container.Container) (*types.StatsJSON, error) { + return nil, nil +} + +// setDefaultIsolation determine the default isolation mode for the +// daemon to run in. This is only applicable on Windows +func (daemon *Daemon) setDefaultIsolation() error { + daemon.defaultIsolation = containertypes.Isolation("process") + for _, option := range daemon.configStore.ExecOptions { + key, val, err := parsers.ParseKeyValueOpt(option) + if err != nil { + return err + } + key = strings.ToLower(key) + switch key { + + case "isolation": + if !containertypes.Isolation(val).IsValid() { + return fmt.Errorf("Invalid exec-opt value for 'isolation':'%s'", val) + } + if containertypes.Isolation(val).IsHyperV() { + daemon.defaultIsolation = containertypes.Isolation("hyperv") + } + default: + return fmt.Errorf("Unrecognised exec-opt '%s'\n", key) + } + } + + logrus.Infof("Windows default isolation mode: %s", daemon.defaultIsolation) + return nil +} diff --git a/components/engine/daemon/delete.go b/components/engine/daemon/delete.go index 75af4c01a1..008eefaa88 100644 --- a/components/engine/daemon/delete.go +++ b/components/engine/daemon/delete.go @@ -129,9 +129,6 @@ func (daemon *Daemon) cleanupContainer(container *container.Container, forceRemo return fmt.Errorf("Driver %s failed to remove root filesystem %s: %s", daemon.GraphDriverName(), container.ID, err) } - if err = daemon.execDriver.Clean(container.ID); err != nil { - return fmt.Errorf("Unable to remove execdriver data for %s: %s", container.ID, err) - } return nil } diff --git a/components/engine/daemon/exec.go b/components/engine/daemon/exec.go index 2b5250520f..be06845c68 100644 --- a/components/engine/daemon/exec.go +++ b/components/engine/daemon/exec.go @@ -11,10 +11,9 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/container" "github.com/docker/docker/daemon/exec" - "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/errors" + "github.com/docker/docker/libcontainerd" "github.com/docker/docker/pkg/pools" - "github.com/docker/docker/pkg/promise" "github.com/docker/docker/pkg/term" "github.com/docker/engine-api/types" "github.com/docker/engine-api/types/strslice" @@ -106,33 +105,31 @@ func (d *Daemon) ContainerExecCreate(config *types.ExecConfig) (string, 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 + execConfig.Entrypoint = entrypoint + execConfig.Args = args + execConfig.Tty = config.Tty + execConfig.Privileged = config.Privileged + execConfig.User = config.User + if len(execConfig.User) == 0 { + execConfig.User = container.Config.User + } d.registerExecCommand(container, execConfig) - d.LogContainerEvent(container, "exec_create: "+execConfig.ProcessConfig.Entrypoint+" "+strings.Join(execConfig.ProcessConfig.Arguments, " ")) + d.LogContainerEvent(container, "exec_create: "+execConfig.Entrypoint+" "+strings.Join(execConfig.Args, " ")) 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 { +func (d *Daemon) ContainerExecStart(name string, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) (err error) { var ( cStdin io.ReadCloser cStdout, cStderr io.Writer @@ -155,11 +152,18 @@ func (d *Daemon) ContainerExecStart(name string, stdin io.ReadCloser, stdout io. return fmt.Errorf("Error: Exec command %s is already running", ec.ID) } ec.Running = true + defer func() { + if err != nil { + ec.Running = false + exitCode := 126 + ec.ExitCode = &exitCode + } + }() 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, " ")) + d.LogContainerEvent(c, "exec_start: "+ec.Entrypoint+" "+strings.Join(ec.Args, " ")) if ec.OpenStdin && stdin != nil { r, w := io.Pipe() @@ -183,56 +187,26 @@ func (d *Daemon) ContainerExecStart(name string, stdin io.ReadCloser, stdout io. ec.NewNopInputPipe() } - attachErr := container.AttachStreams(context.Background(), ec.StreamConfig, ec.OpenStdin, true, ec.ProcessConfig.Tty, cStdin, cStdout, cStderr, ec.DetachKeys) + p := libcontainerd.Process{ + Args: append([]string{ec.Entrypoint}, ec.Args...), + Terminal: ec.Tty, + } - 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 fmt.Errorf("attach failed with error: %v", err) - } + if err := execSetPlatformOpt(c, ec, &p); err != nil { return nil - case err := <-execErr: - if aErr := <-attachErr; aErr != nil && err == nil { - return fmt.Errorf("attach failed with error: %v", aErr) - } - if err == nil { - return nil - } - - // Maybe the container stopped while we were trying to exec - if !c.IsRunning() { - return fmt.Errorf("container stopped while running exec: %s", c.ID) - } - return fmt.Errorf("Cannot run exec command %s in container %s: %s", 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 + attachErr := container.AttachStreams(context.Background(), ec.StreamConfig, ec.OpenStdin, true, ec.Tty, cStdin, cStdout, cStderr, ec.DetachKeys) - return exitStatus, err + if err := d.containerd.AddProcess(c.ID, name, p); err != nil { + return err + } + + err = <-attachErr + if err != nil { + return fmt.Errorf("attach failed with error: %v", err) + } + return nil } // execCommandGC runs a ticker to clean up the daemon references @@ -270,52 +244,3 @@ func (d *Daemon) containerExecIds() map[string]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.WaitResize(); err != nil { - logrus.Errorf("Error waiting for resize: %v", err) - } - 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 -} diff --git a/components/engine/daemon/exec/exec.go b/components/engine/daemon/exec/exec.go index 7d10dd9f4e..bbeb1c16a6 100644 --- a/components/engine/daemon/exec/exec.go +++ b/components/engine/daemon/exec/exec.go @@ -1,11 +1,8 @@ package exec import ( - "fmt" "sync" - "time" - "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/runconfig" ) @@ -16,22 +13,20 @@ import ( 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{} - - // waitResize will be closed after Resize is finished. - waitResize chan struct{} + ID string + Running bool + ExitCode *int + OpenStdin bool + OpenStderr bool + OpenStdout bool + CanRemove bool + ContainerID string + DetachKeys []byte + Entrypoint string + Args []string + Tty bool + Privileged bool + User string } // NewConfig initializes the a new exec configuration @@ -39,8 +34,6 @@ func NewConfig() *Config { return &Config{ ID: stringid.GenerateNonCryptoID(), StreamConfig: runconfig.NewStreamConfig(), - waitStart: make(chan struct{}), - waitResize: make(chan struct{}), } } @@ -98,45 +91,3 @@ func (e *Store) List() []string { 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 -} - -// WaitResize waits until terminal resize finishes or time out. -func (c *Config) WaitResize() error { - select { - case <-c.waitResize: - case <-time.After(time.Second): - return fmt.Errorf("Terminal resize for exec %s time out.", c.ID) - } - return nil -} - -// Close closes the wait channel for the progress. -func (c *Config) Close() { - close(c.waitStart) -} - -// CloseResize closes the wait channel for resizing terminal. -func (c *Config) CloseResize() { - close(c.waitResize) -} - -// Resize changes the size of the terminal for the exec process. -func (c *Config) Resize(h, w int) error { - defer c.CloseResize() - select { - case <-c.waitStart: - case <-time.After(time.Second): - return fmt.Errorf("Exec %s is not running, so it can not be resized.", c.ID) - } - return c.ProcessConfig.Terminal.Resize(h, w) -} diff --git a/components/engine/daemon/exec_linux.go b/components/engine/daemon/exec_linux.go new file mode 100644 index 0000000000..a2c86b2868 --- /dev/null +++ b/components/engine/daemon/exec_linux.go @@ -0,0 +1,26 @@ +package daemon + +import ( + "github.com/docker/docker/container" + "github.com/docker/docker/daemon/caps" + "github.com/docker/docker/daemon/exec" + "github.com/docker/docker/libcontainerd" +) + +func execSetPlatformOpt(c *container.Container, ec *exec.Config, p *libcontainerd.Process) error { + if len(ec.User) > 0 { + uid, gid, additionalGids, err := getUser(c, ec.User) + if err != nil { + return err + } + p.User = &libcontainerd.User{ + UID: uid, + GID: gid, + AdditionalGids: additionalGids, + } + } + if ec.Privileged { + p.Capabilities = caps.GetAllCapabilities() + } + return nil +} diff --git a/components/engine/daemon/exec_unix.go b/components/engine/daemon/exec_unix.go deleted file mode 100644 index 754f73138a..0000000000 --- a/components/engine/daemon/exec_unix.go +++ /dev/null @@ -1,21 +0,0 @@ -// +build linux freebsd - -package daemon - -import ( - "github.com/docker/docker/container" - "github.com/docker/docker/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 -} diff --git a/components/engine/daemon/exec_windows.go b/components/engine/daemon/exec_windows.go index 09efa82a2e..be25d20007 100644 --- a/components/engine/daemon/exec_windows.go +++ b/components/engine/daemon/exec_windows.go @@ -2,11 +2,13 @@ package daemon import ( "github.com/docker/docker/container" - "github.com/docker/docker/daemon/execdriver" - "github.com/docker/engine-api/types" + "github.com/docker/docker/daemon/exec" + "github.com/docker/docker/libcontainerd" ) -// 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) { +func execSetPlatformOpt(c *container.Container, ec *exec.Config, p *libcontainerd.Process) error { + // Process arguments need to be escaped before sending to OCI. + // TODO (jstarks): escape the entrypoint too once the tests are fixed to not rely on this behavior + p.Args = append([]string{p.Args[0]}, escapeArgs(p.Args[1:])...) + return nil } diff --git a/components/engine/daemon/execdriver/driver.go b/components/engine/daemon/execdriver/driver.go deleted file mode 100644 index 1a626929a4..0000000000 --- a/components/engine/daemon/execdriver/driver.go +++ /dev/null @@ -1,133 +0,0 @@ -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. -} diff --git a/components/engine/daemon/execdriver/driver_unix.go b/components/engine/daemon/execdriver/driver_unix.go deleted file mode 100644 index 988737df19..0000000000 --- a/components/engine/daemon/execdriver/driver_unix.go +++ /dev/null @@ -1,323 +0,0 @@ -// +build !windows - -package execdriver - -import ( - "encoding/json" - "io/ioutil" - "os" - "path/filepath" - "strconv" - "strings" - "time" - - "github.com/docker/docker/daemon/execdriver/native/template" - "github.com/docker/docker/pkg/idtools" - "github.com/docker/docker/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"` - PidsLimit int64 `json:"pids_limit"` - 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"` - NoNewPrivileges bool `json:"no_new_privileges"` -} - -// 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 = filterDevices(c.AutoCreatedDevices, (c.RemappedRoot.UID != 0)) - 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 filterDevices(devices []*configs.Device, userNamespacesEnabled bool) []*configs.Device { - if !userNamespacesEnabled { - return devices - } - - filtered := []*configs.Device{} - // if we have user namespaces enabled, these devices will not be created - // because of the mknod limitation in the kernel for an unprivileged process. - // Rather, they will be bind-mounted, which will only work if they exist; - // check for existence and remove non-existent entries from the list - for _, device := range devices { - if _, err := os.Stat(device.Path); err == nil { - filtered = append(filtered, device) - } - } - return filtered -} - -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.PidsLimit = c.Resources.PidsLimit - 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//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 -} diff --git a/components/engine/daemon/execdriver/driver_windows.go b/components/engine/daemon/execdriver/driver_windows.go deleted file mode 100644 index 4b2505623f..0000000000 --- a/components/engine/daemon/execdriver/driver_windows.go +++ /dev/null @@ -1,66 +0,0 @@ -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"` // Optimization 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 technology 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 - EpList []string `json:"endpoints"` // List of network endpoints for HNS -} - -// ExitStatus provides exit reasons for a container. -type ExitStatus struct { - // The exit code with which the container exited. - ExitCode int -} diff --git a/components/engine/daemon/execdriver/execdrivers/execdrivers_freebsd.go b/components/engine/daemon/execdriver/execdrivers/execdrivers_freebsd.go deleted file mode 100644 index 6a65201081..0000000000 --- a/components/engine/daemon/execdriver/execdrivers/execdrivers_freebsd.go +++ /dev/null @@ -1,15 +0,0 @@ -// +build freebsd - -package execdrivers - -import ( - "fmt" - - "github.com/docker/docker/daemon/execdriver" - "github.com/docker/docker/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") -} diff --git a/components/engine/daemon/execdriver/execdrivers/execdrivers_linux.go b/components/engine/daemon/execdriver/execdrivers/execdrivers_linux.go deleted file mode 100644 index 1ba57f4173..0000000000 --- a/components/engine/daemon/execdriver/execdrivers/execdrivers_linux.go +++ /dev/null @@ -1,16 +0,0 @@ -// +build linux - -package execdrivers - -import ( - "path" - - "github.com/docker/docker/daemon/execdriver" - "github.com/docker/docker/daemon/execdriver/native" - "github.com/docker/docker/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) -} diff --git a/components/engine/daemon/execdriver/execdrivers/execdrivers_windows.go b/components/engine/daemon/execdriver/execdrivers/execdrivers_windows.go deleted file mode 100644 index bc4e64f623..0000000000 --- a/components/engine/daemon/execdriver/execdrivers/execdrivers_windows.go +++ /dev/null @@ -1,14 +0,0 @@ -// +build windows - -package execdrivers - -import ( - "github.com/docker/docker/daemon/execdriver" - "github.com/docker/docker/daemon/execdriver/windows" - "github.com/docker/docker/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) -} diff --git a/components/engine/daemon/execdriver/native/create.go b/components/engine/daemon/execdriver/native/create.go deleted file mode 100644 index d85898f673..0000000000 --- a/components/engine/daemon/execdriver/native/create.go +++ /dev/null @@ -1,514 +0,0 @@ -// +build linux,cgo - -package native - -import ( - "fmt" - "path/filepath" - "strings" - "syscall" - - "github.com/docker/docker/daemon/execdriver" - "github.com/docker/docker/pkg/mount" - "github.com/docker/docker/profiles/seccomp" - - "github.com/docker/docker/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, err = seccomp.GetDefaultProfile() - if err != nil { - return nil, err - } - } - } - // 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", "/dev/mqueue": - 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) - - container.NoNewPrivileges = c.NoNewPrivileges - 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 fmt.Errorf("Duplicate mount point '%s'", 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 -} diff --git a/components/engine/daemon/execdriver/native/driver.go b/components/engine/daemon/execdriver/native/driver.go deleted file mode 100644 index a765305013..0000000000 --- a/components/engine/daemon/execdriver/native/driver.go +++ /dev/null @@ -1,606 +0,0 @@ -// +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/docker/docker/daemon/execdriver" - "github.com/docker/docker/pkg/parsers" - "github.com/docker/docker/pkg/pools" - "github.com/docker/docker/pkg/reexec" - sysinfo "github.com/docker/docker/pkg/system" - "github.com/docker/docker/pkg/term" - aaprofile "github.com/docker/docker/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 -} - -// 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, - } - - wg := sync.WaitGroup{} - writers, err := setupPipes(container, &c.ProcessConfig, p, pipes, &wg) - if 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 - } - - if err := cont.Start(p); 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) - }() - - //close the write end of any opened pipes now that they are dup'ed into the container - for _, writer := range writers { - writer.Close() - } - // '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 - } - // wait for all IO goroutine copiers to finish - wg.Wait() - - 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 - } - if state.InitProcessPid == -1 { - return fmt.Errorf("avoid sending signal %d to container %s with pid -1", sig, c.ID) - } - 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, wg *sync.WaitGroup) (*TtyConsole, error) { - tty := &TtyConsole{ - console: console, - } - - if err := tty.AttachPipes(pipes, wg); 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, wg *sync.WaitGroup) error { - wg.Add(1) - go func() { - defer wg.Done() - 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, wg *sync.WaitGroup) ([]io.WriteCloser, error) { - - writers := []io.WriteCloser{} - - rootuid, err := container.HostUID() - if err != nil { - return writers, err - } - - if processConfig.Tty { - cons, err := p.NewConsole(rootuid) - if err != nil { - return writers, err - } - term, err := NewTtyConsole(cons, pipes, wg) - if err != nil { - return writers, err - } - processConfig.Terminal = term - return writers, 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 writers, err - } - if pipes.Stdin != nil { - go func() { - io.Copy(w, pipes.Stdin) - w.Close() - }() - p.Stdin = r - } - return writers, 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 []uintptr - - copyPipes := func(out io.Writer, in io.ReadCloser) { - defer wg.Done() - io.Copy(out, in) - in.Close() - } - - //setup stdout - r, w, err := os.Pipe() - if err != nil { - w.Close() - return writers, err - } - writers = append(writers, w) - fds = append(fds, r.Fd(), w.Fd()) - if pipes.Stdout != nil { - wg.Add(1) - go copyPipes(pipes.Stdout, r) - } - term.Closers = append(term.Closers, r) - p.Stdout = w - - //setup stderr - r, w, err = os.Pipe() - if err != nil { - w.Close() - return writers, err - } - writers = append(writers, w) - fds = append(fds, r.Fd(), w.Fd()) - if pipes.Stderr != nil { - wg.Add(1) - go copyPipes(pipes.Stderr, r) - } - term.Closers = append(term.Closers, r) - p.Stderr = w - - //setup stdin - r, w, err = os.Pipe() - if err != nil { - r.Close() - return writers, err - } - fds = append(fds, r.Fd(), 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(int(fd), rootuid, rootuid); err != nil { - return writers, fmt.Errorf("Failed to chown pipes fd: %v", err) - } - } - return writers, 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 -} diff --git a/components/engine/daemon/execdriver/native/driver_unsupported.go b/components/engine/daemon/execdriver/native/driver_unsupported.go deleted file mode 100644 index 0162f1bf77..0000000000 --- a/components/engine/daemon/execdriver/native/driver_unsupported.go +++ /dev/null @@ -1,14 +0,0 @@ -// +build !linux - -package native - -import ( - "fmt" - - "github.com/docker/docker/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") -} diff --git a/components/engine/daemon/execdriver/native/driver_unsupported_nocgo.go b/components/engine/daemon/execdriver/native/driver_unsupported_nocgo.go deleted file mode 100644 index bc18d1ba5b..0000000000 --- a/components/engine/daemon/execdriver/native/driver_unsupported_nocgo.go +++ /dev/null @@ -1,14 +0,0 @@ -// +build linux,!cgo - -package native - -import ( - "fmt" - - "github.com/docker/docker/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") -} diff --git a/components/engine/daemon/execdriver/native/exec.go b/components/engine/daemon/execdriver/native/exec.go deleted file mode 100644 index d62fe5f405..0000000000 --- a/components/engine/daemon/execdriver/native/exec.go +++ /dev/null @@ -1,96 +0,0 @@ -// +build linux - -package native - -import ( - "fmt" - "os" - "os/exec" - "strings" - "sync" - "syscall" - - "github.com/docker/docker/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() - wg := sync.WaitGroup{} - writers, err := setupPipes(&config, processConfig, p, pipes, &wg) - if err != nil { - return -1, err - } - - if err := active.Start(p); err != nil { - return -1, err - } - //close the write end of any opened pipes now that they are dup'ed into the container - for _, writer := range writers { - writer.Close() - } - - 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 - } - // wait for all IO goroutine copiers to finish - wg.Wait() - return utils.ExitStatus(ps.Sys().(syscall.WaitStatus)), nil -} diff --git a/components/engine/daemon/execdriver/native/init.go b/components/engine/daemon/execdriver/native/init.go deleted file mode 100644 index 015b1678c3..0000000000 --- a/components/engine/daemon/execdriver/native/init.go +++ /dev/null @@ -1,40 +0,0 @@ -// +build linux - -package native - -import ( - "fmt" - "os" - "runtime" - - "github.com/docker/docker/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") -} diff --git a/components/engine/daemon/execdriver/native/template/default_template_linux.go b/components/engine/daemon/execdriver/native/template/default_template_linux.go deleted file mode 100644 index 073bcac9a3..0000000000 --- a/components/engine/daemon/execdriver/native/template/default_template_linux.go +++ /dev/null @@ -1,106 +0,0 @@ -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: "mqueue", - Destination: "/dev/mqueue", - Device: "mqueue", - Flags: defaultMountFlags, - }, - { - 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 -} diff --git a/components/engine/daemon/execdriver/native/template/default_template_unsupported.go b/components/engine/daemon/execdriver/native/template/default_template_unsupported.go deleted file mode 100644 index b01ec1d70d..0000000000 --- a/components/engine/daemon/execdriver/native/template/default_template_unsupported.go +++ /dev/null @@ -1,3 +0,0 @@ -// +build !linux - -package template diff --git a/components/engine/daemon/execdriver/pipes.go b/components/engine/daemon/execdriver/pipes.go deleted file mode 100644 index fdddaee9b0..0000000000 --- a/components/engine/daemon/execdriver/pipes.go +++ /dev/null @@ -1,24 +0,0 @@ -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 -} diff --git a/components/engine/daemon/execdriver/termconsole.go b/components/engine/daemon/execdriver/termconsole.go deleted file mode 100644 index 888836020f..0000000000 --- a/components/engine/daemon/execdriver/termconsole.go +++ /dev/null @@ -1,55 +0,0 @@ -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 -} diff --git a/components/engine/daemon/execdriver/windows/clean.go b/components/engine/daemon/execdriver/windows/clean.go deleted file mode 100644 index 3a99cf86aa..0000000000 --- a/components/engine/daemon/execdriver/windows/clean.go +++ /dev/null @@ -1,8 +0,0 @@ -// +build windows - -package windows - -// Clean implements the exec driver Driver interface. -func (d *Driver) Clean(id string) error { - return nil -} diff --git a/components/engine/daemon/execdriver/windows/commandlinebuilder.go b/components/engine/daemon/execdriver/windows/commandlinebuilder.go deleted file mode 100644 index 3ce8f46fb3..0000000000 --- a/components/engine/daemon/execdriver/windows/commandlinebuilder.go +++ /dev/null @@ -1,36 +0,0 @@ -//+build windows - -package windows - -import ( - "errors" - "syscall" - - "github.com/Sirupsen/logrus" - "github.com/docker/docker/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 -} diff --git a/components/engine/daemon/execdriver/windows/exec.go b/components/engine/daemon/execdriver/windows/exec.go deleted file mode 100644 index c9129a8bbd..0000000000 --- a/components/engine/daemon/execdriver/windows/exec.go +++ /dev/null @@ -1,89 +0,0 @@ -// +build windows - -package windows - -import ( - "fmt" - "syscall" - - "github.com/Microsoft/hcsshim" - "github.com/Sirupsen/logrus" - "github.com/docker/docker/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 - ) - - 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, 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 herr, ok := err.(*hcsshim.HcsError); ok && herr.Err == hcsshim.WSAEINVAL { - 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, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid, hcsshim.TimeoutInfinite); err != nil { - if herr, ok := err.(*hcsshim.HcsError); ok && herr.Err == syscall.ERROR_BROKEN_PIPE { - logrus.Debugf("Exiting Run() after WaitForProcessInComputeSystem failed with recognised error %s", err) - return hcsshim.WaitErrExecFailed, nil - } - logrus.Warnf("WaitForProcessInComputeSystem failed (container may have been killed): %s", err) - return -1, err - } - - logrus.Debugln("Exiting Run()", c.ID) - return int(exitCode), nil -} diff --git a/components/engine/daemon/execdriver/windows/getpids.go b/components/engine/daemon/execdriver/windows/getpids.go deleted file mode 100644 index 5b1a9eb59a..0000000000 --- a/components/engine/daemon/execdriver/windows/getpids.go +++ /dev/null @@ -1,11 +0,0 @@ -// +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") -} diff --git a/components/engine/daemon/execdriver/windows/namedpipes.go b/components/engine/daemon/execdriver/windows/namedpipes.go deleted file mode 100644 index 14e744210b..0000000000 --- a/components/engine/daemon/execdriver/windows/namedpipes.go +++ /dev/null @@ -1,63 +0,0 @@ -// +build windows - -package windows - -import ( - "fmt" - "io" - - "github.com/Sirupsen/logrus" - "github.com/docker/docker/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") - } -} diff --git a/components/engine/daemon/execdriver/windows/pauseunpause.go b/components/engine/daemon/execdriver/windows/pauseunpause.go deleted file mode 100644 index c408669c3c..0000000000 --- a/components/engine/daemon/execdriver/windows/pauseunpause.go +++ /dev/null @@ -1,19 +0,0 @@ -// +build windows - -package windows - -import ( - "fmt" - - "github.com/docker/docker/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") -} diff --git a/components/engine/daemon/execdriver/windows/run.go b/components/engine/daemon/execdriver/windows/run.go deleted file mode 100644 index 8c13448b15..0000000000 --- a/components/engine/daemon/execdriver/windows/run.go +++ /dev/null @@ -1,366 +0,0 @@ -// +build windows - -package windows - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "strconv" - "strings" - "syscall" - "time" - - "github.com/Microsoft/hcsshim" - "github.com/Sirupsen/logrus" - "github.com/docker/docker/daemon/execdriver" -) - -// defaultContainerNAT is the default name of the container NAT device that is -// preconfigured on the server. -const defaultContainerNAT = "ContainerNAT" - -// Win32 error codes that are used for various workarounds -// These really should be ALL_CAPS to match golangs syscall library and standard -// Win32 error conventions, but golint insists on CamelCase. -const ( - CoEClassstring = syscall.Errno(0x800401F3) // Invalid class string - ErrorNoNetwork = syscall.Errno(1222) // The network is not present or not started - ErrorBadPathname = syscall.Errno(161) // The specified path is invalid - ErrorInvalidObject = syscall.Errno(0x800710D8) // The object identifier does not represent a valid object -) - -type layer struct { - ID string - Path 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 // Optimization 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 omitted 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 - EndpointList []string // List of endpoints to be attached to 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 - ) - - // Allocate Network only if there is no network interface - 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, - EndpointList: c.EpList, - } - - 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 := 5 - for i := 0; i < maxAttempts; i++ { - err = hcsshim.CreateComputeSystem(c.ID, configuration) - if err == nil { - break - } - - if !TP4RetryHack { - return execdriver.ExitStatus{ExitCode: -1}, err - } - - if herr, ok := err.(*hcsshim.HcsError); ok { - if herr.Err != syscall.ERROR_NOT_FOUND && // Element not found - herr.Err != syscall.ERROR_FILE_NOT_FOUND && // The system cannot find the file specified - herr.Err != ErrorNoNetwork && // The network is not present or not started - herr.Err != ErrorBadPathname && // The specified path is invalid - herr.Err != CoEClassstring && // Invalid class string - herr.Err != ErrorInvalidObject { // The object identifier does not represent a valid object - 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) - } - } - - // 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 err := hcsshim.TerminateComputeSystem(c.ID, hcsshim.TimeoutInfinite, "exec-run-defer"); err != nil { - logrus.Warnf("Ignoring error from TerminateComputeSystem %s", err) - } - } else { - logrus.Debugf("Shutting down container %s", c.ID) - if err := hcsshim.ShutdownComputeSystem(c.ID, hcsshim.TimeoutInfinite, "exec-run-defer"); err != nil { - if herr, ok := err.(*hcsshim.HcsError); !ok || - (herr.Err != hcsshim.ERROR_SHUTDOWN_IN_PROGRESS && - herr.Err != ErrorBadPathname && - herr.Err != syscall.ERROR_PATH_NOT_FOUND) { - logrus.Warnf("Ignoring error from ShutdownComputeSystem %s", 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) - } - - exitCode, err := hcsshim.WaitForProcessInComputeSystem(c.ID, pid, hcsshim.TimeoutInfinite) - if err != nil { - if herr, ok := err.(*hcsshim.HcsError); ok && herr.Err != syscall.ERROR_BROKEN_PIPE { - 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 -} diff --git a/components/engine/daemon/execdriver/windows/stats.go b/components/engine/daemon/execdriver/windows/stats.go deleted file mode 100644 index 25bd49eaed..0000000000 --- a/components/engine/daemon/execdriver/windows/stats.go +++ /dev/null @@ -1,14 +0,0 @@ -// +build windows - -package windows - -import ( - "fmt" - - "github.com/docker/docker/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") -} diff --git a/components/engine/daemon/execdriver/windows/stdconsole.go b/components/engine/daemon/execdriver/windows/stdconsole.go deleted file mode 100644 index fb1eda2a20..0000000000 --- a/components/engine/daemon/execdriver/windows/stdconsole.go +++ /dev/null @@ -1,24 +0,0 @@ -// +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 -} diff --git a/components/engine/daemon/execdriver/windows/terminatekill.go b/components/engine/daemon/execdriver/windows/terminatekill.go deleted file mode 100644 index d20b0e2b9d..0000000000 --- a/components/engine/daemon/execdriver/windows/terminatekill.go +++ /dev/null @@ -1,49 +0,0 @@ -// +build windows - -package windows - -import ( - "fmt" - "syscall" - - "github.com/Microsoft/hcsshim" - "github.com/Sirupsen/logrus" - "github.com/docker/docker/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 err := hcsshim.TerminateComputeSystem(id, hcsshim.TimeoutInfinite, context); err != nil { - logrus.Errorf("Failed to terminate %s - %q", id, 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 err := hcsshim.ShutdownComputeSystem(id, hcsshim.TimeoutInfinite, context); err != nil { - logrus.Errorf("Failed to shutdown %s - %q", id, err) - } - } - return err -} diff --git a/components/engine/daemon/execdriver/windows/ttyconsole.go b/components/engine/daemon/execdriver/windows/ttyconsole.go deleted file mode 100644 index 78642f742a..0000000000 --- a/components/engine/daemon/execdriver/windows/ttyconsole.go +++ /dev/null @@ -1,32 +0,0 @@ -// +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 -} diff --git a/components/engine/daemon/execdriver/windows/unsupported.go b/components/engine/daemon/execdriver/windows/unsupported.go deleted file mode 100644 index 89be21f9a9..0000000000 --- a/components/engine/daemon/execdriver/windows/unsupported.go +++ /dev/null @@ -1,14 +0,0 @@ -// +build !windows - -package windows - -import ( - "fmt" - - "github.com/docker/docker/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") -} diff --git a/components/engine/daemon/execdriver/windows/update.go b/components/engine/daemon/execdriver/windows/update.go deleted file mode 100644 index a4c42a6b08..0000000000 --- a/components/engine/daemon/execdriver/windows/update.go +++ /dev/null @@ -1,14 +0,0 @@ -// +build windows - -package windows - -import ( - "github.com/docker/docker/daemon/execdriver" -) - -// Update updates resource configs for a container. -func (d *Driver) Update(c *execdriver.Command) error { - // Updating resource isn't supported on Windows - // but we should return nil for enabling updating container - return nil -} diff --git a/components/engine/daemon/execdriver/windows/windows.go b/components/engine/daemon/execdriver/windows/windows.go deleted file mode 100644 index e6c8f10b91..0000000000 --- a/components/engine/daemon/execdriver/windows/windows.go +++ /dev/null @@ -1,123 +0,0 @@ -// +build windows - -package windows - -import ( - "fmt" - "strings" - "sync" - - "github.com/Microsoft/hcsshim" - "github.com/Sirupsen/logrus" - "github.com/docker/docker/daemon/execdriver" - "github.com/docker/docker/dockerversion" - "github.com/docker/docker/pkg/parsers" - "github.com/docker/engine-api/types/container" -) - -// 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 technology 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.Isolation = "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.Isolation(val).IsValid() { - return nil, fmt.Errorf("Unrecognised exec driver option 'isolation':'%s'", val) - } - if container.Isolation(val).IsHyperV() { - DefaultIsolation = "hyperv" - } - logrus.Infof("Windows default isolation: '%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. - // 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 = hcsshim.IsTP4() - - 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 -} diff --git a/components/engine/daemon/info.go b/components/engine/daemon/info.go index 057b6cf50f..062b96493d 100644 --- a/components/engine/daemon/info.go +++ b/components/engine/daemon/info.go @@ -84,7 +84,6 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) { NFd: fileutils.GetTotalUsedFds(), NGoroutines: runtime.NumGoroutine(), SystemTime: time.Now().Format(time.RFC3339Nano), - ExecutionDriver: daemon.ExecutionDriver().Name(), LoggingDriver: daemon.defaultLogConfig.Type, CgroupDriver: daemon.getCgroupDriver(), NEventsListener: daemon.EventsService.SubscribersCount(), diff --git a/components/engine/daemon/inspect_unix.go b/components/engine/daemon/inspect_unix.go index bb224c8796..6033c02dd7 100644 --- a/components/engine/daemon/inspect_unix.go +++ b/components/engine/daemon/inspect_unix.go @@ -82,10 +82,10 @@ func addMountPoints(container *container.Container) []types.MountPoint { func inspectExecProcessConfig(e *exec.Config) *backend.ExecProcessConfig { return &backend.ExecProcessConfig{ - Tty: e.ProcessConfig.Tty, - Entrypoint: e.ProcessConfig.Entrypoint, - Arguments: e.ProcessConfig.Arguments, - Privileged: &e.ProcessConfig.Privileged, - User: e.ProcessConfig.User, + Tty: e.Tty, + Entrypoint: e.Entrypoint, + Arguments: e.Args, + Privileged: &e.Privileged, + User: e.User, } } diff --git a/components/engine/daemon/inspect_windows.go b/components/engine/daemon/inspect_windows.go index f20571d052..22496e5b07 100644 --- a/components/engine/daemon/inspect_windows.go +++ b/components/engine/daemon/inspect_windows.go @@ -33,8 +33,8 @@ func (daemon *Daemon) containerInspectPre120(name string) (*types.ContainerJSON, func inspectExecProcessConfig(e *exec.Config) *backend.ExecProcessConfig { return &backend.ExecProcessConfig{ - Tty: e.ProcessConfig.Tty, - Entrypoint: e.ProcessConfig.Entrypoint, - Arguments: e.ProcessConfig.Arguments, + Tty: e.Tty, + Entrypoint: e.Entrypoint, + Arguments: e.Args, } } diff --git a/components/engine/daemon/kill.go b/components/engine/daemon/kill.go index ffed439ce0..69a9c5e67b 100644 --- a/components/engine/daemon/kill.go +++ b/components/engine/daemon/kill.go @@ -69,6 +69,10 @@ func (daemon *Daemon) killWithSignal(container *container.Container, sig int) er container.ExitOnNext() + if !daemon.IsShuttingDown() { + container.HasBeenManuallyStopped = true + } + // if the container is currently restarting we do not need to send the signal // to the process. Telling the monitor that it should exit on it's next event // loop is enough diff --git a/components/engine/daemon/monitor.go b/components/engine/daemon/monitor.go new file mode 100644 index 0000000000..0a82c5f8fd --- /dev/null +++ b/components/engine/daemon/monitor.go @@ -0,0 +1,143 @@ +package daemon + +import ( + "errors" + "fmt" + "io" + "runtime" + "strconv" + + "github.com/Sirupsen/logrus" + "github.com/docker/docker/libcontainerd" + "github.com/docker/docker/runconfig" +) + +// StateChanged updates daemon state changes from containerd +func (daemon *Daemon) StateChanged(id string, e libcontainerd.StateInfo) error { + c := daemon.containers.Get(id) + if c == nil { + return fmt.Errorf("no such container: %s", id) + } + + switch e.State { + case libcontainerd.StateOOM: + // StateOOM is Linux specific and should never be hit on Windows + if runtime.GOOS == "windows" { + return errors.New("Received StateOOM from libcontainerd on Windows. This should never happen.") + } + daemon.LogContainerEvent(c, "oom") + case libcontainerd.StateExit: + c.Lock() + defer c.Unlock() + c.Wait() + c.Reset(false) + c.SetStopped(platformConstructExitStatus(e)) + attributes := map[string]string{ + "exitCode": strconv.Itoa(int(e.ExitCode)), + } + daemon.LogContainerEventWithAttributes(c, "die", attributes) + daemon.Cleanup(c) + // 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 + return c.ToDisk() + case libcontainerd.StateRestart: + c.Lock() + defer c.Unlock() + c.Reset(false) + c.RestartCount++ + c.SetRestarting(platformConstructExitStatus(e)) + attributes := map[string]string{ + "exitCode": strconv.Itoa(int(e.ExitCode)), + } + daemon.LogContainerEventWithAttributes(c, "die", attributes) + return c.ToDisk() + case libcontainerd.StateExitProcess: + c.Lock() + defer c.Unlock() + if execConfig := c.ExecCommands.Get(e.ProcessID); execConfig != nil { + ec := int(e.ExitCode) + execConfig.ExitCode = &ec + execConfig.Running = false + execConfig.Wait() + if err := execConfig.CloseStreams(); err != nil { + logrus.Errorf("%s: %s", c.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. + c.ExecCommands.Delete(execConfig.ID) + } else { + logrus.Warnf("Ignoring StateExitProcess for %v but no exec command found", e) + } + case libcontainerd.StateStart, libcontainerd.StateRestore: + c.SetRunning(int(e.Pid), e.State == libcontainerd.StateStart) + c.HasBeenManuallyStopped = false + if err := c.ToDisk(); err != nil { + c.Reset(false) + return err + } + case libcontainerd.StatePause: + c.Paused = true + daemon.LogContainerEvent(c, "pause") + case libcontainerd.StateResume: + c.Paused = false + daemon.LogContainerEvent(c, "unpause") + } + + return nil +} + +// AttachStreams is called by libcontainerd to connect the stdio. +func (daemon *Daemon) AttachStreams(id string, iop libcontainerd.IOPipe) error { + var s *runconfig.StreamConfig + c := daemon.containers.Get(id) + if c == nil { + ec, err := daemon.getExecConfig(id) + if err != nil { + return fmt.Errorf("no such exec/container: %s", id) + } + s = ec.StreamConfig + } else { + s = c.StreamConfig + if err := daemon.StartLogging(c); err != nil { + c.Reset(false) + return err + } + } + + if stdin := s.Stdin(); stdin != nil { + if iop.Stdin != nil { + go func() { + io.Copy(iop.Stdin, stdin) + iop.Stdin.Close() + }() + } + } else { + if c != nil && !c.Config.Tty { + // tty is enabled, so dont close containerd's iopipe stdin. + if iop.Stdin != nil { + iop.Stdin.Close() + } + } + } + + copy := func(w io.Writer, r io.Reader) { + s.Add(1) + go func() { + if _, err := io.Copy(w, r); err != nil { + logrus.Errorf("%v stream copy error: %v", id, err) + } + s.Done() + }() + } + + if iop.Stdout != nil { + copy(s.Stdout(), iop.Stdout) + } + if iop.Stderr != nil { + copy(s.Stderr(), iop.Stderr) + } + + return nil +} diff --git a/components/engine/daemon/monitor_linux.go b/components/engine/daemon/monitor_linux.go new file mode 100644 index 0000000000..df8b6c5dba --- /dev/null +++ b/components/engine/daemon/monitor_linux.go @@ -0,0 +1,14 @@ +package daemon + +import ( + "github.com/docker/docker/container" + "github.com/docker/docker/libcontainerd" +) + +// platformConstructExitStatus returns a platform specific exit status structure +func platformConstructExitStatus(e libcontainerd.StateInfo) *container.ExitStatus { + return &container.ExitStatus{ + ExitCode: int(e.ExitCode), + OOMKilled: e.OOMKilled, + } +} diff --git a/components/engine/daemon/monitor_windows.go b/components/engine/daemon/monitor_windows.go new file mode 100644 index 0000000000..b808ed3d03 --- /dev/null +++ b/components/engine/daemon/monitor_windows.go @@ -0,0 +1,13 @@ +package daemon + +import ( + "github.com/docker/docker/container" + "github.com/docker/docker/libcontainerd" +) + +// platformConstructExitStatus returns a platform specific exit status structure +func platformConstructExitStatus(e libcontainerd.StateInfo) *container.ExitStatus { + return &container.ExitStatus{ + ExitCode: int(e.ExitCode), + } +} diff --git a/components/engine/daemon/oci_linux.go b/components/engine/daemon/oci_linux.go new file mode 100644 index 0000000000..0b61f5326b --- /dev/null +++ b/components/engine/daemon/oci_linux.go @@ -0,0 +1,652 @@ +package daemon + +import ( + "fmt" + "io" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/docker/docker/container" + "github.com/docker/docker/daemon/caps" + "github.com/docker/docker/libcontainerd" + "github.com/docker/docker/oci" + "github.com/docker/docker/pkg/idtools" + "github.com/docker/docker/pkg/mount" + "github.com/docker/docker/pkg/stringutils" + "github.com/docker/docker/pkg/symlink" + "github.com/docker/docker/volume" + containertypes "github.com/docker/engine-api/types/container" + "github.com/opencontainers/runc/libcontainer/apparmor" + "github.com/opencontainers/runc/libcontainer/devices" + "github.com/opencontainers/runc/libcontainer/user" + "github.com/opencontainers/specs/specs-go" +) + +func setResources(s *specs.Spec, r containertypes.Resources) error { + weightDevices, err := getBlkioWeightDevices(r) + if err != nil { + return err + } + readBpsDevice, err := getBlkioReadBpsDevices(r) + if err != nil { + return err + } + writeBpsDevice, err := getBlkioWriteBpsDevices(r) + if err != nil { + return err + } + readIOpsDevice, err := getBlkioReadIOpsDevices(r) + if err != nil { + return err + } + writeIOpsDevice, err := getBlkioWriteIOpsDevices(r) + if err != nil { + return err + } + + memoryRes := getMemoryResources(r) + cpuRes := getCPUResources(r) + blkioWeight := r.BlkioWeight + + specResources := &specs.Resources{ + Memory: memoryRes, + CPU: cpuRes, + BlockIO: &specs.BlockIO{ + Weight: &blkioWeight, + WeightDevice: weightDevices, + ThrottleReadBpsDevice: readBpsDevice, + ThrottleWriteBpsDevice: writeBpsDevice, + ThrottleReadIOPSDevice: readIOpsDevice, + ThrottleWriteIOPSDevice: writeIOpsDevice, + }, + DisableOOMKiller: r.OomKillDisable, + Pids: &specs.Pids{ + Limit: &r.PidsLimit, + }, + } + + if s.Linux.Resources != nil && len(s.Linux.Resources.Devices) > 0 { + specResources.Devices = s.Linux.Resources.Devices + } + + s.Linux.Resources = specResources + return nil +} + +func setDevices(s *specs.Spec, c *container.Container) error { + // Build lists of devices allowed and created within the container. + var devs []specs.Device + if c.HostConfig.Privileged { + hostDevices, err := devices.HostDevices() + if err != nil { + return err + } + for _, d := range hostDevices { + devs = append(devs, specDevice(d)) + } + } else { + for _, deviceMapping := range c.HostConfig.Devices { + d, err := getDevicesFromPath(deviceMapping) + if err != nil { + return err + } + + devs = append(devs, d...) + } + } + + s.Linux.Devices = append(s.Linux.Devices, devs...) + return nil +} + +func setRlimits(daemon *Daemon, s *specs.Spec, c *container.Container) error { + var rlimits []specs.Rlimit + + ulimits := c.HostConfig.Ulimits + // Merge ulimits with daemon defaults + ulIdx := make(map[string]struct{}) + for _, ul := range ulimits { + ulIdx[ul.Name] = struct{}{} + } + for name, ul := range daemon.configStore.Ulimits { + if _, exists := ulIdx[name]; !exists { + ulimits = append(ulimits, ul) + } + } + + for _, ul := range ulimits { + rlimits = append(rlimits, specs.Rlimit{ + Type: "RLIMIT_" + strings.ToUpper(ul.Name), + Soft: uint64(ul.Soft), + Hard: uint64(ul.Hard), + }) + } + + s.Process.Rlimits = rlimits + return nil +} + +func setUser(s *specs.Spec, c *container.Container) error { + uid, gid, additionalGids, err := getUser(c, c.Config.User) + if err != nil { + return err + } + s.Process.User.UID = uid + s.Process.User.GID = gid + s.Process.User.AdditionalGids = additionalGids + return nil +} + +func readUserFile(c *container.Container, p string) (io.ReadCloser, error) { + fp, err := symlink.FollowSymlinkInScope(filepath.Join(c.BaseFS, p), c.BaseFS) + if err != nil { + return nil, err + } + return os.Open(fp) +} + +func getUser(c *container.Container, username string) (uint32, uint32, []uint32, error) { + passwdPath, err := user.GetPasswdPath() + if err != nil { + return 0, 0, nil, err + } + groupPath, err := user.GetGroupPath() + if err != nil { + return 0, 0, nil, err + } + passwdFile, err := readUserFile(c, passwdPath) + if err == nil { + defer passwdFile.Close() + } + groupFile, err := readUserFile(c, groupPath) + if err == nil { + defer groupFile.Close() + } + + execUser, err := user.GetExecUser(username, nil, passwdFile, groupFile) + if err != nil { + return 0, 0, nil, err + } + + // todo: fix this double read by a change to libcontainer/user pkg + groupFile, err = readUserFile(c, groupPath) + if err == nil { + defer groupFile.Close() + } + var addGroups []int + if len(c.HostConfig.GroupAdd) > 0 { + addGroups, err = user.GetAdditionalGroups(c.HostConfig.GroupAdd, groupFile) + if err != nil { + return 0, 0, nil, err + } + } + uid := uint32(execUser.Uid) + gid := uint32(execUser.Gid) + sgids := append(execUser.Sgids, addGroups...) + var additionalGids []uint32 + for _, g := range sgids { + additionalGids = append(additionalGids, uint32(g)) + } + return uid, gid, additionalGids, nil +} + +func setNamespace(s *specs.Spec, ns specs.Namespace) { + for i, n := range s.Linux.Namespaces { + if n.Type == ns.Type { + s.Linux.Namespaces[i] = ns + return + } + } + s.Linux.Namespaces = append(s.Linux.Namespaces, ns) +} + +func setCapabilities(s *specs.Spec, c *container.Container) error { + var caplist []string + var err error + if c.HostConfig.Privileged { + caplist = caps.GetAllCapabilities() + } else { + caplist, err = caps.TweakCapabilities(s.Process.Capabilities, c.HostConfig.CapAdd, c.HostConfig.CapDrop) + if err != nil { + return err + } + } + s.Process.Capabilities = caplist + return nil +} + +func delNamespace(s *specs.Spec, nsType specs.NamespaceType) { + idx := -1 + for i, n := range s.Linux.Namespaces { + if n.Type == nsType { + idx = i + } + } + if idx >= 0 { + s.Linux.Namespaces = append(s.Linux.Namespaces[:idx], s.Linux.Namespaces[idx+1:]...) + } +} + +func setNamespaces(daemon *Daemon, s *specs.Spec, c *container.Container) error { + // network + if !c.Config.NetworkDisabled { + ns := specs.Namespace{Type: "network"} + parts := strings.SplitN(string(c.HostConfig.NetworkMode), ":", 2) + if parts[0] == "container" { + nc, err := daemon.getNetworkedContainer(c.ID, c.HostConfig.NetworkMode.ConnectedContainer()) + if err != nil { + return err + } + ns.Path = fmt.Sprintf("/proc/%d/ns/net", nc.State.GetPID()) + } else if c.HostConfig.NetworkMode.IsHost() { + ns.Path = c.NetworkSettings.SandboxKey + } + setNamespace(s, ns) + } + // ipc + if c.HostConfig.IpcMode.IsContainer() { + ns := specs.Namespace{Type: "ipc"} + ic, err := daemon.getIpcContainer(c) + if err != nil { + return err + } + ns.Path = fmt.Sprintf("/proc/%d/ns/ipc", ic.State.GetPID()) + setNamespace(s, ns) + } else if c.HostConfig.IpcMode.IsHost() { + delNamespace(s, specs.NamespaceType("ipc")) + } else { + ns := specs.Namespace{Type: "ipc"} + setNamespace(s, ns) + } + // pid + if c.HostConfig.PidMode.IsHost() { + delNamespace(s, specs.NamespaceType("pid")) + } + // uts + if c.HostConfig.UTSMode.IsHost() { + delNamespace(s, specs.NamespaceType("uts")) + s.Hostname = "" + } + // user + if c.HostConfig.UsernsMode.IsPrivate() { + uidMap, gidMap := daemon.GetUIDGIDMaps() + if uidMap != nil { + ns := specs.Namespace{Type: "user"} + setNamespace(s, ns) + s.Linux.UIDMappings = specMapping(uidMap) + s.Linux.GIDMappings = specMapping(gidMap) + } + } + + return nil +} + +func specMapping(s []idtools.IDMap) []specs.IDMapping { + var ids []specs.IDMapping + for _, item := range s { + ids = append(ids, specs.IDMapping{ + HostID: uint32(item.HostID), + ContainerID: uint32(item.ContainerID), + Size: uint32(item.Size), + }) + } + return ids +} + +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 +} + +var ( + mountPropagationMap = map[string]int{ + "private": mount.PRIVATE, + "rprivate": mount.RPRIVATE, + "shared": mount.SHARED, + "rshared": mount.RSHARED, + "slave": mount.SLAVE, + "rslave": mount.RSLAVE, + } + + mountPropagationReverseMap = map[int]string{ + mount.PRIVATE: "private", + mount.RPRIVATE: "rprivate", + mount.SHARED: "shared", + mount.RSHARED: "rshared", + mount.SLAVE: "slave", + mount.RSLAVE: "rslave", + } +) + +func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []container.Mount) error { + userMounts := make(map[string]struct{}) + for _, m := range mounts { + userMounts[m.Destination] = struct{}{} + } + + // Filter out mounts that are overriden by user supplied mounts + var defaultMounts []specs.Mount + _, mountDev := userMounts["/dev"] + for _, m := range s.Mounts { + if _, ok := userMounts[m.Destination]; !ok { + if mountDev && strings.HasPrefix(m.Destination, "/dev/") { + continue + } + defaultMounts = append(defaultMounts, m) + } + } + + s.Mounts = defaultMounts + for _, m := range mounts { + for _, cm := range s.Mounts { + if cm.Destination == m.Destination { + return fmt.Errorf("Duplicate mount point '%s'", m.Destination) + } + } + + if m.Source == "tmpfs" { + opt := []string{"noexec", "nosuid", "nodev", volume.DefaultPropagationMode} + if m.Data != "" { + opt = append(opt, strings.Split(m.Data, ",")...) + } else { + opt = append(opt, "size=65536k") + } + + s.Mounts = append(s.Mounts, specs.Mount{Destination: m.Destination, Source: m.Source, Type: "tmpfs", Options: opt}) + continue + } + + mt := specs.Mount{Destination: m.Destination, Source: m.Source, Type: "bind"} + + // 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 := mountPropagationMap[s.Linux.RootfsPropagation] + if rootpg != mount.SHARED && rootpg != mount.RSHARED { + s.Linux.RootfsPropagation = mountPropagationReverseMap[mount.SHARED] + } + } else if pFlag == mount.SLAVE || pFlag == mount.RSLAVE { + if err := ensureSharedOrSlave(m.Source); err != nil { + return err + } + rootpg := mountPropagationMap[s.Linux.RootfsPropagation] + if rootpg != mount.SHARED && rootpg != mount.RSHARED && rootpg != mount.SLAVE && rootpg != mount.RSLAVE { + s.Linux.RootfsPropagation = mountPropagationReverseMap[mount.RSLAVE] + } + } + + opts := []string{"rbind"} + if !m.Writable { + opts = append(opts, "ro") + } + if pFlag != 0 { + opts = append(opts, mountPropagationReverseMap[pFlag]) + } + + mt.Options = opts + s.Mounts = append(s.Mounts, mt) + } + + if s.Root.Readonly { + for i, m := range s.Mounts { + switch m.Destination { + case "/proc", "/dev/pts", "/dev/mqueue": // /dev is remounted by runc + continue + } + if _, ok := userMounts[m.Destination]; !ok { + if !stringutils.InSlice(m.Options, "ro") { + s.Mounts[i].Options = append(s.Mounts[i].Options, "ro") + } + } + } + } + + if c.HostConfig.Privileged { + if !s.Root.Readonly { + // clear readonly for /sys + for i := range s.Mounts { + if s.Mounts[i].Destination == "/sys" { + clearReadOnly(&s.Mounts[i]) + } + } + } + } + + // 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) + if uidMap, _ := daemon.GetUIDGIDMaps(); uidMap != nil || c.HostConfig.Privileged { + for i, m := range s.Mounts { + if m.Type == "cgroup" { + clearReadOnly(&s.Mounts[i]) + } + } + } + + return nil +} + +func (daemon *Daemon) populateCommonSpec(s *specs.Spec, c *container.Container) error { + linkedEnv, err := daemon.setupLinkedContainers(c) + if err != nil { + return err + } + s.Root = specs.Root{ + Path: c.BaseFS, + Readonly: c.HostConfig.ReadonlyRootfs, + } + rootUID, rootGID := daemon.GetRemappedUIDGID() + if err := c.SetupWorkingDirectory(rootUID, rootGID); err != nil { + return err + } + cwd := c.Config.WorkingDir + if len(cwd) == 0 { + cwd = "/" + } + s.Process.Args = append([]string{c.Path}, c.Args...) + s.Process.Cwd = cwd + s.Process.Env = c.CreateDaemonEnvironment(linkedEnv) + s.Process.Terminal = c.Config.Tty + s.Hostname = c.FullHostname() + + return nil +} + +func (daemon *Daemon) createSpec(c *container.Container) (*libcontainerd.Spec, error) { + s := oci.DefaultSpec() + if err := daemon.populateCommonSpec(&s, c); err != nil { + return nil, err + } + + var cgroupsPath string + if c.HostConfig.CgroupParent != "" { + cgroupsPath = filepath.Join(c.HostConfig.CgroupParent, c.ID) + } else { + defaultCgroupParent := "/docker" + if daemon.configStore.CgroupParent != "" { + defaultCgroupParent = daemon.configStore.CgroupParent + } else if daemon.usingSystemd() { + defaultCgroupParent = "system.slice" + } + cgroupsPath = filepath.Join(defaultCgroupParent, c.ID) + } + s.Linux.CgroupsPath = &cgroupsPath + + if err := setResources(&s, c.HostConfig.Resources); err != nil { + return nil, fmt.Errorf("linux runtime spec resources: %v", err) + } + s.Linux.Resources.OOMScoreAdj = &c.HostConfig.OomScoreAdj + if err := setDevices(&s, c); err != nil { + return nil, fmt.Errorf("linux runtime spec devices: %v", err) + } + if err := setRlimits(daemon, &s, c); err != nil { + return nil, fmt.Errorf("linux runtime spec rlimits: %v", err) + } + if err := setUser(&s, c); err != nil { + return nil, fmt.Errorf("linux spec user: %v", err) + } + if err := setNamespaces(daemon, &s, c); err != nil { + return nil, fmt.Errorf("linux spec namespaces: %v", err) + } + if err := setCapabilities(&s, c); err != nil { + return nil, fmt.Errorf("linux spec capabilities: %v", err) + } + if err := setSeccomp(daemon, &s, c); err != nil { + return nil, fmt.Errorf("linux seccomp: %v", err) + } + + if err := daemon.setupIpcDirs(c); err != nil { + return nil, err + } + + mounts, err := daemon.setupMounts(c) + if err != nil { + return nil, err + } + mounts = append(mounts, c.IpcMounts()...) + mounts = append(mounts, c.TmpfsMounts()...) + if err := setMounts(daemon, &s, c, mounts); err != nil { + return nil, fmt.Errorf("linux mounts: %v", err) + } + + for _, ns := range s.Linux.Namespaces { + if ns.Type == "network" && ns.Path == "" && !c.Config.NetworkDisabled { + target, err := os.Readlink(filepath.Join("/proc", strconv.Itoa(os.Getpid()), "exe")) + if err != nil { + return nil, err + } + + s.Hooks = specs.Hooks{ + Prestart: []specs.Hook{{ + Path: target, // FIXME: cross-platform + Args: []string{"libnetwork-setkey", c.ID, daemon.netController.ID()}, + }}, + } + } + } + + if apparmor.IsEnabled() { + appArmorProfile := "docker-default" + if c.HostConfig.Privileged { + appArmorProfile = "unconfined" + } else if len(c.AppArmorProfile) > 0 { + appArmorProfile = c.AppArmorProfile + } + s.Process.ApparmorProfile = appArmorProfile + } + s.Process.SelinuxLabel = c.GetProcessLabel() + s.Process.NoNewPrivileges = c.NoNewPrivileges + + return (*libcontainerd.Spec)(&s), nil +} + +func clearReadOnly(m *specs.Mount) { + var opt []string + for _, o := range m.Options { + if o != "ro" { + opt = append(opt, o) + } + } + m.Options = opt +} diff --git a/components/engine/daemon/oci_windows.go b/components/engine/daemon/oci_windows.go new file mode 100644 index 0000000000..2af99dc820 --- /dev/null +++ b/components/engine/daemon/oci_windows.go @@ -0,0 +1,204 @@ +package daemon + +import ( + "fmt" + "strings" + "syscall" + + "github.com/docker/docker/container" + "github.com/docker/docker/layer" + "github.com/docker/docker/libcontainerd" + "github.com/docker/docker/libcontainerd/windowsoci" + "github.com/docker/docker/oci" +) + +func (daemon *Daemon) createSpec(c *container.Container) (*libcontainerd.Spec, error) { + s := oci.DefaultSpec() + + linkedEnv, err := daemon.setupLinkedContainers(c) + if err != nil { + return nil, err + } + + // TODO Windows - this can be removed. Not used (UID/GID) + rootUID, rootGID := daemon.GetRemappedUIDGID() + if err := c.SetupWorkingDirectory(rootUID, rootGID); err != nil { + return nil, err + } + + img, err := daemon.imageStore.Get(c.ImageID) + if err != nil { + return nil, fmt.Errorf("Failed to graph.Get on ImageID %s - %s", c.ImageID, err) + } + + // In base spec + s.Hostname = c.FullHostname() + + // In s.Mounts + mounts, err := daemon.setupMounts(c) + if err != nil { + return nil, err + } + for _, mount := range mounts { + s.Mounts = append(s.Mounts, windowsoci.Mount{ + Source: mount.Source, + Destination: mount.Destination, + Readonly: !mount.Writable, + }) + } + + // Are we going to run as a Hyper-V container? + hv := false + if c.HostConfig.Isolation.IsDefault() { + // Container is set to use the default, so take the default from the daemon configuration + hv = daemon.defaultIsolation.IsHyperV() + } else { + // Container is requesting an isolation mode. Honour it. + hv = c.HostConfig.Isolation.IsHyperV() + } + if hv { + // TODO We don't yet have the ImagePath hooked up. But set to + // something non-nil to pickup in libcontainerd. + s.Windows.HvRuntime = &windowsoci.HvRuntime{} + } + + // In s.Process + if c.Config.ArgsEscaped { + s.Process.Args = append([]string{c.Path}, c.Args...) + } else { + // TODO (jstarks): escape the entrypoint too once the tests are fixed to not rely on this behavior + s.Process.Args = append([]string{c.Path}, escapeArgs(c.Args)...) + } + s.Process.Cwd = c.Config.WorkingDir + s.Process.Env = c.CreateDaemonEnvironment(linkedEnv) + s.Process.InitialConsoleSize = c.HostConfig.ConsoleSize + s.Process.Terminal = c.Config.Tty + s.Process.User.User = c.Config.User + + // In spec.Root + s.Root.Path = c.BaseFS + s.Root.Readonly = c.HostConfig.ReadonlyRootfs + + // In s.Windows + s.Windows.FirstStart = !c.HasBeenStartedBefore + + // s.Windows.LayerFolder. + m, err := c.RWLayer.Metadata() + if err != nil { + return nil, fmt.Errorf("Failed to get layer metadata - %s", err) + } + s.Windows.LayerFolder = m["dir"] + + // s.Windows.LayerPaths + var layerPaths []string + 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 nil, fmt.Errorf("Failed to get layer path from graphdriver %s for ImageID %s - %s", daemon.layerStore, img.RootFS.ChainID(), err) + } + // Reverse order, expecting parent most first + layerPaths = append([]string{path}, layerPaths...) + } + } + s.Windows.LayerPaths = layerPaths + + // In s.Windows.Networking (TP5+ libnetwork way of doing things) + // Connect all the libnetwork allocated networks to the container + var epList []string + if c.NetworkSettings != nil { + for n := range c.NetworkSettings.Networks { + sn, err := daemon.FindNetwork(n) + if err != nil { + continue + } + + ep, err := c.GetEndpointInNetwork(sn) + if err != nil { + continue + } + + data, err := ep.DriverInfo() + if err != nil { + continue + } + if data["hnsid"] != nil { + epList = append(epList, data["hnsid"].(string)) + } + } + } + s.Windows.Networking = &windowsoci.Networking{ + EndpointList: epList, + } + + // In s.Windows.Networking (TP4 back compat) + // TODO Windows: Post TP4 - Remove this along with definitions from spec + // and changes to libcontainerd to not read these fields. + if daemon.netController == 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 { + s.Windows.Networking = &windowsoci.Networking{ + MacAddress: c.Config.MacAddress, + Bridge: daemon.configStore.bridgeConfig.Iface, + PortBindings: c.HostConfig.PortBindings, + } + } + default: + return nil, fmt.Errorf("invalid network mode: %s", c.HostConfig.NetworkMode) + } + } + + // In s.Windows.Resources + // @darrenstahlmsft implement these resources + cpuShares := uint64(c.HostConfig.CPUShares) + s.Windows.Resources = &windowsoci.Resources{ + CPU: &windowsoci.CPU{ + //TODO Count: ..., + //TODO Percent: ..., + Shares: &cpuShares, + }, + Memory: &windowsoci.Memory{ + //TODO Limit: ..., + //TODO Reservation: ..., + }, + Network: &windowsoci.Network{ + //TODO Bandwidth: ..., + }, + Storage: &windowsoci.Storage{ + //TODO Bps: ..., + //TODO Iops: ..., + //TODO SandboxSize: ..., + }, + } + + // BUGBUG - Next problem. This was an exec opt. Where do we now get these? + // Come back to this when add Xenon support. + // 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() + // } + + // Isolation: string(c.HostConfig.Isolation), + // HvPartition: hvPartition, + // } + + return (*libcontainerd.Spec)(&s), nil +} + +func escapeArgs(args []string) []string { + escapedArgs := make([]string, len(args)) + for i, a := range args { + escapedArgs[i] = syscall.EscapeArg(a) + } + return escapedArgs +} diff --git a/components/engine/daemon/pause.go b/components/engine/daemon/pause.go index 2ec0df7030..dbfafbc5fd 100644 --- a/components/engine/daemon/pause.go +++ b/components/engine/daemon/pause.go @@ -41,10 +41,9 @@ func (daemon *Daemon) containerPause(container *container.Container) error { return errContainerIsRestarting(container.ID) } - if err := daemon.execDriver.Pause(container.Command); err != nil { + if err := daemon.containerd.Pause(container.ID); err != nil { return fmt.Errorf("Cannot pause container %s: %s", container.ID, err) } - container.Paused = true - daemon.LogContainerEvent(container, "pause") + return nil } diff --git a/components/engine/daemon/resize.go b/components/engine/daemon/resize.go index d7bb105b36..747353852e 100644 --- a/components/engine/daemon/resize.go +++ b/components/engine/daemon/resize.go @@ -1,6 +1,10 @@ package daemon -import "fmt" +import ( + "fmt" + + "github.com/docker/docker/libcontainerd" +) // ContainerResize changes the size of the TTY of the process running // in the container with the given name to the given height and width. @@ -14,7 +18,7 @@ func (daemon *Daemon) ContainerResize(name string, height, width int) error { return errNotRunning{container.ID} } - if err = container.Resize(height, width); err == nil { + if err = daemon.containerd.Resize(container.ID, libcontainerd.InitFriendlyName, width, height); err == nil { attributes := map[string]string{ "height": fmt.Sprintf("%d", height), "width": fmt.Sprintf("%d", width), @@ -28,10 +32,9 @@ func (daemon *Daemon) ContainerResize(name string, height, width int) error { // running in the exec with the given name to the given height and // width. func (daemon *Daemon) ContainerExecResize(name string, height, width int) error { - ExecConfig, err := daemon.getExecConfig(name) + ec, err := daemon.getExecConfig(name) if err != nil { return err } - - return ExecConfig.Resize(height, width) + return daemon.containerd.Resize(ec.ContainerID, ec.ID, width, height) } diff --git a/components/engine/daemon/seccomp_default_linux.go b/components/engine/daemon/seccomp_default_linux.go new file mode 100644 index 0000000000..c5121dd9a6 --- /dev/null +++ b/components/engine/daemon/seccomp_default_linux.go @@ -0,0 +1,1600 @@ +// +build linux,seccomp + +package daemon + +import ( + "syscall" + + "github.com/opencontainers/specs/specs-go" + libseccomp "github.com/seccomp/libseccomp-golang" +) + +func arches() []specs.Arch { + var native, err = libseccomp.GetNativeArch() + if err != nil { + return []specs.Arch{} + } + var a = native.String() + switch a { + case "amd64": + return []specs.Arch{specs.ArchX86_64, specs.ArchX86, specs.ArchX32} + case "arm64": + return []specs.Arch{specs.ArchAARCH64, specs.ArchARM} + case "mips64": + return []specs.Arch{specs.ArchMIPS, specs.ArchMIPS64, specs.ArchMIPS64N32} + case "mips64n32": + return []specs.Arch{specs.ArchMIPS, specs.ArchMIPS64, specs.ArchMIPS64N32} + case "mipsel64": + return []specs.Arch{specs.ArchMIPSEL, specs.ArchMIPSEL64, specs.ArchMIPSEL64N32} + case "mipsel64n32": + return []specs.Arch{specs.ArchMIPSEL, specs.ArchMIPSEL64, specs.ArchMIPSEL64N32} + default: + return []specs.Arch{} + } +} + +var defaultSeccompProfile = specs.Seccomp{ + DefaultAction: specs.ActErrno, + Architectures: arches(), + Syscalls: []specs.Syscall{ + { + Name: "accept", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "accept4", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "access", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "alarm", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "arch_prctl", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "bind", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "brk", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "capget", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "capset", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "chdir", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "chmod", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "chown", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "chown32", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "chroot", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "clock_getres", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "clock_gettime", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "clock_nanosleep", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "clone", + Action: specs.ActAllow, + Args: []specs.Arg{ + { + Index: 0, + Value: syscall.CLONE_NEWNS | syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWUSER | syscall.CLONE_NEWPID | syscall.CLONE_NEWNET, + ValueTwo: 0, + Op: specs.OpMaskedEqual, + }, + }, + }, + { + Name: "close", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "connect", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "creat", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "dup", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "dup2", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "dup3", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "epoll_create", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "epoll_create1", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "epoll_ctl", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "epoll_ctl_old", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "epoll_pwait", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "epoll_wait", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "epoll_wait_old", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "eventfd", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "eventfd2", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "execve", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "execveat", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "exit", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "exit_group", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "faccessat", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "fadvise64", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "fadvise64_64", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "fallocate", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "fanotify_init", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "fanotify_mark", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "fchdir", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "fchmod", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "fchmodat", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "fchown", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "fchown32", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "fchownat", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "fcntl", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "fcntl64", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "fdatasync", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "fgetxattr", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "flistxattr", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "flock", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "fork", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "fremovexattr", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "fsetxattr", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "fstat", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "fstat64", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "fstatat64", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "fstatfs", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "fstatfs64", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "fsync", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "ftruncate", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "ftruncate64", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "futex", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "futimesat", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getcpu", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getcwd", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getdents", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getdents64", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getegid", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getegid32", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "geteuid", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "geteuid32", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getgid", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getgid32", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getgroups", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getgroups32", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getitimer", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getpeername", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getpgid", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getpgrp", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getpid", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getppid", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getpriority", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getrandom", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getresgid", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getresgid32", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getresuid", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getresuid32", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getrlimit", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "get_robust_list", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getrusage", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getsid", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getsockname", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getsockopt", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "get_thread_area", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "gettid", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "gettimeofday", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getuid", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getuid32", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "getxattr", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "inotify_add_watch", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "inotify_init", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "inotify_init1", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "inotify_rm_watch", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "io_cancel", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "ioctl", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "io_destroy", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "io_getevents", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "ioprio_get", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "ioprio_set", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "io_setup", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "io_submit", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "kill", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "lchown", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "lchown32", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "lgetxattr", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "link", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "linkat", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "listen", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "listxattr", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "llistxattr", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "_llseek", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "lremovexattr", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "lseek", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "lsetxattr", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "lstat", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "lstat64", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "madvise", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "memfd_create", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "mincore", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "mkdir", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "mkdirat", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "mknod", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "mknodat", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "mlock", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "mlockall", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "mmap", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "mmap2", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "mprotect", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "mq_getsetattr", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "mq_notify", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "mq_open", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "mq_timedreceive", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "mq_timedsend", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "mq_unlink", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "mremap", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "msgctl", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "msgget", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "msgrcv", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "msgsnd", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "msync", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "munlock", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "munlockall", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "munmap", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "nanosleep", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "newfstatat", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "_newselect", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "open", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "openat", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "pause", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "pipe", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "pipe2", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "poll", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "ppoll", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "prctl", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "pread64", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "preadv", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "prlimit64", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "pselect6", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "pwrite64", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "pwritev", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "read", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "readahead", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "readlink", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "readlinkat", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "readv", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "recv", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "recvfrom", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "recvmmsg", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "recvmsg", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "remap_file_pages", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "removexattr", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "rename", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "renameat", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "renameat2", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "rmdir", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "rt_sigaction", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "rt_sigpending", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "rt_sigprocmask", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "rt_sigqueueinfo", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "rt_sigreturn", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "rt_sigsuspend", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "rt_sigtimedwait", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "rt_tgsigqueueinfo", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "sched_getaffinity", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "sched_getattr", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "sched_getparam", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "sched_get_priority_max", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "sched_get_priority_min", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "sched_getscheduler", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "sched_rr_get_interval", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "sched_setaffinity", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "sched_setattr", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "sched_setparam", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "sched_setscheduler", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "sched_yield", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "seccomp", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "select", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "semctl", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "semget", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "semop", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "semtimedop", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "send", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "sendfile", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "sendfile64", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "sendmmsg", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "sendmsg", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "sendto", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setdomainname", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setfsgid", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setfsgid32", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setfsuid", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setfsuid32", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setgid", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setgid32", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setgroups", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setgroups32", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "sethostname", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setitimer", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setpgid", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setpriority", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setregid", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setregid32", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setresgid", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setresgid32", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setresuid", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setresuid32", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setreuid", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setreuid32", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setrlimit", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "set_robust_list", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setsid", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setsockopt", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "set_thread_area", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "set_tid_address", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setuid", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setuid32", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "setxattr", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "shmat", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "shmctl", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "shmdt", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "shmget", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "shutdown", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "sigaltstack", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "signalfd", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "signalfd4", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "sigreturn", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "socket", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "socketpair", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "splice", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "stat", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "stat64", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "statfs", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "statfs64", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "symlink", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "symlinkat", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "sync", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "sync_file_range", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "syncfs", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "sysinfo", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "syslog", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "tee", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "tgkill", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "time", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "timer_create", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "timer_delete", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "timerfd_create", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "timerfd_gettime", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "timerfd_settime", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "timer_getoverrun", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "timer_gettime", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "timer_settime", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "times", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "tkill", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "truncate", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "truncate64", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "ugetrlimit", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "umask", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "uname", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "unlink", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "unlinkat", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "utime", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "utimensat", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "utimes", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "vfork", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "vhangup", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "vmsplice", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "wait4", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "waitid", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "waitpid", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "write", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "writev", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + // i386 specific syscalls + { + Name: "modify_ldt", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + // arm specific syscalls + { + Name: "breakpoint", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "cacheflush", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + { + Name: "set_tls", + Action: specs.ActAllow, + Args: []specs.Arg{}, + }, + }, +} diff --git a/components/engine/daemon/seccomp_disabled.go b/components/engine/daemon/seccomp_disabled.go new file mode 100644 index 0000000000..620eee29bf --- /dev/null +++ b/components/engine/daemon/seccomp_disabled.go @@ -0,0 +1,12 @@ +// +build !seccomp,!windows + +package daemon + +import ( + "github.com/docker/docker/container" + "github.com/opencontainers/specs/specs-go" +) + +func setSeccomp(daemon *Daemon, rs *specs.Spec, c *container.Container) error { + return nil +} diff --git a/components/engine/daemon/seccomp_linux.go b/components/engine/daemon/seccomp_linux.go new file mode 100644 index 0000000000..02a7650ea6 --- /dev/null +++ b/components/engine/daemon/seccomp_linux.go @@ -0,0 +1,100 @@ +// +build linux,seccomp + +package daemon + +import ( + "encoding/json" + "fmt" + + "github.com/Sirupsen/logrus" + "github.com/docker/docker/container" + "github.com/docker/engine-api/types" + "github.com/opencontainers/specs/specs-go" +) + +func setSeccomp(daemon *Daemon, rs *specs.Spec, c *container.Container) error { + var seccomp *specs.Seccomp + var err error + + if c.HostConfig.Privileged { + return nil + } + + if !daemon.seccompEnabled { + if c.SeccompProfile != "" && c.SeccompProfile != "unconfined" { + return fmt.Errorf("Seccomp is not enabled in your kernel, cannot run a custom seccomp profile.") + } + logrus.Warn("Seccomp is not enabled in your kernel, running container without default profile.") + c.SeccompProfile = "unconfined" + } + if c.SeccompProfile == "unconfined" { + return nil + } + if c.SeccompProfile != "" { + seccomp, err = loadSeccompProfile(c.SeccompProfile) + if err != nil { + return err + } + } else { + seccomp = &defaultSeccompProfile + } + + rs.Linux.Seccomp = seccomp + return nil +} + +func loadSeccompProfile(body string) (*specs.Seccomp, error) { + var config types.Seccomp + if err := json.Unmarshal([]byte(body), &config); err != nil { + return nil, fmt.Errorf("Decoding seccomp profile failed: %v", err) + } + + return setupSeccomp(&config) +} + +func setupSeccomp(config *types.Seccomp) (newConfig *specs.Seccomp, err error) { + if config == nil { + return nil, nil + } + + // No default action specified, no syscalls listed, assume seccomp disabled + if config.DefaultAction == "" && len(config.Syscalls) == 0 { + return nil, nil + } + + newConfig = &specs.Seccomp{} + + // if config.Architectures == 0 then libseccomp will figure out the architecture to use + if len(config.Architectures) > 0 { + // newConfig.Architectures = []string{} + for _, arch := range config.Architectures { + newConfig.Architectures = append(newConfig.Architectures, specs.Arch(arch)) + } + } + + newConfig.DefaultAction = specs.Action(config.DefaultAction) + + // Loop through all syscall blocks and convert them to libcontainer format + for _, call := range config.Syscalls { + newCall := specs.Syscall{ + Name: call.Name, + Action: specs.Action(call.Action), + } + + // Loop through all the arguments of the syscall and convert them + for _, arg := range call.Args { + newArg := specs.Arg{ + Index: arg.Index, + Value: arg.Value, + ValueTwo: arg.ValueTwo, + Op: specs.Operator(arg.Op), + } + + newCall.Args = append(newCall.Args, newArg) + } + + newConfig.Syscalls = append(newConfig.Syscalls, newCall) + } + + return newConfig, nil +} diff --git a/components/engine/daemon/start.go b/components/engine/daemon/start.go index 8c7e5bf5aa..be1fef7737 100644 --- a/components/engine/daemon/start.go +++ b/components/engine/daemon/start.go @@ -4,10 +4,13 @@ import ( "fmt" "net/http" "runtime" + "strings" + "syscall" "github.com/Sirupsen/logrus" "github.com/docker/docker/container" "github.com/docker/docker/errors" + "github.com/docker/docker/libcontainerd" "github.com/docker/docker/runconfig" containertypes "github.com/docker/engine-api/types/container" ) @@ -122,44 +125,36 @@ func (daemon *Daemon) containerStart(container *container.Container) (err error) if err := daemon.initializeNetworking(container); err != nil { return err } - linkedEnv, err := daemon.setupLinkedContainers(container) + + spec, err := daemon.createSpec(container) if err != nil { return err } - rootUID, rootGID := daemon.GetRemappedUIDGID() - if err := container.SetupWorkingDirectory(rootUID, rootGID); err != nil { - return err - } - env := container.CreateDaemonEnvironment(linkedEnv) - if err := daemon.populateCommand(container, env); err != nil { - return err - } - if !container.HostConfig.IpcMode.IsContainer() && !container.HostConfig.IpcMode.IsHost() { - if err := daemon.setupIpcDirs(container); err != nil { - return err + defer daemon.LogContainerEvent(container, "start") // this is logged even on error + if err := daemon.containerd.Create(container.ID, *spec, libcontainerd.WithRestartManager(container.RestartManager(true))); 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") { + container.ExitCode = 127 + err = fmt.Errorf("Container command not found or does not exist.") + } + // set to 126 for container cmd can't be invoked errors + if strings.Contains(err.Error(), syscall.EACCES.Error()) { + container.ExitCode = 126 + err = fmt.Errorf("Container command could not be invoked.") } - } - mounts, err := daemon.setupMounts(container) - if err != nil { + container.Reset(false) return err } - mounts = append(mounts, container.IpcMounts()...) - mounts = append(mounts, container.TmpfsMounts()...) - container.Command.Mounts = mounts - if err := daemon.waitForStart(container); err != nil { - return err - } - container.HasBeenStartedBefore = true return nil } -func (daemon *Daemon) waitForStart(container *container.Container) error { - return container.StartMonitor(daemon) -} - // Cleanup releases any network resources allocated to the container along with any rules // around how containers are linked together. It also unmounts the container's root filesystem. func (daemon *Daemon) Cleanup(container *container.Container) { @@ -167,7 +162,13 @@ func (daemon *Daemon) Cleanup(container *container.Container) { container.UnmountIpcMounts(detachMounted) - daemon.conditionalUnmountOnCleanup(container) + if err := daemon.conditionalUnmountOnCleanup(container); err != nil { + // FIXME: remove once reference counting for graphdrivers has been refactored + // Ensure that all the mounts are gone + if mountid, err := daemon.layerStore.GetMountID(container.ID); err == nil { + daemon.cleanupMountsByID(mountid) + } + } for _, eConfig := range container.ExecCommands.Commands() { daemon.unregisterExecCommand(container, eConfig) diff --git a/components/engine/daemon/stats.go b/components/engine/daemon/stats.go index 1cbc1193bf..1942ccac3c 100644 --- a/components/engine/daemon/stats.go +++ b/components/engine/daemon/stats.go @@ -6,7 +6,6 @@ import ( "runtime" "github.com/docker/docker/api/types/backend" - "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/version" "github.com/docker/engine-api/types" @@ -42,12 +41,9 @@ func (daemon *Daemon) ContainerStats(prefixOrName string, config *backend.Contai var preCPUStats types.CPUStats getStatJSON := func(v interface{}) *types.StatsJSON { - update := v.(*execdriver.ResourceStats) - ss := convertStatsToAPITypes(update.Stats) + ss := v.(*types.StatsJSON) ss.PreCPUStats = preCPUStats - ss.MemoryStats.Limit = uint64(update.MemoryLimit) - ss.Read = update.Read - ss.CPUStats.SystemUsage = update.SystemUsage + // ss.MemoryStats.Limit = uint64(update.MemoryLimit) preCPUStats = ss.CPUStats return ss } diff --git a/components/engine/daemon/stats_collector_unix.go b/components/engine/daemon/stats_collector_unix.go index a8de5a2062..7c9a45c649 100644 --- a/components/engine/daemon/stats_collector_unix.go +++ b/components/engine/daemon/stats_collector_unix.go @@ -13,14 +13,14 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/container" - "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/pkg/pubsub" + "github.com/docker/engine-api/types" "github.com/opencontainers/runc/libcontainer/system" ) type statsSupervisor interface { // GetContainerStats collects all the stats related to a container - GetContainerStats(container *container.Container) (*execdriver.ResourceStats, error) + GetContainerStats(container *container.Container) (*types.StatsJSON, error) } // newStatsCollector returns a new statsCollector that collections @@ -120,12 +120,13 @@ func (s *statsCollector) run() { for _, pair := range pairs { stats, err := s.supervisor.GetContainerStats(pair.container) if err != nil { - if err != execdriver.ErrNotRunning { + if err, ok := err.(errNotRunning); ok { logrus.Errorf("collecting stats for %s: %v", pair.container.ID, err) } continue } - stats.SystemUsage = systemUsage + // FIXME: move to containerd + stats.CPUStats.SystemUsage = systemUsage pair.publisher.Publish(stats) } diff --git a/components/engine/daemon/stats_linux.go b/components/engine/daemon/stats_linux.go deleted file mode 100644 index 1a907e015a..0000000000 --- a/components/engine/daemon/stats_linux.go +++ /dev/null @@ -1,84 +0,0 @@ -package daemon - -import ( - "github.com/docker/engine-api/types" - "github.com/opencontainers/runc/libcontainer" - "github.com/opencontainers/runc/libcontainer/cgroups" -) - -// convertStatsToAPITypes converts the libcontainer.Stats to the api specific -// structs. This is done to preserve API compatibility and versioning. -func convertStatsToAPITypes(ls *libcontainer.Stats) *types.StatsJSON { - s := &types.StatsJSON{} - if ls.Interfaces != nil { - s.Networks = make(map[string]types.NetworkStats) - for _, iface := range ls.Interfaces { - // For API Version >= 1.21, the original data of network will - // be returned. - s.Networks[iface.Name] = types.NetworkStats{ - RxBytes: iface.RxBytes, - RxPackets: iface.RxPackets, - RxErrors: iface.RxErrors, - RxDropped: iface.RxDropped, - TxBytes: iface.TxBytes, - TxPackets: iface.TxPackets, - TxErrors: iface.TxErrors, - TxDropped: iface.TxDropped, - } - } - } - - cs := ls.CgroupStats - if cs != nil { - s.BlkioStats = types.BlkioStats{ - IoServiceBytesRecursive: copyBlkioEntry(cs.BlkioStats.IoServiceBytesRecursive), - IoServicedRecursive: copyBlkioEntry(cs.BlkioStats.IoServicedRecursive), - IoQueuedRecursive: copyBlkioEntry(cs.BlkioStats.IoQueuedRecursive), - IoServiceTimeRecursive: copyBlkioEntry(cs.BlkioStats.IoServiceTimeRecursive), - IoWaitTimeRecursive: copyBlkioEntry(cs.BlkioStats.IoWaitTimeRecursive), - IoMergedRecursive: copyBlkioEntry(cs.BlkioStats.IoMergedRecursive), - IoTimeRecursive: copyBlkioEntry(cs.BlkioStats.IoTimeRecursive), - SectorsRecursive: copyBlkioEntry(cs.BlkioStats.SectorsRecursive), - } - cpu := cs.CpuStats - s.CPUStats = types.CPUStats{ - CPUUsage: types.CPUUsage{ - TotalUsage: cpu.CpuUsage.TotalUsage, - PercpuUsage: cpu.CpuUsage.PercpuUsage, - UsageInKernelmode: cpu.CpuUsage.UsageInKernelmode, - UsageInUsermode: cpu.CpuUsage.UsageInUsermode, - }, - ThrottlingData: types.ThrottlingData{ - Periods: cpu.ThrottlingData.Periods, - ThrottledPeriods: cpu.ThrottlingData.ThrottledPeriods, - ThrottledTime: cpu.ThrottlingData.ThrottledTime, - }, - } - mem := cs.MemoryStats - s.MemoryStats = types.MemoryStats{ - Usage: mem.Usage.Usage, - MaxUsage: mem.Usage.MaxUsage, - Stats: mem.Stats, - Failcnt: mem.Usage.Failcnt, - } - pids := cs.PidsStats - s.PidsStats = types.PidsStats{ - Current: pids.Current, - } - } - - return s -} - -func copyBlkioEntry(entries []cgroups.BlkioStatEntry) []types.BlkioStatEntry { - out := make([]types.BlkioStatEntry, len(entries)) - for i, re := range entries { - out[i] = types.BlkioStatEntry{ - Major: re.Major, - Minor: re.Minor, - Op: re.Op, - Value: re.Value, - } - } - return out -} diff --git a/components/engine/daemon/stats_windows.go b/components/engine/daemon/stats_windows.go deleted file mode 100644 index 0f47cf09e0..0000000000 --- a/components/engine/daemon/stats_windows.go +++ /dev/null @@ -1,14 +0,0 @@ -package daemon - -import ( - "github.com/docker/engine-api/types" - "github.com/opencontainers/runc/libcontainer" -) - -// convertStatsToAPITypes converts the libcontainer.Stats to the api specific -// structs. This is done to preserve API compatibility and versioning. -func convertStatsToAPITypes(ls *libcontainer.Stats) *types.StatsJSON { - // TODO Windows. Refactor accordingly to fill in stats. - s := &types.StatsJSON{} - return s -} diff --git a/components/engine/daemon/top_unix.go b/components/engine/daemon/top_unix.go index 1f8ab07f04..d4a9528c98 100644 --- a/components/engine/daemon/top_unix.go +++ b/components/engine/daemon/top_unix.go @@ -33,7 +33,8 @@ func (daemon *Daemon) ContainerTop(name string, psArgs string) (*types.Container if container.IsRestarting() { return nil, errContainerIsRestarting(container.ID) } - pids, err := daemon.ExecutionDriver().GetPidsForContainer(container.ID) + + pids, err := daemon.containerd.GetPidsForContainer(container.ID) if err != nil { return nil, err } diff --git a/components/engine/daemon/unpause.go b/components/engine/daemon/unpause.go index 4af6f11222..c1ab74b0bf 100644 --- a/components/engine/daemon/unpause.go +++ b/components/engine/daemon/unpause.go @@ -35,11 +35,9 @@ func (daemon *Daemon) containerUnpause(container *container.Container) error { return fmt.Errorf("Container %s is not paused", container.ID) } - if err := daemon.execDriver.Unpause(container.Command); err != nil { + if err := daemon.containerd.Resume(container.ID); err != nil { return fmt.Errorf("Cannot unpause container %s: %s", container.ID, err) } - container.Paused = false - daemon.LogContainerEvent(container, "unpause") return nil } diff --git a/components/engine/daemon/update.go b/components/engine/daemon/update.go index ef1f6bcfd7..fee470a39c 100644 --- a/components/engine/daemon/update.go +++ b/components/engine/daemon/update.go @@ -84,7 +84,7 @@ func (daemon *Daemon) update(name string, hostConfig *container.HostConfig) erro // If container is running (including paused), we need to update configs // to the real world. if container.IsRunning() && !container.IsRestarting() { - if err := daemon.execDriver.Update(container.Command); err != nil { + if err := daemon.containerd.UpdateResources(container.ID, toContainerdResources(hostConfig.Resources)); err != nil { restoreConfig = true return errCannotUpdate(container.ID, err) } diff --git a/components/engine/daemon/update_linux.go b/components/engine/daemon/update_linux.go new file mode 100644 index 0000000000..97ba7c09a4 --- /dev/null +++ b/components/engine/daemon/update_linux.go @@ -0,0 +1,25 @@ +// +build linux + +package daemon + +import ( + "github.com/docker/docker/libcontainerd" + "github.com/docker/engine-api/types/container" +) + +func toContainerdResources(resources container.Resources) libcontainerd.Resources { + var r libcontainerd.Resources + r.BlkioWeight = uint32(resources.BlkioWeight) + r.CpuShares = uint32(resources.CPUShares) + r.CpuPeriod = uint32(resources.CPUPeriod) + r.CpuQuota = uint32(resources.CPUQuota) + r.CpusetCpus = resources.CpusetCpus + r.CpusetMems = resources.CpusetMems + r.MemoryLimit = uint32(resources.Memory) + if resources.MemorySwap > 0 { + r.MemorySwap = uint32(resources.MemorySwap) + } + r.MemoryReservation = uint32(resources.MemoryReservation) + r.KernelMemoryLimit = uint32(resources.KernelMemory) + return r +} diff --git a/components/engine/daemon/update_windows.go b/components/engine/daemon/update_windows.go new file mode 100644 index 0000000000..2cd0ff2618 --- /dev/null +++ b/components/engine/daemon/update_windows.go @@ -0,0 +1,13 @@ +// +build windows + +package daemon + +import ( + "github.com/docker/docker/libcontainerd" + "github.com/docker/engine-api/types/container" +) + +func toContainerdResources(resources container.Resources) libcontainerd.Resources { + var r libcontainerd.Resources + return r +} diff --git a/components/engine/daemon/volumes.go b/components/engine/daemon/volumes.go index d32715997d..d1b220cd91 100644 --- a/components/engine/daemon/volumes.go +++ b/components/engine/daemon/volumes.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/docker/docker/container" - "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/volume" "github.com/docker/engine-api/types" containertypes "github.com/docker/engine-api/types/container" @@ -21,7 +20,7 @@ var ( ErrVolumeReadonly = errors.New("mounted volume is marked read-only") ) -type mounts []execdriver.Mount +type mounts []container.Mount // volumeToAPIType converts a volume.Volume to the type used by the remote API func volumeToAPIType(v volume.Volume) *types.Volume { diff --git a/components/engine/daemon/volumes_unix.go b/components/engine/daemon/volumes_unix.go index 2668114f47..078fd10bf0 100644 --- a/components/engine/daemon/volumes_unix.go +++ b/components/engine/daemon/volumes_unix.go @@ -8,25 +8,24 @@ import ( "strconv" "github.com/docker/docker/container" - "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/volume" ) // setupMounts iterates through each of the mount points for a container and // calls Setup() on each. It also looks to see if is a network mount such as // /etc/resolv.conf, and if it is not, appends it to the array of mounts. -func (daemon *Daemon) setupMounts(container *container.Container) ([]execdriver.Mount, error) { - var mounts []execdriver.Mount - for _, m := range container.MountPoints { - if err := daemon.lazyInitializeVolume(container.ID, m); err != nil { +func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, error) { + var mounts []container.Mount + for _, m := range c.MountPoints { + if err := daemon.lazyInitializeVolume(c.ID, m); err != nil { return nil, err } path, err := m.Setup() if err != nil { return nil, err } - if !container.TrySetNetworkMount(m.Destination, path) { - mnt := execdriver.Mount{ + if !c.TrySetNetworkMount(m.Destination, path) { + mnt := container.Mount{ Source: path, Destination: m.Destination, Writable: m.RW, @@ -35,7 +34,7 @@ func (daemon *Daemon) setupMounts(container *container.Container) ([]execdriver. if m.Volume != nil { attributes := map[string]string{ "driver": m.Volume.DriverName(), - "container": container.ID, + "container": c.ID, "destination": m.Destination, "read/write": strconv.FormatBool(m.RW), "propagation": m.Propagation, @@ -47,7 +46,7 @@ func (daemon *Daemon) setupMounts(container *container.Container) ([]execdriver. } mounts = sortMounts(mounts) - netMounts := container.NetworkMounts() + netMounts := c.NetworkMounts() // if we are going to mount any of the network files from container // metadata, the ownership must be set properly for potential container // remapped root (user namespaces) @@ -63,7 +62,7 @@ func (daemon *Daemon) setupMounts(container *container.Container) ([]execdriver. // sortMounts sorts an array of mounts in lexicographic order. This ensure that // when mounting, the mounts don't shadow other mounts. For example, if mounting // /etc and /etc/resolv.conf, /etc/resolv.conf must not be mounted first. -func sortMounts(m []execdriver.Mount) []execdriver.Mount { +func sortMounts(m []container.Mount) []container.Mount { sort.Sort(mounts(m)) return m } diff --git a/components/engine/daemon/volumes_windows.go b/components/engine/daemon/volumes_windows.go index 23c6a3b5e3..b0ab4d6dd5 100644 --- a/components/engine/daemon/volumes_windows.go +++ b/components/engine/daemon/volumes_windows.go @@ -7,18 +7,22 @@ import ( "sort" "github.com/docker/docker/container" - "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/volume" ) // setupMounts configures the mount points for a container by appending each -// of the configured mounts on the container to the execdriver mount structure +// of the configured mounts on the container to the oci mount structure // which will ultimately be passed into the exec driver during container creation. // It also ensures each of the mounts are lexographically sorted. -func (daemon *Daemon) setupMounts(container *container.Container) ([]execdriver.Mount, error) { - var mnts []execdriver.Mount - for _, mount := range container.MountPoints { // type is volume.MountPoint - if err := daemon.lazyInitializeVolume(container.ID, mount); err != nil { + +// BUGBUG TODO Windows containerd. This would be much better if it returned +// an array of windowsoci mounts, not container mounts. Then no need to +// do multiple transitions. + +func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, error) { + var mnts []container.Mount + for _, mount := range c.MountPoints { // type is volume.MountPoint + if err := daemon.lazyInitializeVolume(c.ID, mount); err != nil { return nil, err } // If there is no source, take it from the volume path @@ -29,7 +33,7 @@ func (daemon *Daemon) setupMounts(container *container.Container) ([]execdriver. if s == "" { return nil, fmt.Errorf("No source for mount name '%s' driver %q destination '%s'", mount.Name, mount.Driver, mount.Destination) } - mnts = append(mnts, execdriver.Mount{ + mnts = append(mnts, container.Mount{ Source: s, Destination: mount.Destination, Writable: mount.RW, diff --git a/components/engine/distribution/xfer/download_test.go b/components/engine/distribution/xfer/download_test.go index 9be9a24a3b..2e4d724cd2 100644 --- a/components/engine/distribution/xfer/download_test.go +++ b/components/engine/distribution/xfer/download_test.go @@ -112,12 +112,13 @@ func (ls *mockLayerStore) CreateRWLayer(string, layer.ChainID, string, layer.Mou func (ls *mockLayerStore) GetRWLayer(string) (layer.RWLayer, error) { return nil, errors.New("not implemented") - } func (ls *mockLayerStore) ReleaseRWLayer(layer.RWLayer) ([]layer.Metadata, error) { return nil, errors.New("not implemented") - +} +func (ls *mockLayerStore) GetMountID(string) (string, error) { + return "", errors.New("not implemented") } func (ls *mockLayerStore) Cleanup() error { diff --git a/components/engine/docker/daemon.go b/components/engine/docker/daemon.go index 9846e6280b..bee921c782 100644 --- a/components/engine/docker/daemon.go +++ b/components/engine/docker/daemon.go @@ -29,6 +29,7 @@ import ( "github.com/docker/docker/daemon/logger" "github.com/docker/docker/docker/listeners" "github.com/docker/docker/dockerversion" + "github.com/docker/docker/libcontainerd" "github.com/docker/docker/opts" "github.com/docker/docker/pkg/jsonlog" flag "github.com/docker/docker/pkg/mflag" @@ -264,7 +265,13 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error { cli.TrustKeyPath = commonFlags.TrustKey registryService := registry.NewService(cli.Config.ServiceOptions) - d, err := daemon.NewDaemon(cli.Config, registryService) + + containerdRemote, err := libcontainerd.New(filepath.Join(cli.Config.ExecRoot, "libcontainerd"), cli.getPlatformRemoteOptions()...) + if err != nil { + logrus.Fatal(err) + } + + d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote) if err != nil { if pfile != nil { if err := pfile.Remove(); err != nil { @@ -279,7 +286,6 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error { logrus.WithFields(logrus.Fields{ "version": dockerversion.Version, "commit": dockerversion.GitCommit, - "execdriver": d.ExecutionDriver().Name(), "graphdriver": d.GraphDriverName(), }).Info("Docker daemon") @@ -330,6 +336,7 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error { // Wait for serve API to complete errAPI := <-serveAPIWait shutdownDaemon(d, 15) + containerdRemote.Cleanup() if errAPI != nil { if pfile != nil { if err := pfile.Remove(); err != nil { diff --git a/components/engine/docker/daemon_unix.go b/components/engine/docker/daemon_unix.go index c76700f014..775a20aa75 100644 --- a/components/engine/docker/daemon_unix.go +++ b/components/engine/docker/daemon_unix.go @@ -11,10 +11,9 @@ import ( "github.com/Sirupsen/logrus" apiserver "github.com/docker/docker/api/server" "github.com/docker/docker/daemon" + "github.com/docker/docker/libcontainerd" "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/system" - - _ "github.com/docker/docker/daemon/execdriver/native" ) const defaultDaemonConfigFile = "/etc/docker/daemon.json" @@ -65,3 +64,15 @@ func setupConfigReloadTrap(configFile string, flags *mflag.FlagSet, reload func( } }() } + +func (cli *DaemonCli) getPlatformRemoteOptions() []libcontainerd.RemoteOption { + opts := []libcontainerd.RemoteOption{ + libcontainerd.WithDebugLog(cli.Config.Debug), + } + if cli.Config.ContainerdAddr != "" { + opts = append(opts, libcontainerd.WithRemoteAddr(cli.Config.ContainerdAddr)) + } else { + opts = append(opts, libcontainerd.WithStartDaemon(true)) + } + return opts +} diff --git a/components/engine/docker/daemon_windows.go b/components/engine/docker/daemon_windows.go index 52649daf0b..ae8d737d6c 100644 --- a/components/engine/docker/daemon_windows.go +++ b/components/engine/docker/daemon_windows.go @@ -10,6 +10,7 @@ import ( "github.com/Sirupsen/logrus" apiserver "github.com/docker/docker/api/server" "github.com/docker/docker/daemon" + "github.com/docker/docker/libcontainerd" "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/system" ) @@ -57,3 +58,7 @@ func setupConfigReloadTrap(configFile string, flags *mflag.FlagSet, reload func( } }() } + +func (cli *DaemonCli) getPlatformRemoteOptions() []libcontainerd.RemoteOption { + return nil +} diff --git a/components/engine/hack/vendor.sh b/components/engine/hack/vendor.sh index 054525a4c5..b1087c767c 100755 --- a/components/engine/hack/vendor.sh +++ b/components/engine/hack/vendor.sh @@ -54,18 +54,19 @@ clone git github.com/vbatts/tar-split v0.9.11 # get desired notary commit, might also need to be updated in Dockerfile clone git github.com/docker/notary v0.2.0 -clone git google.golang.org/grpc 174192fc93efcb188fc8f46ca447f0da606b6885 https://github.com/grpc/grpc-go.git +clone git google.golang.org/grpc a22b6611561e9f0a3e0919690dd2caf48f14c517 https://github.com/grpc/grpc-go.git clone git github.com/miekg/pkcs11 df8ae6ca730422dba20c768ff38ef7d79077a59f clone git github.com/docker/go v1.5.1-1-1-gbaf439e clone git github.com/agl/ed25519 d2b94fd789ea21d12fac1a4443dd3a3f79cda72c -clone git github.com/opencontainers/runc 2c3115481ee1782ad687a9e0b4834f89533c2acf # libcontainer +clone git github.com/opencontainers/runc 7b6c4c418d5090f4f11eee949fdf49afd15838c9 # libcontainer +clone git github.com/opencontainers/specs 3ce138b1934bf227a418e241ead496c383eaba1c # specs clone git github.com/seccomp/libseccomp-golang 1b506fc7c24eec5a3693cdcbed40d9c226cfc6a1 # libcontainer deps (see src/github.com/opencontainers/runc/Godeps/Godeps.json) clone git github.com/coreos/go-systemd v4 clone git github.com/godbus/dbus v3 clone git github.com/syndtr/gocapability 2c00daeb6c3b45114c80ac44119e7b8801fdd852 -clone git github.com/golang/protobuf f7137ae6b19afbfd61a94b746fda3b3fe0491874 +clone git github.com/golang/protobuf 68415e7123da32b07eab49c96d2c4d6158360e9b # gelf logging driver deps clone git github.com/Graylog2/go-gelf aab2f594e4585d43468ac57287b0dece9d806883 @@ -87,4 +88,6 @@ clone git golang.org/x/oauth2 2baa8a1b9338cf13d9eeb27696d761155fa480be https://g clone git google.golang.org/api dc6d2353af16e2a2b0ff6986af051d473a4ed468 https://code.googlesource.com/google-api-go-client clone git google.golang.org/cloud dae7e3d993bc3812a2185af60552bb6b847e52a0 https://code.googlesource.com/gocloud +# containerd +clone git github.com/docker/containerd ab5eae56bf3a800e062c6d63fb94f766a732813f clean diff --git a/components/engine/integration-cli/daemon.go b/components/engine/integration-cli/daemon.go index 3d28b709b6..00493ab38a 100644 --- a/components/engine/integration-cli/daemon.go +++ b/components/engine/integration-cli/daemon.go @@ -142,6 +142,7 @@ func (d *Daemon) StartWithLogFile(out *os.File, providedArgs ...string) error { args := append(d.GlobalFlags, d.Command, + "--containerd", "/var/run/docker/libcontainerd/containerd.sock", "--graph", d.root, "--pidfile", fmt.Sprintf("%s/docker.pid", d.folder), fmt.Sprintf("--userland-proxy=%t", d.userlandProxy), @@ -245,6 +246,29 @@ func (d *Daemon) StartWithBusybox(arg ...string) error { return d.LoadBusybox() } +// Kill will send a SIGKILL to the daemon +func (d *Daemon) Kill() error { + if d.cmd == nil || d.wait == nil { + return errors.New("daemon not started") + } + + defer func() { + d.logFile.Close() + d.cmd = nil + }() + + if err := d.cmd.Process.Kill(); err != nil { + d.c.Logf("Could not kill daemon: %v", err) + return err + } + + if err := os.Remove(fmt.Sprintf("%s/docker.pid", d.folder)); err != nil { + return err + } + + return nil +} + // Stop will send a SIGINT every second and wait for the daemon to stop. // If it timeouts, a SIGKILL is sent. // Stop will not delete the daemon directory. If a purged daemon is needed, @@ -300,6 +324,10 @@ out2: return err } + if err := os.Remove(fmt.Sprintf("%s/docker.pid", d.folder)); err != nil { + return err + } + return nil } diff --git a/components/engine/integration-cli/docker_cli_daemon_experimental_test.go b/components/engine/integration-cli/docker_cli_daemon_experimental_test.go new file mode 100644 index 0000000000..d450858c6c --- /dev/null +++ b/components/engine/integration-cli/docker_cli_daemon_experimental_test.go @@ -0,0 +1,150 @@ +// +build daemon,!windows,experimental + +package main + +import ( + "os/exec" + "strings" + "time" + + "github.com/go-check/check" +) + +// TestDaemonRestartWithKilledRunningContainer requires live restore of running containers +func (s *DockerDaemonSuite) TestDaemonRestartWithKilledRunningContainer(t *check.C) { + // TODO(mlaventure): Not sure what would the exit code be on windows + testRequires(t, DaemonIsLinux) + if err := s.d.StartWithBusybox(); err != nil { + t.Fatal(err) + } + + cid, err := s.d.Cmd("run", "-d", "--name", "test", "busybox", "top") + defer s.d.Stop() + if err != nil { + t.Fatal(cid, err) + } + cid = strings.TrimSpace(cid) + + // Kill the daemon + if err := s.d.Kill(); err != nil { + t.Fatal(err) + } + + // kill the container + runCmd := exec.Command("ctr", "--address", "/var/run/docker/libcontainerd/containerd.sock", "containers", "kill", cid) + if out, ec, err := runCommandWithOutput(runCmd); err != nil { + t.Fatalf("Failed to run ctr, ExitCode: %d, err: '%v' output: '%s' cid: '%s'\n", ec, err, out, cid) + } + + // Give time to containerd to process the command if we don't + // the exit event might be received after we do the inspect + time.Sleep(3 * time.Second) + + // restart the daemon + if err := s.d.Start(); err != nil { + t.Fatal(err) + } + + // Check that we've got the correct exit code + out, err := s.d.Cmd("inspect", "-f", "{{.State.ExitCode}}", cid) + t.Assert(err, check.IsNil) + + out = strings.TrimSpace(out) + if out != "143" { + t.Fatalf("Expected exit code '%s' got '%s' for container '%s'\n", "143", out, cid) + } + +} + +// TestDaemonRestartWithPausedRunningContainer requires live restore of running containers +func (s *DockerDaemonSuite) TestDaemonRestartWithPausedRunningContainer(t *check.C) { + if err := s.d.StartWithBusybox(); err != nil { + t.Fatal(err) + } + + cid, err := s.d.Cmd("run", "-d", "--name", "test", "busybox", "top") + defer s.d.Stop() + if err != nil { + t.Fatal(cid, err) + } + cid = strings.TrimSpace(cid) + + // Kill the daemon + if err := s.d.Kill(); err != nil { + t.Fatal(err) + } + + // kill the container + runCmd := exec.Command("ctr", "--address", "/var/run/docker/libcontainerd/containerd.sock", "containers", "pause", cid) + if out, ec, err := runCommandWithOutput(runCmd); err != nil { + t.Fatalf("Failed to run ctr, ExitCode: %d, err: '%v' output: '%s' cid: '%s'\n", ec, err, out, cid) + } + + // Give time to containerd to process the command if we don't + // the pause event might be received after we do the inspect + time.Sleep(3 * time.Second) + + // restart the daemon + if err := s.d.Start(); err != nil { + t.Fatal(err) + } + + // Check that we've got the correct status + out, err := s.d.Cmd("inspect", "-f", "{{.State.Status}}", cid) + t.Assert(err, check.IsNil) + + out = strings.TrimSpace(out) + if out != "paused" { + t.Fatalf("Expected exit code '%s' got '%s' for container '%s'\n", "paused", out, cid) + } +} + +// TestDaemonRestartWithUnpausedRunningContainer requires live restore of running containers. +func (s *DockerDaemonSuite) TestDaemonRestartWithUnpausedRunningContainer(t *check.C) { + // TODO(mlaventure): Not sure what would the exit code be on windows + testRequires(t, DaemonIsLinux) + if err := s.d.StartWithBusybox(); err != nil { + t.Fatal(err) + } + + cid, err := s.d.Cmd("run", "-d", "--name", "test", "busybox", "top") + defer s.d.Stop() + if err != nil { + t.Fatal(cid, err) + } + cid = strings.TrimSpace(cid) + + // pause the container + if _, err := s.d.Cmd("pause", cid); err != nil { + t.Fatal(cid, err) + } + + // Kill the daemon + if err := s.d.Kill(); err != nil { + t.Fatal(err) + } + + // resume the container + runCmd := exec.Command("ctr", "--address", "/var/run/docker/libcontainerd/containerd.sock", "containers", "resume", cid) + if out, ec, err := runCommandWithOutput(runCmd); err != nil { + t.Fatalf("Failed to run ctr, ExitCode: %d, err: '%v' output: '%s' cid: '%s'\n", ec, err, out, cid) + } + + // Give time to containerd to process the command if we don't + // the resume event might be received after we do the inspect + time.Sleep(3 * time.Second) + + // restart the daemon + if err := s.d.Start(); err != nil { + t.Fatal(err) + } + + // Check that we've got the correct status + out, err := s.d.Cmd("inspect", "-f", "{{.State.Status}}", cid) + t.Assert(err, check.IsNil) + + out = strings.TrimSpace(out) + if out != "running" { + t.Fatalf("Expected exit code '%s' got '%s' for container '%s'\n", "running", out, cid) + } +} diff --git a/components/engine/integration-cli/docker_cli_daemon_test.go b/components/engine/integration-cli/docker_cli_daemon_test.go index b474867cf3..e9514c281f 100644 --- a/components/engine/integration-cli/docker_cli_daemon_test.go +++ b/components/engine/integration-cli/docker_cli_daemon_test.go @@ -1507,7 +1507,18 @@ func (s *DockerDaemonSuite) TestCleanupMountsAfterCrash(c *check.C) { out, err := s.d.Cmd("run", "-d", "busybox", "top") c.Assert(err, check.IsNil, check.Commentf("Output: %s", out)) id := strings.TrimSpace(out) - c.Assert(s.d.cmd.Process.Signal(os.Kill), check.IsNil) + c.Assert(s.d.Kill(), check.IsNil) + + // kill the container + runCmd := exec.Command("ctr", "--address", "/var/run/docker/libcontainerd/containerd.sock", "containers", "kill", id) + if out, ec, err := runCommandWithOutput(runCmd); err != nil { + c.Fatalf("Failed to run ctr, ExitCode: %d, err: '%v' output: '%s' cid: '%s'\n", ec, err, out, id) + } + + // Give time to containerd to process the command if we don't + // the exit event might be received after we do the inspect + time.Sleep(3 * time.Second) + c.Assert(s.d.Start(), check.IsNil) mountOut, err := ioutil.ReadFile("/proc/self/mountinfo") c.Assert(err, check.IsNil, check.Commentf("Output: %s", mountOut)) @@ -1840,6 +1851,7 @@ func (s *DockerDaemonSuite) TestDaemonNoSpaceleftOnDeviceError(c *check.C) { // Test daemon restart with container links + auto restart func (s *DockerDaemonSuite) TestDaemonRestartContainerLinksRestart(c *check.C) { d := NewDaemon(c) + defer d.Stop() err := d.StartWithBusybox() c.Assert(err, checker.IsNil) diff --git a/components/engine/integration-cli/docker_cli_exec_test.go b/components/engine/integration-cli/docker_cli_exec_test.go index 4d04126350..81f36711bd 100644 --- a/components/engine/integration-cli/docker_cli_exec_test.go +++ b/components/engine/integration-cli/docker_cli_exec_test.go @@ -8,7 +8,6 @@ import ( "net/http" "os" "os/exec" - "path/filepath" "reflect" "sort" "strings" @@ -375,57 +374,6 @@ func (s *DockerSuite) TestLinksPingLinkedContainersOnRename(c *check.C) { dockerCmd(c, "exec", "container2", "ping", "-c", "1", "alias1", "-W", "1") } -func (s *DockerSuite) TestExecDir(c *check.C) { - // TODO Windows CI. This requires some work to port as it uses execDriverPath - // which is currently (and incorrectly) hard coded as a string assuming - // the daemon is running Linux :( - testRequires(c, SameHostDaemon, DaemonIsLinux) - - out, _ := runSleepingContainer(c, "-d") - id := strings.TrimSpace(out) - - execDir := filepath.Join(execDriverPath, id) - stateFile := filepath.Join(execDir, "state.json") - - { - fi, err := os.Stat(execDir) - c.Assert(err, checker.IsNil) - if !fi.IsDir() { - c.Fatalf("%q must be a directory", execDir) - } - fi, err = os.Stat(stateFile) - c.Assert(err, checker.IsNil) - } - - dockerCmd(c, "stop", id) - { - _, err := os.Stat(execDir) - c.Assert(err, checker.NotNil) - c.Assert(err, checker.NotNil, check.Commentf("Exec directory %q exists for removed container!", execDir)) - if !os.IsNotExist(err) { - c.Fatalf("Error should be about non-existing, got %s", err) - } - } - dockerCmd(c, "start", id) - { - fi, err := os.Stat(execDir) - c.Assert(err, checker.IsNil) - if !fi.IsDir() { - c.Fatalf("%q must be a directory", execDir) - } - fi, err = os.Stat(stateFile) - c.Assert(err, checker.IsNil) - } - dockerCmd(c, "rm", "-f", id) - { - _, err := os.Stat(execDir) - c.Assert(err, checker.NotNil, check.Commentf("Exec directory %q exists for removed container!", execDir)) - if !os.IsNotExist(err) { - c.Fatalf("Error should be about non-existing, got %s", err) - } - } -} - func (s *DockerSuite) TestRunMutableNetworkFiles(c *check.C) { // Not applicable on Windows to Windows CI. testRequires(c, SameHostDaemon, DaemonIsLinux) diff --git a/components/engine/integration-cli/docker_cli_info_test.go b/components/engine/integration-cli/docker_cli_info_test.go index c74f7b4eac..dd2369452d 100644 --- a/components/engine/integration-cli/docker_cli_info_test.go +++ b/components/engine/integration-cli/docker_cli_info_test.go @@ -22,7 +22,6 @@ func (s *DockerSuite) TestInfoEnsureSucceeds(c *check.C) { " Paused:", " Stopped:", "Images:", - "Execution Driver:", "OSType:", "Architecture:", "Logging Driver:", diff --git a/components/engine/integration-cli/docker_cli_run_test.go b/components/engine/integration-cli/docker_cli_run_test.go index de95d50aa6..309912bb70 100644 --- a/components/engine/integration-cli/docker_cli_run_test.go +++ b/components/engine/integration-cli/docker_cli_run_test.go @@ -1109,7 +1109,7 @@ func (s *DockerSuite) TestRunProcNotWritableInNonPrivilegedContainers(c *check.C func (s *DockerSuite) TestRunProcWritableInPrivilegedContainers(c *check.C) { // Not applicable for Windows as there is no concept of --privileged testRequires(c, DaemonIsLinux, NotUserNamespace) - if _, code := dockerCmd(c, "run", "--privileged", "busybox", "touch", "/proc/sysrq-trigger"); code != 0 { + if _, code := dockerCmd(c, "run", "--privileged", "busybox", "sh", "-c", "umount /proc/sysrq-trigger && touch /proc/sysrq-trigger"); code != 0 { c.Fatalf("proc should be writable in privileged container") } } @@ -3021,7 +3021,8 @@ func (s *DockerSuite) TestRunUnshareProc(c *check.C) { out, _, err := dockerCmdWithError("run", "--name", name, "--security-opt", "seccomp=unconfined", "debian:jessie", "unshare", "-p", "-m", "-f", "-r", "mount", "-t", "proc", "none", "/proc") if err == nil || !(strings.Contains(strings.ToLower(out), "mount: cannot mount none") || - strings.Contains(strings.ToLower(out), "permission denied")) { + strings.Contains(strings.ToLower(out), "permission denied") || + strings.Contains(strings.ToLower(out), "operation not permitted")) { errChan <- fmt.Errorf("unshare and mount of /proc should have failed with 'mount: cannot mount none' or 'permission denied', got: %s, %v", out, err) } else { errChan <- nil @@ -3034,7 +3035,8 @@ func (s *DockerSuite) TestRunUnshareProc(c *check.C) { out, _, err := dockerCmdWithError("run", "--privileged", "--security-opt", "seccomp=unconfined", "--security-opt", "apparmor=docker-default", "--name", name, "debian:jessie", "unshare", "-p", "-m", "-f", "-r", "mount", "-t", "proc", "none", "/proc") if err == nil || !(strings.Contains(strings.ToLower(out), "mount: cannot mount none") || - strings.Contains(strings.ToLower(out), "permission denied")) { + strings.Contains(strings.ToLower(out), "permission denied") || + strings.Contains(strings.ToLower(out), "operation not permitted")) { errChan <- fmt.Errorf("privileged unshare with apparmor should have failed with 'mount: cannot mount none' or 'permission denied', got: %s, %v", out, err) } else { errChan <- nil @@ -4232,7 +4234,10 @@ func (s *DockerSuite) TestRunAttachFailedNoLeak(c *check.C) { out, _, err := dockerCmdWithError("run", "-p", "8000:8000", "busybox", "true") c.Assert(err, checker.NotNil) // check for windows error as well - c.Assert(strings.Contains(string(out), "port is already allocated") || strings.Contains(string(out), "were not connected because a duplicate name exists"), checker.Equals, true, check.Commentf("Output: %s", out)) + // TODO Windows Post TP5. Fix the error message string + c.Assert(strings.Contains(string(out), "port is already allocated") || + strings.Contains(string(out), "were not connected because a duplicate name exists") || + strings.Contains(string(out), "HNS failed with error : Failed to create endpoint"), checker.Equals, true, check.Commentf("Output: %s", out)) dockerCmd(c, "rm", "-f", "test") // NGoroutines is not updated right away, so we need to wait before failing diff --git a/components/engine/layer/layer.go b/components/engine/layer/layer.go index bdfe6e75e5..26a82440ea 100644 --- a/components/engine/layer/layer.go +++ b/components/engine/layer/layer.go @@ -169,6 +169,7 @@ type Store interface { CreateRWLayer(id string, parent ChainID, mountLabel string, initFunc MountInit) (RWLayer, error) GetRWLayer(id string) (RWLayer, error) + GetMountID(id string) (string, error) ReleaseRWLayer(RWLayer) ([]Metadata, error) Cleanup() error diff --git a/components/engine/layer/layer_store.go b/components/engine/layer/layer_store.go index 4b01ea0fc0..fa436f098b 100644 --- a/components/engine/layer/layer_store.go +++ b/components/engine/layer/layer_store.go @@ -480,6 +480,18 @@ func (ls *layerStore) GetRWLayer(id string) (RWLayer, error) { return mount.getReference(), nil } +func (ls *layerStore) GetMountID(id string) (string, error) { + ls.mountL.Lock() + defer ls.mountL.Unlock() + mount, ok := ls.mounts[id] + if !ok { + return "", ErrMountDoesNotExist + } + logrus.Debugf("GetRWLayer id: %s -> mountID: %s", id, mount.mountID) + + return mount.mountID, nil +} + func (ls *layerStore) ReleaseRWLayer(l RWLayer) ([]Metadata, error) { ls.mountL.Lock() defer ls.mountL.Unlock() diff --git a/components/engine/libcontainerd/client.go b/components/engine/libcontainerd/client.go new file mode 100644 index 0000000000..4485b75cf9 --- /dev/null +++ b/components/engine/libcontainerd/client.go @@ -0,0 +1,58 @@ +package libcontainerd + +import ( + "fmt" + "sync" + + "github.com/Sirupsen/logrus" +) + +// clientCommon contains the platform agnostic fields used in the client structure +type clientCommon struct { + backend Backend + containers map[string]*container + containerMutexes map[string]*sync.Mutex // lock by container ID + mapMutex sync.RWMutex // protects read/write oprations from containers map + sync.Mutex // lock for containerMutexes map access +} + +func (clnt *client) lock(containerID string) { + clnt.Lock() + if _, ok := clnt.containerMutexes[containerID]; !ok { + clnt.containerMutexes[containerID] = &sync.Mutex{} + } + clnt.Unlock() + clnt.containerMutexes[containerID].Lock() +} + +func (clnt *client) unlock(containerID string) { + clnt.Lock() + if l, ok := clnt.containerMutexes[containerID]; ok { + l.Unlock() + } else { + logrus.Warnf("unlock of non-existing mutex: %s", containerID) + } + clnt.Unlock() +} + +// must hold a lock for cont.containerID +func (clnt *client) appendContainer(cont *container) { + clnt.mapMutex.Lock() + clnt.containers[cont.containerID] = cont + clnt.mapMutex.Unlock() +} +func (clnt *client) deleteContainer(friendlyName string) { + clnt.mapMutex.Lock() + delete(clnt.containers, friendlyName) + clnt.mapMutex.Unlock() +} + +func (clnt *client) getContainer(containerID string) (*container, error) { + clnt.mapMutex.RLock() + container, ok := clnt.containers[containerID] + defer clnt.mapMutex.RUnlock() + if !ok { + return nil, fmt.Errorf("invalid container: %s", containerID) // fixme: typed error + } + return container, nil +} diff --git a/components/engine/libcontainerd/client_linux.go b/components/engine/libcontainerd/client_linux.go new file mode 100644 index 0000000000..189345b286 --- /dev/null +++ b/components/engine/libcontainerd/client_linux.go @@ -0,0 +1,394 @@ +package libcontainerd + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "sync" + "syscall" + + "github.com/Sirupsen/logrus" + containerd "github.com/docker/containerd/api/grpc/types" + "github.com/docker/docker/pkg/idtools" + "github.com/docker/docker/pkg/mount" + "github.com/opencontainers/specs/specs-go" + "golang.org/x/net/context" +) + +type client struct { + clientCommon + + // Platform specific properties below here. + remote *remote + q queue + exitNotifiers map[string]*exitNotifier +} + +func (clnt *client) AddProcess(containerID, processFriendlyName string, specp Process) error { + clnt.lock(containerID) + defer clnt.unlock(containerID) + container, err := clnt.getContainer(containerID) + if err != nil { + return err + } + + spec, err := container.spec() + if err != nil { + return err + } + sp := spec.Process + sp.Args = specp.Args + sp.Terminal = specp.Terminal + if specp.Env != nil { + sp.Env = specp.Env + } + if specp.Cwd != nil { + sp.Cwd = *specp.Cwd + } + if specp.User != nil { + sp.User = specs.User{ + UID: specp.User.UID, + GID: specp.User.GID, + AdditionalGids: specp.User.AdditionalGids, + } + } + if specp.Capabilities != nil { + sp.Capabilities = specp.Capabilities + } + + p := container.newProcess(processFriendlyName) + + r := &containerd.AddProcessRequest{ + Args: sp.Args, + Cwd: sp.Cwd, + Terminal: sp.Terminal, + Id: containerID, + Env: sp.Env, + User: &containerd.User{ + Uid: sp.User.UID, + Gid: sp.User.GID, + AdditionalGids: sp.User.AdditionalGids, + }, + Pid: processFriendlyName, + Stdin: p.fifo(syscall.Stdin), + Stdout: p.fifo(syscall.Stdout), + Stderr: p.fifo(syscall.Stderr), + Capabilities: sp.Capabilities, + ApparmorProfile: sp.ApparmorProfile, + SelinuxLabel: sp.SelinuxLabel, + NoNewPrivileges: sp.NoNewPrivileges, + } + + iopipe, err := p.openFifos(sp.Terminal) + if err != nil { + return err + } + + if _, err := clnt.remote.apiClient.AddProcess(context.Background(), r); err != nil { + p.closeFifos(iopipe) + return err + } + + container.processes[processFriendlyName] = p + + clnt.unlock(containerID) + + if err := clnt.backend.AttachStreams(processFriendlyName, *iopipe); err != nil { + return err + } + clnt.lock(containerID) + + return nil +} + +func (clnt *client) prepareBundleDir(uid, gid int) (string, error) { + root, err := filepath.Abs(clnt.remote.stateDir) + if err != nil { + return "", err + } + if uid == 0 && gid == 0 { + return root, nil + } + p := string(filepath.Separator) + for _, d := range strings.Split(root, string(filepath.Separator))[1:] { + p = filepath.Join(p, d) + fi, err := os.Stat(p) + if err != nil && !os.IsNotExist(err) { + return "", err + } + if os.IsNotExist(err) || fi.Mode()&1 == 0 { + p = fmt.Sprintf("%s.%d.%d", p, uid, gid) + if err := idtools.MkdirAs(p, 0700, uid, gid); err != nil && !os.IsExist(err) { + return "", err + } + } + } + return p, nil +} + +func (clnt *client) Create(containerID string, spec Spec, options ...CreateOption) (err error) { + clnt.lock(containerID) + defer clnt.unlock(containerID) + + if ctr, err := clnt.getContainer(containerID); err == nil { + if ctr.restarting { // docker doesn't actually call start if restart is on atm, but probably should in the future + ctr.restartManager.Cancel() + ctr.clean() + } else { + return fmt.Errorf("Container %s is aleady active", containerID) + } + } + + uid, gid, err := getRootIDs(specs.Spec(spec)) + if err != nil { + return err + } + dir, err := clnt.prepareBundleDir(uid, gid) + if err != nil { + return err + } + + container := clnt.newContainer(filepath.Join(dir, containerID), options...) + if err := container.clean(); err != nil { + return err + } + + defer func() { + if err != nil { + container.clean() + clnt.deleteContainer(containerID) + } + }() + + // uid/gid + rootfsDir := filepath.Join(container.dir, "rootfs") + if err := idtools.MkdirAllAs(rootfsDir, 0700, uid, gid); err != nil && !os.IsExist(err) { + return err + } + if err := syscall.Mount(spec.Root.Path, rootfsDir, "bind", syscall.MS_REC|syscall.MS_BIND, ""); err != nil { + return err + } + spec.Root.Path = "rootfs" + + f, err := os.Create(filepath.Join(container.dir, configFilename)) + if err != nil { + return err + } + defer f.Close() + if err := json.NewEncoder(f).Encode(spec); err != nil { + return err + } + + return container.start() +} + +func (clnt *client) Signal(containerID string, sig int) error { + clnt.lock(containerID) + defer clnt.unlock(containerID) + _, err := clnt.remote.apiClient.Signal(context.Background(), &containerd.SignalRequest{ + Id: containerID, + Pid: InitFriendlyName, + Signal: uint32(sig), + }) + return err +} + +func (clnt *client) Resize(containerID, processFriendlyName string, width, height int) error { + clnt.lock(containerID) + defer clnt.unlock(containerID) + if _, err := clnt.getContainer(containerID); err != nil { + return err + } + _, err := clnt.remote.apiClient.UpdateProcess(context.Background(), &containerd.UpdateProcessRequest{ + Id: containerID, + Pid: processFriendlyName, + Width: uint32(width), + Height: uint32(height), + }) + return err +} + +func (clnt *client) Pause(containerID string) error { + return clnt.setState(containerID, StatePause) +} + +func (clnt *client) setState(containerID, state string) error { + clnt.lock(containerID) + container, err := clnt.getContainer(containerID) + if err != nil { + clnt.unlock(containerID) + return err + } + if container.systemPid == 0 { + clnt.unlock(containerID) + return fmt.Errorf("No active process for container %s", containerID) + } + st := "running" + if state == StatePause { + st = "paused" + } + chstate := make(chan struct{}) + _, err = clnt.remote.apiClient.UpdateContainer(context.Background(), &containerd.UpdateContainerRequest{ + Id: containerID, + Pid: InitFriendlyName, + Status: st, + }) + if err != nil { + clnt.unlock(containerID) + return err + } + container.pauseMonitor.append(state, chstate) + clnt.unlock(containerID) + <-chstate + return nil +} + +func (clnt *client) Resume(containerID string) error { + return clnt.setState(containerID, StateResume) +} + +func (clnt *client) Stats(containerID string) (*Stats, error) { + resp, err := clnt.remote.apiClient.Stats(context.Background(), &containerd.StatsRequest{containerID}) + if err != nil { + return nil, err + } + return (*Stats)(resp), nil +} + +func (clnt *client) setExited(containerID string) error { + clnt.lock(containerID) + defer clnt.unlock(containerID) + + var exitCode uint32 + if event, ok := clnt.remote.pastEvents[containerID]; ok { + exitCode = event.Status + delete(clnt.remote.pastEvents, containerID) + } + + err := clnt.backend.StateChanged(containerID, StateInfo{ + State: StateExit, + ExitCode: exitCode, + }) + + // Unmount and delete the bundle folder + if mts, err := mount.GetMounts(); err == nil { + for _, mts := range mts { + if strings.HasSuffix(mts.Mountpoint, containerID+"/rootfs") { + if err := syscall.Unmount(mts.Mountpoint, syscall.MNT_DETACH); err == nil { + os.RemoveAll(strings.TrimSuffix(mts.Mountpoint, "/rootfs")) + } + break + } + } + } + + return err +} + +func (clnt *client) GetPidsForContainer(containerID string) ([]int, error) { + cont, err := clnt.getContainerdContainer(containerID) + if err != nil { + return nil, err + } + pids := make([]int, len(cont.Pids)) + for i, p := range cont.Pids { + pids[i] = int(p) + } + return pids, nil +} + +func (clnt *client) getContainerdContainer(containerID string) (*containerd.Container, error) { + resp, err := clnt.remote.apiClient.State(context.Background(), &containerd.StateRequest{Id: containerID}) + if err != nil { + return nil, err + } + for _, cont := range resp.Containers { + if cont.Id == containerID { + return cont, nil + } + } + return nil, fmt.Errorf("invalid state response") +} + +func (clnt *client) newContainer(dir string, options ...CreateOption) *container { + container := &container{ + containerCommon: containerCommon{ + process: process{ + dir: dir, + processCommon: processCommon{ + containerID: filepath.Base(dir), + client: clnt, + friendlyName: InitFriendlyName, + }, + }, + processes: make(map[string]*process), + }, + } + for _, option := range options { + if err := option.Apply(container); err != nil { + logrus.Error(err) + } + } + return container +} + +func (clnt *client) UpdateResources(containerID string, resources Resources) error { + clnt.lock(containerID) + defer clnt.unlock(containerID) + container, err := clnt.getContainer(containerID) + if err != nil { + return err + } + if container.systemPid == 0 { + return fmt.Errorf("No active process for container %s", containerID) + } + _, err = clnt.remote.apiClient.UpdateContainer(context.Background(), &containerd.UpdateContainerRequest{ + Id: containerID, + Pid: InitFriendlyName, + Resources: (*containerd.UpdateResource)(&resources), + }) + if err != nil { + return err + } + return nil +} + +func (clnt *client) getExitNotifier(containerID string) *exitNotifier { + clnt.mapMutex.RLock() + defer clnt.mapMutex.RUnlock() + return clnt.exitNotifiers[containerID] +} + +func (clnt *client) getOrCreateExitNotifier(containerID string) *exitNotifier { + clnt.mapMutex.Lock() + w, ok := clnt.exitNotifiers[containerID] + defer clnt.mapMutex.Unlock() + if !ok { + w = &exitNotifier{c: make(chan struct{}), client: clnt} + clnt.exitNotifiers[containerID] = w + } + return w +} + +type exitNotifier struct { + id string + client *client + c chan struct{} + once sync.Once +} + +func (en *exitNotifier) close() { + en.once.Do(func() { + close(en.c) + en.client.mapMutex.Lock() + if en == en.client.exitNotifiers[en.id] { + delete(en.client.exitNotifiers, en.id) + } + en.client.mapMutex.Unlock() + }) +} +func (en *exitNotifier) wait() <-chan struct{} { + return en.c +} diff --git a/components/engine/libcontainerd/client_liverestore_linux.go b/components/engine/libcontainerd/client_liverestore_linux.go new file mode 100644 index 0000000000..1a1f7fe73c --- /dev/null +++ b/components/engine/libcontainerd/client_liverestore_linux.go @@ -0,0 +1,83 @@ +// +build experimental + +package libcontainerd + +import ( + "fmt" + + "github.com/Sirupsen/logrus" + containerd "github.com/docker/containerd/api/grpc/types" +) + +func (clnt *client) restore(cont *containerd.Container, options ...CreateOption) (err error) { + clnt.lock(cont.Id) + defer clnt.unlock(cont.Id) + + logrus.Debugf("restore container %s state %s", cont.Id, cont.Status) + + containerID := cont.Id + if _, err := clnt.getContainer(containerID); err == nil { + return fmt.Errorf("container %s is aleady active", containerID) + } + + defer func() { + if err != nil { + clnt.deleteContainer(cont.Id) + } + }() + + container := clnt.newContainer(cont.BundlePath, options...) + container.systemPid = systemPid(cont) + + var terminal bool + for _, p := range cont.Processes { + if p.Pid == InitFriendlyName { + terminal = p.Terminal + } + } + + iopipe, err := container.openFifos(terminal) + if err != nil { + return err + } + + if err := clnt.backend.AttachStreams(containerID, *iopipe); err != nil { + return err + } + + clnt.appendContainer(container) + + err = clnt.backend.StateChanged(containerID, StateInfo{ + State: StateRestore, + Pid: container.systemPid, + }) + + if err != nil { + return err + } + + if event, ok := clnt.remote.pastEvents[containerID]; ok { + // This should only be a pause or resume event + if event.Type == StatePause || event.Type == StateResume { + return clnt.backend.StateChanged(containerID, StateInfo{ + State: event.Type, + Pid: container.systemPid, + }) + } + + logrus.Warnf("unexpected backlog event: %#v", event) + } + + return nil +} + +func (clnt *client) Restore(containerID string, options ...CreateOption) error { + cont, err := clnt.getContainerdContainer(containerID) + if err == nil && cont.Status != "stopped" { + if err := clnt.restore(cont, options...); err != nil { + logrus.Errorf("error restoring %s: %v", containerID, err) + } + return nil + } + return clnt.setExited(containerID) +} diff --git a/components/engine/libcontainerd/client_shutdownrestore_linux.go b/components/engine/libcontainerd/client_shutdownrestore_linux.go new file mode 100644 index 0000000000..9d32b1d6ca --- /dev/null +++ b/components/engine/libcontainerd/client_shutdownrestore_linux.go @@ -0,0 +1,39 @@ +// +build !experimental + +package libcontainerd + +import ( + "syscall" + "time" + + "github.com/Sirupsen/logrus" +) + +func (clnt *client) Restore(containerID string, options ...CreateOption) error { + w := clnt.getOrCreateExitNotifier(containerID) + defer w.close() + cont, err := clnt.getContainerdContainer(containerID) + if err == nil && cont.Status != "stopped" { + clnt.lock(cont.Id) + container := clnt.newContainer(cont.BundlePath) + container.systemPid = systemPid(cont) + clnt.appendContainer(container) + clnt.unlock(cont.Id) + + if err := clnt.Signal(containerID, int(syscall.SIGTERM)); err != nil { + logrus.Errorf("error sending sigterm to %v: %v", containerID, err) + } + select { + case <-time.After(10 * time.Second): + if err := clnt.Signal(containerID, int(syscall.SIGKILL)); err != nil { + logrus.Errorf("error sending sigkill to %v: %v", containerID, err) + } + select { + case <-time.After(2 * time.Second): + case <-w.wait(): + } + case <-w.wait(): + } + } + return clnt.setExited(containerID) +} diff --git a/components/engine/libcontainerd/client_windows.go b/components/engine/libcontainerd/client_windows.go new file mode 100644 index 0000000000..b97d03d234 --- /dev/null +++ b/components/engine/libcontainerd/client_windows.go @@ -0,0 +1,579 @@ +package libcontainerd + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "path/filepath" + "strconv" + "strings" + + "syscall" + "time" + + "github.com/Microsoft/hcsshim" + "github.com/Sirupsen/logrus" +) + +type client struct { + clientCommon + + // Platform specific properties below here (none presently on Windows) +} + +// defaultContainerNAT is the default name of the container NAT device that is +// preconfigured on the server. TODO Windows - Remove for TP5 support as not needed. +const defaultContainerNAT = "ContainerNAT" + +// Win32 error codes that are used for various workarounds +// These really should be ALL_CAPS to match golangs syscall library and standard +// Win32 error conventions, but golint insists on CamelCase. +const ( + CoEClassstring = syscall.Errno(0x800401F3) // Invalid class string + ErrorNoNetwork = syscall.Errno(1222) // The network is not present or not started + ErrorBadPathname = syscall.Errno(161) // The specified path is invalid + ErrorInvalidObject = syscall.Errno(0x800710D8) // The object identifier does not represent a valid object +) + +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 + //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 +} + +// TODO Windows RTM: @darrenstahlmsft Add ProcessorCount +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 // Optimization hint for container startup in Windows + LayerFolderPath string // Where the layer folders are located + Layers []layer // List of storage layers + ProcessorWeight uint64 `json:",omitempty"` // CPU Shares 0..10000 on Windows; where 0 will be omitted and HCS will default. + ProcessorMaximum int64 `json:",omitempty"` // CPU maximum usage percent 1..100 + StorageIOPSMaximum uint64 `json:",omitempty"` // Maximum Storage IOPS + StorageBandwidthMaximum uint64 `json:",omitempty"` // Maximum Storage Bandwidth in bytes per second + StorageSandboxSize uint64 `json:",omitempty"` // Size in bytes that the container system drive should be expanded to if smaller + MemoryMaximumInMB int64 `json:",omitempty"` // Maximum memory available to the container in Megabytes + HostName string // Hostname + MappedDirectories []mappedDir // List of mapped directories (volumes/mounts) + SandboxPath string // Location of unmounted sandbox (used for Hyper-V containers) + HvPartition bool // True if it a Hyper-V Container + EndpointList []string // List of networking endpoints to be attached to 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" + +// Create is the entrypoint to create a container from a spec, and if successfully +// created, start it too. +func (clnt *client) Create(containerID string, spec Spec, options ...CreateOption) error { + logrus.Debugln("LCD client.Create() with spec", spec) + + cu := &containerInit{ + SystemType: "Container", + Name: containerID, + Owner: defaultOwner, + + VolumePath: spec.Root.Path, + IgnoreFlushesDuringBoot: spec.Windows.FirstStart, + LayerFolderPath: spec.Windows.LayerFolder, + HostName: spec.Hostname, + } + + if spec.Windows.Networking != nil { + cu.EndpointList = spec.Windows.Networking.EndpointList + } + + if spec.Windows.Resources != nil { + if spec.Windows.Resources.CPU != nil { + if spec.Windows.Resources.CPU.Shares != nil { + cu.ProcessorWeight = *spec.Windows.Resources.CPU.Shares + } + if spec.Windows.Resources.CPU.Percent != nil { + cu.ProcessorMaximum = *spec.Windows.Resources.CPU.Percent * 100 // ProcessorMaximum is a value between 1 and 10000 + } + } + if spec.Windows.Resources.Memory != nil { + if spec.Windows.Resources.Memory.Limit != nil { + cu.MemoryMaximumInMB = *spec.Windows.Resources.Memory.Limit / 1024 / 1024 + } + } + if spec.Windows.Resources.Storage != nil { + if spec.Windows.Resources.Storage.Bps != nil { + cu.StorageBandwidthMaximum = *spec.Windows.Resources.Storage.Bps + } + if spec.Windows.Resources.Storage.Iops != nil { + cu.StorageIOPSMaximum = *spec.Windows.Resources.Storage.Iops + } + if spec.Windows.Resources.Storage.SandboxSize != nil { + cu.StorageSandboxSize = *spec.Windows.Resources.Storage.SandboxSize + } + } + } + + // TODO Ultimately need to set the path from HvRuntime.ImagePath + cu.HvPartition = (spec.Windows.HvRuntime != nil) + // if spec.Windows.HvRuntime != nil { + // cu.HvPartition = len(spec.Windows.HvRuntime.ImagePath) > 0 + // } + + if cu.HvPartition { + cu.SandboxPath = filepath.Dir(spec.Windows.LayerFolder) + } else { + cu.VolumePath = spec.Root.Path + cu.LayerFolderPath = spec.Windows.LayerFolder + } + + for _, layerPath := range spec.Windows.LayerPaths { + _, filename := filepath.Split(layerPath) + g, err := hcsshim.NameToGuid(filename) + if err != nil { + return 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(spec.Mounts)) + for i, mount := range spec.Mounts { + mds[i] = mappedDir{ + HostPath: mount.Source, + ContainerPath: mount.Destination, + ReadOnly: mount.Readonly} + } + cu.MappedDirectories = mds + + // TODO Windows: vv START OF TP4 BLOCK OF CODE. REMOVE ONCE TP4 IS NO LONGER SUPPORTED + if hcsshim.IsTP4() && + spec.Windows.Networking != nil && + spec.Windows.Networking.Bridge != "" { + // 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. + var pbs []portBinding + for i, v := range spec.Windows.Networking.PortBindings { + proto := strings.ToUpper(i.Proto()) + if proto != "TCP" && proto != "UDP" { + return fmt.Errorf("invalid protocol %s", i.Proto()) + } + + if len(v) > 1 { + return 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 fmt.Errorf("Windows does not support host IP addresses in NAT settings") + } + if ePort, err = strconv.Atoi(v2.HostPort); err != nil { + return fmt.Errorf("invalid container port %s: %s", v2.HostPort, err) + } + if iPort, err = strconv.Atoi(i.Port()); err != nil { + return fmt.Errorf("invalid internal port %s: %s", i.Port(), err) + } + if iPort < 0 || iPort > 65535 || ePort < 0 || ePort > 65535 { + return fmt.Errorf("specified NAT port is not in allowed range") + } + pbs = append(pbs, + portBinding{ExternalPort: ePort, + InternalPort: iPort, + Protocol: proto}) + } + } + + dev := device{ + DeviceType: "Network", + Connection: &networkConnection{ + NetworkName: spec.Windows.Networking.Bridge, + Nat: natSettings{ + Name: defaultContainerNAT, + PortBindings: pbs, + }, + }, + } + + if spec.Windows.Networking.MacAddress != "" { + windowsStyleMAC := strings.Replace( + spec.Windows.Networking.MacAddress, ":", "-", -1) + dev.Settings = networkSettings{ + MacAddress: windowsStyleMAC, + } + } + cu.Devices = append(cu.Devices, dev) + } else { + logrus.Debugln("No network interface") + } + // TODO Windows: ^^ END OF TP4 BLOCK OF CODE. REMOVE ONCE TP4 IS NO LONGER SUPPORTED + + configurationb, err := json.Marshal(cu) + if err != nil { + return 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. + if !hcsshim.IsTP4() { + if err := hcsshim.CreateComputeSystem(containerID, configuration); err != nil { + return err + } + } else { + maxAttempts := 5 + for i := 1; i <= maxAttempts; i++ { + err = hcsshim.CreateComputeSystem(containerID, configuration) + if err == nil { + break + } + + if herr, ok := err.(*hcsshim.HcsError); ok { + if herr.Err != syscall.ERROR_NOT_FOUND && // Element not found + herr.Err != syscall.ERROR_FILE_NOT_FOUND && // The system cannot find the file specified + herr.Err != ErrorNoNetwork && // The network is not present or not started + herr.Err != ErrorBadPathname && // The specified path is invalid + herr.Err != CoEClassstring && // Invalid class string + herr.Err != ErrorInvalidObject { // The object identifier does not represent a valid object + logrus.Debugln("Failed to create temporary container ", err) + return err + } + logrus.Warnf("Invoking Windows TP4 retry hack (%d of %d)", i, maxAttempts-1) + time.Sleep(50 * time.Millisecond) + } + } + } + + // Construct a container object for calling start on it. + container := &container{ + containerCommon: containerCommon{ + process: process{ + processCommon: processCommon{ + containerID: containerID, + client: clnt, + friendlyName: InitFriendlyName, + }, + }, + processes: make(map[string]*process), + }, + ociSpec: spec, + } + + container.options = options + for _, option := range options { + if err := option.Apply(container); err != nil { + logrus.Error(err) + } + } + + // Call start, and if it fails, delete the container from our + // internal structure, and also keep HCS in sync by deleting the + // container there. + logrus.Debugf("Create() id=%s, Calling start()", containerID) + if err := container.start(); err != nil { + clnt.deleteContainer(containerID) + return err + } + + logrus.Debugf("Create() id=%s completed successfully", containerID) + return nil + +} + +// AddProcess is the handler for adding a process to an already running +// container. It's called through docker exec. +func (clnt *client) AddProcess(containerID, processFriendlyName string, procToAdd Process) error { + + clnt.lock(containerID) + defer clnt.unlock(containerID) + container, err := clnt.getContainer(containerID) + if err != nil { + return err + } + + createProcessParms := hcsshim.CreateProcessParams{ + EmulateConsole: procToAdd.Terminal, + ConsoleSize: procToAdd.InitialConsoleSize, + } + + // Take working directory from the process to add if it is defined, + // otherwise take from the first process. + if procToAdd.Cwd != "" { + createProcessParms.WorkingDirectory = procToAdd.Cwd + } else { + createProcessParms.WorkingDirectory = container.ociSpec.Process.Cwd + } + + // Configure the environment for the process + createProcessParms.Environment = setupEnvironmentVariables(procToAdd.Env) + createProcessParms.CommandLine = strings.Join(procToAdd.Args, " ") + + logrus.Debugf("commandLine: %s", createProcessParms.CommandLine) + + // Start the command running in the container. Note we always tell HCS to + // create stdout as it's required regardless of '-i' or '-t' options, so that + // docker can always grab the output through logs. We also tell HCS to always + // create stdin, even if it's not used - it will be closed shortly. Stderr + // is only created if it we're not -t. + var stdout, stderr io.ReadCloser + var pid uint32 + iopipe := &IOPipe{Terminal: procToAdd.Terminal} + pid, iopipe.Stdin, stdout, stderr, err = hcsshim.CreateProcessInComputeSystem( + containerID, + true, + true, + !procToAdd.Terminal, + createProcessParms) + if err != nil { + logrus.Errorf("AddProcess %s CreateProcessInComputeSystem() failed %s", containerID, err) + return err + } + + // Convert io.ReadClosers to io.Readers + if stdout != nil { + iopipe.Stdout = openReaderFromPipe(stdout) + } + if stderr != nil { + iopipe.Stderr = openReaderFromPipe(stderr) + } + + // Add the process to the containers list of processes + container.processes[processFriendlyName] = + &process{ + processCommon: processCommon{ + containerID: containerID, + friendlyName: processFriendlyName, + client: clnt, + systemPid: pid, + }, + } + + // Make sure the lock is not held while calling back into the daemon + clnt.unlock(containerID) + + // Tell the engine to attach streams back to the client + if err := clnt.backend.AttachStreams(processFriendlyName, *iopipe); err != nil { + return err + } + + // Lock again so that the defer unlock doesn't fail. (I really don't like this code) + clnt.lock(containerID) + + // Spin up a go routine waiting for exit to handle cleanup + go container.waitExit(pid, processFriendlyName, false) + + return nil +} + +// Signal handles `docker stop` on Windows. While Linux has support for +// the full range of signals, signals aren't really implemented on Windows. +// We fake supporting regular stop and -9 to force kill. +func (clnt *client) Signal(containerID string, sig int) error { + var ( + cont *container + err error + ) + + // Get the container as we need it to find the pid of the process. + clnt.lock(containerID) + defer clnt.unlock(containerID) + if cont, err = clnt.getContainer(containerID); err != nil { + return err + } + + logrus.Debugf("lcd: Signal() containerID=%s sig=%d pid=%d", containerID, sig, cont.systemPid) + context := fmt.Sprintf("Signal: sig=%d pid=%d", sig, cont.systemPid) + + if syscall.Signal(sig) == syscall.SIGKILL { + // Terminate the compute system + if err := hcsshim.TerminateComputeSystem(containerID, hcsshim.TimeoutInfinite, context); err != nil { + logrus.Errorf("Failed to terminate %s - %q", containerID, err) + } + + } else { + // Terminate Process + if err = hcsshim.TerminateProcessInComputeSystem(containerID, cont.systemPid); err != nil { + logrus.Warnf("Failed to terminate pid %d in %s: %q", cont.systemPid, containerID, err) + // Ignore errors + err = nil + } + + // Shutdown the compute system + if err := hcsshim.ShutdownComputeSystem(containerID, hcsshim.TimeoutInfinite, context); err != nil { + logrus.Errorf("Failed to shutdown %s - %q", containerID, err) + } + } + return nil +} + +// Resize handles a CLI event to resize an interactive docker run or docker exec +// window. +func (clnt *client) Resize(containerID, processFriendlyName string, width, height int) error { + // Get the libcontainerd container object + clnt.lock(containerID) + defer clnt.unlock(containerID) + cont, err := clnt.getContainer(containerID) + if err != nil { + return err + } + + if processFriendlyName == InitFriendlyName { + logrus.Debugln("Resizing systemPID in", containerID, cont.process.systemPid) + return hcsshim.ResizeConsoleInComputeSystem(containerID, cont.process.systemPid, height, width) + } + + for _, p := range cont.processes { + if p.friendlyName == processFriendlyName { + logrus.Debugln("Resizing exec'd process", containerID, p.systemPid) + return hcsshim.ResizeConsoleInComputeSystem(containerID, p.systemPid, height, width) + } + } + + return fmt.Errorf("Resize could not find containerID %s to resize", containerID) + +} + +// Pause handles pause requests for containers +func (clnt *client) Pause(containerID string) error { + return errors.New("Windows: Containers cannot be paused") +} + +// Resume handles resume requests for containers +func (clnt *client) Resume(containerID string) error { + return errors.New("Windows: Containers cannot be paused") +} + +// Stats handles stats requests for containers +func (clnt *client) Stats(containerID string) (*Stats, error) { + return nil, errors.New("Windows: Stats not implemented") +} + +// Restore is the handler for restoring a container +func (clnt *client) Restore(containerID string, unusedOnWindows ...CreateOption) error { + + logrus.Debugf("lcd Restore %s", containerID) + return clnt.backend.StateChanged(containerID, StateInfo{ + State: StateExit, + ExitCode: 1 << 31, + }) + + // var err error + // clnt.lock(containerID) + // defer clnt.unlock(containerID) + + // logrus.Debugf("restore container %s state %s", containerID) + + // if _, err := clnt.getContainer(containerID); err == nil { + // return fmt.Errorf("container %s is aleady active", containerID) + // } + + // defer func() { + // if err != nil { + // clnt.deleteContainer(containerID) + // } + // }() + + // // ====> BUGBUG Where does linux get the pid from systemPid: pid, + // container := &container{ + // containerCommon: containerCommon{ + // process: process{ + // processCommon: processCommon{ + // containerID: containerID, + // client: clnt, + // friendlyName: InitFriendlyName, + // }, + // }, + // processes: make(map[string]*process), + // }, + // } + + // container.systemPid = systemPid(cont) + + // var terminal bool + // for _, p := range cont.Processes { + // if p.Pid == InitFriendlyName { + // terminal = p.Terminal + // } + // } + + // iopipe, err := container.openFifos(terminal) + // if err != nil { + // return err + // } + + // if err := clnt.backend.AttachStreams(containerID, *iopipe); err != nil { + // return err + // } + + // clnt.appendContainer(container) + + // err = clnt.backend.StateChanged(containerID, StateInfo{ + // State: StateRestore, + // Pid: container.systemPid, + // }) + + // if err != nil { + // return err + // } + + // return nil +} + +// GetPidsForContainers is not implemented on Windows. +func (clnt *client) GetPidsForContainer(containerID string) ([]int, error) { + return nil, errors.New("GetPidsForContainer: GetPidsForContainer() not implemented") +} + +func (clnt *client) UpdateResources(containerID string, resources Resources) error { + // Updating resource isn't supported on Windows + // but we should return nil for enabling updating container + return nil +} diff --git a/components/engine/libcontainerd/container.go b/components/engine/libcontainerd/container.go new file mode 100644 index 0000000000..197990f2b2 --- /dev/null +++ b/components/engine/libcontainerd/container.go @@ -0,0 +1,38 @@ +package libcontainerd + +import ( + "fmt" + + "github.com/docker/docker/restartmanager" +) + +const ( + // InitFriendlyName is the name given in the lookup map of processes + // for the first process started in a container. + InitFriendlyName = "init" + configFilename = "config.json" +) + +type containerCommon struct { + process + restartManager restartmanager.RestartManager + restarting bool + processes map[string]*process +} + +// WithRestartManager sets the restartmanager to be used with the container. +func WithRestartManager(rm restartmanager.RestartManager) CreateOption { + return restartManager{rm} +} + +type restartManager struct { + rm restartmanager.RestartManager +} + +func (rm restartManager) Apply(p interface{}) error { + if pr, ok := p.(*container); ok { + pr.restartManager = rm.rm + return nil + } + return fmt.Errorf("WithRestartManager option not supported for this client") +} diff --git a/components/engine/libcontainerd/container_linux.go b/components/engine/libcontainerd/container_linux.go new file mode 100644 index 0000000000..8efd06ac22 --- /dev/null +++ b/components/engine/libcontainerd/container_linux.go @@ -0,0 +1,166 @@ +package libcontainerd + +import ( + "encoding/json" + "io/ioutil" + "os" + "path/filepath" + "syscall" + + "github.com/Sirupsen/logrus" + containerd "github.com/docker/containerd/api/grpc/types" + "github.com/opencontainers/specs/specs-go" + "golang.org/x/net/context" +) + +type container struct { + containerCommon + + // Platform specific fields are below here. + pauseMonitor + oom bool +} + +func (ctr *container) clean() error { + if _, err := os.Lstat(ctr.dir); err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + + syscall.Unmount(filepath.Join(ctr.dir, "rootfs"), syscall.MNT_DETACH) // ignore error + if err := os.RemoveAll(ctr.dir); err != nil { + return err + } + return nil +} + +func (ctr *container) spec() (*specs.Spec, error) { + var spec specs.Spec + dt, err := ioutil.ReadFile(filepath.Join(ctr.dir, configFilename)) + if err != nil { + return nil, err + } + if err := json.Unmarshal(dt, &spec); err != nil { + return nil, err + } + return &spec, nil +} + +func (ctr *container) start() error { + spec, err := ctr.spec() + if err != nil { + return nil + } + iopipe, err := ctr.openFifos(spec.Process.Terminal) + if err != nil { + return err + } + + r := &containerd.CreateContainerRequest{ + Id: ctr.containerID, + BundlePath: ctr.dir, + Stdin: ctr.fifo(syscall.Stdin), + Stdout: ctr.fifo(syscall.Stdout), + Stderr: ctr.fifo(syscall.Stderr), + } + ctr.client.appendContainer(ctr) + + resp, err := ctr.client.remote.apiClient.CreateContainer(context.Background(), r) + if err != nil { + ctr.closeFifos(iopipe) + return err + } + + if err := ctr.client.backend.AttachStreams(ctr.containerID, *iopipe); err != nil { + return err + } + ctr.systemPid = systemPid(resp.Container) + + return ctr.client.backend.StateChanged(ctr.containerID, StateInfo{ + State: StateStart, + Pid: ctr.systemPid, + }) +} + +func (ctr *container) newProcess(friendlyName string) *process { + return &process{ + dir: ctr.dir, + processCommon: processCommon{ + containerID: ctr.containerID, + friendlyName: friendlyName, + client: ctr.client, + }, + } +} + +func (ctr *container) handleEvent(e *containerd.Event) error { + ctr.client.lock(ctr.containerID) + defer ctr.client.unlock(ctr.containerID) + switch e.Type { + case StateExit, StatePause, StateResume, StateOOM: + st := StateInfo{ + State: e.Type, + ExitCode: e.Status, + OOMKilled: e.Type == StateExit && ctr.oom, + } + if e.Type == StateOOM { + ctr.oom = true + } + if e.Type == StateExit && e.Pid != InitFriendlyName { + st.ProcessID = e.Pid + st.State = StateExitProcess + } + if st.State == StateExit && ctr.restartManager != nil { + restart, wait, err := ctr.restartManager.ShouldRestart(e.Status) + if err != nil { + logrus.Error(err) + } else if restart { + st.State = StateRestart + ctr.restarting = true + go func() { + err := <-wait + ctr.restarting = false + if err != nil { + st.State = StateExit + ctr.client.q.append(e.Id, func() { + if err := ctr.client.backend.StateChanged(e.Id, st); err != nil { + logrus.Error(err) + } + }) + logrus.Error(err) + } else { + ctr.start() + } + }() + } + } + + // Remove process from list if we have exited + // We need to do so here in case the Message Handler decides to restart it. + if st.State == StateExit { + if os.Getenv("LIBCONTAINERD_NOCLEAN") != "1" { + ctr.clean() + } + ctr.client.deleteContainer(e.Id) + } + ctr.client.q.append(e.Id, func() { + if err := ctr.client.backend.StateChanged(e.Id, st); err != nil { + logrus.Error(err) + } + if e.Type == StatePause || e.Type == StateResume { + ctr.pauseMonitor.handle(e.Type) + } + if e.Type == StateExit { + if en := ctr.client.getExitNotifier(e.Id); en != nil { + en.close() + } + } + }) + + default: + logrus.Debugf("event unhandled: %+v", e) + } + return nil +} diff --git a/components/engine/libcontainerd/container_windows.go b/components/engine/libcontainerd/container_windows.go new file mode 100644 index 0000000000..f1df64aa4f --- /dev/null +++ b/components/engine/libcontainerd/container_windows.go @@ -0,0 +1,204 @@ +package libcontainerd + +import ( + "io" + "strings" + "syscall" + + "github.com/Microsoft/hcsshim" + "github.com/Sirupsen/logrus" +) + +type container struct { + containerCommon + + // Platform specific fields are below here. There are none presently on Windows. + options []CreateOption + + // The ociSpec is required, as client.Create() needs a spec, + // but can be called from the RestartManager context which does not + // otherwise have access to the Spec + ociSpec Spec +} + +func (ctr *container) newProcess(friendlyName string) *process { + return &process{ + processCommon: processCommon{ + containerID: ctr.containerID, + friendlyName: friendlyName, + client: ctr.client, + }, + } +} + +func (ctr *container) start() error { + var err error + + // Start the container + logrus.Debugln("Starting container ", ctr.containerID) + if err = hcsshim.StartComputeSystem(ctr.containerID); err != nil { + logrus.Errorf("Failed to start compute system: %s", err) + return err + } + + createProcessParms := hcsshim.CreateProcessParams{ + EmulateConsole: ctr.ociSpec.Process.Terminal, + WorkingDirectory: ctr.ociSpec.Process.Cwd, + ConsoleSize: ctr.ociSpec.Process.InitialConsoleSize, + } + + // Configure the environment for the process + createProcessParms.Environment = setupEnvironmentVariables(ctr.ociSpec.Process.Env) + createProcessParms.CommandLine = strings.Join(ctr.ociSpec.Process.Args, " ") + + iopipe := &IOPipe{Terminal: ctr.ociSpec.Process.Terminal} + + // Start the command running in the container. Note we always tell HCS to + // create stdout as it's required regardless of '-i' or '-t' options, so that + // docker can always grab the output through logs. We also tell HCS to always + // create stdin, even if it's not used - it will be closed shortly. Stderr + // is only created if it we're not -t. + var pid uint32 + var stdout, stderr io.ReadCloser + pid, iopipe.Stdin, stdout, stderr, err = hcsshim.CreateProcessInComputeSystem( + ctr.containerID, + true, + true, + !ctr.ociSpec.Process.Terminal, + createProcessParms) + if err != nil { + logrus.Errorf("CreateProcessInComputeSystem() failed %s", err) + + // Explicitly terminate the compute system here. + if err2 := hcsshim.TerminateComputeSystem(ctr.containerID, hcsshim.TimeoutInfinite, "CreateProcessInComputeSystem failed"); err2 != nil { + // Ignore this error, there's not a lot we can do except log it + logrus.Warnf("Failed to TerminateComputeSystem after a failed CreateProcessInComputeSystem. Ignoring this.", err2) + } else { + logrus.Debugln("Cleaned up after failed CreateProcessInComputeSystem by calling TerminateComputeSystem") + } + return err + } + + // Convert io.ReadClosers to io.Readers + if stdout != nil { + iopipe.Stdout = openReaderFromPipe(stdout) + } + if stderr != nil { + iopipe.Stderr = openReaderFromPipe(stderr) + } + + // Save the PID + logrus.Debugf("Process started - PID %d", pid) + ctr.systemPid = uint32(pid) + + // Spin up a go routine waiting for exit to handle cleanup + go ctr.waitExit(pid, InitFriendlyName, true) + + ctr.client.appendContainer(ctr) + + if err := ctr.client.backend.AttachStreams(ctr.containerID, *iopipe); err != nil { + // OK to return the error here, as waitExit will handle tear-down in HCS + return err + } + + // Tell the docker engine that the container has started. + si := StateInfo{ + State: StateStart, + Pid: ctr.systemPid, // Not sure this is needed? Double-check monitor.go in daemon BUGBUG @jhowardmsft + } + return ctr.client.backend.StateChanged(ctr.containerID, si) + +} + +// waitExit runs as a goroutine waiting for the process to exit. It's +// equivalent to (in the linux containerd world) where events come in for +// state change notifications from containerd. +func (ctr *container) waitExit(pid uint32, processFriendlyName string, isFirstProcessToStart bool) error { + logrus.Debugln("waitExit on pid", pid) + + // Block indefinitely for the process to exit. + exitCode, err := hcsshim.WaitForProcessInComputeSystem(ctr.containerID, pid, hcsshim.TimeoutInfinite) + if err != nil { + if herr, ok := err.(*hcsshim.HcsError); ok && herr.Err != syscall.ERROR_BROKEN_PIPE { + logrus.Warnf("WaitForProcessInComputeSystem failed (container may have been killed): %s", err) + } + // Fall through here, do not return. This ensures we attempt to continue the + // shutdown in HCS nad tell the docker engine that the process/container + // has exited to avoid a container being dropped on the floor. + } + + // Assume the container has exited + si := StateInfo{ + State: StateExit, + ExitCode: uint32(exitCode), + Pid: pid, + ProcessID: processFriendlyName, + } + + // But it could have been an exec'd process which exited + if !isFirstProcessToStart { + si.State = StateExitProcess + } + + // If this is the init process, always call into vmcompute.dll to + // shutdown the container after we have completed. + if isFirstProcessToStart { + logrus.Debugf("Shutting down container %s", ctr.containerID) + // Explicit timeout here rather than hcsshim.TimeoutInfinte to avoid a + // (remote) possibility that ShutdownComputeSystem hangs indefinitely. + const shutdownTimeout = 5 * 60 * 1000 // 5 minutes + if err := hcsshim.ShutdownComputeSystem(ctr.containerID, shutdownTimeout, "waitExit"); err != nil { + if herr, ok := err.(*hcsshim.HcsError); !ok || + (herr.Err != hcsshim.ERROR_SHUTDOWN_IN_PROGRESS && + herr.Err != ErrorBadPathname && + herr.Err != syscall.ERROR_PATH_NOT_FOUND) { + logrus.Warnf("Ignoring error from ShutdownComputeSystem %s", err) + } + } else { + logrus.Debugf("Completed shutting down container %s", ctr.containerID) + } + + // BUGBUG - Is taking the lock necessary here? Should it just be taken for + // the deleteContainer call, not for the restart logic? @jhowardmsft + ctr.client.lock(ctr.containerID) + defer ctr.client.unlock(ctr.containerID) + + if si.State == StateExit && ctr.restartManager != nil { + restart, wait, err := ctr.restartManager.ShouldRestart(uint32(exitCode)) + if err != nil { + logrus.Error(err) + } else if restart { + si.State = StateRestart + ctr.restarting = true + go func() { + err := <-wait + ctr.restarting = false + if err != nil { + si.State = StateExit + if err := ctr.client.backend.StateChanged(ctr.containerID, si); err != nil { + logrus.Error(err) + } + logrus.Error(err) + } else { + ctr.client.Create(ctr.containerID, ctr.ociSpec, ctr.options...) + } + }() + } + } + + // Remove process from list if we have exited + // We need to do so here in case the Message Handler decides to restart it. + if si.State == StateExit { + ctr.client.deleteContainer(ctr.friendlyName) + } + } + + // Call into the backend to notify it of the state change. + logrus.Debugf("waitExit() calling backend.StateChanged %v", si) + if err := ctr.client.backend.StateChanged(ctr.containerID, si); err != nil { + logrus.Error(err) + } + + logrus.Debugln("waitExit() completed OK") + return nil +} diff --git a/components/engine/libcontainerd/pausemonitor_linux.go b/components/engine/libcontainerd/pausemonitor_linux.go new file mode 100644 index 0000000000..379cbf1fcb --- /dev/null +++ b/components/engine/libcontainerd/pausemonitor_linux.go @@ -0,0 +1,31 @@ +package libcontainerd + +// pauseMonitor is helper to get notifications from pause state changes. +type pauseMonitor struct { + waiters map[string][]chan struct{} +} + +func (m *pauseMonitor) handle(t string) { + if m.waiters == nil { + return + } + q, ok := m.waiters[t] + if !ok { + return + } + if len(q) > 0 { + close(q[0]) + m.waiters[t] = q[1:] + } +} + +func (m *pauseMonitor) append(t string, waiter chan struct{}) { + if m.waiters == nil { + m.waiters = make(map[string][]chan struct{}) + } + _, ok := m.waiters[t] + if !ok { + m.waiters[t] = make([]chan struct{}, 0) + } + m.waiters[t] = append(m.waiters[t], waiter) +} diff --git a/components/engine/libcontainerd/process.go b/components/engine/libcontainerd/process.go new file mode 100644 index 0000000000..57562c8789 --- /dev/null +++ b/components/engine/libcontainerd/process.go @@ -0,0 +1,18 @@ +package libcontainerd + +// processCommon are the platform common fields as part of the process structure +// which keeps the state for the main container process, as well as any exec +// processes. +type processCommon struct { + client *client + + // containerID is the Container ID + containerID string + + // friendlyName is an identifier for the process (or `InitFriendlyName` + // for the first process) + friendlyName string + + // systemPid is the PID of the main container process + systemPid uint32 +} diff --git a/components/engine/libcontainerd/process_linux.go b/components/engine/libcontainerd/process_linux.go new file mode 100644 index 0000000000..136a6e250c --- /dev/null +++ b/components/engine/libcontainerd/process_linux.go @@ -0,0 +1,107 @@ +package libcontainerd + +import ( + "fmt" + "io" + "os" + "path/filepath" + "syscall" + + containerd "github.com/docker/containerd/api/grpc/types" + "github.com/docker/docker/pkg/ioutils" + "golang.org/x/net/context" +) + +var fdNames = map[int]string{ + syscall.Stdin: "stdin", + syscall.Stdout: "stdout", + syscall.Stderr: "stderr", +} + +// process keeps the state for both main container process and exec process. +type process struct { + processCommon + + // Platform specific fields are below here. + dir string +} + +func (p *process) openFifos(terminal bool) (*IOPipe, error) { + bundleDir := p.dir + if err := os.MkdirAll(bundleDir, 0700); err != nil { + return nil, err + } + + for i := 0; i < 3; i++ { + f := p.fifo(i) + if err := syscall.Mkfifo(f, 0700); err != nil && !os.IsExist(err) { + return nil, fmt.Errorf("mkfifo: %s %v", f, err) + } + } + + io := &IOPipe{} + stdinf, err := os.OpenFile(p.fifo(syscall.Stdin), syscall.O_RDWR, 0) + if err != nil { + return nil, err + } + + io.Stdout = openReaderFromFifo(p.fifo(syscall.Stdout)) + if !terminal { + io.Stderr = openReaderFromFifo(p.fifo(syscall.Stderr)) + } else { + io.Stderr = emptyReader{} + } + + io.Stdin = ioutils.NewWriteCloserWrapper(stdinf, func() error { + stdinf.Close() + _, err := p.client.remote.apiClient.UpdateProcess(context.Background(), &containerd.UpdateProcessRequest{ + Id: p.containerID, + Pid: p.friendlyName, + CloseStdin: true, + }) + return err + }) + + return io, nil +} + +func (p *process) closeFifos(io *IOPipe) { + io.Stdin.Close() + closeReaderFifo(p.fifo(syscall.Stdout)) + closeReaderFifo(p.fifo(syscall.Stderr)) +} + +type emptyReader struct{} + +func (r emptyReader) Read(b []byte) (int, error) { + return 0, io.EOF +} + +func openReaderFromFifo(fn string) io.Reader { + r, w := io.Pipe() + go func() { + stdoutf, err := os.OpenFile(fn, syscall.O_RDONLY, 0) + if err != nil { + r.CloseWithError(err) + } + if _, err := io.Copy(w, stdoutf); err != nil { + r.CloseWithError(err) + } + w.Close() + stdoutf.Close() + }() + return r +} + +// closeReaderFifo closes fifo that may be blocked on open by opening the write side. +func closeReaderFifo(fn string) { + f, err := os.OpenFile(fn, syscall.O_WRONLY|syscall.O_NONBLOCK, 0) + if err != nil { + return + } + f.Close() +} + +func (p *process) fifo(index int) string { + return filepath.Join(p.dir, p.friendlyName+"-"+fdNames[index]) +} diff --git a/components/engine/libcontainerd/process_windows.go b/components/engine/libcontainerd/process_windows.go new file mode 100644 index 0000000000..2905fad1fb --- /dev/null +++ b/components/engine/libcontainerd/process_windows.go @@ -0,0 +1,24 @@ +package libcontainerd + +import ( + "io" +) + +// process keeps the state for both main container process and exec process. + +// process keeps the state for both main container process and exec process. +type process struct { + processCommon +} + +func openReaderFromPipe(p io.ReadCloser) io.Reader { + r, w := io.Pipe() + go func() { + if _, err := io.Copy(w, p); err != nil { + r.CloseWithError(err) + } + w.Close() + p.Close() + }() + return r +} diff --git a/components/engine/libcontainerd/queue_linux.go b/components/engine/libcontainerd/queue_linux.go new file mode 100644 index 0000000000..34bc81d24e --- /dev/null +++ b/components/engine/libcontainerd/queue_linux.go @@ -0,0 +1,29 @@ +package libcontainerd + +import "sync" + +type queue struct { + sync.Mutex + fns map[string]chan struct{} +} + +func (q *queue) append(id string, f func()) { + q.Lock() + defer q.Unlock() + + if q.fns == nil { + q.fns = make(map[string]chan struct{}) + } + + done := make(chan struct{}) + + fn, ok := q.fns[id] + q.fns[id] = done + go func() { + if ok { + <-fn + } + f() + close(done) + }() +} diff --git a/components/engine/libcontainerd/remote.go b/components/engine/libcontainerd/remote.go new file mode 100644 index 0000000000..a679edcfdc --- /dev/null +++ b/components/engine/libcontainerd/remote.go @@ -0,0 +1,18 @@ +package libcontainerd + +// Remote on Linux defines the accesspoint to the containerd grpc API. +// Remote on Windows is largely an unimplemented interface as there is +// no remote containerd. +type Remote interface { + // Client returns a new Client instance connected with given Backend. + Client(Backend) (Client, error) + // Cleanup stops containerd if it was started by libcontainerd. + // Note this is not used on Windows as there is no remote containerd. + Cleanup() +} + +// RemoteOption allows to configure paramters of remotes. +// This is unused on Windows. +type RemoteOption interface { + Apply(Remote) error +} diff --git a/components/engine/libcontainerd/remote_linux.go b/components/engine/libcontainerd/remote_linux.go new file mode 100644 index 0000000000..8c817e4c59 --- /dev/null +++ b/components/engine/libcontainerd/remote_linux.go @@ -0,0 +1,401 @@ +package libcontainerd + +import ( + "fmt" + "io" + "net" + "os" + "os/exec" + "path/filepath" + "strconv" + "sync" + "syscall" + "time" + + "github.com/Sirupsen/logrus" + containerd "github.com/docker/containerd/api/grpc/types" + sysinfo "github.com/docker/docker/pkg/system" + "github.com/docker/docker/utils" + "golang.org/x/net/context" + "google.golang.org/grpc" +) + +const ( + maxConnectionRetryCount = 3 + connectionRetryDelay = 3 * time.Second + containerdShutdownTimeout = 15 * time.Second + containerdBinary = "containerd" + containerdPidFilename = "containerd.pid" + containerdSockFilename = "containerd.sock" + eventTimestampFilename = "event.ts" +) + +type remote struct { + sync.RWMutex + apiClient containerd.APIClient + daemonPid int + stateDir string + rpcAddr string + startDaemon bool + debugLog bool + rpcConn *grpc.ClientConn + clients []*client + eventTsPath string + pastEvents map[string]*containerd.Event +} + +// New creates a fresh instance of libcontainerd remote. +func New(stateDir string, options ...RemoteOption) (_ Remote, err error) { + defer func() { + if err != nil { + err = fmt.Errorf("Failed to connect to containerd. Please make sure containerd is installed in your PATH or you have specificed the correct address. Got error: %v", err) + } + }() + r := &remote{ + stateDir: stateDir, + daemonPid: -1, + eventTsPath: filepath.Join(stateDir, eventTimestampFilename), + pastEvents: make(map[string]*containerd.Event), + } + for _, option := range options { + if err := option.Apply(r); err != nil { + return nil, err + } + } + + if err := sysinfo.MkdirAll(stateDir, 0700); err != nil { + return nil, err + } + + if r.rpcAddr == "" { + r.rpcAddr = filepath.Join(stateDir, containerdSockFilename) + } + + if r.startDaemon { + if err := r.runContainerdDaemon(); err != nil { + return nil, err + } + } + + dialOpts := append([]grpc.DialOption{grpc.WithInsecure()}, + grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) { + return net.DialTimeout("unix", addr, timeout) + }), + ) + conn, err := grpc.Dial(r.rpcAddr, dialOpts...) + if err != nil { + return nil, fmt.Errorf("error connecting to containerd: %v", err) + } + + r.rpcConn = conn + r.apiClient = containerd.NewAPIClient(conn) + + go r.handleConnectionChange() + + if err := r.startEventsMonitor(); err != nil { + return nil, err + } + + return r, nil +} + +func (r *remote) handleConnectionChange() { + var transientFailureCount = 0 + state := grpc.Idle + for { + s, err := r.rpcConn.WaitForStateChange(context.Background(), state) + if err != nil { + break + } + state = s + logrus.Debugf("containerd connection state change: %v", s) + + if r.daemonPid != -1 { + switch state { + case grpc.TransientFailure: + // Reset state to be notified of next failure + transientFailureCount++ + if transientFailureCount >= maxConnectionRetryCount { + transientFailureCount = 0 + if utils.IsProcessAlive(r.daemonPid) { + utils.KillProcess(r.daemonPid) + } + if err := r.runContainerdDaemon(); err != nil { //FIXME: Handle error + logrus.Errorf("error restarting containerd: %v", err) + } + } else { + state = grpc.Idle + time.Sleep(connectionRetryDelay) + } + case grpc.Shutdown: + // Well, we asked for it to stop, just return + return + } + } + } +} + +func (r *remote) Cleanup() { + if r.daemonPid == -1 { + return + } + r.rpcConn.Close() + // Ask the daemon to quit + syscall.Kill(r.daemonPid, syscall.SIGTERM) + + // Wait up to 15secs for it to stop + for i := time.Duration(0); i < containerdShutdownTimeout; i += time.Second { + if !utils.IsProcessAlive(r.daemonPid) { + break + } + time.Sleep(time.Second) + } + + if utils.IsProcessAlive(r.daemonPid) { + logrus.Warnf("libcontainerd: containerd (%d) didn't stop within 15 secs, killing it\n", r.daemonPid) + syscall.Kill(r.daemonPid, syscall.SIGKILL) + } + + // cleanup some files + os.Remove(filepath.Join(r.stateDir, containerdPidFilename)) + os.Remove(filepath.Join(r.stateDir, containerdSockFilename)) +} + +func (r *remote) Client(b Backend) (Client, error) { + c := &client{ + clientCommon: clientCommon{ + backend: b, + containerMutexes: make(map[string]*sync.Mutex), + containers: make(map[string]*container), + }, + remote: r, + exitNotifiers: make(map[string]*exitNotifier), + } + + r.Lock() + r.clients = append(r.clients, c) + r.Unlock() + return c, nil +} + +func (r *remote) updateEventTimestamp(t time.Time) { + f, err := os.OpenFile(r.eventTsPath, syscall.O_CREAT|syscall.O_WRONLY|syscall.O_TRUNC, 0600) + defer f.Close() + if err != nil { + logrus.Warnf("libcontainerd: failed to open event timestamp file: %v", err) + return + } + + b, err := t.MarshalText() + if err != nil { + logrus.Warnf("libcontainerd: failed to encode timestamp: %v", err) + return + } + + n, err := f.Write(b) + if err != nil || n != len(b) { + logrus.Warnf("libcontainerd: failed to update event timestamp file: %v", err) + f.Truncate(0) + return + } + +} + +func (r *remote) getLastEventTimestamp() int64 { + t := time.Now() + + fi, err := os.Stat(r.eventTsPath) + if os.IsNotExist(err) { + return t.Unix() + } + + f, err := os.Open(r.eventTsPath) + defer f.Close() + if err != nil { + logrus.Warn("libcontainerd: Unable to access last event ts: %v", err) + return t.Unix() + } + + b := make([]byte, fi.Size()) + n, err := f.Read(b) + if err != nil || n != len(b) { + logrus.Warn("libcontainerd: Unable to read last event ts: %v", err) + return t.Unix() + } + + t.UnmarshalText(b) + + return t.Unix() +} + +func (r *remote) startEventsMonitor() error { + // First, get past events + er := &containerd.EventsRequest{ + Timestamp: uint64(r.getLastEventTimestamp()), + } + events, err := r.apiClient.Events(context.Background(), er) + if err != nil { + return err + } + go r.handleEventStream(events) + return nil +} + +func (r *remote) handleEventStream(events containerd.API_EventsClient) { + live := false + for { + e, err := events.Recv() + if err != nil { + logrus.Errorf("failed to receive event from containerd: %v", err) + go r.startEventsMonitor() + return + } + + if live == false { + logrus.Debugf("received past containerd event: %#v", e) + + // Pause/Resume events should never happens after exit one + switch e.Type { + case StateExit: + r.pastEvents[e.Id] = e + case StatePause: + r.pastEvents[e.Id] = e + case StateResume: + r.pastEvents[e.Id] = e + case stateLive: + live = true + r.updateEventTimestamp(time.Unix(int64(e.Timestamp), 0)) + } + } else { + logrus.Debugf("received containerd event: %#v", e) + + var container *container + var c *client + r.RLock() + for _, c = range r.clients { + container, err = c.getContainer(e.Id) + if err == nil { + break + } + } + r.RUnlock() + if container == nil { + logrus.Errorf("no state for container: %q", err) + continue + } + + if err := container.handleEvent(e); err != nil { + logrus.Errorf("error processing state change for %s: %v", e.Id, err) + } + + r.updateEventTimestamp(time.Unix(int64(e.Timestamp), 0)) + } + } +} + +func (r *remote) runContainerdDaemon() error { + pidFilename := filepath.Join(r.stateDir, containerdPidFilename) + f, err := os.OpenFile(pidFilename, os.O_RDWR|os.O_CREATE, 0600) + defer f.Close() + if err != nil { + return err + } + + // File exist, check if the daemon is alive + b := make([]byte, 8) + n, err := f.Read(b) + if err != nil && err != io.EOF { + return err + } + + if n > 0 { + pid, err := strconv.ParseUint(string(b[:n]), 10, 64) + if err != nil { + return err + } + if utils.IsProcessAlive(int(pid)) { + logrus.Infof("previous instance of containerd still alive (%d)", pid) + r.daemonPid = int(pid) + return nil + } + } + + // rewind the file + _, err = f.Seek(0, os.SEEK_SET) + if err != nil { + return err + } + + // Truncate it + err = f.Truncate(0) + if err != nil { + return err + } + + // Start a new instance + args := []string{"-l", r.rpcAddr} + if r.debugLog { + args = append(args, "--debug", "true") + } + cmd := exec.Command(containerdBinary, args...) + // TODO: store logs? + cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} + if err := cmd.Start(); err != nil { + return err + } + logrus.Infof("New containerd process, pid: %d\n", cmd.Process.Pid) + + if _, err := f.WriteString(fmt.Sprintf("%d", cmd.Process.Pid)); err != nil { + utils.KillProcess(cmd.Process.Pid) + return err + } + + go cmd.Wait() // Reap our child when needed + r.daemonPid = cmd.Process.Pid + return nil +} + +// WithRemoteAddr sets the external containerd socket to connect to. +func WithRemoteAddr(addr string) RemoteOption { + return rpcAddr(addr) +} + +type rpcAddr string + +func (a rpcAddr) Apply(r Remote) error { + if remote, ok := r.(*remote); ok { + remote.rpcAddr = string(a) + return nil + } + return fmt.Errorf("WithRemoteAddr option not supported for this remote") +} + +// WithStartDaemon defines if libcontainerd should also run containerd daemon. +func WithStartDaemon(start bool) RemoteOption { + return startDaemon(start) +} + +type startDaemon bool + +func (s startDaemon) Apply(r Remote) error { + if remote, ok := r.(*remote); ok { + remote.startDaemon = bool(s) + return nil + } + return fmt.Errorf("WithStartDaemon option not supported for this remote") +} + +// WithDebugLog defines if containerd debug logs will be enabled for daemon. +func WithDebugLog(debug bool) RemoteOption { + return debugLog(debug) +} + +type debugLog bool + +func (d debugLog) Apply(r Remote) error { + if remote, ok := r.(*remote); ok { + remote.debugLog = bool(d) + return nil + } + return fmt.Errorf("WithDebugLog option not supported for this remote") +} diff --git a/components/engine/libcontainerd/remote_windows.go b/components/engine/libcontainerd/remote_windows.go new file mode 100644 index 0000000000..b6133fe16c --- /dev/null +++ b/components/engine/libcontainerd/remote_windows.go @@ -0,0 +1,28 @@ +package libcontainerd + +import "sync" + +type remote struct { +} + +func (r *remote) Client(b Backend) (Client, error) { + c := &client{ + clientCommon: clientCommon{ + backend: b, + containerMutexes: make(map[string]*sync.Mutex), + containers: make(map[string]*container), + }, + } + return c, nil +} + +// Cleanup is a no-op on Windows. It is here to implement the same interface +// to meet compilation requirements. +func (r *remote) Cleanup() { +} + +// New creates a fresh instance of libcontainerd remote. This is largely +// a no-op on Windows. +func New(_ string, _ ...RemoteOption) (Remote, error) { + return &remote{}, nil +} diff --git a/components/engine/libcontainerd/types.go b/components/engine/libcontainerd/types.go new file mode 100644 index 0000000000..85b9a30218 --- /dev/null +++ b/components/engine/libcontainerd/types.go @@ -0,0 +1,59 @@ +package libcontainerd + +import "io" + +// State constants used in state change reporting. +const ( + StateStart = "start-container" + StatePause = "pause" + StateResume = "resume" + StateExit = "exit" + StateRestart = "restart" + StateRestore = "restore" + StateStartProcess = "start-process" + StateExitProcess = "exit-process" + StateOOM = "oom" // fake state + stateLive = "live" +) + +// StateInfo contains description about the new state container has entered. +type StateInfo struct { // FIXME: event? + State string + Pid uint32 + ExitCode uint32 + ProcessID string + OOMKilled bool // TODO Windows containerd factor out +} + +// Backend defines callbacks that the client of the library needs to implement. +type Backend interface { + StateChanged(containerID string, state StateInfo) error + AttachStreams(processFriendlyName string, io IOPipe) error +} + +// Client provides access to containerd features. +type Client interface { + Create(containerID string, spec Spec, options ...CreateOption) error + Signal(containerID string, sig int) error + AddProcess(containerID, processFriendlyName string, process Process) error + Resize(containerID, processFriendlyName string, width, height int) error + Pause(containerID string) error + Resume(containerID string) error + Restore(containerID string, options ...CreateOption) error + Stats(containerID string) (*Stats, error) + GetPidsForContainer(containerID string) ([]int, error) + UpdateResources(containerID string, resources Resources) error +} + +// CreateOption allows to configure parameters of container creation. +type CreateOption interface { + Apply(interface{}) error +} + +// IOPipe contains the stdio streams. +type IOPipe struct { + Stdin io.WriteCloser + Stdout io.Reader + Stderr io.Reader + Terminal bool // Whether stderr is connected on Windows +} diff --git a/components/engine/libcontainerd/types_linux.go b/components/engine/libcontainerd/types_linux.go new file mode 100644 index 0000000000..7cbfe79d6f --- /dev/null +++ b/components/engine/libcontainerd/types_linux.go @@ -0,0 +1,44 @@ +package libcontainerd + +import ( + containerd "github.com/docker/containerd/api/grpc/types" + "github.com/opencontainers/specs/specs-go" +) + +// Spec is the base configuration for the container. It specifies platform +// independent configuration. This information must be included when the +// bundle is packaged for distribution. +type Spec specs.Spec + +// Process contains information to start a specific application inside the container. +type Process struct { + // Terminal creates an interactive terminal for the container. + Terminal bool `json:"terminal"` + // User specifies user information for the process. + User *User `json:"user"` + // Args specifies the binary and arguments for the application to execute. + Args []string `json:"args"` + // Env populates the process environment for the process. + Env []string `json:"env,omitempty"` + // Cwd is the current working directory for the process and must be + // relative to the container's root. + Cwd *string `json:"cwd"` + // Capabilities are linux capabilities that are kept for the container. + Capabilities []string `json:"capabilities,omitempty"` + // Rlimits specifies rlimit options to apply to the process. + Rlimits []specs.Rlimit `json:"rlimits,omitempty"` + // ApparmorProfile specified the apparmor profile for the container. + ApparmorProfile *string `json:"apparmorProfile,omitempty"` + // SelinuxProcessLabel specifies the selinux context that the container process is run as. + SelinuxLabel *string `json:"selinuxLabel,omitempty"` +} + +// Stats contains a stats properties from containerd. +type Stats containerd.StatsResponse + +// User specifies linux specific user and group information for the container's +// main process. +type User specs.User + +// Resources defines updatable container resource values. +type Resources containerd.UpdateResource diff --git a/components/engine/libcontainerd/types_windows.go b/components/engine/libcontainerd/types_windows.go new file mode 100644 index 0000000000..69cfb27d07 --- /dev/null +++ b/components/engine/libcontainerd/types_windows.go @@ -0,0 +1,18 @@ +package libcontainerd + +import "github.com/docker/docker/libcontainerd/windowsoci" + +// Spec is the base configuration for the container. +type Spec windowsoci.WindowsSpec + +// Process contains information to start a specific application inside the container. +type Process windowsoci.Process + +// User specifies user information for the containers main process. +type User windowsoci.User + +// Stats contains a stats properties from containerd. +type Stats struct{} + +// Resources defines updatable container resource values. +type Resources struct{} diff --git a/components/engine/libcontainerd/utils_linux.go b/components/engine/libcontainerd/utils_linux.go new file mode 100644 index 0000000000..cf0f2e0d31 --- /dev/null +++ b/components/engine/libcontainerd/utils_linux.go @@ -0,0 +1,41 @@ +package libcontainerd + +import ( + containerd "github.com/docker/containerd/api/grpc/types" + "github.com/opencontainers/specs/specs-go" +) + +func getRootIDs(s specs.Spec) (int, int, error) { + var hasUserns bool + for _, ns := range s.Linux.Namespaces { + if ns.Type == specs.UserNamespace { + hasUserns = true + break + } + } + if !hasUserns { + return 0, 0, nil + } + uid := hostIDFromMap(0, s.Linux.UIDMappings) + gid := hostIDFromMap(0, s.Linux.GIDMappings) + return uid, gid, nil +} + +func hostIDFromMap(id uint32, mp []specs.IDMapping) int { + for _, m := range mp { + if id >= m.ContainerID && id <= m.ContainerID+m.Size-1 { + return int(m.HostID + id - m.ContainerID) + } + } + return 0 +} + +func systemPid(ctr *containerd.Container) uint32 { + var pid uint32 + for _, p := range ctr.Processes { + if p.Pid == InitFriendlyName { + pid = p.SystemPid + } + } + return pid +} diff --git a/components/engine/libcontainerd/utils_windows.go b/components/engine/libcontainerd/utils_windows.go new file mode 100644 index 0000000000..a9d95d635f --- /dev/null +++ b/components/engine/libcontainerd/utils_windows.go @@ -0,0 +1,16 @@ +package libcontainerd + +import "strings" + +// 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 +} diff --git a/components/engine/libcontainerd/windowsoci/oci_windows.go b/components/engine/libcontainerd/windowsoci/oci_windows.go new file mode 100644 index 0000000000..c948805954 --- /dev/null +++ b/components/engine/libcontainerd/windowsoci/oci_windows.go @@ -0,0 +1,188 @@ +package windowsoci + +// This file is a hack - essentially a mirror of OCI spec for Windows. + +import ( + "fmt" + + "github.com/docker/go-connections/nat" +) + +// WindowsSpec is the full specification for Windows containers. +type WindowsSpec struct { + Spec + + // Windows is platform specific configuration for Windows based containers. + Windows Windows `json:"windows"` +} + +// Spec is the base configuration for the container. It specifies platform +// independent configuration. This information must be included when the +// bundle is packaged for distribution. +type Spec struct { + + // Version is the version of the specification that is supported. + Version string `json:"ociVersion"` + // Platform is the host information for OS and Arch. + Platform Platform `json:"platform"` + // Process is the container's main process. + Process Process `json:"process"` + // Root is the root information for the container's filesystem. + Root Root `json:"root"` + // Hostname is the container's host name. + Hostname string `json:"hostname,omitempty"` + // Mounts profile configuration for adding mounts to the container's filesystem. + Mounts []Mount `json:"mounts"` +} + +// Windows contains platform specific configuration for Windows based containers. +type Windows struct { + // Resources contain information for handling resource constraints for the container + Resources *Resources `json:"resources,omitempty"` + // Networking contains the platform specific network settings for the container. + Networking *Networking `json:"networking,omitempty"` + // FirstStart is used for an optimization on first boot of Windows + FirstStart bool `json:"first_start,omitempty"` + // LayerFolder is the path to the current layer folder + LayerFolder string `json:"layer_folder,omitempty"` + // Layer paths of the parent layers + LayerPaths []string `json:"layer_paths,omitempty"` + // HvRuntime contains settings specific to Hyper-V containers, omitted if not using Hyper-V isolation + HvRuntime *HvRuntime `json:"hv_runtime,omitempty"` +} + +// Process contains information to start a specific application inside the container. +type Process struct { + // Terminal indicates if stderr should NOT be attached for the container. + Terminal bool `json:"terminal"` + // ConsoleSize contains the initial h,w of the console size + InitialConsoleSize [2]int `json:"-"` + // User specifies user information for the process. + User User `json:"user"` + // Args specifies the binary and arguments for the application to execute. + Args []string `json:"args"` + // Env populates the process environment for the process. + Env []string `json:"env,omitempty"` + // Cwd is the current working directory for the process and must be + // relative to the container's root. + Cwd string `json:"cwd"` +} + +// User contains the user information for Windows +type User struct { + User string `json:"user,omitempty"` +} + +// Root contains information about the container's root filesystem on the host. +type Root struct { + // Path is the absolute path to the container's root filesystem. + Path string `json:"path"` + // Readonly makes the root filesystem for the container readonly before the process is executed. + Readonly bool `json:"readonly"` +} + +// Platform specifies OS and arch information for the host system that the container +// is created for. +type Platform struct { + // OS is the operating system. + OS string `json:"os"` + // Arch is the architecture + Arch string `json:"arch"` +} + +// Mount specifies a mount for a container. +type Mount struct { + // Destination is the path where the mount will be placed relative to the container's root. The path and child directories MUST exist, a runtime MUST NOT create directories automatically to a mount point. + Destination string `json:"destination"` + // Type specifies the mount kind. + Type string `json:"type"` + // Source specifies the source path of the mount. In the case of bind mounts + // this would be the file on the host. + Source string `json:"source"` + // Readonly specifies if the mount should be read-only + Readonly bool `json:"readonly"` +} + +// HvRuntime contains settings specific to Hyper-V containers +type HvRuntime struct { + // ImagePath is the path to the Utility VM image for this container + ImagePath string `json:"image_path,omitempty"` +} + +// Networking contains the platform specific network settings for the container +type Networking struct { + // TODO Windows TP5. The following three fields are for 'legacy' non- + // libnetwork networking through HCS. They can be removed once TP4 is + // no longer supported. Also remove in libcontainerd\client_windows.go, + // function Create(), and in daemon\oci_windows.go, function CreateSpec() + MacAddress string `json:"mac,omitempty"` + Bridge string `json:"bridge,omitempty"` + PortBindings nat.PortMap `json:"port_bindings,omitempty"` + // End of TODO Windows TP5. + + // List of endpoints to be attached to the container + EndpointList []string `json:"endpoints,omitempty"` +} + +// Storage contains storage resource management settings +type Storage struct { + // Specifies maximum Iops for the system drive + Iops *uint64 `json:"iops,omitempty"` + // Specifies maximum bytes per second for the system drive + Bps *uint64 `json:"bps,omitempty"` + // Sandbox size indicates the size to expand the system drive to if it is currently smaller + SandboxSize *uint64 `json:"sandbox_size,omitempty"` +} + +// Memory contains memory settings for the container +type Memory struct { + // Memory limit (in bytes). + Limit *int64 `json:"limit,omitempty"` + // Memory reservation (in bytes). + Reservation *uint64 `json:"reservation,omitempty"` +} + +// CPU contains information for cpu resource management +type CPU struct { + // Number of CPUs available to the container. This is an appoximation for Windows Server Containers. + Count *uint64 `json:"count,omitempty"` + // CPU shares (relative weight (ratio) vs. other containers with cpu shares). Range is from 1 to 10000. + Shares *uint64 `json:"shares,omitempty"` + // Percent of available CPUs usable by the container. + Percent *int64 `json:"percent,omitempty"` +} + +// Network network resource management information +type Network struct { + // Bandwidth is the maximum egress bandwidth in bytes per second + Bandwidth *uint64 `json:"bandwidth,omitempty"` +} + +// Resources has container runtime resource constraints +// TODO Windows containerd. This structure needs ratifying with the old resources +// structure used on Windows and the latest OCI spec. +type Resources struct { + // Memory restriction configuration + Memory *Memory `json:"memory,omitempty"` + // CPU resource restriction configuration + CPU *CPU `json:"cpu,omitempty"` + // Storage restriction configuration + Storage *Storage `json:"storage,omitempty"` + // Network restriction configuration + Network *Network `json:"network,omitempty"` +} + +const ( + // VersionMajor is for an API incompatible changes + VersionMajor = 0 + // VersionMinor is for functionality in a backwards-compatible manner + VersionMinor = 3 + // VersionPatch is for backwards-compatible bug fixes + VersionPatch = 0 + + // VersionDev indicates development branch. Releases will be empty string. + VersionDev = "" +) + +// Version is the specification version that the package types support. +var Version = fmt.Sprintf("%d.%d.%d%s (Windows)", VersionMajor, VersionMinor, VersionPatch, VersionDev) diff --git a/components/engine/libcontainerd/windowsoci/unsupported.go b/components/engine/libcontainerd/windowsoci/unsupported.go new file mode 100644 index 0000000000..a97c282995 --- /dev/null +++ b/components/engine/libcontainerd/windowsoci/unsupported.go @@ -0,0 +1,3 @@ +// +build !windows + +package windowsoci diff --git a/components/engine/oci/defaults_linux.go b/components/engine/oci/defaults_linux.go new file mode 100644 index 0000000000..4159500cc5 --- /dev/null +++ b/components/engine/oci/defaults_linux.go @@ -0,0 +1,214 @@ +package oci + +import ( + "os" + "runtime" + + "github.com/opencontainers/specs/specs-go" +) + +func sPtr(s string) *string { return &s } +func rPtr(r rune) *rune { return &r } +func iPtr(i int64) *int64 { return &i } +func u32Ptr(i int64) *uint32 { u := uint32(i); return &u } +func fmPtr(i int64) *os.FileMode { fm := os.FileMode(i); return &fm } + +// DefaultSpec returns default oci spec used by docker. +func DefaultSpec() specs.Spec { + s := specs.Spec{ + Version: specs.Version, + Platform: specs.Platform{ + OS: runtime.GOOS, + Arch: runtime.GOARCH, + }, + } + s.Mounts = []specs.Mount{ + { + Destination: "/proc", + Type: "proc", + Source: "proc", + Options: []string{"nosuid", "noexec", "nodev"}, + }, + { + Destination: "/dev", + Type: "tmpfs", + Source: "tmpfs", + Options: []string{"nosuid", "strictatime", "mode=755"}, + }, + { + Destination: "/dev/pts", + Type: "devpts", + Source: "devpts", + Options: []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"}, + }, + { + Destination: "/sys", + Type: "sysfs", + Source: "sysfs", + Options: []string{"nosuid", "noexec", "nodev", "ro"}, + }, + { + Destination: "/sys/fs/cgroup", + Type: "cgroup", + Source: "cgroup", + Options: []string{"ro", "nosuid", "noexec", "nodev"}, + }, + { + Destination: "/dev/mqueue", + Type: "mqueue", + Source: "mqueue", + Options: []string{"nosuid", "noexec", "nodev"}, + }, + } + + s.Process.Capabilities = []string{ + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FSETID", + "CAP_FOWNER", + "CAP_MKNOD", + "CAP_NET_RAW", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETFCAP", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_SYS_CHROOT", + "CAP_KILL", + "CAP_AUDIT_WRITE", + } + + s.Linux = specs.Linux{ + Namespaces: []specs.Namespace{ + {Type: "mount"}, + {Type: "network"}, + {Type: "uts"}, + {Type: "pid"}, + {Type: "ipc"}, + }, + Devices: []specs.Device{ + { + Type: "c", + Path: "/dev/zero", + Major: 1, + Minor: 5, + FileMode: fmPtr(0666), + UID: u32Ptr(0), + GID: u32Ptr(0), + }, + { + Type: "c", + Path: "/dev/null", + Major: 1, + Minor: 3, + FileMode: fmPtr(0666), + UID: u32Ptr(0), + GID: u32Ptr(0), + }, + { + Type: "c", + Path: "/dev/urandom", + Major: 1, + Minor: 9, + FileMode: fmPtr(0666), + UID: u32Ptr(0), + GID: u32Ptr(0), + }, + { + Type: "c", + Path: "/dev/random", + Major: 1, + Minor: 8, + FileMode: fmPtr(0666), + UID: u32Ptr(0), + GID: u32Ptr(0), + }, + // { + // Type: "c", + // Path: "/dev/tty", + // Major: 5, + // Minor: 0, + // FileMode: fmPtr(0666), + // UID: u32Ptr(0), + // GID: u32Ptr(0), + // }, + // { + // Type: "c", + // Path: "/dev/console", + // Major: 5, + // Minor: 1, + // FileMode: fmPtr(0666), + // UID: u32Ptr(0), + // GID: u32Ptr(0), + // }, + { + Type: "c", + Path: "/dev/fuse", + Major: 10, + Minor: 229, + FileMode: fmPtr(0666), + UID: u32Ptr(0), + GID: u32Ptr(0), + }, + }, + Resources: &specs.Resources{ + Devices: []specs.DeviceCgroup{ + { + Allow: false, + Access: sPtr("rwm"), + }, + { + Allow: true, + Type: sPtr("c"), + Major: iPtr(1), + Minor: iPtr(5), + Access: sPtr("rwm"), + }, + { + Allow: true, + Type: sPtr("c"), + Major: iPtr(1), + Minor: iPtr(3), + Access: sPtr("rwm"), + }, + { + Allow: true, + Type: sPtr("c"), + Major: iPtr(1), + Minor: iPtr(9), + Access: sPtr("rwm"), + }, + { + Allow: true, + Type: sPtr("c"), + Major: iPtr(1), + Minor: iPtr(8), + Access: sPtr("rwm"), + }, + { + Allow: true, + Type: sPtr("c"), + Major: iPtr(5), + Minor: iPtr(0), + Access: sPtr("rwm"), + }, + { + Allow: true, + Type: sPtr("c"), + Major: iPtr(5), + Minor: iPtr(1), + Access: sPtr("rwm"), + }, + { + Allow: false, + Type: sPtr("c"), + Major: iPtr(10), + Minor: iPtr(229), + Access: sPtr("rwm"), + }, + }, + }, + } + + return s +} diff --git a/components/engine/oci/defaults_windows.go b/components/engine/oci/defaults_windows.go new file mode 100644 index 0000000000..03dc942eb1 --- /dev/null +++ b/components/engine/oci/defaults_windows.go @@ -0,0 +1,23 @@ +package oci + +import ( + "runtime" + + "github.com/docker/docker/libcontainerd/windowsoci" +) + +// DefaultSpec returns default spec used by docker. +func DefaultSpec() windowsoci.WindowsSpec { + s := windowsoci.Spec{ + Version: windowsoci.Version, + Platform: windowsoci.Platform{ + OS: runtime.GOOS, + Arch: runtime.GOARCH, + }, + } + + return windowsoci.WindowsSpec{ + Spec: s, + Windows: windowsoci.Windows{}, + } +} diff --git a/components/engine/pkg/system/syscall_unix.go b/components/engine/pkg/system/syscall_unix.go index f1497c587e..3ae9128468 100644 --- a/components/engine/pkg/system/syscall_unix.go +++ b/components/engine/pkg/system/syscall_unix.go @@ -9,3 +9,9 @@ import "syscall" func Unmount(dest string) error { return syscall.Unmount(dest, 0) } + +// CommandLineToArgv should not be used on Unix. +// It simply returns commandLine in the only element in the returned array. +func CommandLineToArgv(commandLine string) ([]string, error) { + return []string{commandLine}, nil +} diff --git a/components/engine/pkg/system/syscall_windows.go b/components/engine/pkg/system/syscall_windows.go index 273aa234bb..061e220f79 100644 --- a/components/engine/pkg/system/syscall_windows.go +++ b/components/engine/pkg/system/syscall_windows.go @@ -3,6 +3,7 @@ package system import ( "fmt" "syscall" + "unsafe" ) // OSVersion is a wrapper for Windows version information @@ -34,3 +35,26 @@ func GetOSVersion() (OSVersion, error) { func Unmount(dest string) error { return nil } + +// CommandLineToArgv wraps the Windows syscall to turn a commandline into an argument array. +func CommandLineToArgv(commandLine string) ([]string, error) { + var argc int32 + + argsPtr, err := syscall.UTF16PtrFromString(commandLine) + if err != nil { + return nil, err + } + + argv, err := syscall.CommandLineToArgv(argsPtr, &argc) + if err != nil { + return nil, err + } + defer syscall.LocalFree(syscall.Handle(uintptr(unsafe.Pointer(argv)))) + + newArgs := make([]string, argc) + for i, v := range (*argv)[:argc] { + newArgs[i] = string(syscall.UTF16ToString((*v)[:])) + } + + return newArgs, nil +} diff --git a/components/engine/restartmanager/restartmanager.go b/components/engine/restartmanager/restartmanager.go new file mode 100644 index 0000000000..e534b2cf73 --- /dev/null +++ b/components/engine/restartmanager/restartmanager.go @@ -0,0 +1,118 @@ +package restartmanager + +import ( + "fmt" + "sync" + "time" + + "github.com/docker/engine-api/types/container" +) + +const ( + backoffMultiplier = 2 + defaultTimeout = 100 * time.Millisecond +) + +// RestartManager defines object that controls container restarting rules. +type RestartManager interface { + Cancel() error + ShouldRestart(exitCode uint32) (bool, chan error, error) +} + +type restartManager struct { + sync.Mutex + sync.Once + policy container.RestartPolicy + failureCount int + timeout time.Duration + active bool + cancel chan struct{} + canceled bool +} + +// New returns a new restartmanager based on a policy. +func New(policy container.RestartPolicy) RestartManager { + return &restartManager{policy: policy, cancel: make(chan struct{})} +} + +func (rm *restartManager) SetPolicy(policy container.RestartPolicy) { + rm.Lock() + rm.policy = policy + rm.Unlock() +} + +func (rm *restartManager) ShouldRestart(exitCode uint32) (bool, chan error, error) { + rm.Lock() + unlockOnExit := true + defer func() { + if unlockOnExit { + rm.Unlock() + } + }() + + if rm.canceled { + return false, nil, nil + } + + if rm.active { + return false, nil, fmt.Errorf("invalid call on active restartmanager") + } + + if exitCode != 0 { + rm.failureCount++ + } else { + rm.failureCount = 0 + } + + if rm.timeout == 0 { + rm.timeout = defaultTimeout + } else { + rm.timeout *= backoffMultiplier + } + + var restart bool + switch { + case rm.policy.IsAlways(), rm.policy.IsUnlessStopped(): + restart = true + case rm.policy.IsOnFailure(): + // the default value of 0 for MaximumRetryCount means that we will not enforce a maximum count + if max := rm.policy.MaximumRetryCount; max == 0 || rm.failureCount <= max { + restart = exitCode != 0 + } + } + + if !restart { + rm.active = false + return false, nil, nil + } + + unlockOnExit = false + rm.active = true + rm.Unlock() + + ch := make(chan error) + go func() { + select { + case <-rm.cancel: + ch <- fmt.Errorf("restartmanager canceled") + close(ch) + case <-time.After(rm.timeout): + rm.Lock() + close(ch) + rm.active = false + rm.Unlock() + } + }() + + return true, ch, nil +} + +func (rm *restartManager) Cancel() error { + rm.Do(func() { + rm.Lock() + rm.canceled = true + close(rm.cancel) + rm.Unlock() + }) + return nil +} diff --git a/components/engine/restartmanager/restartmanager_test.go b/components/engine/restartmanager/restartmanager_test.go new file mode 100644 index 0000000000..22f6a0c20e --- /dev/null +++ b/components/engine/restartmanager/restartmanager_test.go @@ -0,0 +1,3 @@ +package restartmanager + +// FIXME diff --git a/components/engine/runconfig/streams.go b/components/engine/runconfig/streams.go index 7a35dd7d31..548c7826ee 100644 --- a/components/engine/runconfig/streams.go +++ b/components/engine/runconfig/streams.go @@ -5,6 +5,7 @@ import ( "io" "io/ioutil" "strings" + "sync" "github.com/docker/docker/pkg/broadcaster" "github.com/docker/docker/pkg/ioutils" @@ -20,6 +21,7 @@ import ( // copied and delivered to all StdoutPipe and StderrPipe consumers, using // a kind of "broadcaster". type StreamConfig struct { + sync.WaitGroup stdout *broadcaster.Unbuffered stderr *broadcaster.Unbuffered stdin io.ReadCloser diff --git a/components/engine/utils/process_unix.go b/components/engine/utils/process_unix.go new file mode 100644 index 0000000000..bdb1b46b3d --- /dev/null +++ b/components/engine/utils/process_unix.go @@ -0,0 +1,22 @@ +// +build linux freebsd + +package utils + +import ( + "syscall" +) + +// IsProcessAlive returns true if process with a given pid is running. +func IsProcessAlive(pid int) bool { + err := syscall.Kill(pid, syscall.Signal(0)) + if err == nil || err == syscall.EPERM { + return true + } + + return false +} + +// KillProcess force-stops a process. +func KillProcess(pid int) { + syscall.Kill(pid, syscall.SIGKILL) +} diff --git a/components/engine/utils/process_windows.go b/components/engine/utils/process_windows.go new file mode 100644 index 0000000000..03cb855197 --- /dev/null +++ b/components/engine/utils/process_windows.go @@ -0,0 +1,20 @@ +package utils + +// IsProcessAlive returns true if process with a given pid is running. +func IsProcessAlive(pid int) bool { + // TODO Windows containerd. Not sure this is needed + // p, err := os.FindProcess(pid) + // if err == nil { + // return true + // } + return false +} + +// KillProcess force-stops a process. +func KillProcess(pid int) { + // TODO Windows containerd. Not sure this is needed + // p, err := os.FindProcess(pid) + // if err == nil { + // p.Kill() + // } +} diff --git a/components/engine/vendor/src/github.com/docker/containerd/LICENSE.code b/components/engine/vendor/src/github.com/docker/containerd/LICENSE.code new file mode 100644 index 0000000000..c7a3f0cfd4 --- /dev/null +++ b/components/engine/vendor/src/github.com/docker/containerd/LICENSE.code @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2013-2015 Docker, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/components/engine/vendor/src/github.com/docker/containerd/LICENSE.docs b/components/engine/vendor/src/github.com/docker/containerd/LICENSE.docs new file mode 100644 index 0000000000..e26cd4fc8e --- /dev/null +++ b/components/engine/vendor/src/github.com/docker/containerd/LICENSE.docs @@ -0,0 +1,425 @@ +Attribution-ShareAlike 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More_considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-ShareAlike 4.0 International Public +License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-ShareAlike 4.0 International Public License ("Public +License"). To the extent this Public License may be interpreted as a +contract, You are granted the Licensed Rights in consideration of Your +acceptance of these terms and conditions, and the Licensor grants You +such rights in consideration of benefits the Licensor receives from +making the Licensed Material available under these terms and +conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + l. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + m. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + + including for purposes of Section 3(b); and + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public licenses. +Notwithstanding, Creative Commons may elect to apply one of its public +licenses to material it publishes and in those instances will be +considered the "Licensor." Except for the limited purpose of indicating +that material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the public +licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/components/engine/vendor/src/github.com/docker/containerd/api/grpc/types/api.pb.go b/components/engine/vendor/src/github.com/docker/containerd/api/grpc/types/api.pb.go new file mode 100644 index 0000000000..25e0e9c1a0 --- /dev/null +++ b/components/engine/vendor/src/github.com/docker/containerd/api/grpc/types/api.pb.go @@ -0,0 +1,1290 @@ +// Code generated by protoc-gen-go. +// source: api.proto +// DO NOT EDIT! + +/* +Package types is a generated protocol buffer package. + +It is generated from these files: + api.proto + +It has these top-level messages: + UpdateProcessRequest + UpdateProcessResponse + CreateContainerRequest + CreateContainerResponse + SignalRequest + SignalResponse + AddProcessRequest + User + AddProcessResponse + CreateCheckpointRequest + CreateCheckpointResponse + DeleteCheckpointRequest + DeleteCheckpointResponse + ListCheckpointRequest + Checkpoint + ListCheckpointResponse + StateRequest + ContainerState + Process + Container + Machine + StateResponse + UpdateContainerRequest + UpdateResource + UpdateContainerResponse + EventsRequest + Event + NetworkStats + CpuUsage + ThrottlingData + CpuStats + PidsStats + MemoryData + MemoryStats + BlkioStatsEntry + BlkioStats + HugetlbStats + CgroupStats + StatsResponse + StatsRequest +*/ +package types + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type UpdateProcessRequest struct { + Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` + Pid string `protobuf:"bytes,2,opt,name=pid" json:"pid,omitempty"` + CloseStdin bool `protobuf:"varint,3,opt,name=closeStdin" json:"closeStdin,omitempty"` + Width uint32 `protobuf:"varint,4,opt,name=width" json:"width,omitempty"` + Height uint32 `protobuf:"varint,5,opt,name=height" json:"height,omitempty"` +} + +func (m *UpdateProcessRequest) Reset() { *m = UpdateProcessRequest{} } +func (m *UpdateProcessRequest) String() string { return proto.CompactTextString(m) } +func (*UpdateProcessRequest) ProtoMessage() {} +func (*UpdateProcessRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +type UpdateProcessResponse struct { +} + +func (m *UpdateProcessResponse) Reset() { *m = UpdateProcessResponse{} } +func (m *UpdateProcessResponse) String() string { return proto.CompactTextString(m) } +func (*UpdateProcessResponse) ProtoMessage() {} +func (*UpdateProcessResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +type CreateContainerRequest struct { + Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` + BundlePath string `protobuf:"bytes,2,opt,name=bundlePath" json:"bundlePath,omitempty"` + Checkpoint string `protobuf:"bytes,3,opt,name=checkpoint" json:"checkpoint,omitempty"` + Stdin string `protobuf:"bytes,4,opt,name=stdin" json:"stdin,omitempty"` + Stdout string `protobuf:"bytes,5,opt,name=stdout" json:"stdout,omitempty"` + Stderr string `protobuf:"bytes,6,opt,name=stderr" json:"stderr,omitempty"` + Labels []string `protobuf:"bytes,7,rep,name=labels" json:"labels,omitempty"` +} + +func (m *CreateContainerRequest) Reset() { *m = CreateContainerRequest{} } +func (m *CreateContainerRequest) String() string { return proto.CompactTextString(m) } +func (*CreateContainerRequest) ProtoMessage() {} +func (*CreateContainerRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } + +type CreateContainerResponse struct { + Container *Container `protobuf:"bytes,1,opt,name=container" json:"container,omitempty"` +} + +func (m *CreateContainerResponse) Reset() { *m = CreateContainerResponse{} } +func (m *CreateContainerResponse) String() string { return proto.CompactTextString(m) } +func (*CreateContainerResponse) ProtoMessage() {} +func (*CreateContainerResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } + +func (m *CreateContainerResponse) GetContainer() *Container { + if m != nil { + return m.Container + } + return nil +} + +type SignalRequest struct { + Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` + Pid string `protobuf:"bytes,2,opt,name=pid" json:"pid,omitempty"` + Signal uint32 `protobuf:"varint,3,opt,name=signal" json:"signal,omitempty"` +} + +func (m *SignalRequest) Reset() { *m = SignalRequest{} } +func (m *SignalRequest) String() string { return proto.CompactTextString(m) } +func (*SignalRequest) ProtoMessage() {} +func (*SignalRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } + +type SignalResponse struct { +} + +func (m *SignalResponse) Reset() { *m = SignalResponse{} } +func (m *SignalResponse) String() string { return proto.CompactTextString(m) } +func (*SignalResponse) ProtoMessage() {} +func (*SignalResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } + +type AddProcessRequest struct { + Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` + Terminal bool `protobuf:"varint,2,opt,name=terminal" json:"terminal,omitempty"` + User *User `protobuf:"bytes,3,opt,name=user" json:"user,omitempty"` + Args []string `protobuf:"bytes,4,rep,name=args" json:"args,omitempty"` + Env []string `protobuf:"bytes,5,rep,name=env" json:"env,omitempty"` + Cwd string `protobuf:"bytes,6,opt,name=cwd" json:"cwd,omitempty"` + Pid string `protobuf:"bytes,7,opt,name=pid" json:"pid,omitempty"` + Stdin string `protobuf:"bytes,8,opt,name=stdin" json:"stdin,omitempty"` + Stdout string `protobuf:"bytes,9,opt,name=stdout" json:"stdout,omitempty"` + Stderr string `protobuf:"bytes,10,opt,name=stderr" json:"stderr,omitempty"` + Capabilities []string `protobuf:"bytes,11,rep,name=capabilities" json:"capabilities,omitempty"` + ApparmorProfile string `protobuf:"bytes,12,opt,name=apparmorProfile" json:"apparmorProfile,omitempty"` + SelinuxLabel string `protobuf:"bytes,13,opt,name=selinuxLabel" json:"selinuxLabel,omitempty"` + NoNewPrivileges bool `protobuf:"varint,14,opt,name=noNewPrivileges" json:"noNewPrivileges,omitempty"` +} + +func (m *AddProcessRequest) Reset() { *m = AddProcessRequest{} } +func (m *AddProcessRequest) String() string { return proto.CompactTextString(m) } +func (*AddProcessRequest) ProtoMessage() {} +func (*AddProcessRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } + +func (m *AddProcessRequest) GetUser() *User { + if m != nil { + return m.User + } + return nil +} + +type User struct { + Uid uint32 `protobuf:"varint,1,opt,name=uid" json:"uid,omitempty"` + Gid uint32 `protobuf:"varint,2,opt,name=gid" json:"gid,omitempty"` + AdditionalGids []uint32 `protobuf:"varint,3,rep,name=additionalGids" json:"additionalGids,omitempty"` +} + +func (m *User) Reset() { *m = User{} } +func (m *User) String() string { return proto.CompactTextString(m) } +func (*User) ProtoMessage() {} +func (*User) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} } + +type AddProcessResponse struct { +} + +func (m *AddProcessResponse) Reset() { *m = AddProcessResponse{} } +func (m *AddProcessResponse) String() string { return proto.CompactTextString(m) } +func (*AddProcessResponse) ProtoMessage() {} +func (*AddProcessResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} } + +type CreateCheckpointRequest struct { + Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` + Checkpoint *Checkpoint `protobuf:"bytes,2,opt,name=checkpoint" json:"checkpoint,omitempty"` +} + +func (m *CreateCheckpointRequest) Reset() { *m = CreateCheckpointRequest{} } +func (m *CreateCheckpointRequest) String() string { return proto.CompactTextString(m) } +func (*CreateCheckpointRequest) ProtoMessage() {} +func (*CreateCheckpointRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} } + +func (m *CreateCheckpointRequest) GetCheckpoint() *Checkpoint { + if m != nil { + return m.Checkpoint + } + return nil +} + +type CreateCheckpointResponse struct { +} + +func (m *CreateCheckpointResponse) Reset() { *m = CreateCheckpointResponse{} } +func (m *CreateCheckpointResponse) String() string { return proto.CompactTextString(m) } +func (*CreateCheckpointResponse) ProtoMessage() {} +func (*CreateCheckpointResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} } + +type DeleteCheckpointRequest struct { + Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"` +} + +func (m *DeleteCheckpointRequest) Reset() { *m = DeleteCheckpointRequest{} } +func (m *DeleteCheckpointRequest) String() string { return proto.CompactTextString(m) } +func (*DeleteCheckpointRequest) ProtoMessage() {} +func (*DeleteCheckpointRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{11} } + +type DeleteCheckpointResponse struct { +} + +func (m *DeleteCheckpointResponse) Reset() { *m = DeleteCheckpointResponse{} } +func (m *DeleteCheckpointResponse) String() string { return proto.CompactTextString(m) } +func (*DeleteCheckpointResponse) ProtoMessage() {} +func (*DeleteCheckpointResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{12} } + +type ListCheckpointRequest struct { + Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` +} + +func (m *ListCheckpointRequest) Reset() { *m = ListCheckpointRequest{} } +func (m *ListCheckpointRequest) String() string { return proto.CompactTextString(m) } +func (*ListCheckpointRequest) ProtoMessage() {} +func (*ListCheckpointRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{13} } + +type Checkpoint struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Exit bool `protobuf:"varint,2,opt,name=exit" json:"exit,omitempty"` + Tcp bool `protobuf:"varint,3,opt,name=tcp" json:"tcp,omitempty"` + UnixSockets bool `protobuf:"varint,4,opt,name=unixSockets" json:"unixSockets,omitempty"` + Shell bool `protobuf:"varint,5,opt,name=shell" json:"shell,omitempty"` +} + +func (m *Checkpoint) Reset() { *m = Checkpoint{} } +func (m *Checkpoint) String() string { return proto.CompactTextString(m) } +func (*Checkpoint) ProtoMessage() {} +func (*Checkpoint) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{14} } + +type ListCheckpointResponse struct { + Checkpoints []*Checkpoint `protobuf:"bytes,1,rep,name=checkpoints" json:"checkpoints,omitempty"` +} + +func (m *ListCheckpointResponse) Reset() { *m = ListCheckpointResponse{} } +func (m *ListCheckpointResponse) String() string { return proto.CompactTextString(m) } +func (*ListCheckpointResponse) ProtoMessage() {} +func (*ListCheckpointResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{15} } + +func (m *ListCheckpointResponse) GetCheckpoints() []*Checkpoint { + if m != nil { + return m.Checkpoints + } + return nil +} + +type StateRequest struct { + Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` +} + +func (m *StateRequest) Reset() { *m = StateRequest{} } +func (m *StateRequest) String() string { return proto.CompactTextString(m) } +func (*StateRequest) ProtoMessage() {} +func (*StateRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{16} } + +type ContainerState struct { + Status string `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"` +} + +func (m *ContainerState) Reset() { *m = ContainerState{} } +func (m *ContainerState) String() string { return proto.CompactTextString(m) } +func (*ContainerState) ProtoMessage() {} +func (*ContainerState) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{17} } + +type Process struct { + Pid string `protobuf:"bytes,1,opt,name=pid" json:"pid,omitempty"` + Terminal bool `protobuf:"varint,2,opt,name=terminal" json:"terminal,omitempty"` + User *User `protobuf:"bytes,3,opt,name=user" json:"user,omitempty"` + Args []string `protobuf:"bytes,4,rep,name=args" json:"args,omitempty"` + Env []string `protobuf:"bytes,5,rep,name=env" json:"env,omitempty"` + Cwd string `protobuf:"bytes,6,opt,name=cwd" json:"cwd,omitempty"` + SystemPid uint32 `protobuf:"varint,7,opt,name=systemPid" json:"systemPid,omitempty"` + Stdin string `protobuf:"bytes,8,opt,name=stdin" json:"stdin,omitempty"` + Stdout string `protobuf:"bytes,9,opt,name=stdout" json:"stdout,omitempty"` + Stderr string `protobuf:"bytes,10,opt,name=stderr" json:"stderr,omitempty"` + Capabilities []string `protobuf:"bytes,11,rep,name=capabilities" json:"capabilities,omitempty"` + ApparmorProfile string `protobuf:"bytes,12,opt,name=apparmorProfile" json:"apparmorProfile,omitempty"` + SelinuxLabel string `protobuf:"bytes,13,opt,name=selinuxLabel" json:"selinuxLabel,omitempty"` + NoNewPrivileges bool `protobuf:"varint,14,opt,name=noNewPrivileges" json:"noNewPrivileges,omitempty"` +} + +func (m *Process) Reset() { *m = Process{} } +func (m *Process) String() string { return proto.CompactTextString(m) } +func (*Process) ProtoMessage() {} +func (*Process) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{18} } + +func (m *Process) GetUser() *User { + if m != nil { + return m.User + } + return nil +} + +type Container struct { + Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` + BundlePath string `protobuf:"bytes,2,opt,name=bundlePath" json:"bundlePath,omitempty"` + Processes []*Process `protobuf:"bytes,3,rep,name=processes" json:"processes,omitempty"` + Status string `protobuf:"bytes,4,opt,name=status" json:"status,omitempty"` + Labels []string `protobuf:"bytes,5,rep,name=labels" json:"labels,omitempty"` + Pids []uint32 `protobuf:"varint,6,rep,name=pids" json:"pids,omitempty"` + Runtime string `protobuf:"bytes,7,opt,name=runtime" json:"runtime,omitempty"` +} + +func (m *Container) Reset() { *m = Container{} } +func (m *Container) String() string { return proto.CompactTextString(m) } +func (*Container) ProtoMessage() {} +func (*Container) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{19} } + +func (m *Container) GetProcesses() []*Process { + if m != nil { + return m.Processes + } + return nil +} + +// Machine is information about machine on which containerd is run +type Machine struct { + Cpus uint32 `protobuf:"varint,1,opt,name=cpus" json:"cpus,omitempty"` + Memory uint64 `protobuf:"varint,2,opt,name=memory" json:"memory,omitempty"` +} + +func (m *Machine) Reset() { *m = Machine{} } +func (m *Machine) String() string { return proto.CompactTextString(m) } +func (*Machine) ProtoMessage() {} +func (*Machine) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{20} } + +// StateResponse is information about containerd daemon +type StateResponse struct { + Containers []*Container `protobuf:"bytes,1,rep,name=containers" json:"containers,omitempty"` + Machine *Machine `protobuf:"bytes,2,opt,name=machine" json:"machine,omitempty"` +} + +func (m *StateResponse) Reset() { *m = StateResponse{} } +func (m *StateResponse) String() string { return proto.CompactTextString(m) } +func (*StateResponse) ProtoMessage() {} +func (*StateResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{21} } + +func (m *StateResponse) GetContainers() []*Container { + if m != nil { + return m.Containers + } + return nil +} + +func (m *StateResponse) GetMachine() *Machine { + if m != nil { + return m.Machine + } + return nil +} + +type UpdateContainerRequest struct { + Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` + Pid string `protobuf:"bytes,2,opt,name=pid" json:"pid,omitempty"` + Status string `protobuf:"bytes,3,opt,name=status" json:"status,omitempty"` + Resources *UpdateResource `protobuf:"bytes,4,opt,name=resources" json:"resources,omitempty"` +} + +func (m *UpdateContainerRequest) Reset() { *m = UpdateContainerRequest{} } +func (m *UpdateContainerRequest) String() string { return proto.CompactTextString(m) } +func (*UpdateContainerRequest) ProtoMessage() {} +func (*UpdateContainerRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{22} } + +func (m *UpdateContainerRequest) GetResources() *UpdateResource { + if m != nil { + return m.Resources + } + return nil +} + +type UpdateResource struct { + BlkioWeight uint32 `protobuf:"varint,1,opt,name=blkioWeight" json:"blkioWeight,omitempty"` + CpuShares uint32 `protobuf:"varint,2,opt,name=cpuShares" json:"cpuShares,omitempty"` + CpuPeriod uint32 `protobuf:"varint,3,opt,name=cpuPeriod" json:"cpuPeriod,omitempty"` + CpuQuota uint32 `protobuf:"varint,4,opt,name=cpuQuota" json:"cpuQuota,omitempty"` + CpusetCpus string `protobuf:"bytes,5,opt,name=cpusetCpus" json:"cpusetCpus,omitempty"` + CpusetMems string `protobuf:"bytes,6,opt,name=cpusetMems" json:"cpusetMems,omitempty"` + MemoryLimit uint32 `protobuf:"varint,7,opt,name=memoryLimit" json:"memoryLimit,omitempty"` + MemorySwap uint32 `protobuf:"varint,8,opt,name=memorySwap" json:"memorySwap,omitempty"` + MemoryReservation uint32 `protobuf:"varint,9,opt,name=memoryReservation" json:"memoryReservation,omitempty"` + KernelMemoryLimit uint32 `protobuf:"varint,10,opt,name=kernelMemoryLimit" json:"kernelMemoryLimit,omitempty"` +} + +func (m *UpdateResource) Reset() { *m = UpdateResource{} } +func (m *UpdateResource) String() string { return proto.CompactTextString(m) } +func (*UpdateResource) ProtoMessage() {} +func (*UpdateResource) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{23} } + +type UpdateContainerResponse struct { +} + +func (m *UpdateContainerResponse) Reset() { *m = UpdateContainerResponse{} } +func (m *UpdateContainerResponse) String() string { return proto.CompactTextString(m) } +func (*UpdateContainerResponse) ProtoMessage() {} +func (*UpdateContainerResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{24} } + +type EventsRequest struct { + Timestamp uint64 `protobuf:"varint,1,opt,name=timestamp" json:"timestamp,omitempty"` +} + +func (m *EventsRequest) Reset() { *m = EventsRequest{} } +func (m *EventsRequest) String() string { return proto.CompactTextString(m) } +func (*EventsRequest) ProtoMessage() {} +func (*EventsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{25} } + +type Event struct { + Type string `protobuf:"bytes,1,opt,name=type" json:"type,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id" json:"id,omitempty"` + Status uint32 `protobuf:"varint,3,opt,name=status" json:"status,omitempty"` + Pid string `protobuf:"bytes,4,opt,name=pid" json:"pid,omitempty"` + Timestamp uint64 `protobuf:"varint,5,opt,name=timestamp" json:"timestamp,omitempty"` +} + +func (m *Event) Reset() { *m = Event{} } +func (m *Event) String() string { return proto.CompactTextString(m) } +func (*Event) ProtoMessage() {} +func (*Event) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{26} } + +type NetworkStats struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + RxBytes uint64 `protobuf:"varint,2,opt,name=rx_bytes" json:"rx_bytes,omitempty"` + Rx_Packets uint64 `protobuf:"varint,3,opt,name=rx_Packets" json:"rx_Packets,omitempty"` + RxErrors uint64 `protobuf:"varint,4,opt,name=Rx_errors" json:"Rx_errors,omitempty"` + RxDropped uint64 `protobuf:"varint,5,opt,name=Rx_dropped" json:"Rx_dropped,omitempty"` + TxBytes uint64 `protobuf:"varint,6,opt,name=Tx_bytes" json:"Tx_bytes,omitempty"` + TxPackets uint64 `protobuf:"varint,7,opt,name=Tx_packets" json:"Tx_packets,omitempty"` + TxErrors uint64 `protobuf:"varint,8,opt,name=Tx_errors" json:"Tx_errors,omitempty"` + TxDropped uint64 `protobuf:"varint,9,opt,name=Tx_dropped" json:"Tx_dropped,omitempty"` +} + +func (m *NetworkStats) Reset() { *m = NetworkStats{} } +func (m *NetworkStats) String() string { return proto.CompactTextString(m) } +func (*NetworkStats) ProtoMessage() {} +func (*NetworkStats) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{27} } + +type CpuUsage struct { + TotalUsage uint64 `protobuf:"varint,1,opt,name=total_usage" json:"total_usage,omitempty"` + PercpuUsage []uint64 `protobuf:"varint,2,rep,name=percpu_usage" json:"percpu_usage,omitempty"` + UsageInKernelmode uint64 `protobuf:"varint,3,opt,name=usage_in_kernelmode" json:"usage_in_kernelmode,omitempty"` + UsageInUsermode uint64 `protobuf:"varint,4,opt,name=usage_in_usermode" json:"usage_in_usermode,omitempty"` +} + +func (m *CpuUsage) Reset() { *m = CpuUsage{} } +func (m *CpuUsage) String() string { return proto.CompactTextString(m) } +func (*CpuUsage) ProtoMessage() {} +func (*CpuUsage) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{28} } + +type ThrottlingData struct { + Periods uint64 `protobuf:"varint,1,opt,name=periods" json:"periods,omitempty"` + ThrottledPeriods uint64 `protobuf:"varint,2,opt,name=throttled_periods" json:"throttled_periods,omitempty"` + ThrottledTime uint64 `protobuf:"varint,3,opt,name=throttled_time" json:"throttled_time,omitempty"` +} + +func (m *ThrottlingData) Reset() { *m = ThrottlingData{} } +func (m *ThrottlingData) String() string { return proto.CompactTextString(m) } +func (*ThrottlingData) ProtoMessage() {} +func (*ThrottlingData) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{29} } + +type CpuStats struct { + CpuUsage *CpuUsage `protobuf:"bytes,1,opt,name=cpu_usage" json:"cpu_usage,omitempty"` + ThrottlingData *ThrottlingData `protobuf:"bytes,2,opt,name=throttling_data" json:"throttling_data,omitempty"` + SystemUsage uint64 `protobuf:"varint,3,opt,name=system_usage" json:"system_usage,omitempty"` +} + +func (m *CpuStats) Reset() { *m = CpuStats{} } +func (m *CpuStats) String() string { return proto.CompactTextString(m) } +func (*CpuStats) ProtoMessage() {} +func (*CpuStats) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{30} } + +func (m *CpuStats) GetCpuUsage() *CpuUsage { + if m != nil { + return m.CpuUsage + } + return nil +} + +func (m *CpuStats) GetThrottlingData() *ThrottlingData { + if m != nil { + return m.ThrottlingData + } + return nil +} + +type PidsStats struct { + Current uint64 `protobuf:"varint,1,opt,name=current" json:"current,omitempty"` +} + +func (m *PidsStats) Reset() { *m = PidsStats{} } +func (m *PidsStats) String() string { return proto.CompactTextString(m) } +func (*PidsStats) ProtoMessage() {} +func (*PidsStats) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{31} } + +type MemoryData struct { + Usage uint64 `protobuf:"varint,1,opt,name=usage" json:"usage,omitempty"` + MaxUsage uint64 `protobuf:"varint,2,opt,name=max_usage" json:"max_usage,omitempty"` + Failcnt uint64 `protobuf:"varint,3,opt,name=failcnt" json:"failcnt,omitempty"` + Limit uint64 `protobuf:"varint,4,opt,name=limit" json:"limit,omitempty"` +} + +func (m *MemoryData) Reset() { *m = MemoryData{} } +func (m *MemoryData) String() string { return proto.CompactTextString(m) } +func (*MemoryData) ProtoMessage() {} +func (*MemoryData) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{32} } + +type MemoryStats struct { + Cache uint64 `protobuf:"varint,1,opt,name=cache" json:"cache,omitempty"` + Usage *MemoryData `protobuf:"bytes,2,opt,name=usage" json:"usage,omitempty"` + SwapUsage *MemoryData `protobuf:"bytes,3,opt,name=swap_usage" json:"swap_usage,omitempty"` + KernelUsage *MemoryData `protobuf:"bytes,4,opt,name=kernel_usage" json:"kernel_usage,omitempty"` + Stats map[string]uint64 `protobuf:"bytes,5,rep,name=stats" json:"stats,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` +} + +func (m *MemoryStats) Reset() { *m = MemoryStats{} } +func (m *MemoryStats) String() string { return proto.CompactTextString(m) } +func (*MemoryStats) ProtoMessage() {} +func (*MemoryStats) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{33} } + +func (m *MemoryStats) GetUsage() *MemoryData { + if m != nil { + return m.Usage + } + return nil +} + +func (m *MemoryStats) GetSwapUsage() *MemoryData { + if m != nil { + return m.SwapUsage + } + return nil +} + +func (m *MemoryStats) GetKernelUsage() *MemoryData { + if m != nil { + return m.KernelUsage + } + return nil +} + +func (m *MemoryStats) GetStats() map[string]uint64 { + if m != nil { + return m.Stats + } + return nil +} + +type BlkioStatsEntry struct { + Major uint64 `protobuf:"varint,1,opt,name=major" json:"major,omitempty"` + Minor uint64 `protobuf:"varint,2,opt,name=minor" json:"minor,omitempty"` + Op string `protobuf:"bytes,3,opt,name=op" json:"op,omitempty"` + Value uint64 `protobuf:"varint,4,opt,name=value" json:"value,omitempty"` +} + +func (m *BlkioStatsEntry) Reset() { *m = BlkioStatsEntry{} } +func (m *BlkioStatsEntry) String() string { return proto.CompactTextString(m) } +func (*BlkioStatsEntry) ProtoMessage() {} +func (*BlkioStatsEntry) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{34} } + +type BlkioStats struct { + IoServiceBytesRecursive []*BlkioStatsEntry `protobuf:"bytes,1,rep,name=io_service_bytes_recursive" json:"io_service_bytes_recursive,omitempty"` + IoServicedRecursive []*BlkioStatsEntry `protobuf:"bytes,2,rep,name=io_serviced_recursive" json:"io_serviced_recursive,omitempty"` + IoQueuedRecursive []*BlkioStatsEntry `protobuf:"bytes,3,rep,name=io_queued_recursive" json:"io_queued_recursive,omitempty"` + IoServiceTimeRecursive []*BlkioStatsEntry `protobuf:"bytes,4,rep,name=io_service_time_recursive" json:"io_service_time_recursive,omitempty"` + IoWaitTimeRecursive []*BlkioStatsEntry `protobuf:"bytes,5,rep,name=io_wait_time_recursive" json:"io_wait_time_recursive,omitempty"` + IoMergedRecursive []*BlkioStatsEntry `protobuf:"bytes,6,rep,name=io_merged_recursive" json:"io_merged_recursive,omitempty"` + IoTimeRecursive []*BlkioStatsEntry `protobuf:"bytes,7,rep,name=io_time_recursive" json:"io_time_recursive,omitempty"` + SectorsRecursive []*BlkioStatsEntry `protobuf:"bytes,8,rep,name=sectors_recursive" json:"sectors_recursive,omitempty"` +} + +func (m *BlkioStats) Reset() { *m = BlkioStats{} } +func (m *BlkioStats) String() string { return proto.CompactTextString(m) } +func (*BlkioStats) ProtoMessage() {} +func (*BlkioStats) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{35} } + +func (m *BlkioStats) GetIoServiceBytesRecursive() []*BlkioStatsEntry { + if m != nil { + return m.IoServiceBytesRecursive + } + return nil +} + +func (m *BlkioStats) GetIoServicedRecursive() []*BlkioStatsEntry { + if m != nil { + return m.IoServicedRecursive + } + return nil +} + +func (m *BlkioStats) GetIoQueuedRecursive() []*BlkioStatsEntry { + if m != nil { + return m.IoQueuedRecursive + } + return nil +} + +func (m *BlkioStats) GetIoServiceTimeRecursive() []*BlkioStatsEntry { + if m != nil { + return m.IoServiceTimeRecursive + } + return nil +} + +func (m *BlkioStats) GetIoWaitTimeRecursive() []*BlkioStatsEntry { + if m != nil { + return m.IoWaitTimeRecursive + } + return nil +} + +func (m *BlkioStats) GetIoMergedRecursive() []*BlkioStatsEntry { + if m != nil { + return m.IoMergedRecursive + } + return nil +} + +func (m *BlkioStats) GetIoTimeRecursive() []*BlkioStatsEntry { + if m != nil { + return m.IoTimeRecursive + } + return nil +} + +func (m *BlkioStats) GetSectorsRecursive() []*BlkioStatsEntry { + if m != nil { + return m.SectorsRecursive + } + return nil +} + +type HugetlbStats struct { + Usage uint64 `protobuf:"varint,1,opt,name=usage" json:"usage,omitempty"` + MaxUsage uint64 `protobuf:"varint,2,opt,name=max_usage" json:"max_usage,omitempty"` + Failcnt uint64 `protobuf:"varint,3,opt,name=failcnt" json:"failcnt,omitempty"` + Limit uint64 `protobuf:"varint,4,opt,name=limit" json:"limit,omitempty"` +} + +func (m *HugetlbStats) Reset() { *m = HugetlbStats{} } +func (m *HugetlbStats) String() string { return proto.CompactTextString(m) } +func (*HugetlbStats) ProtoMessage() {} +func (*HugetlbStats) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{36} } + +type CgroupStats struct { + CpuStats *CpuStats `protobuf:"bytes,1,opt,name=cpu_stats" json:"cpu_stats,omitempty"` + MemoryStats *MemoryStats `protobuf:"bytes,2,opt,name=memory_stats" json:"memory_stats,omitempty"` + BlkioStats *BlkioStats `protobuf:"bytes,3,opt,name=blkio_stats" json:"blkio_stats,omitempty"` + HugetlbStats map[string]*HugetlbStats `protobuf:"bytes,4,rep,name=hugetlb_stats" json:"hugetlb_stats,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + PidsStats *PidsStats `protobuf:"bytes,5,opt,name=pids_stats" json:"pids_stats,omitempty"` +} + +func (m *CgroupStats) Reset() { *m = CgroupStats{} } +func (m *CgroupStats) String() string { return proto.CompactTextString(m) } +func (*CgroupStats) ProtoMessage() {} +func (*CgroupStats) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{37} } + +func (m *CgroupStats) GetCpuStats() *CpuStats { + if m != nil { + return m.CpuStats + } + return nil +} + +func (m *CgroupStats) GetMemoryStats() *MemoryStats { + if m != nil { + return m.MemoryStats + } + return nil +} + +func (m *CgroupStats) GetBlkioStats() *BlkioStats { + if m != nil { + return m.BlkioStats + } + return nil +} + +func (m *CgroupStats) GetHugetlbStats() map[string]*HugetlbStats { + if m != nil { + return m.HugetlbStats + } + return nil +} + +func (m *CgroupStats) GetPidsStats() *PidsStats { + if m != nil { + return m.PidsStats + } + return nil +} + +type StatsResponse struct { + NetworkStats []*NetworkStats `protobuf:"bytes,1,rep,name=network_stats" json:"network_stats,omitempty"` + CgroupStats *CgroupStats `protobuf:"bytes,2,opt,name=cgroup_stats" json:"cgroup_stats,omitempty"` + Timestamp uint64 `protobuf:"varint,3,opt,name=timestamp" json:"timestamp,omitempty"` +} + +func (m *StatsResponse) Reset() { *m = StatsResponse{} } +func (m *StatsResponse) String() string { return proto.CompactTextString(m) } +func (*StatsResponse) ProtoMessage() {} +func (*StatsResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{38} } + +func (m *StatsResponse) GetNetworkStats() []*NetworkStats { + if m != nil { + return m.NetworkStats + } + return nil +} + +func (m *StatsResponse) GetCgroupStats() *CgroupStats { + if m != nil { + return m.CgroupStats + } + return nil +} + +type StatsRequest struct { + Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` +} + +func (m *StatsRequest) Reset() { *m = StatsRequest{} } +func (m *StatsRequest) String() string { return proto.CompactTextString(m) } +func (*StatsRequest) ProtoMessage() {} +func (*StatsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{39} } + +func init() { + proto.RegisterType((*UpdateProcessRequest)(nil), "types.UpdateProcessRequest") + proto.RegisterType((*UpdateProcessResponse)(nil), "types.UpdateProcessResponse") + proto.RegisterType((*CreateContainerRequest)(nil), "types.CreateContainerRequest") + proto.RegisterType((*CreateContainerResponse)(nil), "types.CreateContainerResponse") + proto.RegisterType((*SignalRequest)(nil), "types.SignalRequest") + proto.RegisterType((*SignalResponse)(nil), "types.SignalResponse") + proto.RegisterType((*AddProcessRequest)(nil), "types.AddProcessRequest") + proto.RegisterType((*User)(nil), "types.User") + proto.RegisterType((*AddProcessResponse)(nil), "types.AddProcessResponse") + proto.RegisterType((*CreateCheckpointRequest)(nil), "types.CreateCheckpointRequest") + proto.RegisterType((*CreateCheckpointResponse)(nil), "types.CreateCheckpointResponse") + proto.RegisterType((*DeleteCheckpointRequest)(nil), "types.DeleteCheckpointRequest") + proto.RegisterType((*DeleteCheckpointResponse)(nil), "types.DeleteCheckpointResponse") + proto.RegisterType((*ListCheckpointRequest)(nil), "types.ListCheckpointRequest") + proto.RegisterType((*Checkpoint)(nil), "types.Checkpoint") + proto.RegisterType((*ListCheckpointResponse)(nil), "types.ListCheckpointResponse") + proto.RegisterType((*StateRequest)(nil), "types.StateRequest") + proto.RegisterType((*ContainerState)(nil), "types.ContainerState") + proto.RegisterType((*Process)(nil), "types.Process") + proto.RegisterType((*Container)(nil), "types.Container") + proto.RegisterType((*Machine)(nil), "types.Machine") + proto.RegisterType((*StateResponse)(nil), "types.StateResponse") + proto.RegisterType((*UpdateContainerRequest)(nil), "types.UpdateContainerRequest") + proto.RegisterType((*UpdateResource)(nil), "types.UpdateResource") + proto.RegisterType((*UpdateContainerResponse)(nil), "types.UpdateContainerResponse") + proto.RegisterType((*EventsRequest)(nil), "types.EventsRequest") + proto.RegisterType((*Event)(nil), "types.Event") + proto.RegisterType((*NetworkStats)(nil), "types.NetworkStats") + proto.RegisterType((*CpuUsage)(nil), "types.CpuUsage") + proto.RegisterType((*ThrottlingData)(nil), "types.ThrottlingData") + proto.RegisterType((*CpuStats)(nil), "types.CpuStats") + proto.RegisterType((*PidsStats)(nil), "types.PidsStats") + proto.RegisterType((*MemoryData)(nil), "types.MemoryData") + proto.RegisterType((*MemoryStats)(nil), "types.MemoryStats") + proto.RegisterType((*BlkioStatsEntry)(nil), "types.BlkioStatsEntry") + proto.RegisterType((*BlkioStats)(nil), "types.BlkioStats") + proto.RegisterType((*HugetlbStats)(nil), "types.HugetlbStats") + proto.RegisterType((*CgroupStats)(nil), "types.CgroupStats") + proto.RegisterType((*StatsResponse)(nil), "types.StatsResponse") + proto.RegisterType((*StatsRequest)(nil), "types.StatsRequest") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// Client API for API service + +type APIClient interface { + CreateContainer(ctx context.Context, in *CreateContainerRequest, opts ...grpc.CallOption) (*CreateContainerResponse, error) + UpdateContainer(ctx context.Context, in *UpdateContainerRequest, opts ...grpc.CallOption) (*UpdateContainerResponse, error) + Signal(ctx context.Context, in *SignalRequest, opts ...grpc.CallOption) (*SignalResponse, error) + UpdateProcess(ctx context.Context, in *UpdateProcessRequest, opts ...grpc.CallOption) (*UpdateProcessResponse, error) + AddProcess(ctx context.Context, in *AddProcessRequest, opts ...grpc.CallOption) (*AddProcessResponse, error) + CreateCheckpoint(ctx context.Context, in *CreateCheckpointRequest, opts ...grpc.CallOption) (*CreateCheckpointResponse, error) + DeleteCheckpoint(ctx context.Context, in *DeleteCheckpointRequest, opts ...grpc.CallOption) (*DeleteCheckpointResponse, error) + ListCheckpoint(ctx context.Context, in *ListCheckpointRequest, opts ...grpc.CallOption) (*ListCheckpointResponse, error) + State(ctx context.Context, in *StateRequest, opts ...grpc.CallOption) (*StateResponse, error) + Events(ctx context.Context, in *EventsRequest, opts ...grpc.CallOption) (API_EventsClient, error) + Stats(ctx context.Context, in *StatsRequest, opts ...grpc.CallOption) (*StatsResponse, error) +} + +type aPIClient struct { + cc *grpc.ClientConn +} + +func NewAPIClient(cc *grpc.ClientConn) APIClient { + return &aPIClient{cc} +} + +func (c *aPIClient) CreateContainer(ctx context.Context, in *CreateContainerRequest, opts ...grpc.CallOption) (*CreateContainerResponse, error) { + out := new(CreateContainerResponse) + err := grpc.Invoke(ctx, "/types.API/CreateContainer", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *aPIClient) UpdateContainer(ctx context.Context, in *UpdateContainerRequest, opts ...grpc.CallOption) (*UpdateContainerResponse, error) { + out := new(UpdateContainerResponse) + err := grpc.Invoke(ctx, "/types.API/UpdateContainer", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *aPIClient) Signal(ctx context.Context, in *SignalRequest, opts ...grpc.CallOption) (*SignalResponse, error) { + out := new(SignalResponse) + err := grpc.Invoke(ctx, "/types.API/Signal", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *aPIClient) UpdateProcess(ctx context.Context, in *UpdateProcessRequest, opts ...grpc.CallOption) (*UpdateProcessResponse, error) { + out := new(UpdateProcessResponse) + err := grpc.Invoke(ctx, "/types.API/UpdateProcess", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *aPIClient) AddProcess(ctx context.Context, in *AddProcessRequest, opts ...grpc.CallOption) (*AddProcessResponse, error) { + out := new(AddProcessResponse) + err := grpc.Invoke(ctx, "/types.API/AddProcess", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *aPIClient) CreateCheckpoint(ctx context.Context, in *CreateCheckpointRequest, opts ...grpc.CallOption) (*CreateCheckpointResponse, error) { + out := new(CreateCheckpointResponse) + err := grpc.Invoke(ctx, "/types.API/CreateCheckpoint", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *aPIClient) DeleteCheckpoint(ctx context.Context, in *DeleteCheckpointRequest, opts ...grpc.CallOption) (*DeleteCheckpointResponse, error) { + out := new(DeleteCheckpointResponse) + err := grpc.Invoke(ctx, "/types.API/DeleteCheckpoint", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *aPIClient) ListCheckpoint(ctx context.Context, in *ListCheckpointRequest, opts ...grpc.CallOption) (*ListCheckpointResponse, error) { + out := new(ListCheckpointResponse) + err := grpc.Invoke(ctx, "/types.API/ListCheckpoint", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *aPIClient) State(ctx context.Context, in *StateRequest, opts ...grpc.CallOption) (*StateResponse, error) { + out := new(StateResponse) + err := grpc.Invoke(ctx, "/types.API/State", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *aPIClient) Events(ctx context.Context, in *EventsRequest, opts ...grpc.CallOption) (API_EventsClient, error) { + stream, err := grpc.NewClientStream(ctx, &_API_serviceDesc.Streams[0], c.cc, "/types.API/Events", opts...) + if err != nil { + return nil, err + } + x := &aPIEventsClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type API_EventsClient interface { + Recv() (*Event, error) + grpc.ClientStream +} + +type aPIEventsClient struct { + grpc.ClientStream +} + +func (x *aPIEventsClient) Recv() (*Event, error) { + m := new(Event) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *aPIClient) Stats(ctx context.Context, in *StatsRequest, opts ...grpc.CallOption) (*StatsResponse, error) { + out := new(StatsResponse) + err := grpc.Invoke(ctx, "/types.API/Stats", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for API service + +type APIServer interface { + CreateContainer(context.Context, *CreateContainerRequest) (*CreateContainerResponse, error) + UpdateContainer(context.Context, *UpdateContainerRequest) (*UpdateContainerResponse, error) + Signal(context.Context, *SignalRequest) (*SignalResponse, error) + UpdateProcess(context.Context, *UpdateProcessRequest) (*UpdateProcessResponse, error) + AddProcess(context.Context, *AddProcessRequest) (*AddProcessResponse, error) + CreateCheckpoint(context.Context, *CreateCheckpointRequest) (*CreateCheckpointResponse, error) + DeleteCheckpoint(context.Context, *DeleteCheckpointRequest) (*DeleteCheckpointResponse, error) + ListCheckpoint(context.Context, *ListCheckpointRequest) (*ListCheckpointResponse, error) + State(context.Context, *StateRequest) (*StateResponse, error) + Events(*EventsRequest, API_EventsServer) error + Stats(context.Context, *StatsRequest) (*StatsResponse, error) +} + +func RegisterAPIServer(s *grpc.Server, srv APIServer) { + s.RegisterService(&_API_serviceDesc, srv) +} + +func _API_CreateContainer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(CreateContainerRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(APIServer).CreateContainer(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _API_UpdateContainer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(UpdateContainerRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(APIServer).UpdateContainer(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _API_Signal_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(SignalRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(APIServer).Signal(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _API_UpdateProcess_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(UpdateProcessRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(APIServer).UpdateProcess(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _API_AddProcess_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(AddProcessRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(APIServer).AddProcess(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _API_CreateCheckpoint_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(CreateCheckpointRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(APIServer).CreateCheckpoint(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _API_DeleteCheckpoint_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(DeleteCheckpointRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(APIServer).DeleteCheckpoint(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _API_ListCheckpoint_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(ListCheckpointRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(APIServer).ListCheckpoint(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _API_State_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(StateRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(APIServer).State(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _API_Events_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(EventsRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(APIServer).Events(m, &aPIEventsServer{stream}) +} + +type API_EventsServer interface { + Send(*Event) error + grpc.ServerStream +} + +type aPIEventsServer struct { + grpc.ServerStream +} + +func (x *aPIEventsServer) Send(m *Event) error { + return x.ServerStream.SendMsg(m) +} + +func _API_Stats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(StatsRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(APIServer).Stats(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +var _API_serviceDesc = grpc.ServiceDesc{ + ServiceName: "types.API", + HandlerType: (*APIServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CreateContainer", + Handler: _API_CreateContainer_Handler, + }, + { + MethodName: "UpdateContainer", + Handler: _API_UpdateContainer_Handler, + }, + { + MethodName: "Signal", + Handler: _API_Signal_Handler, + }, + { + MethodName: "UpdateProcess", + Handler: _API_UpdateProcess_Handler, + }, + { + MethodName: "AddProcess", + Handler: _API_AddProcess_Handler, + }, + { + MethodName: "CreateCheckpoint", + Handler: _API_CreateCheckpoint_Handler, + }, + { + MethodName: "DeleteCheckpoint", + Handler: _API_DeleteCheckpoint_Handler, + }, + { + MethodName: "ListCheckpoint", + Handler: _API_ListCheckpoint_Handler, + }, + { + MethodName: "State", + Handler: _API_State_Handler, + }, + { + MethodName: "Stats", + Handler: _API_Stats_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "Events", + Handler: _API_Events_Handler, + ServerStreams: true, + }, + }, +} + +var fileDescriptor0 = []byte{ + // 2142 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe4, 0x19, 0x4d, 0x6f, 0x23, 0x49, + 0x75, 0xfc, 0x11, 0xc7, 0x7e, 0xfe, 0x48, 0xdc, 0x93, 0x0f, 0x8f, 0x77, 0x67, 0x76, 0x68, 0xb1, + 0xec, 0x00, 0x4b, 0x18, 0x32, 0xbb, 0x62, 0x84, 0x04, 0xd2, 0x4e, 0x66, 0x80, 0x61, 0x27, 0x4b, + 0xa6, 0x93, 0x68, 0x2f, 0x48, 0x56, 0xc7, 0x5d, 0x6b, 0x37, 0x69, 0x77, 0xf7, 0x76, 0xb7, 0x13, + 0xe7, 0xc2, 0x11, 0x2e, 0x08, 0x71, 0xe2, 0x82, 0xc4, 0x85, 0x1b, 0x3f, 0x04, 0xfe, 0x09, 0xe2, + 0x27, 0x70, 0xe4, 0x55, 0xbd, 0xaa, 0xea, 0xea, 0xb6, 0x9d, 0x2c, 0x07, 0xc4, 0x61, 0x2f, 0x56, + 0xbd, 0x8f, 0x7a, 0xdf, 0xef, 0x55, 0x75, 0x19, 0x5a, 0x6e, 0xec, 0x1f, 0xc4, 0x49, 0x94, 0x45, + 0xd6, 0x46, 0x76, 0x13, 0xb3, 0xd4, 0xfe, 0x6d, 0x05, 0x76, 0xce, 0x63, 0xcf, 0xcd, 0xd8, 0x49, + 0x12, 0x8d, 0x59, 0x9a, 0x3a, 0xec, 0xcb, 0x39, 0x4b, 0x33, 0xab, 0x07, 0x55, 0xdf, 0x1b, 0x54, + 0x1e, 0x57, 0x9e, 0xb4, 0x1c, 0x5c, 0x59, 0xdb, 0x50, 0x8b, 0x11, 0x51, 0x15, 0x08, 0xbe, 0xb4, + 0x1e, 0x01, 0x8c, 0x83, 0x28, 0x65, 0xa7, 0x99, 0xe7, 0x87, 0x83, 0x1a, 0x12, 0x9a, 0x8e, 0x81, + 0xb1, 0x76, 0x60, 0xe3, 0xda, 0xf7, 0xb2, 0xe9, 0xa0, 0x8e, 0xa4, 0xae, 0x43, 0x80, 0xb5, 0x07, + 0x8d, 0x29, 0xf3, 0x27, 0xd3, 0x6c, 0xb0, 0x21, 0xd0, 0x12, 0xb2, 0xf7, 0x61, 0xb7, 0x64, 0x47, + 0x1a, 0x47, 0x61, 0xca, 0xec, 0xbf, 0x57, 0x60, 0xef, 0x28, 0x61, 0x48, 0x39, 0x8a, 0xc2, 0xcc, + 0xf5, 0x43, 0x96, 0xac, 0xb3, 0x11, 0x2d, 0xba, 0x98, 0x87, 0x5e, 0xc0, 0x4e, 0x5c, 0x54, 0x4b, + 0xa6, 0x1a, 0x18, 0x61, 0xf1, 0x94, 0x8d, 0x2f, 0xe3, 0xc8, 0x0f, 0x33, 0x61, 0x31, 0xd2, 0x73, + 0x0c, 0xb7, 0x38, 0x15, 0xce, 0xd4, 0x05, 0x89, 0x00, 0x6e, 0x31, 0x2e, 0xa2, 0x39, 0x59, 0xdc, + 0x72, 0x24, 0x24, 0xf1, 0x2c, 0x49, 0x06, 0x0d, 0x8d, 0x47, 0x88, 0xe3, 0x03, 0xf7, 0x82, 0x05, + 0xe9, 0x60, 0xf3, 0x71, 0x8d, 0xe3, 0x09, 0xb2, 0x5f, 0xc3, 0xfe, 0x92, 0x1f, 0xe4, 0xa3, 0x75, + 0x00, 0xad, 0xb1, 0x42, 0x0a, 0x7f, 0xda, 0x87, 0xdb, 0x07, 0x22, 0x41, 0x07, 0x39, 0x73, 0xce, + 0x82, 0xa2, 0xba, 0xa7, 0xfe, 0x24, 0x74, 0x83, 0xaf, 0x9e, 0x2d, 0x6e, 0xad, 0xd8, 0x22, 0xfc, + 0xc6, 0xb8, 0x13, 0x64, 0x6f, 0x43, 0x4f, 0x89, 0x92, 0x01, 0xff, 0x7d, 0x0d, 0xfa, 0x9f, 0x78, + 0xde, 0x1d, 0xf5, 0x30, 0x84, 0x66, 0xc6, 0x92, 0x99, 0xcf, 0x25, 0x56, 0x45, 0xee, 0x35, 0x6c, + 0xbd, 0x07, 0xf5, 0x79, 0x8a, 0x9e, 0xd4, 0x84, 0x27, 0x6d, 0xe9, 0xc9, 0x39, 0xa2, 0x1c, 0x41, + 0xb0, 0x2c, 0xa8, 0xbb, 0xc9, 0x24, 0xc5, 0x38, 0xf3, 0x00, 0x89, 0x35, 0x37, 0x99, 0x85, 0x57, + 0x18, 0x63, 0x8e, 0xe2, 0x4b, 0x8e, 0x19, 0x5f, 0x7b, 0x32, 0xba, 0x7c, 0xa9, 0xdc, 0xda, 0xcc, + 0xdd, 0xd2, 0x29, 0x6b, 0xae, 0x4e, 0x59, 0x6b, 0x4d, 0xca, 0xa0, 0x90, 0x32, 0x1b, 0x3a, 0x63, + 0x37, 0x76, 0x2f, 0xfc, 0xc0, 0xcf, 0x7c, 0x96, 0x0e, 0xda, 0xc2, 0x88, 0x02, 0xce, 0x7a, 0x02, + 0x5b, 0x6e, 0x1c, 0xbb, 0xc9, 0x2c, 0x4a, 0x30, 0x34, 0x5f, 0xf8, 0x01, 0x1b, 0x74, 0x84, 0x90, + 0x32, 0x9a, 0x4b, 0x4b, 0x59, 0xe0, 0x87, 0xf3, 0xc5, 0x1b, 0x9e, 0xf9, 0x41, 0x57, 0xb0, 0x15, + 0x70, 0x5c, 0x5a, 0x18, 0x7d, 0xc6, 0xae, 0x4f, 0x12, 0xff, 0x0a, 0xf7, 0x4c, 0x50, 0x69, 0x4f, + 0x44, 0xb1, 0x8c, 0xb6, 0x1d, 0xa8, 0xf3, 0xc8, 0x71, 0xdf, 0xe7, 0x32, 0x03, 0x5d, 0x87, 0x2f, + 0x39, 0x66, 0x22, 0x93, 0x8c, 0x18, 0x5c, 0x5a, 0xdf, 0x82, 0x9e, 0xeb, 0x79, 0x68, 0x6f, 0x84, + 0x69, 0xf8, 0x99, 0xef, 0xa5, 0x98, 0x82, 0x1a, 0x12, 0x4b, 0x58, 0x7b, 0x07, 0x2c, 0x33, 0xc3, + 0x32, 0xf1, 0xbf, 0xd2, 0x05, 0xaa, 0x5b, 0x62, 0x5d, 0xf6, 0x7f, 0x50, 0xe8, 0xa4, 0xaa, 0xc8, + 0x73, 0x5f, 0x55, 0x6c, 0xbe, 0xdb, 0x60, 0xb2, 0x87, 0x30, 0x58, 0x96, 0x2e, 0x35, 0xff, 0x18, + 0xf6, 0x5f, 0xb2, 0x80, 0x7d, 0x15, 0xcd, 0x58, 0x3a, 0xa1, 0x3b, 0x63, 0xb2, 0xb4, 0xc5, 0x9a, + 0x8b, 0x5e, 0xde, 0x2e, 0x45, 0x7f, 0x00, 0xbb, 0x6f, 0xfc, 0x34, 0xbb, 0x53, 0xb0, 0xfd, 0x1b, + 0x80, 0x9c, 0x49, 0xab, 0xa9, 0xe4, 0x6a, 0x38, 0x8e, 0x2d, 0xfc, 0x4c, 0x96, 0xbb, 0x58, 0xf3, + 0x1c, 0x64, 0xe3, 0x58, 0x4e, 0x3f, 0xbe, 0xb4, 0x1e, 0x43, 0x7b, 0x1e, 0xfa, 0x8b, 0xd3, 0x68, + 0x7c, 0xc9, 0xb2, 0x54, 0x8c, 0x92, 0xa6, 0x63, 0xa2, 0x44, 0xcd, 0x4e, 0x59, 0x10, 0x88, 0x79, + 0xd2, 0x74, 0x08, 0xb0, 0x8f, 0x61, 0xaf, 0x6c, 0xa8, 0x9c, 0x0e, 0xcf, 0xa0, 0x9d, 0xc7, 0x31, + 0x45, 0x93, 0x6a, 0xab, 0xa3, 0x6d, 0x72, 0xd9, 0x8f, 0xa0, 0x73, 0x9a, 0x61, 0xb4, 0xd7, 0xb9, + 0xfb, 0x04, 0x7a, 0x7a, 0xb4, 0x08, 0x46, 0x6a, 0x0e, 0x37, 0x9b, 0xa7, 0x92, 0x4b, 0x42, 0xf6, + 0x1f, 0x6b, 0xb0, 0x29, 0x4b, 0x45, 0x35, 0x60, 0x25, 0x6f, 0xc0, 0xff, 0xcb, 0x1c, 0x78, 0x17, + 0x5a, 0xe9, 0x4d, 0x9a, 0xb1, 0xd9, 0x89, 0x9c, 0x06, 0x5d, 0x27, 0x47, 0x7c, 0x0d, 0x66, 0xc2, + 0x3f, 0x2a, 0xd0, 0xd2, 0xd9, 0xfb, 0xaf, 0x8f, 0xc1, 0x0f, 0xa1, 0x15, 0x53, 0x3e, 0x19, 0x0d, + 0x88, 0xf6, 0x61, 0x4f, 0xe6, 0x46, 0x8d, 0x84, 0x9c, 0xc1, 0x28, 0x8b, 0xba, 0x59, 0x16, 0xc6, + 0x31, 0xb7, 0x61, 0x1e, 0x73, 0x3c, 0xa7, 0x31, 0x9f, 0x3c, 0x0d, 0x31, 0x79, 0xc4, 0xda, 0x1a, + 0xc0, 0x66, 0x32, 0x0f, 0x33, 0x1f, 0x1b, 0x8a, 0x66, 0xb7, 0x02, 0xed, 0x8f, 0x61, 0xf3, 0xd8, + 0x1d, 0x4f, 0xd1, 0x0f, 0xbe, 0x71, 0x1c, 0xcb, 0xea, 0xc3, 0x8d, 0x7c, 0xcd, 0x95, 0xcc, 0x18, + 0x86, 0xf1, 0x46, 0xb8, 0x51, 0x77, 0x24, 0x64, 0x5f, 0xe2, 0x01, 0x48, 0xd5, 0x2d, 0x7b, 0xe4, + 0x29, 0x0e, 0x24, 0x15, 0x10, 0xd5, 0x22, 0xcb, 0x47, 0xa8, 0xc1, 0x83, 0xd1, 0xde, 0x9c, 0x91, + 0x66, 0x39, 0xbf, 0x54, 0x0c, 0xa4, 0x3d, 0x8e, 0x22, 0xdb, 0xbf, 0xc3, 0x1b, 0x08, 0xdd, 0x4d, + 0xee, 0xbc, 0x81, 0xac, 0x3e, 0x77, 0x29, 0x7c, 0xb5, 0x42, 0xf8, 0x9e, 0x41, 0x2b, 0x61, 0x69, + 0x34, 0x4f, 0x30, 0xcc, 0x22, 0xb2, 0xed, 0xc3, 0x5d, 0xd5, 0x20, 0x42, 0x97, 0x23, 0xa9, 0x4e, + 0xce, 0x67, 0xff, 0xab, 0x0a, 0xbd, 0x22, 0x95, 0x8f, 0x9b, 0x8b, 0xe0, 0xd2, 0x8f, 0x3e, 0xa7, + 0x4b, 0x15, 0x05, 0xcf, 0x44, 0xf1, 0x66, 0xc1, 0x58, 0x9e, 0x4e, 0x5d, 0x94, 0x23, 0x0f, 0x8b, + 0x1c, 0x21, 0xa9, 0x27, 0x2c, 0xf1, 0x23, 0x4f, 0x5e, 0x0d, 0x72, 0x04, 0xef, 0x6e, 0x04, 0xde, + 0xce, 0xa3, 0xcc, 0x95, 0xd7, 0x38, 0x0d, 0x8b, 0xdb, 0x14, 0xe6, 0x88, 0x65, 0x47, 0x3c, 0x6b, + 0x1b, 0xf2, 0x36, 0xa5, 0x31, 0x39, 0xfd, 0x98, 0xcd, 0x52, 0xd9, 0xbd, 0x06, 0x86, 0x5b, 0x4e, + 0xd9, 0x7c, 0xe3, 0xcf, 0x70, 0xaa, 0x52, 0x1b, 0x9b, 0x28, 0x2e, 0x81, 0xc0, 0xd3, 0x6b, 0x37, + 0x16, 0xdd, 0xdc, 0x75, 0x0c, 0x0c, 0x16, 0x72, 0x9f, 0x20, 0x8c, 0x06, 0x4b, 0xae, 0x5c, 0x7e, + 0xc2, 0x89, 0xee, 0xee, 0x3a, 0xcb, 0x04, 0xce, 0x7d, 0xc9, 0x92, 0x90, 0x05, 0xc7, 0x86, 0x56, + 0x20, 0xee, 0x25, 0x82, 0xfd, 0x00, 0xf6, 0x97, 0x72, 0x2e, 0x8f, 0x94, 0xef, 0x41, 0xf7, 0xd5, + 0x15, 0xc3, 0x21, 0xab, 0xaa, 0x00, 0x63, 0xc8, 0x8b, 0x19, 0x33, 0x3b, 0x8b, 0x45, 0x06, 0xea, + 0x4e, 0x8e, 0xb0, 0x53, 0xd8, 0x10, 0xec, 0xbc, 0xc0, 0x79, 0x82, 0xd5, 0x99, 0xc2, 0xd7, 0xb2, + 0x80, 0xaa, 0xba, 0x80, 0x8a, 0xe5, 0xd2, 0xd5, 0xe5, 0x22, 0x0b, 0xab, 0x9e, 0x17, 0x56, 0x41, + 0xe9, 0x46, 0x59, 0xe9, 0x1f, 0xaa, 0xd0, 0xf9, 0x8c, 0x65, 0xd7, 0x51, 0x72, 0xc9, 0x1b, 0x25, + 0x5d, 0x79, 0xa0, 0x3d, 0x80, 0x66, 0xb2, 0x18, 0x5d, 0xdc, 0x64, 0xb2, 0x30, 0xea, 0xd8, 0x97, + 0x8b, 0x17, 0x1c, 0xb4, 0x1e, 0x02, 0x20, 0xe9, 0xc4, 0xa5, 0x43, 0xac, 0x46, 0xe2, 0x93, 0x85, + 0x44, 0x58, 0xef, 0x40, 0xcb, 0x59, 0x8c, 0x70, 0x4c, 0x46, 0x09, 0x55, 0x6f, 0xdd, 0x41, 0x51, + 0xaf, 0x04, 0xcc, 0xf7, 0x22, 0xd1, 0x4b, 0xa2, 0x38, 0x66, 0x9e, 0x32, 0x2d, 0x59, 0xbc, 0x24, + 0x04, 0xd7, 0x7a, 0xa6, 0xb4, 0x36, 0x48, 0x6b, 0x96, 0x6b, 0x45, 0x52, 0x2c, 0xb5, 0x6e, 0x4a, + 0xa7, 0x4c, 0xad, 0x67, 0x5a, 0x6b, 0x93, 0xb4, 0x66, 0x86, 0xd6, 0xb3, 0x5c, 0x6b, 0x4b, 0xed, + 0x95, 0x5a, 0xed, 0xbf, 0x55, 0xa0, 0x89, 0x65, 0x79, 0x9e, 0xba, 0x13, 0x86, 0x07, 0x53, 0x3b, + 0xc3, 0x12, 0x0e, 0x46, 0x73, 0x0e, 0xca, 0x94, 0x81, 0x40, 0x11, 0xc3, 0x37, 0xa0, 0x13, 0xb3, + 0x04, 0x8b, 0x55, 0x72, 0x54, 0x71, 0xa0, 0xd4, 0x9d, 0x36, 0xe1, 0x88, 0xe5, 0x00, 0xee, 0x0b, + 0xda, 0xc8, 0x0f, 0x47, 0x54, 0x3e, 0xb3, 0xc8, 0x63, 0x32, 0x54, 0x7d, 0x41, 0x7a, 0x1d, 0x7e, + 0xaa, 0x09, 0xd6, 0x77, 0xa0, 0xaf, 0xf9, 0xf9, 0xe1, 0x27, 0xb8, 0x29, 0x74, 0x5b, 0x92, 0xfb, + 0x5c, 0xa2, 0xf1, 0x2e, 0xd2, 0x3b, 0x9b, 0xe2, 0x67, 0x5a, 0x86, 0xa7, 0xc3, 0xe4, 0xa5, 0x8b, + 0xcd, 0x86, 0x13, 0x34, 0x16, 0x2d, 0x99, 0x4a, 0x6b, 0x15, 0x68, 0x7d, 0x17, 0xfa, 0x19, 0xf1, + 0x32, 0x6f, 0xa4, 0x78, 0x28, 0x9b, 0xdb, 0x9a, 0x70, 0x22, 0x99, 0xdf, 0x87, 0x5e, 0xce, 0x2c, + 0xe6, 0x31, 0xd9, 0xdb, 0xd5, 0xd8, 0x33, 0x3e, 0x95, 0xff, 0x4c, 0xc1, 0xa2, 0xca, 0xf9, 0x50, + 0x4c, 0x08, 0x23, 0x54, 0xed, 0xc3, 0x2d, 0x35, 0x59, 0x65, 0x30, 0xc4, 0x54, 0xa0, 0xb0, 0xfc, + 0x04, 0xb6, 0x32, 0x6d, 0xfa, 0x08, 0x1b, 0xc8, 0x95, 0xe3, 0x55, 0x4d, 0xb7, 0xa2, 0x63, 0x4e, + 0x2f, 0x2b, 0x3a, 0x8a, 0x91, 0xa7, 0x93, 0x5c, 0x2a, 0x24, 0xfb, 0xda, 0x84, 0x13, 0x2a, 0xec, + 0xf7, 0xa1, 0x85, 0xc7, 0x7c, 0x4a, 0xd6, 0x61, 0x60, 0xc6, 0xf3, 0x24, 0xc1, 0xfe, 0x52, 0x81, + 0x91, 0xa0, 0x1d, 0x01, 0x50, 0x43, 0x0b, 0xb9, 0x78, 0x29, 0x30, 0x93, 0x4d, 0x00, 0xaf, 0xa8, + 0x99, 0xbb, 0xd0, 0x49, 0x16, 0x15, 0x85, 0x08, 0x72, 0x05, 0x45, 0x7f, 0xe1, 0xfa, 0xc1, 0x58, + 0x7e, 0x2b, 0xa2, 0x68, 0x09, 0x72, 0x61, 0x81, 0x18, 0x1f, 0x94, 0x3f, 0x02, 0xec, 0xbf, 0x56, + 0xa1, 0x4d, 0x1a, 0xc9, 0x34, 0xe4, 0x1a, 0xe3, 0x11, 0xa2, 0x55, 0x0a, 0xc0, 0xfa, 0x40, 0x19, + 0x52, 0xbc, 0x35, 0xe7, 0xa6, 0x2a, 0xdb, 0xf0, 0x48, 0x4b, 0x71, 0xca, 0x19, 0x71, 0x58, 0xc9, + 0xdd, 0xe2, 0x4c, 0x64, 0xf0, 0x47, 0xd0, 0xa1, 0x4a, 0x94, 0x7b, 0xea, 0xeb, 0xf6, 0xb4, 0x89, + 0x8d, 0x76, 0x3d, 0xe3, 0xd7, 0x25, 0xb4, 0x57, 0x9c, 0xe3, 0xed, 0xc3, 0x87, 0x05, 0x76, 0xe1, + 0xc9, 0x81, 0xf8, 0x7d, 0x15, 0x66, 0x38, 0x50, 0x89, 0x77, 0xf8, 0x1c, 0x20, 0x47, 0xf2, 0xe9, + 0x74, 0xc9, 0x6e, 0xd4, 0xb5, 0x10, 0x97, 0xdc, 0xf7, 0x2b, 0x37, 0x98, 0xab, 0xa0, 0x12, 0xf0, + 0xa3, 0xea, 0xf3, 0x8a, 0x3d, 0x86, 0xad, 0x17, 0xfc, 0x74, 0x32, 0xb6, 0x23, 0xf3, 0xcc, 0xfd, + 0x75, 0x94, 0xa8, 0x40, 0x09, 0x40, 0x60, 0xfd, 0x10, 0xb1, 0x52, 0x84, 0x00, 0xf8, 0xc0, 0x8c, + 0x62, 0x79, 0x96, 0xe2, 0x2a, 0x57, 0x54, 0x37, 0x14, 0xd9, 0xff, 0xac, 0x03, 0xe4, 0x5a, 0xac, + 0x53, 0x18, 0xfa, 0xd1, 0x88, 0x1f, 0x05, 0xfe, 0x98, 0xd1, 0xe8, 0x19, 0x25, 0x0c, 0x0b, 0x25, + 0xf5, 0xaf, 0x98, 0xbc, 0x2d, 0xec, 0x49, 0xbf, 0x4b, 0xc6, 0x39, 0xfb, 0x08, 0xd1, 0x46, 0x31, + 0xa3, 0x1c, 0xb5, 0xcd, 0xfa, 0x05, 0xec, 0xe6, 0x42, 0x3d, 0x43, 0x5e, 0xf5, 0x56, 0x79, 0xf7, + 0xb5, 0x3c, 0x2f, 0x97, 0xf5, 0x53, 0x40, 0xf4, 0x08, 0x4f, 0x93, 0x79, 0x41, 0x52, 0xed, 0x56, + 0x49, 0x7d, 0x3f, 0x7a, 0x2b, 0x76, 0xe4, 0x72, 0xde, 0xc2, 0x03, 0xc3, 0x51, 0xde, 0xe0, 0x86, + 0xb4, 0xfa, 0xad, 0xd2, 0xf6, 0xb4, 0x5d, 0x7c, 0x04, 0xe4, 0x22, 0x3f, 0x05, 0xa4, 0x8c, 0xae, + 0x5d, 0x3f, 0x2b, 0xcb, 0xdb, 0xb8, 0xcb, 0xcf, 0xcf, 0x71, 0x53, 0x51, 0x18, 0xf9, 0x39, 0x63, + 0xc9, 0xa4, 0xe0, 0x67, 0xe3, 0x2e, 0x3f, 0x8f, 0xc5, 0x8e, 0x5c, 0xce, 0x0b, 0x40, 0x64, 0xd9, + 0x9e, 0xcd, 0x5b, 0xa5, 0x6c, 0xf9, 0x51, 0xd1, 0x96, 0x23, 0xe8, 0xa7, 0x6c, 0x9c, 0xe1, 0xd9, + 0x61, 0xc8, 0x68, 0xde, 0x2a, 0x63, 0x5b, 0x6e, 0xd0, 0x42, 0xec, 0x2f, 0xa1, 0xf3, 0xf3, 0xf9, + 0x84, 0x65, 0xc1, 0x85, 0xee, 0xf9, 0xff, 0xf5, 0x98, 0xf9, 0x37, 0x8e, 0x99, 0xa3, 0x49, 0x12, + 0xcd, 0xe3, 0xc2, 0x7c, 0xa6, 0x1e, 0x5e, 0x9a, 0xcf, 0x82, 0x47, 0xcc, 0x67, 0xe2, 0xfe, 0x18, + 0x3a, 0x74, 0x35, 0x92, 0x1b, 0x68, 0x0a, 0x59, 0xcb, 0x4d, 0xaf, 0xae, 0x62, 0xb4, 0xed, 0x50, + 0x5e, 0x33, 0xe5, 0xae, 0xe2, 0x34, 0xca, 0xc3, 0x84, 0xdf, 0x19, 0x79, 0xd7, 0xbd, 0x86, 0xee, + 0x94, 0x62, 0x23, 0x77, 0x51, 0x01, 0x7e, 0x53, 0x19, 0x97, 0xfb, 0x70, 0x60, 0xc6, 0x90, 0x42, + 0xdd, 0x99, 0x9a, 0x61, 0xfd, 0x3e, 0x00, 0xff, 0x90, 0x18, 0xa9, 0x41, 0x65, 0xbe, 0x90, 0xe9, + 0xb3, 0x00, 0xbf, 0x5a, 0xd4, 0x72, 0x78, 0x06, 0xfd, 0x25, 0x99, 0x2b, 0xc6, 0xd4, 0xb7, 0xcd, + 0x31, 0xd5, 0x3e, 0xbc, 0x2f, 0x45, 0x9a, 0x5b, 0xcd, 0xd9, 0xf5, 0x97, 0x0a, 0x7d, 0x77, 0xe8, + 0x37, 0x13, 0xeb, 0x39, 0x74, 0x43, 0xba, 0x66, 0xe9, 0x04, 0xd4, 0x0c, 0x41, 0xe6, 0x15, 0xcc, + 0xe9, 0x84, 0xe6, 0x85, 0x0c, 0x13, 0x31, 0x16, 0x11, 0x58, 0x99, 0x08, 0x23, 0x38, 0xf8, 0x5d, + 0x6f, 0x64, 0xbb, 0x70, 0xed, 0xab, 0x95, 0xaf, 0x7d, 0xf2, 0xab, 0x7f, 0xdd, 0xab, 0xdd, 0xe1, + 0x9f, 0x1a, 0x50, 0xfb, 0xe4, 0xe4, 0xb5, 0xe5, 0xc0, 0x56, 0xe9, 0x2d, 0xd2, 0x52, 0x73, 0x7f, + 0xf5, 0x5b, 0xeb, 0xf0, 0xd1, 0x3a, 0xb2, 0xbc, 0x14, 0xdf, 0xe3, 0x32, 0x4b, 0x37, 0x66, 0x2d, + 0x73, 0xf5, 0xd7, 0x93, 0x96, 0xb9, 0xee, 0xa2, 0x7d, 0xcf, 0xfa, 0x21, 0x34, 0xe8, 0x75, 0xd2, + 0xda, 0x91, 0xbc, 0x85, 0x77, 0xcf, 0xe1, 0x6e, 0x09, 0xab, 0x37, 0xbe, 0x81, 0x6e, 0xe1, 0x39, + 0xd9, 0x7a, 0xa7, 0xa0, 0xab, 0xf8, 0xb8, 0x39, 0x7c, 0x77, 0x35, 0x51, 0x4b, 0x3b, 0x02, 0xc8, + 0xdf, 0xcb, 0xac, 0x81, 0xe4, 0x5e, 0x7a, 0x24, 0x1d, 0x3e, 0x58, 0x41, 0xd1, 0x42, 0xce, 0x61, + 0xbb, 0xfc, 0x00, 0x66, 0x95, 0xa2, 0x5a, 0x7e, 0xa4, 0x1a, 0xbe, 0xb7, 0x96, 0x6e, 0x8a, 0x2d, + 0x3f, 0x7e, 0x69, 0xb1, 0x6b, 0x1e, 0xd5, 0xb4, 0xd8, 0xb5, 0xaf, 0x66, 0xf7, 0xac, 0x5f, 0x42, + 0xaf, 0xf8, 0x1c, 0x65, 0xa9, 0x20, 0xad, 0x7c, 0x4e, 0x1b, 0x3e, 0x5c, 0x43, 0xd5, 0x02, 0x3f, + 0x82, 0x0d, 0x7a, 0x67, 0x52, 0xbd, 0x61, 0x3e, 0x4f, 0x0d, 0x77, 0x8a, 0x48, 0xbd, 0xeb, 0x29, + 0x34, 0xe8, 0x5b, 0x4b, 0x17, 0x40, 0xe1, 0xd3, 0x6b, 0xd8, 0x31, 0xb1, 0xf6, 0xbd, 0xa7, 0x15, + 0xa5, 0x27, 0x2d, 0xe8, 0x49, 0x57, 0xe9, 0x31, 0x92, 0x73, 0xd1, 0x10, 0xff, 0x8a, 0x3c, 0xfb, + 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x2a, 0x4e, 0xb6, 0x6b, 0x22, 0x19, 0x00, 0x00, +} diff --git a/components/engine/vendor/src/github.com/docker/containerd/api/grpc/types/api.proto b/components/engine/vendor/src/github.com/docker/containerd/api/grpc/types/api.proto new file mode 100644 index 0000000000..89a3be75e0 --- /dev/null +++ b/components/engine/vendor/src/github.com/docker/containerd/api/grpc/types/api.proto @@ -0,0 +1,284 @@ +syntax = "proto3"; + +package types; + +service API { + rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse) {} + rpc UpdateContainer(UpdateContainerRequest) returns (UpdateContainerResponse) {} + rpc Signal(SignalRequest) returns (SignalResponse) {} + rpc UpdateProcess(UpdateProcessRequest) returns (UpdateProcessResponse) {} + rpc AddProcess(AddProcessRequest) returns (AddProcessResponse) {} + rpc CreateCheckpoint(CreateCheckpointRequest) returns (CreateCheckpointResponse) {} + rpc DeleteCheckpoint(DeleteCheckpointRequest) returns (DeleteCheckpointResponse) {} + rpc ListCheckpoint(ListCheckpointRequest) returns (ListCheckpointResponse) {} + rpc State(StateRequest) returns (StateResponse) {} + rpc Events(EventsRequest) returns (stream Event) {} + rpc Stats(StatsRequest) returns (StatsResponse) {} +} + +message UpdateProcessRequest { + string id = 1; + string pid = 2; + bool closeStdin = 3; // Close stdin of the container + uint32 width = 4; + uint32 height = 5; +} + +message UpdateProcessResponse { +} + +message CreateContainerRequest { + string id = 1; // ID of container + string bundlePath = 2; // path to OCI bundle + string checkpoint = 3; // checkpoint name if you want to create immediate checkpoint (optional) + string stdin = 4; // path to the file where stdin will be read (optional) + string stdout = 5; // path to file where stdout will be written (optional) + string stderr = 6; // path to file where stderr will be written (optional) + repeated string labels = 7; +} + +message CreateContainerResponse { + Container container = 1; +} + +message SignalRequest { + string id = 1; // ID of container + string pid = 2; // PID of process inside container + uint32 signal = 3; // Signal which will be sent, you can find value in "man 7 signal" +} + +message SignalResponse { +} + +message AddProcessRequest { + string id = 1; // ID of container + bool terminal = 2; // Use tty for container stdio + User user = 3; // User under which process will be run + repeated string args = 4; // Arguments for process, first is binary path itself + repeated string env = 5; // List of environment variables for process + string cwd = 6; // Workind directory of process + string pid = 7; // Process ID + string stdin = 8; // path to the file where stdin will be read (optional) + string stdout = 9; // path to file where stdout will be written (optional) + string stderr = 10; // path to file where stderr will be written (optional) + repeated string capabilities = 11; + string apparmorProfile = 12; + string selinuxLabel = 13; + bool noNewPrivileges = 14; +} + +message User { + uint32 uid = 1; // UID of user + uint32 gid = 2; // GID of user + repeated uint32 additionalGids = 3; // Additional groups to which user will be added +} + +message AddProcessResponse { +} + +message CreateCheckpointRequest { + string id = 1; // ID of container + Checkpoint checkpoint = 2; // Checkpoint configuration +} + +message CreateCheckpointResponse { +} + +message DeleteCheckpointRequest { + string id = 1; // ID of container + string name = 2; // Name of checkpoint +} + +message DeleteCheckpointResponse { +} + +message ListCheckpointRequest { + string id = 1; // ID of container +} + +message Checkpoint { + string name = 1; // Name of checkpoint + bool exit = 2; // checkpoint configuration: should container exit on checkpoint or not + bool tcp = 3; // allow open tcp connections + bool unixSockets = 4; // allow external unix sockets + bool shell = 5; // allow shell-jobs +} + +message ListCheckpointResponse { + repeated Checkpoint checkpoints = 1; // List of checkpoints +} + +message StateRequest { + string id = 1; // container id for a single container +} + +message ContainerState { + string status = 1; +} + +message Process { + string pid = 1; + bool terminal = 2; // Use tty for container stdio + User user = 3; // User under which process will be run + repeated string args = 4; // Arguments for process, first is binary path itself + repeated string env = 5; // List of environment variables for process + string cwd = 6; // Workind directory of process + uint32 systemPid = 7; + string stdin = 8; // path to the file where stdin will be read (optional) + string stdout = 9; // path to file where stdout will be written (optional) + string stderr = 10; // path to file where stderr will be written (optional) +repeated string capabilities = 11; + string apparmorProfile = 12; + string selinuxLabel = 13; + bool noNewPrivileges = 14; +} + +message Container { + string id = 1; // ID of container + string bundlePath = 2; // Path to OCI bundle + repeated Process processes = 3; // List of processes which run in container + string status = 4; // Container status ("running", "paused", etc.) + repeated string labels = 5; + repeated uint32 pids = 6; + string runtime = 7; // runtime used to execute the container +} + +// Machine is information about machine on which containerd is run +message Machine { + uint32 cpus = 1; // number of cpus + uint64 memory = 2; // amount of memory +} + +// StateResponse is information about containerd daemon +message StateResponse { + repeated Container containers = 1; + Machine machine = 2; +} + +message UpdateContainerRequest { + string id = 1; // ID of container + string pid = 2; + string status = 3; // Status to whcih containerd will try to change + UpdateResource resources =4; +} + +message UpdateResource { + uint32 blkioWeight =1; + uint32 cpuShares = 2; + uint32 cpuPeriod = 3; + uint32 cpuQuota = 4; + string cpusetCpus = 5; + string cpusetMems = 6; + uint32 memoryLimit = 7; + uint32 memorySwap = 8; + uint32 memoryReservation = 9; + uint32 kernelMemoryLimit = 10; +} + +message UpdateContainerResponse { +} + +message EventsRequest { + uint64 timestamp = 1; +} + +message Event { + string type = 1; + string id = 2; + uint32 status = 3; + string pid = 4; + uint64 timestamp = 5; +} + +message NetworkStats { + string name = 1; // name of network interface + uint64 rx_bytes = 2; + uint64 rx_Packets = 3; + uint64 Rx_errors = 4; + uint64 Rx_dropped = 5; + uint64 Tx_bytes = 6; + uint64 Tx_packets = 7; + uint64 Tx_errors = 8; + uint64 Tx_dropped = 9; +} + +message CpuUsage { + uint64 total_usage = 1; + repeated uint64 percpu_usage = 2; + uint64 usage_in_kernelmode = 3; + uint64 usage_in_usermode = 4; +} + +message ThrottlingData { + uint64 periods = 1; + uint64 throttled_periods = 2; + uint64 throttled_time = 3; +} + +message CpuStats { + CpuUsage cpu_usage = 1; + ThrottlingData throttling_data = 2; + uint64 system_usage = 3; +} + +message PidsStats { + uint64 current = 1; +} + +message MemoryData { + uint64 usage = 1; + uint64 max_usage = 2; + uint64 failcnt = 3; + uint64 limit = 4; +} + +message MemoryStats { + uint64 cache = 1; + MemoryData usage = 2; + MemoryData swap_usage = 3; + MemoryData kernel_usage = 4; + map stats = 5; +} + +message BlkioStatsEntry { + uint64 major = 1; + uint64 minor = 2; + string op = 3; + uint64 value = 4; +} + +message BlkioStats { + repeated BlkioStatsEntry io_service_bytes_recursive = 1; // number of bytes tranferred to and from the block device + repeated BlkioStatsEntry io_serviced_recursive = 2; + repeated BlkioStatsEntry io_queued_recursive = 3; + repeated BlkioStatsEntry io_service_time_recursive = 4; + repeated BlkioStatsEntry io_wait_time_recursive = 5; + repeated BlkioStatsEntry io_merged_recursive = 6; + repeated BlkioStatsEntry io_time_recursive = 7; + repeated BlkioStatsEntry sectors_recursive = 8; +} + +message HugetlbStats { + uint64 usage = 1; + uint64 max_usage = 2; + uint64 failcnt = 3; + uint64 limit = 4; +} + +message CgroupStats { + CpuStats cpu_stats = 1; + MemoryStats memory_stats = 2; + BlkioStats blkio_stats = 3; + map hugetlb_stats = 4; // the map is in the format "size of hugepage: stats of the hugepage" + PidsStats pids_stats = 5; +} + +message StatsResponse { + repeated NetworkStats network_stats = 1; + CgroupStats cgroup_stats = 2; + uint64 timestamp = 3; +}; + +message StatsRequest { + string id = 1; +} diff --git a/components/engine/vendor/src/github.com/golang/protobuf/proto/Makefile b/components/engine/vendor/src/github.com/golang/protobuf/proto/Makefile index fb838ed2d7..f1f06564a1 100644 --- a/components/engine/vendor/src/github.com/golang/protobuf/proto/Makefile +++ b/components/engine/vendor/src/github.com/golang/protobuf/proto/Makefile @@ -39,5 +39,5 @@ test: install generate-test-pbs generate-test-pbs: make install make -C testdata - make -C proto3_proto + protoc --go_out=Mtestdata/test.proto=github.com/golang/protobuf/proto/testdata:. proto3_proto/proto3.proto make diff --git a/components/engine/vendor/src/github.com/golang/protobuf/proto/clone.go b/components/engine/vendor/src/github.com/golang/protobuf/proto/clone.go index 6c6a7d95f2..e98ddec981 100644 --- a/components/engine/vendor/src/github.com/golang/protobuf/proto/clone.go +++ b/components/engine/vendor/src/github.com/golang/protobuf/proto/clone.go @@ -30,7 +30,7 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Protocol buffer deep copy and merge. -// TODO: MessageSet and RawMessage. +// TODO: RawMessage. package proto @@ -75,12 +75,13 @@ func Merge(dst, src Message) { } func mergeStruct(out, in reflect.Value) { + sprop := GetProperties(in.Type()) for i := 0; i < in.NumField(); i++ { f := in.Type().Field(i) if strings.HasPrefix(f.Name, "XXX_") { continue } - mergeAny(out.Field(i), in.Field(i)) + mergeAny(out.Field(i), in.Field(i), false, sprop.Prop[i]) } if emIn, ok := in.Addr().Interface().(extendableProto); ok { @@ -98,7 +99,10 @@ func mergeStruct(out, in reflect.Value) { } } -func mergeAny(out, in reflect.Value) { +// mergeAny performs a merge between two values of the same type. +// viaPtr indicates whether the values were indirected through a pointer (implying proto2). +// prop is set if this is a struct field (it may be nil). +func mergeAny(out, in reflect.Value, viaPtr bool, prop *Properties) { if in.Type() == protoMessageType { if !in.IsNil() { if out.IsNil() { @@ -112,7 +116,21 @@ func mergeAny(out, in reflect.Value) { switch in.Kind() { case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64, reflect.String, reflect.Uint32, reflect.Uint64: + if !viaPtr && isProto3Zero(in) { + return + } out.Set(in) + case reflect.Interface: + // Probably a oneof field; copy non-nil values. + if in.IsNil() { + return + } + // Allocate destination if it is not set, or set to a different type. + // Otherwise we will merge as normal. + if out.IsNil() || out.Elem().Type() != in.Elem().Type() { + out.Set(reflect.New(in.Elem().Elem().Type())) // interface -> *T -> T -> new(T) + } + mergeAny(out.Elem(), in.Elem(), false, nil) case reflect.Map: if in.Len() == 0 { return @@ -127,7 +145,7 @@ func mergeAny(out, in reflect.Value) { switch elemKind { case reflect.Ptr: val = reflect.New(in.Type().Elem().Elem()) - mergeAny(val, in.MapIndex(key)) + mergeAny(val, in.MapIndex(key), false, nil) case reflect.Slice: val = in.MapIndex(key) val = reflect.ValueOf(append([]byte{}, val.Bytes()...)) @@ -143,13 +161,21 @@ func mergeAny(out, in reflect.Value) { if out.IsNil() { out.Set(reflect.New(in.Elem().Type())) } - mergeAny(out.Elem(), in.Elem()) + mergeAny(out.Elem(), in.Elem(), true, nil) case reflect.Slice: if in.IsNil() { return } if in.Type().Elem().Kind() == reflect.Uint8 { // []byte is a scalar bytes field, not a repeated field. + + // Edge case: if this is in a proto3 message, a zero length + // bytes field is considered the zero value, and should not + // be merged. + if prop != nil && prop.proto3 && in.Len() == 0 { + return + } + // Make a deep copy. // Append to []byte{} instead of []byte(nil) so that we never end up // with a nil result. @@ -167,7 +193,7 @@ func mergeAny(out, in reflect.Value) { default: for i := 0; i < n; i++ { x := reflect.Indirect(reflect.New(in.Type().Elem())) - mergeAny(x, in.Index(i)) + mergeAny(x, in.Index(i), false, nil) out.Set(reflect.Append(out, x)) } } @@ -184,7 +210,7 @@ func mergeExtension(out, in map[int32]Extension) { eOut := Extension{desc: eIn.desc} if eIn.value != nil { v := reflect.New(reflect.TypeOf(eIn.value)).Elem() - mergeAny(v, reflect.ValueOf(eIn.value)) + mergeAny(v, reflect.ValueOf(eIn.value), false, nil) eOut.value = v.Interface() } if eIn.enc != nil { diff --git a/components/engine/vendor/src/github.com/golang/protobuf/proto/decode.go b/components/engine/vendor/src/github.com/golang/protobuf/proto/decode.go index 129792ed12..5810782fd8 100644 --- a/components/engine/vendor/src/github.com/golang/protobuf/proto/decode.go +++ b/components/engine/vendor/src/github.com/golang/protobuf/proto/decode.go @@ -46,6 +46,10 @@ import ( // errOverflow is returned when an integer is too large to be represented. var errOverflow = errors.New("proto: integer overflow") +// ErrInternalBadWireType is returned by generated code when an incorrect +// wire type is encountered. It does not get returned to user code. +var ErrInternalBadWireType = errors.New("proto: internal error: bad wiretype for oneof") + // The fundamental decoders that interpret bytes on the wire. // Those that take integer types all return uint64 and are // therefore of type valueDecoder. @@ -314,6 +318,24 @@ func UnmarshalMerge(buf []byte, pb Message) error { return NewBuffer(buf).Unmarshal(pb) } +// DecodeMessage reads a count-delimited message from the Buffer. +func (p *Buffer) DecodeMessage(pb Message) error { + enc, err := p.DecodeRawBytes(false) + if err != nil { + return err + } + return NewBuffer(enc).Unmarshal(pb) +} + +// DecodeGroup reads a tag-delimited group from the Buffer. +func (p *Buffer) DecodeGroup(pb Message) error { + typ, base, err := getbase(pb) + if err != nil { + return err + } + return p.unmarshalType(typ.Elem(), GetProperties(typ.Elem()), true, base) +} + // Unmarshal parses the protocol buffer representation in the // Buffer and places the decoded result in pb. If the struct // underlying pb does not match the data in the buffer, the results can be @@ -377,6 +399,20 @@ func (o *Buffer) unmarshalType(st reflect.Type, prop *StructProperties, is_group continue } } + // Maybe it's a oneof? + if prop.oneofUnmarshaler != nil { + m := structPointer_Interface(base, st).(Message) + // First return value indicates whether tag is a oneof field. + ok, err = prop.oneofUnmarshaler(m, tag, wire, o) + if err == ErrInternalBadWireType { + // Map the error to something more descriptive. + // Do the formatting here to save generated code space. + err = fmt.Errorf("bad wiretype for oneof field in %T", m) + } + if ok { + continue + } + } err = o.skipAndSave(st, tag, wire, base, prop.unrecField) continue } @@ -561,9 +597,13 @@ func (o *Buffer) dec_slice_packed_bool(p *Properties, base structPointer) error return err } nb := int(nn) // number of bytes of encoded bools + fin := o.index + nb + if fin < o.index { + return errOverflow + } y := *v - for i := 0; i < nb; i++ { + for o.index < fin { u, err := p.valDec(o) if err != nil { return err @@ -675,7 +715,7 @@ func (o *Buffer) dec_new_map(p *Properties, base structPointer) error { oi := o.index // index at the end of this map entry o.index -= len(raw) // move buffer back to start of map entry - mptr := structPointer_Map(base, p.field, p.mtype) // *map[K]V + mptr := structPointer_NewAt(base, p.field, p.mtype) // *map[K]V if mptr.Elem().IsNil() { mptr.Elem().Set(reflect.MakeMap(mptr.Type().Elem())) } @@ -727,8 +767,14 @@ func (o *Buffer) dec_new_map(p *Properties, base structPointer) error { return fmt.Errorf("proto: bad map data tag %d", raw[0]) } } + keyelem, valelem := keyptr.Elem(), valptr.Elem() + if !keyelem.IsValid() || !valelem.IsValid() { + // We did not decode the key or the value in the map entry. + // Either way, it's an invalid map entry. + return fmt.Errorf("proto: bad map data: missing key/val") + } - v.SetMapIndex(keyptr.Elem(), valptr.Elem()) + v.SetMapIndex(keyelem, valelem) return nil } diff --git a/components/engine/vendor/src/github.com/golang/protobuf/proto/encode.go b/components/engine/vendor/src/github.com/golang/protobuf/proto/encode.go index cd826e9b1a..231b07401a 100644 --- a/components/engine/vendor/src/github.com/golang/protobuf/proto/encode.go +++ b/components/engine/vendor/src/github.com/golang/protobuf/proto/encode.go @@ -105,6 +105,11 @@ func (p *Buffer) EncodeVarint(x uint64) error { return nil } +// SizeVarint returns the varint encoding size of an integer. +func SizeVarint(x uint64) int { + return sizeVarint(x) +} + func sizeVarint(x uint64) (n int) { for { n++ @@ -228,6 +233,20 @@ func Marshal(pb Message) ([]byte, error) { return p.buf, err } +// EncodeMessage writes the protocol buffer to the Buffer, +// prefixed by a varint-encoded length. +func (p *Buffer) EncodeMessage(pb Message) error { + t, base, err := getbase(pb) + if structPointer_IsNil(base) { + return ErrNil + } + if err == nil { + var state errorState + err = p.enc_len_struct(GetProperties(t.Elem()), base, &state) + } + return err +} + // Marshal takes the protocol buffer // and encodes it into the wire format, writing the result to the // Buffer. @@ -318,7 +337,7 @@ func size_bool(p *Properties, base structPointer) int { func size_proto3_bool(p *Properties, base structPointer) int { v := *structPointer_BoolVal(base, p.field) - if !v { + if !v && !p.oneof { return 0 } return len(p.tagcode) + 1 // each bool takes exactly one byte @@ -361,7 +380,7 @@ func size_int32(p *Properties, base structPointer) (n int) { func size_proto3_int32(p *Properties, base structPointer) (n int) { v := structPointer_Word32Val(base, p.field) x := int32(word32Val_Get(v)) // permit sign extension to use full 64-bit range - if x == 0 { + if x == 0 && !p.oneof { return 0 } n += len(p.tagcode) @@ -407,7 +426,7 @@ func size_uint32(p *Properties, base structPointer) (n int) { func size_proto3_uint32(p *Properties, base structPointer) (n int) { v := structPointer_Word32Val(base, p.field) x := word32Val_Get(v) - if x == 0 { + if x == 0 && !p.oneof { return 0 } n += len(p.tagcode) @@ -452,7 +471,7 @@ func size_int64(p *Properties, base structPointer) (n int) { func size_proto3_int64(p *Properties, base structPointer) (n int) { v := structPointer_Word64Val(base, p.field) x := word64Val_Get(v) - if x == 0 { + if x == 0 && !p.oneof { return 0 } n += len(p.tagcode) @@ -495,7 +514,7 @@ func size_string(p *Properties, base structPointer) (n int) { func size_proto3_string(p *Properties, base structPointer) (n int) { v := *structPointer_StringVal(base, p.field) - if v == "" { + if v == "" && !p.oneof { return 0 } n += len(p.tagcode) @@ -529,7 +548,7 @@ func (o *Buffer) enc_struct_message(p *Properties, base structPointer) error { } o.buf = append(o.buf, p.tagcode...) o.EncodeRawBytes(data) - return nil + return state.err } o.buf = append(o.buf, p.tagcode...) @@ -667,7 +686,7 @@ func (o *Buffer) enc_proto3_slice_byte(p *Properties, base structPointer) error func size_slice_byte(p *Properties, base structPointer) (n int) { s := *structPointer_Bytes(base, p.field) - if s == nil { + if s == nil && !p.oneof { return 0 } n += len(p.tagcode) @@ -677,7 +696,7 @@ func size_slice_byte(p *Properties, base structPointer) (n int) { func size_proto3_slice_byte(p *Properties, base structPointer) (n int) { s := *structPointer_Bytes(base, p.field) - if len(s) == 0 { + if len(s) == 0 && !p.oneof { return 0 } n += len(p.tagcode) @@ -1084,7 +1103,7 @@ func (o *Buffer) enc_new_map(p *Properties, base structPointer) error { repeated MapFieldEntry map_field = N; */ - v := structPointer_Map(base, p.field, p.mtype).Elem() // map[K]V + v := structPointer_NewAt(base, p.field, p.mtype).Elem() // map[K]V if v.Len() == 0 { return nil } @@ -1101,11 +1120,15 @@ func (o *Buffer) enc_new_map(p *Properties, base structPointer) error { return nil } - keys := v.MapKeys() - sort.Sort(mapKeys(keys)) - for _, key := range keys { + // Don't sort map keys. It is not required by the spec, and C++ doesn't do it. + for _, key := range v.MapKeys() { val := v.MapIndex(key) + // The only illegal map entry values are nil message pointers. + if val.Kind() == reflect.Ptr && val.IsNil() { + return errors.New("proto: map has nil element") + } + keycopy.Set(key) valcopy.Set(val) @@ -1118,7 +1141,7 @@ func (o *Buffer) enc_new_map(p *Properties, base structPointer) error { } func size_new_map(p *Properties, base structPointer) int { - v := structPointer_Map(base, p.field, p.mtype).Elem() // map[K]V + v := structPointer_NewAt(base, p.field, p.mtype).Elem() // map[K]V keycopy, valcopy, keybase, valbase := mapEncodeScratch(p.mtype) @@ -1128,10 +1151,12 @@ func size_new_map(p *Properties, base structPointer) int { keycopy.Set(key) valcopy.Set(val) - // Tag codes are two bytes per map entry. - n += 2 - n += p.mkeyprop.size(p.mkeyprop, keybase) - n += p.mvalprop.size(p.mvalprop, valbase) + // Tag codes for key and val are the responsibility of the sub-sizer. + keysize := p.mkeyprop.size(p.mkeyprop, keybase) + valsize := p.mvalprop.size(p.mvalprop, valbase) + entry := keysize + valsize + // Add on tag code and length of map entry itself. + n += len(p.tagcode) + sizeVarint(uint64(entry)) + entry } return n } @@ -1194,6 +1219,14 @@ func (o *Buffer) enc_struct(prop *StructProperties, base structPointer) error { } } + // Do oneof fields. + if prop.oneofMarshaler != nil { + m := structPointer_Interface(base, prop.stype).(Message) + if err := prop.oneofMarshaler(m, o); err != nil { + return err + } + } + // Add unrecognized fields at the end. if prop.unrecField.IsValid() { v := *structPointer_Bytes(base, prop.unrecField) @@ -1219,6 +1252,12 @@ func size_struct(prop *StructProperties, base structPointer) (n int) { n += len(v) } + // Factor in any oneof fields. + if prop.oneofSizer != nil { + m := structPointer_Interface(base, prop.stype).(Message) + n += prop.oneofSizer(m) + } + return } diff --git a/components/engine/vendor/src/github.com/golang/protobuf/proto/equal.go b/components/engine/vendor/src/github.com/golang/protobuf/proto/equal.go index d8673a3e97..cc3f2c95a7 100644 --- a/components/engine/vendor/src/github.com/golang/protobuf/proto/equal.go +++ b/components/engine/vendor/src/github.com/golang/protobuf/proto/equal.go @@ -30,7 +30,6 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Protocol buffer comparison. -// TODO: MessageSet. package proto @@ -154,6 +153,17 @@ func equalAny(v1, v2 reflect.Value) bool { return v1.Float() == v2.Float() case reflect.Int32, reflect.Int64: return v1.Int() == v2.Int() + case reflect.Interface: + // Probably a oneof field; compare the inner values. + n1, n2 := v1.IsNil(), v2.IsNil() + if n1 || n2 { + return n1 == n2 + } + e1, e2 := v1.Elem(), v2.Elem() + if e1.Type() != e2.Type() { + return false + } + return equalAny(e1, e2) case reflect.Map: if v1.Len() != v2.Len() { return false diff --git a/components/engine/vendor/src/github.com/golang/protobuf/proto/extensions.go b/components/engine/vendor/src/github.com/golang/protobuf/proto/extensions.go index 5f62dff247..054f4f1df7 100644 --- a/components/engine/vendor/src/github.com/golang/protobuf/proto/extensions.go +++ b/components/engine/vendor/src/github.com/golang/protobuf/proto/extensions.go @@ -222,7 +222,7 @@ func ClearExtension(pb extendableProto, extension *ExtensionDesc) { } // GetExtension parses and returns the given extension of pb. -// If the extension is not present it returns ErrMissingExtension. +// If the extension is not present and has no default value it returns ErrMissingExtension. func GetExtension(pb extendableProto, extension *ExtensionDesc) (interface{}, error) { if err := checkExtensionTypes(pb, extension); err != nil { return nil, err @@ -231,8 +231,11 @@ func GetExtension(pb extendableProto, extension *ExtensionDesc) (interface{}, er emap := pb.ExtensionMap() e, ok := emap[extension.Field] if !ok { - return nil, ErrMissingExtension + // defaultExtensionValue returns the default value or + // ErrMissingExtension if there is no default. + return defaultExtensionValue(extension) } + if e.value != nil { // Already decoded. Check the descriptor, though. if e.desc != extension { @@ -258,12 +261,46 @@ func GetExtension(pb extendableProto, extension *ExtensionDesc) (interface{}, er return e.value, nil } +// defaultExtensionValue returns the default value for extension. +// If no default for an extension is defined ErrMissingExtension is returned. +func defaultExtensionValue(extension *ExtensionDesc) (interface{}, error) { + t := reflect.TypeOf(extension.ExtensionType) + props := extensionProperties(extension) + + sf, _, err := fieldDefault(t, props) + if err != nil { + return nil, err + } + + if sf == nil || sf.value == nil { + // There is no default value. + return nil, ErrMissingExtension + } + + if t.Kind() != reflect.Ptr { + // We do not need to return a Ptr, we can directly return sf.value. + return sf.value, nil + } + + // We need to return an interface{} that is a pointer to sf.value. + value := reflect.New(t).Elem() + value.Set(reflect.New(value.Type().Elem())) + if sf.kind == reflect.Int32 { + // We may have an int32 or an enum, but the underlying data is int32. + // Since we can't set an int32 into a non int32 reflect.value directly + // set it as a int32. + value.Elem().SetInt(int64(sf.value.(int32))) + } else { + value.Elem().Set(reflect.ValueOf(sf.value)) + } + return value.Interface(), nil +} + // decodeExtension decodes an extension encoded in b. func decodeExtension(b []byte, extension *ExtensionDesc) (interface{}, error) { o := NewBuffer(b) t := reflect.TypeOf(extension.ExtensionType) - rep := extension.repeated() props := extensionProperties(extension) @@ -285,7 +322,7 @@ func decodeExtension(b []byte, extension *ExtensionDesc) (interface{}, error) { return nil, err } - if !rep || o.index >= len(o.buf) { + if o.index >= len(o.buf) { break } } diff --git a/components/engine/vendor/src/github.com/golang/protobuf/proto/lib.go b/components/engine/vendor/src/github.com/golang/protobuf/proto/lib.go index f81052f2ae..dcabe3bc0d 100644 --- a/components/engine/vendor/src/github.com/golang/protobuf/proto/lib.go +++ b/components/engine/vendor/src/github.com/golang/protobuf/proto/lib.go @@ -30,179 +30,230 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /* - Package proto converts data structures to and from the wire format of - protocol buffers. It works in concert with the Go source code generated - for .proto files by the protocol compiler. +Package proto converts data structures to and from the wire format of +protocol buffers. It works in concert with the Go source code generated +for .proto files by the protocol compiler. - A summary of the properties of the protocol buffer interface - for a protocol buffer variable v: +A summary of the properties of the protocol buffer interface +for a protocol buffer variable v: - - Names are turned from camel_case to CamelCase for export. - - There are no methods on v to set fields; just treat - them as structure fields. - - There are getters that return a field's value if set, - and return the field's default value if unset. - The getters work even if the receiver is a nil message. - - The zero value for a struct is its correct initialization state. - All desired fields must be set before marshaling. - - A Reset() method will restore a protobuf struct to its zero state. - - Non-repeated fields are pointers to the values; nil means unset. - That is, optional or required field int32 f becomes F *int32. - - Repeated fields are slices. - - Helper functions are available to aid the setting of fields. - msg.Foo = proto.String("hello") // set field - - Constants are defined to hold the default values of all fields that - have them. They have the form Default_StructName_FieldName. - Because the getter methods handle defaulted values, - direct use of these constants should be rare. - - Enums are given type names and maps from names to values. - Enum values are prefixed by the enclosing message's name, or by the - enum's type name if it is a top-level enum. Enum types have a String - method, and a Enum method to assist in message construction. - - Nested messages, groups and enums have type names prefixed with the name of - the surrounding message type. - - Extensions are given descriptor names that start with E_, - followed by an underscore-delimited list of the nested messages - that contain it (if any) followed by the CamelCased name of the - extension field itself. HasExtension, ClearExtension, GetExtension - and SetExtension are functions for manipulating extensions. - - Marshal and Unmarshal are functions to encode and decode the wire format. + - Names are turned from camel_case to CamelCase for export. + - There are no methods on v to set fields; just treat + them as structure fields. + - There are getters that return a field's value if set, + and return the field's default value if unset. + The getters work even if the receiver is a nil message. + - The zero value for a struct is its correct initialization state. + All desired fields must be set before marshaling. + - A Reset() method will restore a protobuf struct to its zero state. + - Non-repeated fields are pointers to the values; nil means unset. + That is, optional or required field int32 f becomes F *int32. + - Repeated fields are slices. + - Helper functions are available to aid the setting of fields. + msg.Foo = proto.String("hello") // set field + - Constants are defined to hold the default values of all fields that + have them. They have the form Default_StructName_FieldName. + Because the getter methods handle defaulted values, + direct use of these constants should be rare. + - Enums are given type names and maps from names to values. + Enum values are prefixed by the enclosing message's name, or by the + enum's type name if it is a top-level enum. Enum types have a String + method, and a Enum method to assist in message construction. + - Nested messages, groups and enums have type names prefixed with the name of + the surrounding message type. + - Extensions are given descriptor names that start with E_, + followed by an underscore-delimited list of the nested messages + that contain it (if any) followed by the CamelCased name of the + extension field itself. HasExtension, ClearExtension, GetExtension + and SetExtension are functions for manipulating extensions. + - Oneof field sets are given a single field in their message, + with distinguished wrapper types for each possible field value. + - Marshal and Unmarshal are functions to encode and decode the wire format. - The simplest way to describe this is to see an example. - Given file test.proto, containing +The simplest way to describe this is to see an example. +Given file test.proto, containing - package example; + package example; - enum FOO { X = 17; } + enum FOO { X = 17; } - message Test { - required string label = 1; - optional int32 type = 2 [default=77]; - repeated int64 reps = 3; - optional group OptionalGroup = 4 { - required string RequiredField = 5; - } + message Test { + required string label = 1; + optional int32 type = 2 [default=77]; + repeated int64 reps = 3; + optional group OptionalGroup = 4 { + required string RequiredField = 5; + } + oneof union { + int32 number = 6; + string name = 7; + } + } + +The resulting file, test.pb.go, is: + + package example + + import proto "github.com/golang/protobuf/proto" + import math "math" + + type FOO int32 + const ( + FOO_X FOO = 17 + ) + var FOO_name = map[int32]string{ + 17: "X", + } + var FOO_value = map[string]int32{ + "X": 17, + } + + func (x FOO) Enum() *FOO { + p := new(FOO) + *p = x + return p + } + func (x FOO) String() string { + return proto.EnumName(FOO_name, int32(x)) + } + func (x *FOO) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(FOO_value, data) + if err != nil { + return err } + *x = FOO(value) + return nil + } - The resulting file, test.pb.go, is: + type Test struct { + Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"` + Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"` + Reps []int64 `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"` + Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup" json:"optionalgroup,omitempty"` + // Types that are valid to be assigned to Union: + // *Test_Number + // *Test_Name + Union isTest_Union `protobuf_oneof:"union"` + XXX_unrecognized []byte `json:"-"` + } + func (m *Test) Reset() { *m = Test{} } + func (m *Test) String() string { return proto.CompactTextString(m) } + func (*Test) ProtoMessage() {} - package example + type isTest_Union interface { + isTest_Union() + } - import proto "github.com/golang/protobuf/proto" - import math "math" + type Test_Number struct { + Number int32 `protobuf:"varint,6,opt,name=number"` + } + type Test_Name struct { + Name string `protobuf:"bytes,7,opt,name=name"` + } - type FOO int32 - const ( - FOO_X FOO = 17 - ) - var FOO_name = map[int32]string{ - 17: "X", + func (*Test_Number) isTest_Union() {} + func (*Test_Name) isTest_Union() {} + + func (m *Test) GetUnion() isTest_Union { + if m != nil { + return m.Union } - var FOO_value = map[string]int32{ - "X": 17, + return nil + } + const Default_Test_Type int32 = 77 + + func (m *Test) GetLabel() string { + if m != nil && m.Label != nil { + return *m.Label } + return "" + } - func (x FOO) Enum() *FOO { - p := new(FOO) - *p = x - return p + func (m *Test) GetType() int32 { + if m != nil && m.Type != nil { + return *m.Type } - func (x FOO) String() string { - return proto.EnumName(FOO_name, int32(x)) + return Default_Test_Type + } + + func (m *Test) GetOptionalgroup() *Test_OptionalGroup { + if m != nil { + return m.Optionalgroup } - func (x *FOO) UnmarshalJSON(data []byte) error { - value, err := proto.UnmarshalJSONEnum(FOO_value, data) - if err != nil { - return err - } - *x = FOO(value) - return nil + return nil + } + + type Test_OptionalGroup struct { + RequiredField *string `protobuf:"bytes,5,req" json:"RequiredField,omitempty"` + } + func (m *Test_OptionalGroup) Reset() { *m = Test_OptionalGroup{} } + func (m *Test_OptionalGroup) String() string { return proto.CompactTextString(m) } + + func (m *Test_OptionalGroup) GetRequiredField() string { + if m != nil && m.RequiredField != nil { + return *m.RequiredField } + return "" + } - type Test struct { - Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"` - Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"` - Reps []int64 `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"` - Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup" json:"optionalgroup,omitempty"` - XXX_unrecognized []byte `json:"-"` + func (m *Test) GetNumber() int32 { + if x, ok := m.GetUnion().(*Test_Number); ok { + return x.Number } - func (m *Test) Reset() { *m = Test{} } - func (m *Test) String() string { return proto.CompactTextString(m) } - func (*Test) ProtoMessage() {} - const Default_Test_Type int32 = 77 + return 0 + } - func (m *Test) GetLabel() string { - if m != nil && m.Label != nil { - return *m.Label - } - return "" + func (m *Test) GetName() string { + if x, ok := m.GetUnion().(*Test_Name); ok { + return x.Name } + return "" + } - func (m *Test) GetType() int32 { - if m != nil && m.Type != nil { - return *m.Type - } - return Default_Test_Type + func init() { + proto.RegisterEnum("example.FOO", FOO_name, FOO_value) + } + +To create and play with a Test object: + + package main + + import ( + "log" + + "github.com/golang/protobuf/proto" + pb "./example.pb" + ) + + func main() { + test := &pb.Test{ + Label: proto.String("hello"), + Type: proto.Int32(17), + Optionalgroup: &pb.Test_OptionalGroup{ + RequiredField: proto.String("good bye"), + }, + Union: &pb.Test_Name{"fred"}, } - - func (m *Test) GetOptionalgroup() *Test_OptionalGroup { - if m != nil { - return m.Optionalgroup - } - return nil + data, err := proto.Marshal(test) + if err != nil { + log.Fatal("marshaling error: ", err) } - - type Test_OptionalGroup struct { - RequiredField *string `protobuf:"bytes,5,req" json:"RequiredField,omitempty"` + newTest := &pb.Test{} + err = proto.Unmarshal(data, newTest) + if err != nil { + log.Fatal("unmarshaling error: ", err) } - func (m *Test_OptionalGroup) Reset() { *m = Test_OptionalGroup{} } - func (m *Test_OptionalGroup) String() string { return proto.CompactTextString(m) } - - func (m *Test_OptionalGroup) GetRequiredField() string { - if m != nil && m.RequiredField != nil { - return *m.RequiredField - } - return "" + // Now test and newTest contain the same data. + if test.GetLabel() != newTest.GetLabel() { + log.Fatalf("data mismatch %q != %q", test.GetLabel(), newTest.GetLabel()) } - - func init() { - proto.RegisterEnum("example.FOO", FOO_name, FOO_value) - } - - To create and play with a Test object: - - package main - - import ( - "log" - - "github.com/golang/protobuf/proto" - pb "./example.pb" - ) - - func main() { - test := &pb.Test{ - Label: proto.String("hello"), - Type: proto.Int32(17), - Optionalgroup: &pb.Test_OptionalGroup{ - RequiredField: proto.String("good bye"), - }, - } - data, err := proto.Marshal(test) - if err != nil { - log.Fatal("marshaling error: ", err) - } - newTest := &pb.Test{} - err = proto.Unmarshal(data, newTest) - if err != nil { - log.Fatal("unmarshaling error: ", err) - } - // Now test and newTest contain the same data. - if test.GetLabel() != newTest.GetLabel() { - log.Fatalf("data mismatch %q != %q", test.GetLabel(), newTest.GetLabel()) - } - // etc. + // Use a type switch to determine which oneof was set. + switch u := test.Union.(type) { + case *pb.Test_Number: // u.Number contains the number. + case *pb.Test_Name: // u.Name contains the string. } + // etc. + } */ package proto @@ -211,6 +262,7 @@ import ( "fmt" "log" "reflect" + "sort" "strconv" "sync" ) @@ -385,13 +437,13 @@ func UnmarshalJSONEnum(m map[string]int32, data []byte, enumName string) (int32, // DebugPrint dumps the encoded data in b in a debugging format with a header // including the string s. Used in testing but made available for general debugging. -func (o *Buffer) DebugPrint(s string, b []byte) { +func (p *Buffer) DebugPrint(s string, b []byte) { var u uint64 - obuf := o.buf - index := o.index - o.buf = b - o.index = 0 + obuf := p.buf + index := p.index + p.buf = b + p.index = 0 depth := 0 fmt.Printf("\n--- %s ---\n", s) @@ -402,12 +454,12 @@ out: fmt.Print(" ") } - index := o.index - if index == len(o.buf) { + index := p.index + if index == len(p.buf) { break } - op, err := o.DecodeVarint() + op, err := p.DecodeVarint() if err != nil { fmt.Printf("%3d: fetching op err %v\n", index, err) break out @@ -424,7 +476,7 @@ out: case WireBytes: var r []byte - r, err = o.DecodeRawBytes(false) + r, err = p.DecodeRawBytes(false) if err != nil { break out } @@ -445,7 +497,7 @@ out: fmt.Printf("\n") case WireFixed32: - u, err = o.DecodeFixed32() + u, err = p.DecodeFixed32() if err != nil { fmt.Printf("%3d: t=%3d fix32 err %v\n", index, tag, err) break out @@ -453,16 +505,15 @@ out: fmt.Printf("%3d: t=%3d fix32 %d\n", index, tag, u) case WireFixed64: - u, err = o.DecodeFixed64() + u, err = p.DecodeFixed64() if err != nil { fmt.Printf("%3d: t=%3d fix64 err %v\n", index, tag, err) break out } fmt.Printf("%3d: t=%3d fix64 %d\n", index, tag, u) - break case WireVarint: - u, err = o.DecodeVarint() + u, err = p.DecodeVarint() if err != nil { fmt.Printf("%3d: t=%3d varint err %v\n", index, tag, err) break out @@ -470,30 +521,22 @@ out: fmt.Printf("%3d: t=%3d varint %d\n", index, tag, u) case WireStartGroup: - if err != nil { - fmt.Printf("%3d: t=%3d start err %v\n", index, tag, err) - break out - } fmt.Printf("%3d: t=%3d start\n", index, tag) depth++ case WireEndGroup: depth-- - if err != nil { - fmt.Printf("%3d: t=%3d end err %v\n", index, tag, err) - break out - } fmt.Printf("%3d: t=%3d end\n", index, tag) } } if depth != 0 { - fmt.Printf("%3d: start-end not balanced %d\n", o.index, depth) + fmt.Printf("%3d: start-end not balanced %d\n", p.index, depth) } fmt.Printf("\n") - o.buf = obuf - o.index = index + p.buf = obuf + p.index = index } // SetDefaults sets unset protocol buffer fields to their default values. @@ -607,13 +650,15 @@ func setDefaults(v reflect.Value, recur, zeros bool) { for _, ni := range dm.nested { f := v.Field(ni) - if f.IsNil() { - continue - } - // f is *T or []*T - if f.Kind() == reflect.Ptr { + // f is *T or []*T or map[T]*T + switch f.Kind() { + case reflect.Ptr: + if f.IsNil() { + continue + } setDefaults(f, recur, zeros) - } else { + + case reflect.Slice: for i := 0; i < f.Len(); i++ { e := f.Index(i) if e.IsNil() { @@ -621,6 +666,15 @@ func setDefaults(v reflect.Value, recur, zeros bool) { } setDefaults(e, recur, zeros) } + + case reflect.Map: + for _, k := range f.MapKeys() { + e := f.MapIndex(k) + if e.IsNil() { + continue + } + setDefaults(e, recur, zeros) + } } } } @@ -646,10 +700,6 @@ type scalarField struct { value interface{} // the proto-declared default value, or nil } -func ptrToStruct(t reflect.Type) bool { - return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct -} - // t is a struct type. func buildDefaultMessage(t reflect.Type) (dm defaultMessage) { sprop := GetProperties(t) @@ -661,99 +711,173 @@ func buildDefaultMessage(t reflect.Type) (dm defaultMessage) { } ft := t.Field(fi).Type - // nested messages - if ptrToStruct(ft) || (ft.Kind() == reflect.Slice && ptrToStruct(ft.Elem())) { + sf, nested, err := fieldDefault(ft, prop) + switch { + case err != nil: + log.Print(err) + case nested: dm.nested = append(dm.nested, fi) - continue + case sf != nil: + sf.index = fi + dm.scalars = append(dm.scalars, *sf) } - - sf := scalarField{ - index: fi, - kind: ft.Elem().Kind(), - } - - // scalar fields without defaults - if !prop.HasDefault { - dm.scalars = append(dm.scalars, sf) - continue - } - - // a scalar field: either *T or []byte - switch ft.Elem().Kind() { - case reflect.Bool: - x, err := strconv.ParseBool(prop.Default) - if err != nil { - log.Printf("proto: bad default bool %q: %v", prop.Default, err) - continue - } - sf.value = x - case reflect.Float32: - x, err := strconv.ParseFloat(prop.Default, 32) - if err != nil { - log.Printf("proto: bad default float32 %q: %v", prop.Default, err) - continue - } - sf.value = float32(x) - case reflect.Float64: - x, err := strconv.ParseFloat(prop.Default, 64) - if err != nil { - log.Printf("proto: bad default float64 %q: %v", prop.Default, err) - continue - } - sf.value = x - case reflect.Int32: - x, err := strconv.ParseInt(prop.Default, 10, 32) - if err != nil { - log.Printf("proto: bad default int32 %q: %v", prop.Default, err) - continue - } - sf.value = int32(x) - case reflect.Int64: - x, err := strconv.ParseInt(prop.Default, 10, 64) - if err != nil { - log.Printf("proto: bad default int64 %q: %v", prop.Default, err) - continue - } - sf.value = x - case reflect.String: - sf.value = prop.Default - case reflect.Uint8: - // []byte (not *uint8) - sf.value = []byte(prop.Default) - case reflect.Uint32: - x, err := strconv.ParseUint(prop.Default, 10, 32) - if err != nil { - log.Printf("proto: bad default uint32 %q: %v", prop.Default, err) - continue - } - sf.value = uint32(x) - case reflect.Uint64: - x, err := strconv.ParseUint(prop.Default, 10, 64) - if err != nil { - log.Printf("proto: bad default uint64 %q: %v", prop.Default, err) - continue - } - sf.value = x - default: - log.Printf("proto: unhandled def kind %v", ft.Elem().Kind()) - continue - } - - dm.scalars = append(dm.scalars, sf) } return dm } +// fieldDefault returns the scalarField for field type ft. +// sf will be nil if the field can not have a default. +// nestedMessage will be true if this is a nested message. +// Note that sf.index is not set on return. +func fieldDefault(ft reflect.Type, prop *Properties) (sf *scalarField, nestedMessage bool, err error) { + var canHaveDefault bool + switch ft.Kind() { + case reflect.Ptr: + if ft.Elem().Kind() == reflect.Struct { + nestedMessage = true + } else { + canHaveDefault = true // proto2 scalar field + } + + case reflect.Slice: + switch ft.Elem().Kind() { + case reflect.Ptr: + nestedMessage = true // repeated message + case reflect.Uint8: + canHaveDefault = true // bytes field + } + + case reflect.Map: + if ft.Elem().Kind() == reflect.Ptr { + nestedMessage = true // map with message values + } + } + + if !canHaveDefault { + if nestedMessage { + return nil, true, nil + } + return nil, false, nil + } + + // We now know that ft is a pointer or slice. + sf = &scalarField{kind: ft.Elem().Kind()} + + // scalar fields without defaults + if !prop.HasDefault { + return sf, false, nil + } + + // a scalar field: either *T or []byte + switch ft.Elem().Kind() { + case reflect.Bool: + x, err := strconv.ParseBool(prop.Default) + if err != nil { + return nil, false, fmt.Errorf("proto: bad default bool %q: %v", prop.Default, err) + } + sf.value = x + case reflect.Float32: + x, err := strconv.ParseFloat(prop.Default, 32) + if err != nil { + return nil, false, fmt.Errorf("proto: bad default float32 %q: %v", prop.Default, err) + } + sf.value = float32(x) + case reflect.Float64: + x, err := strconv.ParseFloat(prop.Default, 64) + if err != nil { + return nil, false, fmt.Errorf("proto: bad default float64 %q: %v", prop.Default, err) + } + sf.value = x + case reflect.Int32: + x, err := strconv.ParseInt(prop.Default, 10, 32) + if err != nil { + return nil, false, fmt.Errorf("proto: bad default int32 %q: %v", prop.Default, err) + } + sf.value = int32(x) + case reflect.Int64: + x, err := strconv.ParseInt(prop.Default, 10, 64) + if err != nil { + return nil, false, fmt.Errorf("proto: bad default int64 %q: %v", prop.Default, err) + } + sf.value = x + case reflect.String: + sf.value = prop.Default + case reflect.Uint8: + // []byte (not *uint8) + sf.value = []byte(prop.Default) + case reflect.Uint32: + x, err := strconv.ParseUint(prop.Default, 10, 32) + if err != nil { + return nil, false, fmt.Errorf("proto: bad default uint32 %q: %v", prop.Default, err) + } + sf.value = uint32(x) + case reflect.Uint64: + x, err := strconv.ParseUint(prop.Default, 10, 64) + if err != nil { + return nil, false, fmt.Errorf("proto: bad default uint64 %q: %v", prop.Default, err) + } + sf.value = x + default: + return nil, false, fmt.Errorf("proto: unhandled def kind %v", ft.Elem().Kind()) + } + + return sf, false, nil +} + // Map fields may have key types of non-float scalars, strings and enums. // The easiest way to sort them in some deterministic order is to use fmt. // If this turns out to be inefficient we can always consider other options, // such as doing a Schwartzian transform. -type mapKeys []reflect.Value +func mapKeys(vs []reflect.Value) sort.Interface { + s := mapKeySorter{ + vs: vs, + // default Less function: textual comparison + less: func(a, b reflect.Value) bool { + return fmt.Sprint(a.Interface()) < fmt.Sprint(b.Interface()) + }, + } -func (s mapKeys) Len() int { return len(s) } -func (s mapKeys) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s mapKeys) Less(i, j int) bool { - return fmt.Sprint(s[i].Interface()) < fmt.Sprint(s[j].Interface()) + // Type specialization per https://developers.google.com/protocol-buffers/docs/proto#maps; + // numeric keys are sorted numerically. + if len(vs) == 0 { + return s + } + switch vs[0].Kind() { + case reflect.Int32, reflect.Int64: + s.less = func(a, b reflect.Value) bool { return a.Int() < b.Int() } + case reflect.Uint32, reflect.Uint64: + s.less = func(a, b reflect.Value) bool { return a.Uint() < b.Uint() } + } + + return s +} + +type mapKeySorter struct { + vs []reflect.Value + less func(a, b reflect.Value) bool +} + +func (s mapKeySorter) Len() int { return len(s.vs) } +func (s mapKeySorter) Swap(i, j int) { s.vs[i], s.vs[j] = s.vs[j], s.vs[i] } +func (s mapKeySorter) Less(i, j int) bool { + return s.less(s.vs[i], s.vs[j]) +} + +// isProto3Zero reports whether v is a zero proto3 value. +func isProto3Zero(v reflect.Value) bool { + switch v.Kind() { + case reflect.Bool: + return !v.Bool() + case reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint32, reflect.Uint64: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.String: + return v.String() == "" + } + return false } diff --git a/components/engine/vendor/src/github.com/golang/protobuf/proto/message_set.go b/components/engine/vendor/src/github.com/golang/protobuf/proto/message_set.go index 9d912bce19..e25e01e637 100644 --- a/components/engine/vendor/src/github.com/golang/protobuf/proto/message_set.go +++ b/components/engine/vendor/src/github.com/golang/protobuf/proto/message_set.go @@ -44,11 +44,11 @@ import ( "sort" ) -// ErrNoMessageTypeId occurs when a protocol buffer does not have a message type ID. +// errNoMessageTypeID occurs when a protocol buffer does not have a message type ID. // A message type ID is required for storing a protocol buffer in a message set. -var ErrNoMessageTypeId = errors.New("proto does not have a message type ID") +var errNoMessageTypeID = errors.New("proto does not have a message type ID") -// The first two types (_MessageSet_Item and MessageSet) +// The first two types (_MessageSet_Item and messageSet) // model what the protocol compiler produces for the following protocol message: // message MessageSet { // repeated group Item = 1 { @@ -58,27 +58,20 @@ var ErrNoMessageTypeId = errors.New("proto does not have a message type ID") // } // That is the MessageSet wire format. We can't use a proto to generate these // because that would introduce a circular dependency between it and this package. -// -// When a proto1 proto has a field that looks like: -// optional message info = 3; -// the protocol compiler produces a field in the generated struct that looks like: -// Info *_proto_.MessageSet `protobuf:"bytes,3,opt,name=info"` -// The package is automatically inserted so there is no need for that proto file to -// import this package. type _MessageSet_Item struct { TypeId *int32 `protobuf:"varint,2,req,name=type_id"` Message []byte `protobuf:"bytes,3,req,name=message"` } -type MessageSet struct { +type messageSet struct { Item []*_MessageSet_Item `protobuf:"group,1,rep"` XXX_unrecognized []byte // TODO: caching? } -// Make sure MessageSet is a Message. -var _ Message = (*MessageSet)(nil) +// Make sure messageSet is a Message. +var _ Message = (*messageSet)(nil) // messageTypeIder is an interface satisfied by a protocol buffer type // that may be stored in a MessageSet. @@ -86,7 +79,7 @@ type messageTypeIder interface { MessageTypeId() int32 } -func (ms *MessageSet) find(pb Message) *_MessageSet_Item { +func (ms *messageSet) find(pb Message) *_MessageSet_Item { mti, ok := pb.(messageTypeIder) if !ok { return nil @@ -100,24 +93,24 @@ func (ms *MessageSet) find(pb Message) *_MessageSet_Item { return nil } -func (ms *MessageSet) Has(pb Message) bool { +func (ms *messageSet) Has(pb Message) bool { if ms.find(pb) != nil { return true } return false } -func (ms *MessageSet) Unmarshal(pb Message) error { +func (ms *messageSet) Unmarshal(pb Message) error { if item := ms.find(pb); item != nil { return Unmarshal(item.Message, pb) } if _, ok := pb.(messageTypeIder); !ok { - return ErrNoMessageTypeId + return errNoMessageTypeID } return nil // TODO: return error instead? } -func (ms *MessageSet) Marshal(pb Message) error { +func (ms *messageSet) Marshal(pb Message) error { msg, err := Marshal(pb) if err != nil { return err @@ -130,7 +123,7 @@ func (ms *MessageSet) Marshal(pb Message) error { mti, ok := pb.(messageTypeIder) if !ok { - return ErrNoMessageTypeId + return errNoMessageTypeID } mtid := mti.MessageTypeId() @@ -141,9 +134,9 @@ func (ms *MessageSet) Marshal(pb Message) error { return nil } -func (ms *MessageSet) Reset() { *ms = MessageSet{} } -func (ms *MessageSet) String() string { return CompactTextString(ms) } -func (*MessageSet) ProtoMessage() {} +func (ms *messageSet) Reset() { *ms = messageSet{} } +func (ms *messageSet) String() string { return CompactTextString(ms) } +func (*messageSet) ProtoMessage() {} // Support for the message_set_wire_format message option. @@ -169,7 +162,7 @@ func MarshalMessageSet(m map[int32]Extension) ([]byte, error) { } sort.Ints(ids) - ms := &MessageSet{Item: make([]*_MessageSet_Item, 0, len(m))} + ms := &messageSet{Item: make([]*_MessageSet_Item, 0, len(m))} for _, id := range ids { e := m[int32(id)] // Remove the wire type and field number varint, as well as the length varint. @@ -186,7 +179,7 @@ func MarshalMessageSet(m map[int32]Extension) ([]byte, error) { // UnmarshalMessageSet decodes the extension map encoded in buf in the message set wire format. // It is called by generated Unmarshal methods on protocol buffer messages with the message_set_wire_format option. func UnmarshalMessageSet(buf []byte, m map[int32]Extension) error { - ms := new(MessageSet) + ms := new(messageSet) if err := Unmarshal(buf, ms); err != nil { return err } diff --git a/components/engine/vendor/src/github.com/golang/protobuf/proto/pointer_reflect.go b/components/engine/vendor/src/github.com/golang/protobuf/proto/pointer_reflect.go index c68b12525c..749919d250 100644 --- a/components/engine/vendor/src/github.com/golang/protobuf/proto/pointer_reflect.go +++ b/components/engine/vendor/src/github.com/golang/protobuf/proto/pointer_reflect.go @@ -144,8 +144,8 @@ func structPointer_ExtMap(p structPointer, f field) *map[int32]Extension { return structPointer_ifield(p, f).(*map[int32]Extension) } -// Map returns the reflect.Value for the address of a map field in the struct. -func structPointer_Map(p structPointer, f field, typ reflect.Type) reflect.Value { +// NewAt returns the reflect.Value for a pointer to a field in the struct. +func structPointer_NewAt(p structPointer, f field, typ reflect.Type) reflect.Value { return structPointer_field(p, f).Addr() } diff --git a/components/engine/vendor/src/github.com/golang/protobuf/proto/pointer_unsafe.go b/components/engine/vendor/src/github.com/golang/protobuf/proto/pointer_unsafe.go index 48bc0fa482..e9be0fe92e 100644 --- a/components/engine/vendor/src/github.com/golang/protobuf/proto/pointer_unsafe.go +++ b/components/engine/vendor/src/github.com/golang/protobuf/proto/pointer_unsafe.go @@ -130,8 +130,8 @@ func structPointer_ExtMap(p structPointer, f field) *map[int32]Extension { return (*map[int32]Extension)(unsafe.Pointer(uintptr(p) + uintptr(f))) } -// Map returns the reflect.Value for the address of a map field in the struct. -func structPointer_Map(p structPointer, f field, typ reflect.Type) reflect.Value { +// NewAt returns the reflect.Value for a pointer to a field in the struct. +func structPointer_NewAt(p structPointer, f field, typ reflect.Type) reflect.Value { return reflect.NewAt(typ, unsafe.Pointer(uintptr(p)+uintptr(f))) } diff --git a/components/engine/vendor/src/github.com/golang/protobuf/proto/properties.go b/components/engine/vendor/src/github.com/golang/protobuf/proto/properties.go index 7216544815..d4531c0563 100644 --- a/components/engine/vendor/src/github.com/golang/protobuf/proto/properties.go +++ b/components/engine/vendor/src/github.com/golang/protobuf/proto/properties.go @@ -37,6 +37,7 @@ package proto import ( "fmt" + "log" "os" "reflect" "sort" @@ -84,6 +85,15 @@ type decoder func(p *Buffer, prop *Properties, base structPointer) error // A valueDecoder decodes a single integer in a particular encoding. type valueDecoder func(o *Buffer) (x uint64, err error) +// A oneofMarshaler does the marshaling for all oneof fields in a message. +type oneofMarshaler func(Message, *Buffer) error + +// A oneofUnmarshaler does the unmarshaling for a oneof field in a message. +type oneofUnmarshaler func(Message, int, int, *Buffer) (bool, error) + +// A oneofSizer does the sizing for all oneof fields in a message. +type oneofSizer func(Message) int + // tagMap is an optimization over map[int]int for typical protocol buffer // use-cases. Encoded protocol buffers are often in tag order with small tag // numbers. @@ -132,6 +142,22 @@ type StructProperties struct { order []int // list of struct field numbers in tag order unrecField field // field id of the XXX_unrecognized []byte field extendable bool // is this an extendable proto + + oneofMarshaler oneofMarshaler + oneofUnmarshaler oneofUnmarshaler + oneofSizer oneofSizer + stype reflect.Type + + // OneofTypes contains information about the oneof fields in this message. + // It is keyed by the original name of a field. + OneofTypes map[string]*OneofProperties +} + +// OneofProperties represents information about a specific field in a oneof. +type OneofProperties struct { + Type reflect.Type // pointer to generated struct type for this oneof field + Field int // struct field number of the containing oneof in the message + Prop *Properties } // Implement the sorting interface so we can sort the fields in tag order, as recommended by the spec. @@ -156,6 +182,7 @@ type Properties struct { Packed bool // relevant for repeated primitives only Enum string // set for enum types only proto3 bool // whether this is known to be a proto3 field; set for []byte only + oneof bool // whether this is a oneof field Default string // default value HasDefault bool // whether an explicit default was provided @@ -208,6 +235,9 @@ func (p *Properties) String() string { if p.proto3 { s += ",proto3" } + if p.oneof { + s += ",oneof" + } if len(p.Enum) > 0 { s += ",enum=" + p.Enum } @@ -284,6 +314,8 @@ func (p *Properties) Parse(s string) { p.Enum = f[5:] case f == "proto3": p.proto3 = true + case f == "oneof": + p.oneof = true case strings.HasPrefix(f, "def="): p.HasDefault = true p.Default = f[4:] // rest of string @@ -440,7 +472,12 @@ func (p *Properties) setEncAndDec(typ reflect.Type, f *reflect.StructField, lock p.enc = (*Buffer).enc_slice_byte p.dec = (*Buffer).dec_slice_byte p.size = size_slice_byte - if p.proto3 { + // This is a []byte, which is either a bytes field, + // or the value of a map field. In the latter case, + // we always encode an empty []byte, so we should not + // use the proto3 enc/size funcs. + // f == nil iff this is the key/value of a map field. + if p.proto3 && f != nil { p.enc = (*Buffer).enc_proto3_slice_byte p.size = size_proto3_slice_byte } @@ -660,6 +697,7 @@ func getPropertiesLocked(t reflect.Type) *StructProperties { if f.Name == "XXX_unrecognized" { // special case prop.unrecField = toField(&f) } + oneof := f.Tag.Get("protobuf_oneof") != "" // special case prop.Prop[i] = p prop.order[i] = i if debug { @@ -669,7 +707,7 @@ func getPropertiesLocked(t reflect.Type) *StructProperties { } print("\n") } - if p.enc == nil && !strings.HasPrefix(f.Name, "XXX_") { + if p.enc == nil && !strings.HasPrefix(f.Name, "XXX_") && !oneof { fmt.Fprintln(os.Stderr, "proto: no encoder for", f.Name, f.Type.String(), "[GetProperties]") } } @@ -677,6 +715,41 @@ func getPropertiesLocked(t reflect.Type) *StructProperties { // Re-order prop.order. sort.Sort(prop) + type oneofMessage interface { + XXX_OneofFuncs() (func(Message, *Buffer) error, func(Message, int, int, *Buffer) (bool, error), func(Message) int, []interface{}) + } + if om, ok := reflect.Zero(reflect.PtrTo(t)).Interface().(oneofMessage); ok { + var oots []interface{} + prop.oneofMarshaler, prop.oneofUnmarshaler, prop.oneofSizer, oots = om.XXX_OneofFuncs() + prop.stype = t + + // Interpret oneof metadata. + prop.OneofTypes = make(map[string]*OneofProperties) + for _, oot := range oots { + oop := &OneofProperties{ + Type: reflect.ValueOf(oot).Type(), // *T + Prop: new(Properties), + } + sft := oop.Type.Elem().Field(0) + oop.Prop.Name = sft.Name + oop.Prop.Parse(sft.Tag.Get("protobuf")) + // There will be exactly one interface field that + // this new value is assignable to. + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + if f.Type.Kind() != reflect.Interface { + continue + } + if !oop.Type.AssignableTo(f.Type) { + continue + } + oop.Field = i + break + } + prop.OneofTypes[oop.Prop.OrigName] = oop + } + } + // build required counts // build tags reqCount := 0 @@ -735,3 +808,35 @@ func RegisterEnum(typeName string, unusedNameMap map[int32]string, valueMap map[ } enumValueMaps[typeName] = valueMap } + +// EnumValueMap returns the mapping from names to integers of the +// enum type enumType, or a nil if not found. +func EnumValueMap(enumType string) map[string]int32 { + return enumValueMaps[enumType] +} + +// A registry of all linked message types. +// The string is a fully-qualified proto name ("pkg.Message"). +var ( + protoTypes = make(map[string]reflect.Type) + revProtoTypes = make(map[reflect.Type]string) +) + +// RegisterType is called from generated code and maps from the fully qualified +// proto name to the type (pointer to struct) of the protocol buffer. +func RegisterType(x Message, name string) { + if _, ok := protoTypes[name]; ok { + // TODO: Some day, make this a panic. + log.Printf("proto: duplicate proto type registered: %s", name) + return + } + t := reflect.TypeOf(x) + protoTypes[name] = t + revProtoTypes[t] = name +} + +// MessageName returns the fully-qualified proto name for the given message type. +func MessageName(x Message) string { return revProtoTypes[reflect.TypeOf(x)] } + +// MessageType returns the message type (pointer to struct) for a named message. +func MessageType(name string) reflect.Type { return protoTypes[name] } diff --git a/components/engine/vendor/src/github.com/golang/protobuf/proto/text.go b/components/engine/vendor/src/github.com/golang/protobuf/proto/text.go index 720eac4705..2336b144c1 100644 --- a/components/engine/vendor/src/github.com/golang/protobuf/proto/text.go +++ b/components/engine/vendor/src/github.com/golang/protobuf/proto/text.go @@ -37,11 +37,11 @@ import ( "bufio" "bytes" "encoding" + "errors" "fmt" "io" "log" "math" - "os" "reflect" "sort" "strings" @@ -170,20 +170,12 @@ func writeName(w *textWriter, props *Properties) error { return nil } -var ( - messageSetType = reflect.TypeOf((*MessageSet)(nil)).Elem() -) - // raw is the interface satisfied by RawMessage. type raw interface { Bytes() []byte } func writeStruct(w *textWriter, sv reflect.Value) error { - if sv.Type() == messageSetType { - return writeMessageSet(w, sv.Addr().Interface().(*MessageSet)) - } - st := sv.Type() sprops := GetProperties(st) for i := 0; i < sv.NumField(); i++ { @@ -246,7 +238,7 @@ func writeStruct(w *textWriter, sv reflect.Value) error { } if fv.Kind() == reflect.Map { // Map fields are rendered as a repeated struct with key/value fields. - keys := fv.MapKeys() // TODO: should we sort these for deterministic output? + keys := fv.MapKeys() sort.Sort(mapKeys(keys)) for _, key := range keys { val := fv.MapIndex(key) @@ -283,20 +275,23 @@ func writeStruct(w *textWriter, sv reflect.Value) error { if err := w.WriteByte('\n'); err != nil { return err } - // value - if _, err := w.WriteString("value:"); err != nil { - return err - } - if !w.compact { - if err := w.WriteByte(' '); err != nil { + // nil values aren't legal, but we can avoid panicking because of them. + if val.Kind() != reflect.Ptr || !val.IsNil() { + // value + if _, err := w.WriteString("value:"); err != nil { + return err + } + if !w.compact { + if err := w.WriteByte(' '); err != nil { + return err + } + } + if err := writeAny(w, val, props.mvalprop); err != nil { + return err + } + if err := w.WriteByte('\n'); err != nil { return err } - } - if err := writeAny(w, val, props.mvalprop); err != nil { - return err - } - if err := w.WriteByte('\n'); err != nil { - return err } // close struct w.unindent() @@ -315,26 +310,34 @@ func writeStruct(w *textWriter, sv reflect.Value) error { } if fv.Kind() != reflect.Ptr && fv.Kind() != reflect.Slice { // proto3 non-repeated scalar field; skip if zero value - switch fv.Kind() { - case reflect.Bool: - if !fv.Bool() { + if isProto3Zero(fv) { + continue + } + } + + if fv.Kind() == reflect.Interface { + // Check if it is a oneof. + if st.Field(i).Tag.Get("protobuf_oneof") != "" { + // fv is nil, or holds a pointer to generated struct. + // That generated struct has exactly one field, + // which has a protobuf struct tag. + if fv.IsNil() { continue } - case reflect.Int32, reflect.Int64: - if fv.Int() == 0 { - continue - } - case reflect.Uint32, reflect.Uint64: - if fv.Uint() == 0 { - continue - } - case reflect.Float32, reflect.Float64: - if fv.Float() == 0 { - continue - } - case reflect.String: - if fv.String() == "" { - continue + inner := fv.Elem().Elem() // interface -> *T -> T + tag := inner.Type().Field(0).Tag.Get("protobuf") + props = new(Properties) // Overwrite the outer props var, but not its pointee. + props.Parse(tag) + // Write the value in the oneof, not the oneof itself. + fv = inner.Field(0) + + // Special case to cope with malformed messages gracefully: + // If the value in the oneof is a nil pointer, don't panic + // in writeAny. + if fv.Kind() == reflect.Ptr && fv.IsNil() { + // Use errors.New so writeAny won't render quotes. + msg := errors.New("/* nil */") + fv = reflect.ValueOf(&msg).Elem() } } } @@ -514,44 +517,6 @@ func writeString(w *textWriter, s string) error { return w.WriteByte('"') } -func writeMessageSet(w *textWriter, ms *MessageSet) error { - for _, item := range ms.Item { - id := *item.TypeId - if msd, ok := messageSetMap[id]; ok { - // Known message set type. - if _, err := fmt.Fprintf(w, "[%s]: <\n", msd.name); err != nil { - return err - } - w.indent() - - pb := reflect.New(msd.t.Elem()) - if err := Unmarshal(item.Message, pb.Interface().(Message)); err != nil { - if _, err := fmt.Fprintf(w, "/* bad message: %v */\n", err); err != nil { - return err - } - } else { - if err := writeStruct(w, pb.Elem()); err != nil { - return err - } - } - } else { - // Unknown type. - if _, err := fmt.Fprintf(w, "[%d]: <\n", id); err != nil { - return err - } - w.indent() - if err := writeUnknownStruct(w, item.Message); err != nil { - return err - } - } - w.unindent() - if _, err := w.Write(gtNewline); err != nil { - return err - } - } - return nil -} - func writeUnknownStruct(w *textWriter, data []byte) (err error) { if !w.compact { if _, err := fmt.Fprintf(w, "/* %d unknown bytes */\n", len(data)); err != nil { @@ -666,10 +631,7 @@ func writeExtensions(w *textWriter, pv reflect.Value) error { pb, err := GetExtension(ep, desc) if err != nil { - if _, err := fmt.Fprintln(os.Stderr, "proto: failed getting extension: ", err); err != nil { - return err - } - continue + return fmt.Errorf("failed getting extension: %v", err) } // Repeated extensions will appear as a slice. diff --git a/components/engine/vendor/src/github.com/golang/protobuf/proto/text_parser.go b/components/engine/vendor/src/github.com/golang/protobuf/proto/text_parser.go index d1caeff5d9..6d0cf25894 100644 --- a/components/engine/vendor/src/github.com/golang/protobuf/proto/text_parser.go +++ b/components/engine/vendor/src/github.com/golang/protobuf/proto/text_parser.go @@ -174,7 +174,7 @@ func (p *textParser) advance() { } unq, err := unquoteC(p.s[1:i], rune(p.s[0])) if err != nil { - p.errorf("invalid quoted string %v", p.s[0:i+1]) + p.errorf("invalid quoted string %s: %v", p.s[0:i+1], err) return } p.cur.value, p.s = p.s[0:i+1], p.s[i+1:len(p.s)] @@ -385,8 +385,7 @@ func (p *textParser) missingRequiredFieldError(sv reflect.Value) *RequiredNotSet } // Returns the index in the struct for the named field, as well as the parsed tag properties. -func structFieldByName(st reflect.Type, name string) (int, *Properties, bool) { - sprops := GetProperties(st) +func structFieldByName(sprops *StructProperties, name string) (int, *Properties, bool) { i, ok := sprops.decoderOrigNames[name] if ok { return i, sprops.Prop[i], true @@ -438,7 +437,8 @@ func (p *textParser) checkForColon(props *Properties, typ reflect.Type) *ParseEr func (p *textParser) readStruct(sv reflect.Value, terminator string) error { st := sv.Type() - reqCount := GetProperties(st).reqCount + sprops := GetProperties(st) + reqCount := sprops.reqCount var reqFieldErr error fieldSet := make(map[string]bool) // A struct is a sequence of "name: value", terminated by one of @@ -520,99 +520,113 @@ func (p *textParser) readStruct(sv reflect.Value, terminator string) error { sl = reflect.Append(sl, ext) SetExtension(ep, desc, sl.Interface()) } - } else { - // This is a normal, non-extension field. - name := tok.value - fi, props, ok := structFieldByName(st, name) - if !ok { - return p.errorf("unknown field name %q in %v", name, st) + if err := p.consumeOptionalSeparator(); err != nil { + return err } + continue + } - dst := sv.Field(fi) + // This is a normal, non-extension field. + name := tok.value + var dst reflect.Value + fi, props, ok := structFieldByName(sprops, name) + if ok { + dst = sv.Field(fi) + } else if oop, ok := sprops.OneofTypes[name]; ok { + // It is a oneof. + props = oop.Prop + nv := reflect.New(oop.Type.Elem()) + dst = nv.Elem().Field(0) + sv.Field(oop.Field).Set(nv) + } + if !dst.IsValid() { + return p.errorf("unknown field name %q in %v", name, st) + } - if dst.Kind() == reflect.Map { - // Consume any colon. - if err := p.checkForColon(props, dst.Type()); err != nil { - return err - } - - // Construct the map if it doesn't already exist. - if dst.IsNil() { - dst.Set(reflect.MakeMap(dst.Type())) - } - key := reflect.New(dst.Type().Key()).Elem() - val := reflect.New(dst.Type().Elem()).Elem() - - // The map entry should be this sequence of tokens: - // < key : KEY value : VALUE > - // Technically the "key" and "value" could come in any order, - // but in practice they won't. - - tok := p.next() - var terminator string - switch tok.value { - case "<": - terminator = ">" - case "{": - terminator = "}" - default: - return p.errorf("expected '{' or '<', found %q", tok.value) - } - if err := p.consumeToken("key"); err != nil { - return err - } - if err := p.consumeToken(":"); err != nil { - return err - } - if err := p.readAny(key, props.mkeyprop); err != nil { - return err - } - if err := p.consumeToken("value"); err != nil { - return err - } - if err := p.checkForColon(props.mvalprop, dst.Type().Elem()); err != nil { - return err - } - if err := p.readAny(val, props.mvalprop); err != nil { - return err - } - if err := p.consumeToken(terminator); err != nil { - return err - } - - dst.SetMapIndex(key, val) - continue - } - - // Check that it's not already set if it's not a repeated field. - if !props.Repeated && fieldSet[name] { - return p.errorf("non-repeated field %q was repeated", name) - } - - if err := p.checkForColon(props, st.Field(fi).Type); err != nil { + if dst.Kind() == reflect.Map { + // Consume any colon. + if err := p.checkForColon(props, dst.Type()); err != nil { return err } - // Parse into the field. - fieldSet[name] = true - if err := p.readAny(dst, props); err != nil { - if _, ok := err.(*RequiredNotSetError); !ok { - return err - } - reqFieldErr = err - } else if props.Required { - reqCount-- + // Construct the map if it doesn't already exist. + if dst.IsNil() { + dst.Set(reflect.MakeMap(dst.Type())) } + key := reflect.New(dst.Type().Key()).Elem() + val := reflect.New(dst.Type().Elem()).Elem() + + // The map entry should be this sequence of tokens: + // < key : KEY value : VALUE > + // Technically the "key" and "value" could come in any order, + // but in practice they won't. + + tok := p.next() + var terminator string + switch tok.value { + case "<": + terminator = ">" + case "{": + terminator = "}" + default: + return p.errorf("expected '{' or '<', found %q", tok.value) + } + if err := p.consumeToken("key"); err != nil { + return err + } + if err := p.consumeToken(":"); err != nil { + return err + } + if err := p.readAny(key, props.mkeyprop); err != nil { + return err + } + if err := p.consumeOptionalSeparator(); err != nil { + return err + } + if err := p.consumeToken("value"); err != nil { + return err + } + if err := p.checkForColon(props.mvalprop, dst.Type().Elem()); err != nil { + return err + } + if err := p.readAny(val, props.mvalprop); err != nil { + return err + } + if err := p.consumeOptionalSeparator(); err != nil { + return err + } + if err := p.consumeToken(terminator); err != nil { + return err + } + + dst.SetMapIndex(key, val) + continue } - // For backward compatibility, permit a semicolon or comma after a field. - tok = p.next() - if tok.err != nil { - return tok.err + // Check that it's not already set if it's not a repeated field. + if !props.Repeated && fieldSet[name] { + return p.errorf("non-repeated field %q was repeated", name) } - if tok.value != ";" && tok.value != "," { - p.back() + + if err := p.checkForColon(props, dst.Type()); err != nil { + return err } + + // Parse into the field. + fieldSet[name] = true + if err := p.readAny(dst, props); err != nil { + if _, ok := err.(*RequiredNotSetError); !ok { + return err + } + reqFieldErr = err + } else if props.Required { + reqCount-- + } + + if err := p.consumeOptionalSeparator(); err != nil { + return err + } + } if reqCount > 0 { @@ -621,6 +635,19 @@ func (p *textParser) readStruct(sv reflect.Value, terminator string) error { return reqFieldErr } +// consumeOptionalSeparator consumes an optional semicolon or comma. +// It is used in readStruct to provide backward compatibility. +func (p *textParser) consumeOptionalSeparator() error { + tok := p.next() + if tok.err != nil { + return tok.err + } + if tok.value != ";" && tok.value != "," { + p.back() + } + return nil +} + func (p *textParser) readAny(v reflect.Value, props *Properties) error { tok := p.next() if tok.err != nil { @@ -645,18 +672,32 @@ func (p *textParser) readAny(v reflect.Value, props *Properties) error { fv.Set(reflect.ValueOf(bytes)) return nil } - // Repeated field. May already exist. - flen := fv.Len() - if flen == fv.Cap() { - nav := reflect.MakeSlice(at, flen, 2*flen+1) - reflect.Copy(nav, fv) - fv.Set(nav) + // Repeated field. + if tok.value == "[" { + // Repeated field with list notation, like [1,2,3]. + for { + fv.Set(reflect.Append(fv, reflect.New(at.Elem()).Elem())) + err := p.readAny(fv.Index(fv.Len()-1), props) + if err != nil { + return err + } + tok := p.next() + if tok.err != nil { + return tok.err + } + if tok.value == "]" { + break + } + if tok.value != "," { + return p.errorf("Expected ']' or ',' found %q", tok.value) + } + } + return nil } - fv.SetLen(flen + 1) - - // Read one. + // One value of the repeated field. p.back() - return p.readAny(fv.Index(flen), props) + fv.Set(reflect.Append(fv, reflect.New(at.Elem()).Elem())) + return p.readAny(fv.Index(fv.Len()-1), props) case reflect.Bool: // Either "true", "false", 1 or 0. switch tok.value { diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/README.md b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/README.md index fc6b4b0b18..2f63e64cc2 100644 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/README.md +++ b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/README.md @@ -76,7 +76,7 @@ config := &configs.Config{ Name: "test-container", Parent: "system", Resources: &configs.Resources{ - MemorySwappiness: -1, + MemorySwappiness: nil, AllowAllDevices: false, AllowedDevices: configs.DefaultAllowedDevices, }, diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/apply_raw.go b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/apply_raw.go index 758d119621..6def4ea542 100644 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/apply_raw.go +++ b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/apply_raw.go @@ -130,6 +130,8 @@ func (m *Manager) Apply(pid int) (err error) { return cgroups.EnterPid(m.Paths, pid) } + m.mu.Lock() + defer m.mu.Unlock() paths := make(map[string]string) for _, sys := range subsystems { if err := sys.Apply(d); err != nil { diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go index 2121f6d4d6..e3fd327e91 100644 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go +++ b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go @@ -86,14 +86,14 @@ func (s *MemoryGroup) Set(path string, cgroup *configs.Cgroup) error { return err } } - if cgroup.Resources.MemorySwappiness >= 0 && cgroup.Resources.MemorySwappiness <= 100 { - if err := writeFile(path, "memory.swappiness", strconv.FormatInt(cgroup.Resources.MemorySwappiness, 10)); err != nil { + if cgroup.Resources.MemorySwappiness == nil || int64(*cgroup.Resources.MemorySwappiness) == -1 { + return nil + } else if int64(*cgroup.Resources.MemorySwappiness) >= 0 && int64(*cgroup.Resources.MemorySwappiness) <= 100 { + if err := writeFile(path, "memory.swappiness", strconv.FormatInt(*cgroup.Resources.MemorySwappiness, 10)); err != nil { return err } - } else if cgroup.Resources.MemorySwappiness == -1 { - return nil } else { - return fmt.Errorf("invalid value:%d. valid memory swappiness range is 0-100", cgroup.Resources.MemorySwappiness) + return fmt.Errorf("invalid value:%d. valid memory swappiness range is 0-100", int64(*cgroup.Resources.MemorySwappiness)) } return nil @@ -149,7 +149,7 @@ func memoryAssigned(cgroup *configs.Cgroup) bool { cgroup.Resources.MemorySwap > 0 || cgroup.Resources.KernelMemory > 0 || cgroup.Resources.OomKillDisable || - cgroup.Resources.MemorySwappiness != -1 + (cgroup.Resources.MemorySwappiness != nil && *cgroup.Resources.MemorySwappiness != -1) } func getMemoryData(path, name string) (cgroups.MemoryData, error) { diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/stats.go b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/stats.go index 54ace4185d..a9a0743473 100644 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/stats.go +++ b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/stats.go @@ -46,7 +46,7 @@ type MemoryStats struct { Usage MemoryData `json:"usage,omitempty"` // usage of memory + swap SwapUsage MemoryData `json:"swap_usage,omitempty"` - // usafe of kernel memory + // usage of kernel memory KernelUsage MemoryData `json:"kernel_usage,omitempty"` Stats map[string]uint64 `json:"stats,omitempty"` } @@ -80,7 +80,7 @@ type HugetlbStats struct { Usage uint64 `json:"usage,omitempty"` // maximum usage ever recorded. MaxUsage uint64 `json:"max_usage,omitempty"` - // number of times htgetlb usage allocation failure. + // number of times hugetlb usage allocation failure. Failcnt uint64 `json:"failcnt"` } diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/configs/cgroup_unix.go b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/configs/cgroup_unix.go index 40a033f35b..2ea00658f5 100644 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/configs/cgroup_unix.go +++ b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/configs/cgroup_unix.go @@ -111,7 +111,7 @@ type Resources struct { OomKillDisable bool `json:"oom_kill_disable"` // Tuning swappiness behaviour per cgroup - MemorySwappiness int64 `json:"memory_swappiness"` + MemorySwappiness *int64 `json:"memory_swappiness"` // Set priority of network traffic for container NetPrioIfpriomap []*IfPrioMap `json:"net_prio_ifpriomap"` diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/configs/config.go b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/configs/config.go index f6a163b734..668fa5e30f 100644 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/configs/config.go +++ b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/configs/config.go @@ -128,11 +128,11 @@ type Config struct { // AppArmorProfile specifies the profile to apply to the process running in the container and is // change at the time the process is execed - AppArmorProfile string `json:"apparmor_profile"` + AppArmorProfile string `json:"apparmor_profile,omitempty"` // ProcessLabel specifies the label to apply to the process running in the container. It is // commonly used by selinux - ProcessLabel string `json:"process_label"` + ProcessLabel string `json:"process_label,omitempty"` // Rlimits specifies the resource limits, such as max open files, to set in the container // If Rlimits are not set, the container will inherit rlimits from the parent process @@ -172,7 +172,7 @@ type Config struct { Seccomp *Seccomp `json:"seccomp"` // NoNewPrivileges controls whether processes in the container can gain additional privileges. - NoNewPrivileges bool `json:"no_new_privileges"` + NoNewPrivileges bool `json:"no_new_privileges,omitempty"` // Hooks are a collection of actions to perform at various container lifecycle events. // Hooks are not able to be marshaled to json but they are also not needed to. @@ -180,6 +180,9 @@ type Config struct { // Version is the version of opencontainer specification that is supported. Version string `json:"version"` + + // Labels are user defined metadata that is stored in the config and populated on the state + Labels []string `json:"labels"` } type Hooks struct { diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/configs/namespaces_unix.go b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/configs/namespaces_unix.go index 7bc9085468..b9c820d062 100644 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/configs/namespaces_unix.go +++ b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/configs/namespaces_unix.go @@ -2,7 +2,11 @@ package configs -import "fmt" +import ( + "fmt" + "os" + "sync" +) const ( NEWNET NamespaceType = "NEWNET" @@ -13,6 +17,51 @@ const ( NEWUSER NamespaceType = "NEWUSER" ) +var ( + nsLock sync.Mutex + supportedNamespaces = make(map[NamespaceType]bool) +) + +// nsToFile converts the namespace type to its filename +func nsToFile(ns NamespaceType) string { + switch ns { + case NEWNET: + return "net" + case NEWNS: + return "mnt" + case NEWPID: + return "pid" + case NEWIPC: + return "ipc" + case NEWUSER: + return "user" + case NEWUTS: + return "uts" + } + return "" +} + +// IsNamespaceSupported returns whether a namespace is available or +// not +func IsNamespaceSupported(ns NamespaceType) bool { + nsLock.Lock() + defer nsLock.Unlock() + supported, ok := supportedNamespaces[ns] + if ok { + return supported + } + nsFile := nsToFile(ns) + // if the namespace type is unknown, just return false + if nsFile == "" { + return false + } + _, err := os.Stat(fmt.Sprintf("/proc/self/ns/%s", nsFile)) + // a namespace is supported if it exists and we have permissions to read it + supported = err == nil + supportedNamespaces[ns] = supported + return supported +} + func NamespaceTypes() []NamespaceType { return []NamespaceType{ NEWNET, @@ -35,26 +84,7 @@ func (n *Namespace) GetPath(pid int) string { if n.Path != "" { return n.Path } - return fmt.Sprintf("/proc/%d/ns/%s", pid, n.file()) -} - -func (n *Namespace) file() string { - file := "" - switch n.Type { - case NEWNET: - file = "net" - case NEWNS: - file = "mnt" - case NEWPID: - file = "pid" - case NEWIPC: - file = "ipc" - case NEWUSER: - file = "user" - case NEWUTS: - file = "uts" - } - return file + return fmt.Sprintf("/proc/%d/ns/%s", pid, nsToFile(n.Type)) } func (n *Namespaces) Remove(t NamespaceType) bool { @@ -87,3 +117,11 @@ func (n *Namespaces) index(t NamespaceType) int { func (n *Namespaces) Contains(t NamespaceType) bool { return n.index(t) != -1 } + +func (n *Namespaces) PathOf(t NamespaceType) string { + i := n.index(t) + if i == -1 { + return "" + } + return (*n)[i].Path +} diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/configs/validate/config.go b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/configs/validate/config.go index 848a67c343..e155ca1204 100644 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/configs/validate/config.go +++ b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/configs/validate/config.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "github.com/opencontainers/runc/libcontainer/configs" ) @@ -35,6 +36,9 @@ func (v *ConfigValidator) Validate(config *configs.Config) error { if err := v.usernamespace(config); err != nil { return err } + if err := v.sysctl(config); err != nil { + return err + } return nil } @@ -91,3 +95,44 @@ func (v *ConfigValidator) usernamespace(config *configs.Config) error { } return nil } + +// sysctl validates that the specified sysctl keys are valid or not. +// /proc/sys isn't completely namespaced and depending on which namespaces +// are specified, a subset of sysctls are permitted. +func (v *ConfigValidator) sysctl(config *configs.Config) error { + validSysctlPrefixes := []string{} + validSysctlMap := make(map[string]bool) + if config.Namespaces.Contains(configs.NEWNET) { + validSysctlPrefixes = append(validSysctlPrefixes, "net.") + } + if config.Namespaces.Contains(configs.NEWIPC) { + validSysctlPrefixes = append(validSysctlPrefixes, "fs.mqueue.") + validSysctlMap = map[string]bool{ + "kernel.msgmax": true, + "kernel.msgmnb": true, + "kernel.msgmni": true, + "kernel.sem": true, + "kernel.shmall": true, + "kernel.shmmax": true, + "kernel.shmmni": true, + "kernel.shm_rmid_forced": true, + } + } + for s := range config.Sysctl { + if validSysctlMap[s] { + continue + } + valid := false + for _, vp := range validSysctlPrefixes { + if strings.HasPrefix(s, vp) { + valid = true + break + } + } + if !valid { + return fmt.Errorf("sysctl %q is not permitted in the config", s) + } + } + + return nil +} diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/container.go b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/container.go index 68291231ec..32daa97675 100644 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/container.go +++ b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/container.go @@ -27,9 +27,6 @@ const ( // The container exists, but all its processes are paused. Paused - // The container exists, but its state is saved on disk - Checkpointed - // The container does not exist. Destroyed ) @@ -44,8 +41,6 @@ func (s Status) String() string { return "pausing" case Paused: return "paused" - case Checkpointed: - return "checkpointed" case Destroyed: return "destroyed" default: diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/container_linux.go b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/container_linux.go index 284e15ec33..8e308fa624 100644 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/container_linux.go +++ b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/container_linux.go @@ -23,6 +23,7 @@ import ( "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/criurpc" "github.com/opencontainers/runc/libcontainer/utils" + "github.com/syndtr/gocapability/capability" "github.com/vishvananda/netlink/nl" ) @@ -268,37 +269,40 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec. } func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File) (*initProcess, error) { - t := "_LIBCONTAINER_INITTYPE=" + string(initStandard) - cloneFlags := c.config.Namespaces.CloneFlags() - if cloneFlags&syscall.CLONE_NEWUSER != 0 { - if err := c.addUidGidMappings(cmd.SysProcAttr); err != nil { - // user mappings are not supported - return nil, err - } - enableSetgroups(cmd.SysProcAttr) - // Default to root user when user namespaces are enabled. - if cmd.SysProcAttr.Credential == nil { - cmd.SysProcAttr.Credential = &syscall.Credential{} + cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initStandard)) + nsMaps := make(map[configs.NamespaceType]string) + for _, ns := range c.config.Namespaces { + if ns.Path != "" { + nsMaps[ns.Type] = ns.Path } } - cmd.Env = append(cmd.Env, t) - cmd.SysProcAttr.Cloneflags = cloneFlags + _, sharePidns := nsMaps[configs.NEWPID] + data, err := c.bootstrapData(c.config.Namespaces.CloneFlags(), nsMaps, "") + if err != nil { + return nil, err + } return &initProcess{ - cmd: cmd, - childPipe: childPipe, - parentPipe: parentPipe, - manager: c.cgroupManager, - config: c.newInitConfig(p), - container: c, - process: p, + cmd: cmd, + childPipe: childPipe, + parentPipe: parentPipe, + manager: c.cgroupManager, + config: c.newInitConfig(p), + container: c, + process: p, + bootstrapData: data, + sharePidns: sharePidns, }, nil } func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File) (*setnsProcess, error) { cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initSetns)) + state, err := c.currentState() + if err != nil { + return nil, newSystemError(err) + } // for setns process, we dont have to set cloneflags as the process namespaces // will only be set via setns syscall - data, err := c.bootstrapData(0, c.initProcess.pid(), p.consolePath) + data, err := c.bootstrapData(0, state.NamespacePaths, p.consolePath) if err != nil { return nil, err } @@ -315,7 +319,7 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe, } func (c *linuxContainer) newInitConfig(process *Process) *initConfig { - return &initConfig{ + cfg := &initConfig{ Config: c.config, Args: process.Args, Env: process.Env, @@ -324,7 +328,21 @@ func (c *linuxContainer) newInitConfig(process *Process) *initConfig { Console: process.consolePath, Capabilities: process.Capabilities, PassedFilesCount: len(process.ExtraFiles), + ContainerId: c.ID(), + NoNewPrivileges: c.config.NoNewPrivileges, + AppArmorProfile: c.config.AppArmorProfile, + ProcessLabel: c.config.ProcessLabel, } + if process.NoNewPrivileges != nil { + cfg.NoNewPrivileges = *process.NoNewPrivileges + } + if process.AppArmorProfile != "" { + cfg.AppArmorProfile = process.AppArmorProfile + } + if process.Label != "" { + cfg.ProcessLabel = process.Label + } + return cfg } func newPipe() (parent *os.File, child *os.File, err error) { @@ -1059,6 +1077,9 @@ func (c *linuxContainer) currentState() (*State, error) { state.NamespacePaths[ns.Type] = ns.GetPath(pid) } for _, nsType := range configs.NamespaceTypes() { + if !configs.IsNamespaceSupported(nsType) { + continue + } if _, ok := state.NamespacePaths[nsType]; !ok { ns := configs.Namespace{Type: nsType} state.NamespacePaths[ns.Type] = ns.GetPath(pid) @@ -1068,18 +1089,69 @@ func (c *linuxContainer) currentState() (*State, error) { return state, nil } -// bootstrapData encodes the necessary data in netlink binary format as a io.Reader. -// Consumer can write the data to a bootstrap program such as one that uses -// nsenter package to bootstrap the container's init process correctly, i.e. with -// correct namespaces, uid/gid mapping etc. -func (c *linuxContainer) bootstrapData(cloneFlags uintptr, pid int, consolePath string) (io.Reader, error) { +// orderNamespacePaths sorts namespace paths into a list of paths that we +// can setns in order. +func (c *linuxContainer) orderNamespacePaths(namespaces map[configs.NamespaceType]string) ([]string, error) { + paths := []string{} + nsTypes := []configs.NamespaceType{ + configs.NEWIPC, + configs.NEWUTS, + configs.NEWNET, + configs.NEWPID, + configs.NEWNS, + } + // join userns if the init process explicitly requires NEWUSER + if c.config.Namespaces.Contains(configs.NEWUSER) { + nsTypes = append(nsTypes, configs.NEWUSER) + } + for _, nsType := range nsTypes { + if p, ok := namespaces[nsType]; ok && p != "" { + // check if the requested namespace is supported + if !configs.IsNamespaceSupported(nsType) { + return nil, newSystemError(fmt.Errorf("namespace %s is not supported", nsType)) + } + // only set to join this namespace if it exists + if _, err := os.Lstat(p); err != nil { + return nil, newSystemError(err) + } + // do not allow namespace path with comma as we use it to separate + // the namespace paths + if strings.ContainsRune(p, ',') { + return nil, newSystemError(fmt.Errorf("invalid path %s", p)) + } + paths = append(paths, p) + } + } + return paths, nil +} + +func encodeIDMapping(idMap []configs.IDMap) ([]byte, error) { + data := bytes.NewBuffer(nil) + for _, im := range idMap { + line := fmt.Sprintf("%d %d %d\n", im.ContainerID, im.HostID, im.Size) + if _, err := data.WriteString(line); err != nil { + return nil, err + } + } + return data.Bytes(), nil +} + +// bootstrapData encodes the necessary data in netlink binary format +// as a io.Reader. +// Consumer can write the data to a bootstrap program +// such as one that uses nsenter package to bootstrap the container's +// init process correctly, i.e. with correct namespaces, uid/gid +// mapping etc. +func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.NamespaceType]string, consolePath string) (io.Reader, error) { // create the netlink message r := nl.NewNetlinkRequest(int(InitMsg), 0) - // write pid + + // write cloneFlags r.AddData(&Int32msg{ - Type: PidAttr, - Value: uint32(pid), + Type: CloneFlagsAttr, + Value: uint32(cloneFlags), }) + // write console path if consolePath != "" { r.AddData(&Bytemsg{ @@ -1087,5 +1159,57 @@ func (c *linuxContainer) bootstrapData(cloneFlags uintptr, pid int, consolePath Value: []byte(consolePath), }) } + + // write custom namespace paths + if len(nsMaps) > 0 { + nsPaths, err := c.orderNamespacePaths(nsMaps) + if err != nil { + return nil, err + } + r.AddData(&Bytemsg{ + Type: NsPathsAttr, + Value: []byte(strings.Join(nsPaths, ",")), + }) + } + + // write namespace paths only when we are not joining an existing user ns + _, joinExistingUser := nsMaps[configs.NEWUSER] + if !joinExistingUser { + // write uid mappings + if len(c.config.UidMappings) > 0 { + b, err := encodeIDMapping(c.config.UidMappings) + if err != nil { + return nil, err + } + r.AddData(&Bytemsg{ + Type: UidmapAttr, + Value: b, + }) + } + + // write gid mappings + if len(c.config.GidMappings) > 0 { + b, err := encodeIDMapping(c.config.UidMappings) + if err != nil { + return nil, err + } + r.AddData(&Bytemsg{ + Type: GidmapAttr, + Value: b, + }) + // check if we have CAP_SETGID to setgroup properly + pid, err := capability.NewPid(os.Getpid()) + if err != nil { + return nil, err + } + if !pid.Get(capability.EFFECTIVE, capability.CAP_SETGID) { + r.AddData(&Boolmsg{ + Type: SetgroupAttr, + Value: true, + }) + } + } + } + return bytes.NewReader(r.Serialize()), nil } diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/container_nouserns_linux.go b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/container_nouserns_linux.go deleted file mode 100644 index 3b75d593cc..0000000000 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/container_nouserns_linux.go +++ /dev/null @@ -1,13 +0,0 @@ -// +build !go1.4 - -package libcontainer - -import ( - "fmt" - "syscall" -) - -// not available before go 1.4 -func (c *linuxContainer) addUidGidMappings(sys *syscall.SysProcAttr) error { - return fmt.Errorf("User namespace is not supported in golang < 1.4") -} diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/container_userns_linux.go b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/container_userns_linux.go deleted file mode 100644 index 5f4cf3c9fe..0000000000 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/container_userns_linux.go +++ /dev/null @@ -1,26 +0,0 @@ -// +build go1.4 - -package libcontainer - -import "syscall" - -// Converts IDMap to SysProcIDMap array and adds it to SysProcAttr. -func (c *linuxContainer) addUidGidMappings(sys *syscall.SysProcAttr) error { - if c.config.UidMappings != nil { - sys.UidMappings = make([]syscall.SysProcIDMap, len(c.config.UidMappings)) - for i, um := range c.config.UidMappings { - sys.UidMappings[i].ContainerID = um.ContainerID - sys.UidMappings[i].HostID = um.HostID - sys.UidMappings[i].Size = um.Size - } - } - if c.config.GidMappings != nil { - sys.GidMappings = make([]syscall.SysProcIDMap, len(c.config.GidMappings)) - for i, gm := range c.config.GidMappings { - sys.GidMappings[i].ContainerID = gm.ContainerID - sys.GidMappings[i].HostID = gm.HostID - sys.GidMappings[i].Size = gm.Size - } - } - return nil -} diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/factory_linux.go b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/factory_linux.go index 14e4f33a8c..eb327be789 100644 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/factory_linux.go +++ b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/factory_linux.go @@ -227,32 +227,40 @@ func (l *LinuxFactory) StartInitialization() (err error) { pipe = os.NewFile(uintptr(pipefd), "pipe") it = initType(os.Getenv("_LIBCONTAINER_INITTYPE")) ) + defer pipe.Close() // clear the current process's environment to clean any libcontainer // specific env vars. os.Clearenv() - var i initer - defer func() { - // We have an error during the initialization of the container's init, - // send it back to the parent process in the form of an initError. - // If container's init successed, syscall.Exec will not return, hence - // this defer function will never be called. + i, err := newContainerInit(it, pipe) + if err != nil { + l.sendError(nil, pipe, err) + return err + } + if err := i.Init(); err != nil { + if !isExecError(err) { + l.sendError(i, pipe, err) + } + return err + } + return nil +} + +func (l *LinuxFactory) sendError(i initer, pipe *os.File, err error) { + // We have an error during the initialization of the container's init, + // send it back to the parent process in the form of an initError. + // If container's init successed, syscall.Exec will not return, hence + // this defer function will never be called. + if i != nil { if _, ok := i.(*linuxStandardInit); ok { // Synchronisation only necessary for standard init. if err := utils.WriteJSON(pipe, syncT{procError}); err != nil { panic(err) } } - if err := utils.WriteJSON(pipe, newSystemError(err)); err != nil { - panic(err) - } - // ensure that this pipe is always closed - pipe.Close() - }() - i, err = newContainerInit(it, pipe) - if err != nil { - return err } - return i.Init() + if err := utils.WriteJSON(pipe, newSystemError(err)); err != nil { + panic(err) + } } func (l *LinuxFactory) loadState(root string) (*State, error) { @@ -280,3 +288,8 @@ func (l *LinuxFactory) validateID(id string) error { } return nil } + +func isExecError(err error) bool { + _, ok := err.(*exec.Error) + return ok +} diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/generic_error.go b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/generic_error.go index 75e980b1d7..93bb7570db 100644 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/generic_error.go +++ b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/generic_error.go @@ -15,6 +15,8 @@ const ( procReady syncType = iota procError procRun + procHooks + procResume ) type syncT struct { diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/init_linux.go b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/init_linux.go index 918f103016..8a61516d8d 100644 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/init_linux.go +++ b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/init_linux.go @@ -48,11 +48,15 @@ type initConfig struct { Env []string `json:"env"` Cwd string `json:"cwd"` Capabilities []string `json:"capabilities"` + ProcessLabel string `json:"process_label"` + AppArmorProfile string `json:"apparmor_profile"` + NoNewPrivileges bool `json:"no_new_privileges"` User string `json:"user"` Config *configs.Config `json:"config"` Console string `json:"console"` Networks []*network `json:"network"` PassedFilesCount int `json:"passed_files_count"` + ContainerId string `json:"containerid"` } type initer interface { @@ -163,20 +167,22 @@ func syncParentReady(pipe io.ReadWriter) error { return nil } -// joinExistingNamespaces gets all the namespace paths specified for the container and -// does a setns on the namespace fd so that the current process joins the namespace. -func joinExistingNamespaces(namespaces []configs.Namespace) error { - for _, ns := range namespaces { - if ns.Path != "" { - f, err := os.OpenFile(ns.Path, os.O_RDONLY, 0) - if err != nil { - return err - } - err = system.Setns(f.Fd(), uintptr(ns.Syscall())) - f.Close() - if err != nil { - return err - } +// syncParentHooks sends to the given pipe a JSON payload which indicates that +// the parent should execute pre-start hooks. It then waits for the parent to +// indicate that it is cleared to resume. +func syncParentHooks(pipe io.ReadWriter) error { + // Tell parent. + if err := utils.WriteJSON(pipe, syncT{procHooks}); err != nil { + return err + } + // Wait for parent to give the all-clear. + var procSync syncT + if err := json.NewDecoder(pipe).Decode(&procSync); err != nil { + if err == io.EOF { + return fmt.Errorf("parent closed synchronisation channel") + } + if procSync.Type != procResume { + return fmt.Errorf("invalid synchronisation flag from parent") } } return nil @@ -319,9 +325,10 @@ func setupRlimits(config *configs.Config) error { return nil } -func setOomScoreAdj(oomScoreAdj int) error { - path := "/proc/self/oom_score_adj" - return ioutil.WriteFile(path, []byte(strconv.Itoa(oomScoreAdj)), 0700) +func setOomScoreAdj(oomScoreAdj int, pid int) error { + path := fmt.Sprintf("/proc/%d/oom_score_adj", pid) + + return ioutil.WriteFile(path, []byte(strconv.Itoa(oomScoreAdj)), 0600) } // killCgroupProcesses freezes then iterates over all the processes inside the diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/message_linux.go b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/message_linux.go index 0c3301f2bb..166301338c 100644 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/message_linux.go +++ b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/message_linux.go @@ -12,8 +12,12 @@ import ( // The number is randomly chosen to not conflict with known netlink types const ( InitMsg uint16 = 62000 - PidAttr uint16 = 27281 + CloneFlagsAttr uint16 = 27281 ConsolePathAttr uint16 = 27282 + NsPathsAttr uint16 = 27283 + UidmapAttr uint16 = 27284 + GidmapAttr uint16 = 27285 + SetgroupAttr uint16 = 27286 // When syscall.NLA_HDRLEN is in gccgo, take this out. syscall_NLA_HDRLEN = (syscall.SizeofNlAttr + syscall.NLA_ALIGNTO - 1) & ^(syscall.NLA_ALIGNTO - 1) ) @@ -60,3 +64,25 @@ func (msg *Bytemsg) Serialize() []byte { func (msg *Bytemsg) Len() int { return syscall_NLA_HDRLEN + len(msg.Value) + 1 // null-terminated } + +type Boolmsg struct { + Type uint16 + Value bool +} + +func (msg *Boolmsg) Serialize() []byte { + buf := make([]byte, msg.Len()) + native := nl.NativeEndian() + native.PutUint16(buf[0:2], uint16(msg.Len())) + native.PutUint16(buf[2:4], msg.Type) + if msg.Value { + buf[4] = 1 + } else { + buf[4] = 0 + } + return buf +} + +func (msg *Boolmsg) Len() int { + return syscall_NLA_HDRLEN + 1 +} diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/nsenter/README.md b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/nsenter/README.md deleted file mode 100644 index d1a60ef985..0000000000 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/nsenter/README.md +++ /dev/null @@ -1,25 +0,0 @@ -## nsenter - -The `nsenter` package registers a special init constructor that is called before -the Go runtime has a chance to boot. This provides us the ability to `setns` on -existing namespaces and avoid the issues that the Go runtime has with multiple -threads. This constructor will be called if this package is registered, -imported, in your go application. - -The `nsenter` package will `import "C"` and it uses [cgo](https://golang.org/cmd/cgo/) -package. In cgo, if the import of "C" is immediately preceded by a comment, that comment, -called the preamble, is used as a header when compiling the C parts of the package. -So every time we import package `nsenter`, the C code function `nsexec()` would be -called. And package `nsenter` is now only imported in Docker execdriver, so every time -before we call `execdriver.Exec()`, that C code would run. - -`nsexec()` will first check the environment variable `_LIBCONTAINER_INITPID` -which will give the process of the container that should be joined. Namespaces fd will -be found from `/proc/[pid]/ns` and set by `setns` syscall. - -And then get the pipe number from `_LIBCONTAINER_INITPIPE`, error message could -be transfered through it. If tty is added, `_LIBCONTAINER_CONSOLE_PATH` will -have value and start a console for output. - -Finally, `nsexec()` will clone a child process , exit the parent process and let -the Go runtime take over. diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/nsenter/nsenter.go b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/nsenter/nsenter.go deleted file mode 100644 index 07f4d63e43..0000000000 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/nsenter/nsenter.go +++ /dev/null @@ -1,12 +0,0 @@ -// +build linux,!gccgo - -package nsenter - -/* -#cgo CFLAGS: -Wall -extern void nsexec(); -void __attribute__((constructor)) init(void) { - nsexec(); -} -*/ -import "C" diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/nsenter/nsenter_gccgo.go b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/nsenter/nsenter_gccgo.go deleted file mode 100644 index 63c7a3ec22..0000000000 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/nsenter/nsenter_gccgo.go +++ /dev/null @@ -1,25 +0,0 @@ -// +build linux,gccgo - -package nsenter - -/* -#cgo CFLAGS: -Wall -extern void nsexec(); -void __attribute__((constructor)) init(void) { - nsexec(); -} -*/ -import "C" - -// AlwaysFalse is here to stay false -// (and be exported so the compiler doesn't optimize out its reference) -var AlwaysFalse bool - -func init() { - if AlwaysFalse { - // by referencing this C init() in a noop test, it will ensure the compiler - // links in the C function. - // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65134 - C.init() - } -} diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/nsenter/nsenter_unsupported.go b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/nsenter/nsenter_unsupported.go deleted file mode 100644 index ac701ca393..0000000000 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/nsenter/nsenter_unsupported.go +++ /dev/null @@ -1,5 +0,0 @@ -// +build !linux !cgo - -package nsenter - -import "C" diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/nsenter/nsexec.c b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/nsenter/nsexec.c deleted file mode 100644 index 6634afc424..0000000000 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/nsenter/nsexec.c +++ /dev/null @@ -1,261 +0,0 @@ -#define _GNU_SOURCE -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -/* All arguments should be above stack, because it grows down */ -struct clone_arg { - /* - * Reserve some space for clone() to locate arguments - * and retcode in this place - */ - char stack[4096] __attribute__ ((aligned(16))); - char stack_ptr[0]; - jmp_buf *env; -}; - -#define pr_perror(fmt, ...) fprintf(stderr, "nsenter: " fmt ": %m\n", ##__VA_ARGS__) - -static int child_func(void *_arg) -{ - struct clone_arg *arg = (struct clone_arg *)_arg; - longjmp(*arg->env, 1); -} - -// Use raw setns syscall for versions of glibc that don't include it (namely glibc-2.12) -#if __GLIBC__ == 2 && __GLIBC_MINOR__ < 14 -#define _GNU_SOURCE -#include "syscall.h" -#if defined(__NR_setns) && !defined(SYS_setns) -#define SYS_setns __NR_setns -#endif -#ifdef SYS_setns -int setns(int fd, int nstype) -{ - return syscall(SYS_setns, fd, nstype); -} -#endif -#endif - -static int clone_parent(jmp_buf * env) __attribute__ ((noinline)); -static int clone_parent(jmp_buf * env) -{ - struct clone_arg ca; - int child; - - ca.env = env; - child = clone(child_func, ca.stack_ptr, CLONE_PARENT | SIGCHLD, &ca); - - return child; -} - -static uint32_t readint32(char *buf) -{ - return *(uint32_t *) buf; -} - -// list of known message types we want to send to bootstrap program -// These are defined in libcontainer/message_linux.go -#define INIT_MSG 62000 -#define PID_ATTR 27281 -#define CONSOLE_PATH_ATTR 27282 - -void nsexec() -{ - char *namespaces[] = { "ipc", "uts", "net", "pid", "mnt", "user" }; - const int num = sizeof(namespaces) / sizeof(char *); - jmp_buf env; - char buf[PATH_MAX], *val; - int i, tfd, self_tfd, child, n, len, pipenum, consolefd = -1; - pid_t pid = 0; - - // if we dont have INITTYPE or this is the init process, skip the bootstrap process - val = getenv("_LIBCONTAINER_INITTYPE"); - if (val == NULL || strcmp(val, "standard") == 0) { - return; - } - if (strcmp(val, "setns") != 0) { - pr_perror("Invalid inittype %s", val); - exit(1); - } - - val = getenv("_LIBCONTAINER_INITPIPE"); - if (val == NULL) { - pr_perror("Child pipe not found"); - exit(1); - } - pipenum = atoi(val); - snprintf(buf, sizeof(buf), "%d", pipenum); - if (strcmp(val, buf)) { - pr_perror("Unable to parse _LIBCONTAINER_INITPIPE"); - exit(1); - } - - char nlbuf[NLMSG_HDRLEN]; - struct nlmsghdr *nh; - if ((n = read(pipenum, nlbuf, NLMSG_HDRLEN)) != NLMSG_HDRLEN) { - pr_perror("Failed to read netlink header, got %d", n); - exit(1); - } - - nh = (struct nlmsghdr *)nlbuf; - if (nh->nlmsg_type == NLMSG_ERROR) { - pr_perror("Invalid netlink header message"); - exit(1); - } - if (nh->nlmsg_type != INIT_MSG) { - pr_perror("Unexpected netlink message type %d", nh->nlmsg_type); - exit(1); - } - // read the netlink payload - len = NLMSG_PAYLOAD(nh, 0); - char data[len]; - if ((n = read(pipenum, data, len)) != len) { - pr_perror("Failed to read netlink payload, got %d", n); - exit(1); - } - - int start = 0; - struct nlattr *attr; - while (start < len) { - int payload_len; - attr = (struct nlattr *)((void *)data + start); - start += NLA_HDRLEN; - payload_len = attr->nla_len - NLA_HDRLEN; - switch (attr->nla_type) { - case PID_ATTR: - pid = (pid_t) readint32(data + start); - break; - case CONSOLE_PATH_ATTR: - consolefd = open((char *)data + start, O_RDWR); - if (consolefd < 0) { - pr_perror("Failed to open console %s", (char *)data + start); - exit(1); - } - break; - } - start += NLA_ALIGN(payload_len); - } - - // required pid to be passed - if (pid == 0) { - pr_perror("missing pid"); - exit(1); - } - - /* Check that the specified process exists */ - snprintf(buf, PATH_MAX - 1, "/proc/%d/ns", pid); - tfd = open(buf, O_DIRECTORY | O_RDONLY); - if (tfd == -1) { - pr_perror("Failed to open \"%s\"", buf); - exit(1); - } - - self_tfd = open("/proc/self/ns", O_DIRECTORY | O_RDONLY); - if (self_tfd == -1) { - pr_perror("Failed to open /proc/self/ns"); - exit(1); - } - - for (i = 0; i < num; i++) { - struct stat st; - struct stat self_st; - int fd; - - /* Symlinks on all namespaces exist for dead processes, but they can't be opened */ - if (fstatat(tfd, namespaces[i], &st, 0) == -1) { - // Ignore nonexistent namespaces. - if (errno == ENOENT) - continue; - } - - /* Skip namespaces we're already part of */ - if (fstatat(self_tfd, namespaces[i], &self_st, 0) != -1 && st.st_ino == self_st.st_ino) { - continue; - } - - fd = openat(tfd, namespaces[i], O_RDONLY); - if (fd == -1) { - pr_perror("Failed to open ns file %s for ns %s", buf, namespaces[i]); - exit(1); - } - // Set the namespace. - if (setns(fd, 0) == -1) { - pr_perror("Failed to setns for %s", namespaces[i]); - exit(1); - } - close(fd); - } - - close(self_tfd); - close(tfd); - - if (setjmp(env) == 1) { - // Child - - if (setsid() == -1) { - pr_perror("setsid failed"); - exit(1); - } - if (consolefd != -1) { - if (ioctl(consolefd, TIOCSCTTY, 0) == -1) { - pr_perror("ioctl TIOCSCTTY failed"); - exit(1); - } - if (dup3(consolefd, STDIN_FILENO, 0) != STDIN_FILENO) { - pr_perror("Failed to dup 0"); - exit(1); - } - if (dup3(consolefd, STDOUT_FILENO, 0) != STDOUT_FILENO) { - pr_perror("Failed to dup 1"); - exit(1); - } - if (dup3(consolefd, STDERR_FILENO, 0) != STDERR_FILENO) { - pr_perror("Failed to dup 2"); - exit(1); - } - } - // Finish executing, let the Go runtime take over. - return; - } - // Parent - - // We must fork to actually enter the PID namespace, use CLONE_PARENT - // so the child can have the right parent, and we don't need to forward - // the child's exit code or resend its death signal. - child = clone_parent(&env); - if (child < 0) { - pr_perror("Unable to fork"); - exit(1); - } - - len = snprintf(buf, sizeof(buf), "{ \"pid\" : %d }\n", child); - - if (write(pipenum, buf, len) != len) { - pr_perror("Unable to send a child pid"); - kill(child, SIGKILL); - exit(1); - } - - exit(0); -} diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/process.go b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/process.go index 8b4c558bd2..9b82cfaca5 100644 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/process.go +++ b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/process.go @@ -48,6 +48,16 @@ type Process struct { // All capabilities not specified will be dropped from the processes capability mask Capabilities []string + // AppArmorProfile specifies the profile to apply to the process and is + // changed at the time the process is execed + AppArmorProfile string + + // Label specifies the label to apply to the process. It is commonly used by selinux + Label string + + // NoNewPrivileges controls whether processes can gain additional privileges. + NoNewPrivileges *bool + ops processOperations } diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/process_linux.go b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/process_linux.go index aa9b9d0986..9ff4386151 100644 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/process_linux.go +++ b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/process_linux.go @@ -88,6 +88,10 @@ func (p *setnsProcess) start() (err error) { if err := utils.WriteJSON(p.parentPipe, p.config); err != nil { return newSystemError(err) } + // set oom_score_adj + if err := setOomScoreAdj(p.config.Config.OomScoreAdj, p.pid()); err != nil { + return newSystemError(err) + } if err := syscall.Shutdown(int(p.parentPipe.Fd()), syscall.SHUT_WR); err != nil { return newSystemError(err) @@ -167,14 +171,16 @@ func (p *setnsProcess) setExternalDescriptors(newFds []string) { } type initProcess struct { - cmd *exec.Cmd - parentPipe *os.File - childPipe *os.File - config *initConfig - manager cgroups.Manager - container *linuxContainer - fds []string - process *Process + cmd *exec.Cmd + parentPipe *os.File + childPipe *os.File + config *initConfig + manager cgroups.Manager + container *linuxContainer + fds []string + process *Process + bootstrapData io.Reader + sharePidns bool } func (p *initProcess) pid() int { @@ -185,15 +191,49 @@ func (p *initProcess) externalDescriptors() []string { return p.fds } -func (p *initProcess) start() (err error) { +// execSetns runs the process that executes C code to perform the setns calls +// because setns support requires the C process to fork off a child and perform the setns +// before the go runtime boots, we wait on the process to die and receive the child's pid +// over the provided pipe. +// This is called by initProcess.start function +func (p *initProcess) execSetns() error { + status, err := p.cmd.Process.Wait() + if err != nil { + p.cmd.Wait() + return err + } + if !status.Success() { + p.cmd.Wait() + return &exec.ExitError{ProcessState: status} + } + var pid *pid + if err := json.NewDecoder(p.parentPipe).Decode(&pid); err != nil { + p.cmd.Wait() + return err + } + process, err := os.FindProcess(pid.Pid) + if err != nil { + return err + } + p.cmd.Process = process + return nil +} + +func (p *initProcess) start() error { defer p.parentPipe.Close() - err = p.cmd.Start() + err := p.cmd.Start() p.process.ops = p p.childPipe.Close() if err != nil { p.process.ops = nil return newSystemError(err) } + if _, err := io.Copy(p.parentPipe, p.bootstrapData); err != nil { + return err + } + if err := p.execSetns(); err != nil { + return newSystemError(err) + } // Save the standard descriptor names before the container process // can potentially move them (e.g., via dup2()). If we don't do this now, // we won't know at checkpoint time which file descriptor to look up. @@ -213,19 +253,6 @@ func (p *initProcess) start() (err error) { p.manager.Destroy() } }() - if p.config.Config.Hooks != nil { - s := configs.HookState{ - Version: p.container.config.Version, - ID: p.container.id, - Pid: p.pid(), - Root: p.config.Config.Rootfs, - } - for _, hook := range p.config.Config.Hooks.Prestart { - if err := hook.Run(s); err != nil { - return newSystemError(err) - } - } - } if err := p.createNetworkInterfaces(); err != nil { return newSystemError(err) } @@ -233,14 +260,16 @@ func (p *initProcess) start() (err error) { return newSystemError(err) } var ( - procSync syncT - sentRun bool - ierr *genericError + procSync syncT + sentRun bool + sentResume bool + ierr *genericError ) + dec := json.NewDecoder(p.parentPipe) loop: for { - if err := json.NewDecoder(p.parentPipe).Decode(&procSync); err != nil { + if err := dec.Decode(&procSync); err != nil { if err == io.EOF { break loop } @@ -251,15 +280,54 @@ loop: if err := p.manager.Set(p.config.Config); err != nil { return newSystemError(err) } + // set oom_score_adj + if err := setOomScoreAdj(p.config.Config.OomScoreAdj, p.pid()); err != nil { + return newSystemError(err) + } + // call prestart hooks + if !p.config.Config.Namespaces.Contains(configs.NEWNS) { + if p.config.Config.Hooks != nil { + s := configs.HookState{ + Version: p.container.config.Version, + ID: p.container.id, + Pid: p.pid(), + Root: p.config.Config.Rootfs, + } + for _, hook := range p.config.Config.Hooks.Prestart { + if err := hook.Run(s); err != nil { + return newSystemError(err) + } + } + } + } // Sync with child. if err := utils.WriteJSON(p.parentPipe, syncT{procRun}); err != nil { return newSystemError(err) } sentRun = true + case procHooks: + if p.config.Config.Hooks != nil { + s := configs.HookState{ + Version: p.container.config.Version, + ID: p.container.id, + Pid: p.pid(), + Root: p.config.Config.Rootfs, + } + for _, hook := range p.config.Config.Hooks.Prestart { + if err := hook.Run(s); err != nil { + return newSystemError(err) + } + } + } + // Sync with child. + if err := utils.WriteJSON(p.parentPipe, syncT{procResume}); err != nil { + return newSystemError(err) + } + sentResume = true case procError: // wait for the child process to fully complete and receive an error message // if one was encoutered - if err := json.NewDecoder(p.parentPipe).Decode(&ierr); err != nil && err != io.EOF { + if err := dec.Decode(&ierr); err != nil && err != io.EOF { return newSystemError(err) } if ierr != nil { @@ -274,6 +342,9 @@ loop: if !sentRun { return newSystemError(fmt.Errorf("could not synchronise with container process")) } + if p.config.Config.Namespaces.Contains(configs.NEWNS) && !sentResume { + return newSystemError(fmt.Errorf("could not synchronise after executing prestart hooks with container process")) + } if err := syscall.Shutdown(int(p.parentPipe.Fd()), syscall.SHUT_WR); err != nil { return newSystemError(err) } @@ -291,7 +362,7 @@ func (p *initProcess) wait() (*os.ProcessState, error) { return p.cmd.ProcessState, err } // we should kill all processes in cgroup when init is died if we use host PID namespace - if p.cmd.SysProcAttr.Cloneflags&syscall.CLONE_NEWPID == 0 { + if p.sharePidns { killCgroupProcesses(p.manager) } return p.cmd.ProcessState, nil diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/rootfs_linux.go b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/rootfs_linux.go index a2cd43c94f..1a880fe9ee 100644 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/rootfs_linux.go +++ b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/rootfs_linux.go @@ -4,6 +4,7 @@ package libcontainer import ( "fmt" + "io" "io/ioutil" "os" "os/exec" @@ -26,7 +27,7 @@ const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NOD // setupRootfs sets up the devices, mount points, and filesystems for use inside a // new mount namespace. -func setupRootfs(config *configs.Config, console *linuxConsole) (err error) { +func setupRootfs(config *configs.Config, console *linuxConsole, pipe io.ReadWriter) (err error) { if err := prepareRoot(config); err != nil { return newSystemError(err) } @@ -59,6 +60,13 @@ func setupRootfs(config *configs.Config, console *linuxConsole) (err error) { return newSystemError(err) } } + // Signal the parent to run the pre-start hooks. + // The hooks are run after the mounts are setup, but before we switch to the new + // root, so that the old root is still available in the hooks for any mount + // manipulations. + if err := syncParentHooks(pipe); err != nil { + return err + } if err := syscall.Chdir(config.Rootfs); err != nil { return newSystemError(err) } @@ -75,6 +83,18 @@ func setupRootfs(config *configs.Config, console *linuxConsole) (err error) { return newSystemError(err) } } + // remount dev as ro if specifed + for _, m := range config.Mounts { + if m.Destination == "/dev" { + if m.Flags&syscall.MS_RDONLY != 0 { + if err := remountReadonly(m.Destination); err != nil { + return newSystemError(err) + } + } + break + } + } + // set rootfs ( / ) as readonly if config.Readonlyfs { if err := setReadonly(); err != nil { return newSystemError(err) @@ -138,16 +158,6 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { } } return nil - case "devpts": - if err := os.MkdirAll(dest, 0755); err != nil { - return err - } - return mountPropagate(m, rootfs, mountLabel) - case "securityfs": - if err := os.MkdirAll(dest, 0755); err != nil { - return err - } - return mountPropagate(m, rootfs, mountLabel) case "bind": stat, err := os.Stat(m.Source) if err != nil { @@ -253,7 +263,10 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { } } default: - return fmt.Errorf("unknown mount device %q to %q", m.Device, m.Destination) + if err := os.MkdirAll(dest, 0755); err != nil { + return err + } + return mountPropagate(m, rootfs, mountLabel) } return nil } @@ -552,7 +565,7 @@ func setupPtmx(config *configs.Config, console *linuxConsole) error { return nil } -func pivotRoot(rootfs, pivotBaseDir string) error { +func pivotRoot(rootfs, pivotBaseDir string) (err error) { if pivotBaseDir == "" { pivotBaseDir = "/" } @@ -564,6 +577,12 @@ func pivotRoot(rootfs, pivotBaseDir string) error { if err != nil { return fmt.Errorf("can't create pivot_root dir %s, error %v", pivotDir, err) } + defer func() { + errVal := os.Remove(pivotDir) + if err == nil { + err = errVal + } + }() if err := syscall.PivotRoot(rootfs, pivotDir); err != nil { return fmt.Errorf("pivot_root %s", err) } @@ -582,7 +601,7 @@ func pivotRoot(rootfs, pivotBaseDir string) error { if err := syscall.Unmount(pivotDir, syscall.MNT_DETACH); err != nil { return fmt.Errorf("unmount pivot_root dir %s", err) } - return os.Remove(pivotDir) + return nil } func msMoveRoot(rootfs string) error { @@ -671,14 +690,18 @@ func remount(m *configs.Mount, rootfs string) error { // of propagation flags. func mountPropagate(m *configs.Mount, rootfs string, mountLabel string) error { var ( - dest = m.Destination - data = label.FormatMountLabel(m.Data, mountLabel) + dest = m.Destination + data = label.FormatMountLabel(m.Data, mountLabel) + flags = m.Flags ) + if dest == "/dev" { + flags &= ^syscall.MS_RDONLY + } if !strings.HasPrefix(dest, rootfs) { dest = filepath.Join(rootfs, dest) } - if err := syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags), data); err != nil { + if err := syscall.Mount(m.Source, dest, m.Device, uintptr(flags), data); err != nil { return err } diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/selinux/selinux.go b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/selinux/selinux.go index 88d612cade..3bdce10ca3 100644 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/selinux/selinux.go +++ b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/selinux/selinux.go @@ -158,12 +158,14 @@ func Setfilecon(path string, scon string) error { // Getfilecon returns the SELinux label for this path or returns an error. func Getfilecon(path string) (string, error) { con, err := system.Lgetxattr(path, xattrNameSelinux) - + if err != nil { + return "", err + } // Trim the NUL byte at the end of the byte buffer, if present. - if con[len(con)-1] == '\x00' { + if len(con) > 0 && con[len(con)-1] == '\x00' { con = con[:len(con)-1] } - return string(con), err + return string(con), nil } func Setfscreatecon(scon string) error { diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/setns_init_linux.go b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/setns_init_linux.go index 29f5b26e05..5d78cc5be1 100644 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/setns_init_linux.go +++ b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/setns_init_linux.go @@ -3,6 +3,7 @@ package libcontainer import ( + "fmt" "os" "github.com/opencontainers/runc/libcontainer/apparmor" @@ -18,18 +19,19 @@ type linuxSetnsInit struct { config *initConfig } +func (l *linuxSetnsInit) getSessionRingName() string { + return fmt.Sprintf("_ses.%s", l.config.ContainerId) +} + func (l *linuxSetnsInit) Init() error { // do not inherit the parent's session keyring - if _, err := keyctl.JoinSessionKeyring("_ses"); err != nil { + if _, err := keyctl.JoinSessionKeyring(l.getSessionRingName()); err != nil { return err } if err := setupRlimits(l.config.Config); err != nil { return err } - if err := setOomScoreAdj(l.config.Config.OomScoreAdj); err != nil { - return err - } - if l.config.Config.NoNewPrivileges { + if l.config.NoNewPrivileges { if err := system.Prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); err != nil { return err } @@ -42,11 +44,11 @@ func (l *linuxSetnsInit) Init() error { if err := finalizeNamespace(l.config); err != nil { return err } - if err := apparmor.ApplyProfile(l.config.Config.AppArmorProfile); err != nil { + if err := apparmor.ApplyProfile(l.config.AppArmorProfile); err != nil { return err } - if l.config.Config.ProcessLabel != "" { - if err := label.SetProcessLabel(l.config.Config.ProcessLabel); err != nil { + if l.config.ProcessLabel != "" { + if err := label.SetProcessLabel(l.config.ProcessLabel); err != nil { return err } } diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/standard_init_linux.go b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/standard_init_linux.go index 6240347aa4..23604196ff 100644 --- a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/standard_init_linux.go +++ b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/standard_init_linux.go @@ -3,6 +3,7 @@ package libcontainer import ( + "fmt" "io" "os" "syscall" @@ -21,27 +22,39 @@ type linuxStandardInit struct { config *initConfig } +func (l *linuxStandardInit) getSessionRingParams() (string, uint32, uint32) { + var newperms uint32 + + if l.config.Config.Namespaces.Contains(configs.NEWUSER) { + // with user ns we need 'other' search permissions + newperms = 0x8 + } else { + // without user ns we need 'UID' search permissions + newperms = 0x80000 + } + + // create a unique per session container name that we can + // join in setns; however, other containers can also join it + return fmt.Sprintf("_ses.%s", l.config.ContainerId), 0xffffffff, newperms +} + // PR_SET_NO_NEW_PRIVS isn't exposed in Golang so we define it ourselves copying the value // the kernel const PR_SET_NO_NEW_PRIVS = 0x26 func (l *linuxStandardInit) Init() error { + ringname, keepperms, newperms := l.getSessionRingParams() + // do not inherit the parent's session keyring - sessKeyId, err := keyctl.JoinSessionKeyring("") + sessKeyId, err := keyctl.JoinSessionKeyring(ringname) if err != nil { return err } // make session keyring searcheable - // without user ns we need 'UID' search permissions - // with user ns we need 'other' search permissions - if err := keyctl.ModKeyringPerm(sessKeyId, 0xffffffff, 0x080008); err != nil { + if err := keyctl.ModKeyringPerm(sessKeyId, keepperms, newperms); err != nil { return err } - // join any namespaces via a path to the namespace fd if provided - if err := joinExistingNamespaces(l.config.Config.Namespaces); err != nil { - return err - } var console *linuxConsole if l.config.Console != "" { console = newConsoleFromPath(l.config.Console) @@ -49,9 +62,6 @@ func (l *linuxStandardInit) Init() error { return err } } - if _, err := syscall.Setsid(); err != nil { - return err - } if console != nil { if err := system.Setctty(); err != nil { return err @@ -66,13 +76,11 @@ func (l *linuxStandardInit) Init() error { if err := setupRlimits(l.config.Config); err != nil { return err } - if err := setOomScoreAdj(l.config.Config.OomScoreAdj); err != nil { - return err - } + label.Init() // InitializeMountNamespace() can be executed only for a new mount namespace if l.config.Config.Namespaces.Contains(configs.NEWNS) { - if err := setupRootfs(l.config.Config, console); err != nil { + if err := setupRootfs(l.config.Config, console, l.pipe); err != nil { return err } } @@ -81,10 +89,10 @@ func (l *linuxStandardInit) Init() error { return err } } - if err := apparmor.ApplyProfile(l.config.Config.AppArmorProfile); err != nil { + if err := apparmor.ApplyProfile(l.config.AppArmorProfile); err != nil { return err } - if err := label.SetProcessLabel(l.config.Config.ProcessLabel); err != nil { + if err := label.SetProcessLabel(l.config.ProcessLabel); err != nil { return err } @@ -107,7 +115,7 @@ func (l *linuxStandardInit) Init() error { if err != nil { return err } - if l.config.Config.NoNewPrivileges { + if l.config.NoNewPrivileges { if err := system.Prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); err != nil { return err } diff --git a/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/system/unsupported.go b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/system/unsupported.go new file mode 100644 index 0000000000..e7cfd62b29 --- /dev/null +++ b/components/engine/vendor/src/github.com/opencontainers/runc/libcontainer/system/unsupported.go @@ -0,0 +1,9 @@ +// +build !linux + +package system + +// RunningInUserNS is a stub for non-Linux systems +// Always returns false +func RunningInUserNS() bool { + return false +} diff --git a/components/engine/vendor/src/github.com/opencontainers/specs/LICENSE b/components/engine/vendor/src/github.com/opencontainers/specs/LICENSE new file mode 100644 index 0000000000..bdc403653e --- /dev/null +++ b/components/engine/vendor/src/github.com/opencontainers/specs/LICENSE @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2015 The Linux Foundation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/components/engine/vendor/src/github.com/opencontainers/specs/specs-go/config.go b/components/engine/vendor/src/github.com/opencontainers/specs/specs-go/config.go new file mode 100644 index 0000000000..ba66ff1c63 --- /dev/null +++ b/components/engine/vendor/src/github.com/opencontainers/specs/specs-go/config.go @@ -0,0 +1,412 @@ +package specs + +import "os" + +// Spec is the base configuration for the container. It specifies platform +// independent configuration. This information must be included when the +// bundle is packaged for distribution. +type Spec struct { + // Version is the version of the specification that is supported. + Version string `json:"ociVersion"` + // Platform is the host information for OS and Arch. + Platform Platform `json:"platform"` + // Process is the container's main process. + Process Process `json:"process"` + // Root is the root information for the container's filesystem. + Root Root `json:"root"` + // Hostname is the container's host name. + Hostname string `json:"hostname,omitempty"` + // Mounts profile configuration for adding mounts to the container's filesystem. + Mounts []Mount `json:"mounts"` + // Hooks are the commands run at various lifecycle events of the container. + Hooks Hooks `json:"hooks"` + // Annotations is an unstructured key value map that may be set by external tools to store and retrieve arbitrary metadata. + Annotations map[string]string `json:"annotations,omitempty"` + + // Linux is platform specific configuration for Linux based containers. + Linux Linux `json:"linux" platform:"linux"` +} + +// Process contains information to start a specific application inside the container. +type Process struct { + // Terminal creates an interactive terminal for the container. + Terminal bool `json:"terminal"` + // User specifies user information for the process. + User User `json:"user"` + // Args specifies the binary and arguments for the application to execute. + Args []string `json:"args"` + // Env populates the process environment for the process. + Env []string `json:"env,omitempty"` + // Cwd is the current working directory for the process and must be + // relative to the container's root. + Cwd string `json:"cwd"` + // Capabilities are Linux capabilities that are kept for the container. + Capabilities []string `json:"capabilities,omitempty" platform:"linux"` + // Rlimits specifies rlimit options to apply to the process. + Rlimits []Rlimit `json:"rlimits,omitempty"` + // NoNewPrivileges controls whether additional privileges could be gained by processes in the container. + NoNewPrivileges bool `json:"noNewPrivileges,omitempty"` + + // ApparmorProfile specified the apparmor profile for the container. (this field is platform dependent) + ApparmorProfile string `json:"apparmorProfile,omitempty" platform:"linux"` + // SelinuxProcessLabel specifies the selinux context that the container process is run as. (this field is platform dependent) + SelinuxLabel string `json:"selinuxLabel,omitempty" platform:"linux"` +} + +// User specifies Linux specific user and group information for the container's +// main process. +type User struct { + // UID is the user id. (this field is platform dependent) + UID uint32 `json:"uid,omitempty" platform:"linux"` + // GID is the group id. (this field is platform dependent) + GID uint32 `json:"gid,omitempty" platform:"linux"` + // AdditionalGids are additional group ids set for the container's process. (this field is platform dependent) + AdditionalGids []uint32 `json:"additionalGids,omitempty" platform:"linux"` +} + +// Root contains information about the container's root filesystem on the host. +type Root struct { + // Path is the absolute path to the container's root filesystem. + Path string `json:"path"` + // Readonly makes the root filesystem for the container readonly before the process is executed. + Readonly bool `json:"readonly"` +} + +// Platform specifies OS and arch information for the host system that the container +// is created for. +type Platform struct { + // OS is the operating system. + OS string `json:"os"` + // Arch is the architecture + Arch string `json:"arch"` +} + +// Mount specifies a mount for a container. +type Mount struct { + // Destination is the path where the mount will be placed relative to the container's root. The path and child directories MUST exist, a runtime MUST NOT create directories automatically to a mount point. + Destination string `json:"destination"` + // Type specifies the mount kind. + Type string `json:"type"` + // Source specifies the source path of the mount. In the case of bind mounts on + // Linux based systems this would be the file on the host. + Source string `json:"source"` + // Options are fstab style mount options. + Options []string `json:"options,omitempty"` +} + +// Hook specifies a command that is run at a particular event in the lifecycle of a container +type Hook struct { + Path string `json:"path"` + Args []string `json:"args,omitempty"` + Env []string `json:"env,omitempty"` +} + +// Hooks for container setup and teardown +type Hooks struct { + // Prestart is a list of hooks to be run before the container process is executed. + // On Linux, they are run after the container namespaces are created. + Prestart []Hook `json:"prestart,omitempty"` + // Poststart is a list of hooks to be run after the container process is started. + Poststart []Hook `json:"poststart,omitempty"` + // Poststop is a list of hooks to be run after the container process exits. + Poststop []Hook `json:"poststop,omitempty"` +} + +// Linux contains platform specific configuration for Linux based containers. +type Linux struct { + // UIDMapping specifies user mappings for supporting user namespaces on Linux. + UIDMappings []IDMapping `json:"uidMappings,omitempty"` + // GIDMapping specifies group mappings for supporting user namespaces on Linux. + GIDMappings []IDMapping `json:"gidMappings,omitempty"` + // Sysctl are a set of key value pairs that are set for the container on start + Sysctl map[string]string `json:"sysctl,omitempty"` + // Resources contain cgroup information for handling resource constraints + // for the container + Resources *Resources `json:"resources,omitempty"` + // CgroupsPath specifies the path to cgroups that are created and/or joined by the container. + // The path is expected to be relative to the cgroups mountpoint. + // If resources are specified, the cgroups at CgroupsPath will be updated based on resources. + CgroupsPath *string `json:"cgroupsPath,omitempty"` + // Namespaces contains the namespaces that are created and/or joined by the container + Namespaces []Namespace `json:"namespaces"` + // Devices are a list of device nodes that are created for the container + Devices []Device `json:"devices"` + // Seccomp specifies the seccomp security settings for the container. + Seccomp *Seccomp `json:"seccomp,omitempty"` + // RootfsPropagation is the rootfs mount propagation mode for the container. + RootfsPropagation string `json:"rootfsPropagation,omitempty"` +} + +// Namespace is the configuration for a Linux namespace +type Namespace struct { + // Type is the type of Linux namespace + Type NamespaceType `json:"type"` + // Path is a path to an existing namespace persisted on disk that can be joined + // and is of the same type + Path string `json:"path,omitempty"` +} + +// NamespaceType is one of the Linux namespaces +type NamespaceType string + +const ( + // PIDNamespace for isolating process IDs + PIDNamespace NamespaceType = "pid" + // NetworkNamespace for isolating network devices, stacks, ports, etc + NetworkNamespace = "network" + // MountNamespace for isolating mount points + MountNamespace = "mount" + // IPCNamespace for isolating System V IPC, POSIX message queues + IPCNamespace = "ipc" + // UTSNamespace for isolating hostname and NIS domain name + UTSNamespace = "uts" + // UserNamespace for isolating user and group IDs + UserNamespace = "user" +) + +// IDMapping specifies UID/GID mappings +type IDMapping struct { + // HostID is the UID/GID of the host user or group + HostID uint32 `json:"hostID"` + // ContainerID is the UID/GID of the container's user or group + ContainerID uint32 `json:"containerID"` + // Size is the length of the range of IDs mapped between the two namespaces + Size uint32 `json:"size"` +} + +// Rlimit type and restrictions +type Rlimit struct { + // Type of the rlimit to set + Type string `json:"type"` + // Hard is the hard limit for the specified type + Hard uint64 `json:"hard"` + // Soft is the soft limit for the specified type + Soft uint64 `json:"soft"` +} + +// HugepageLimit structure corresponds to limiting kernel hugepages +type HugepageLimit struct { + // Pagesize is the hugepage size + Pagesize *string `json:"pageSize,omitempty"` + // Limit is the limit of "hugepagesize" hugetlb usage + Limit *uint64 `json:"limit,omitempty"` +} + +// InterfacePriority for network interfaces +type InterfacePriority struct { + // Name is the name of the network interface + Name string `json:"name"` + // Priority for the interface + Priority uint32 `json:"priority"` +} + +// blockIODevice holds major:minor format supported in blkio cgroup +type blockIODevice struct { + // Major is the device's major number. + Major int64 `json:"major"` + // Minor is the device's minor number. + Minor int64 `json:"minor"` +} + +// WeightDevice struct holds a `major:minor weight` pair for blkioWeightDevice +type WeightDevice struct { + blockIODevice + // Weight is the bandwidth rate for the device, range is from 10 to 1000 + Weight *uint16 `json:"weight,omitempty"` + // LeafWeight is the bandwidth rate for the device while competing with the cgroup's child cgroups, range is from 10 to 1000, CFQ scheduler only + LeafWeight *uint16 `json:"leafWeight,omitempty"` +} + +// ThrottleDevice struct holds a `major:minor rate_per_second` pair +type ThrottleDevice struct { + blockIODevice + // Rate is the IO rate limit per cgroup per device + Rate *uint64 `json:"rate,omitempty"` +} + +// BlockIO for Linux cgroup 'blkio' resource management +type BlockIO struct { + // Specifies per cgroup weight, range is from 10 to 1000 + Weight *uint16 `json:"blkioWeight,omitempty"` + // Specifies tasks' weight in the given cgroup while competing with the cgroup's child cgroups, range is from 10 to 1000, CFQ scheduler only + LeafWeight *uint16 `json:"blkioLeafWeight,omitempty"` + // Weight per cgroup per device, can override BlkioWeight + WeightDevice []WeightDevice `json:"blkioWeightDevice,omitempty"` + // IO read rate limit per cgroup per device, bytes per second + ThrottleReadBpsDevice []ThrottleDevice `json:"blkioThrottleReadBpsDevice,omitempty"` + // IO write rate limit per cgroup per device, bytes per second + ThrottleWriteBpsDevice []ThrottleDevice `json:"blkioThrottleWriteBpsDevice,omitempty"` + // IO read rate limit per cgroup per device, IO per second + ThrottleReadIOPSDevice []ThrottleDevice `json:"blkioThrottleReadIOPSDevice,omitempty"` + // IO write rate limit per cgroup per device, IO per second + ThrottleWriteIOPSDevice []ThrottleDevice `json:"blkioThrottleWriteIOPSDevice,omitempty"` +} + +// Memory for Linux cgroup 'memory' resource management +type Memory struct { + // Memory limit (in bytes). + Limit *uint64 `json:"limit,omitempty"` + // Memory reservation or soft_limit (in bytes). + Reservation *uint64 `json:"reservation,omitempty"` + // Total memory limit (memory + swap). + Swap *uint64 `json:"swap,omitempty"` + // Kernel memory limit (in bytes). + Kernel *uint64 `json:"kernel,omitempty"` + // Kernel memory limit for tcp (in bytes) + KernelTCP *uint64 `json:"kernelTCP"` + // How aggressive the kernel will swap memory pages. Range from 0 to 100. + Swappiness *uint64 `json:"swappiness,omitempty"` +} + +// CPU for Linux cgroup 'cpu' resource management +type CPU struct { + // CPU shares (relative weight (ratio) vs. other cgroups with cpu shares). + Shares *uint64 `json:"shares,omitempty"` + // CPU hardcap limit (in usecs). Allowed cpu time in a given period. + Quota *uint64 `json:"quota,omitempty"` + // CPU period to be used for hardcapping (in usecs). + Period *uint64 `json:"period,omitempty"` + // How much time realtime scheduling may use (in usecs). + RealtimeRuntime *uint64 `json:"realtimeRuntime,omitempty"` + // CPU period to be used for realtime scheduling (in usecs). + RealtimePeriod *uint64 `json:"realtimePeriod,omitempty"` + // CPUs to use within the cpuset. Default is to use any CPU available. + Cpus *string `json:"cpus,omitempty"` + // List of memory nodes in the cpuset. Default is to use any available memory node. + Mems *string `json:"mems,omitempty"` +} + +// Pids for Linux cgroup 'pids' resource management (Linux 4.3) +type Pids struct { + // Maximum number of PIDs. Default is "no limit". + Limit *int64 `json:"limit,omitempty"` +} + +// Network identification and priority configuration +type Network struct { + // Set class identifier for container's network packets + ClassID *uint32 `json:"classID"` + // Set priority of network traffic for container + Priorities []InterfacePriority `json:"priorities,omitempty"` +} + +// Resources has container runtime resource constraints +type Resources struct { + // Devices are a list of device rules for the whitelist controller + Devices []DeviceCgroup `json:"devices"` + // DisableOOMKiller disables the OOM killer for out of memory conditions + DisableOOMKiller *bool `json:"disableOOMKiller,omitempty"` + // Specify an oom_score_adj for the container. + OOMScoreAdj *int `json:"oomScoreAdj,omitempty"` + // Memory restriction configuration + Memory *Memory `json:"memory,omitempty"` + // CPU resource restriction configuration + CPU *CPU `json:"cpu,omitempty"` + // Task resource restriction configuration. + Pids *Pids `json:"pids,omitempty"` + // BlockIO restriction configuration + BlockIO *BlockIO `json:"blockIO,omitempty"` + // Hugetlb limit (in bytes) + HugepageLimits []HugepageLimit `json:"hugepageLimits,omitempty"` + // Network restriction configuration + Network *Network `json:"network,omitempty"` +} + +// Device represents the mknod information for a Linux special device file +type Device struct { + // Path to the device. + Path string `json:"path"` + // Device type, block, char, etc. + Type string `json:"type"` + // Major is the device's major number. + Major int64 `json:"major"` + // Minor is the device's minor number. + Minor int64 `json:"minor"` + // FileMode permission bits for the device. + FileMode *os.FileMode `json:"fileMode,omitempty"` + // UID of the device. + UID *uint32 `json:"uid,omitempty"` + // Gid of the device. + GID *uint32 `json:"gid,omitempty"` +} + +// DeviceCgroup represents a device rule for the whitelist controller +type DeviceCgroup struct { + // Allow or deny + Allow bool `json:"allow"` + // Device type, block, char, etc. + Type *string `json:"type,omitempty"` + // Major is the device's major number. + Major *int64 `json:"major,omitempty"` + // Minor is the device's minor number. + Minor *int64 `json:"minor,omitempty"` + // Cgroup access permissions format, rwm. + Access *string `json:"access,omitempty"` +} + +// Seccomp represents syscall restrictions +type Seccomp struct { + DefaultAction Action `json:"defaultAction"` + Architectures []Arch `json:"architectures"` + Syscalls []Syscall `json:"syscalls,omitempty"` +} + +// Arch used for additional architectures +type Arch string + +// Additional architectures permitted to be used for system calls +// By default only the native architecture of the kernel is permitted +const ( + ArchX86 Arch = "SCMP_ARCH_X86" + ArchX86_64 Arch = "SCMP_ARCH_X86_64" + ArchX32 Arch = "SCMP_ARCH_X32" + ArchARM Arch = "SCMP_ARCH_ARM" + ArchAARCH64 Arch = "SCMP_ARCH_AARCH64" + ArchMIPS Arch = "SCMP_ARCH_MIPS" + ArchMIPS64 Arch = "SCMP_ARCH_MIPS64" + ArchMIPS64N32 Arch = "SCMP_ARCH_MIPS64N32" + ArchMIPSEL Arch = "SCMP_ARCH_MIPSEL" + ArchMIPSEL64 Arch = "SCMP_ARCH_MIPSEL64" + ArchMIPSEL64N32 Arch = "SCMP_ARCH_MIPSEL64N32" +) + +// Action taken upon Seccomp rule match +type Action string + +// Define actions for Seccomp rules +const ( + ActKill Action = "SCMP_ACT_KILL" + ActTrap Action = "SCMP_ACT_TRAP" + ActErrno Action = "SCMP_ACT_ERRNO" + ActTrace Action = "SCMP_ACT_TRACE" + ActAllow Action = "SCMP_ACT_ALLOW" +) + +// Operator used to match syscall arguments in Seccomp +type Operator string + +// Define operators for syscall arguments in Seccomp +const ( + OpNotEqual Operator = "SCMP_CMP_NE" + OpLessThan Operator = "SCMP_CMP_LT" + OpLessEqual Operator = "SCMP_CMP_LE" + OpEqualTo Operator = "SCMP_CMP_EQ" + OpGreaterEqual Operator = "SCMP_CMP_GE" + OpGreaterThan Operator = "SCMP_CMP_GT" + OpMaskedEqual Operator = "SCMP_CMP_MASKED_EQ" +) + +// Arg used for matching specific syscall arguments in Seccomp +type Arg struct { + Index uint `json:"index"` + Value uint64 `json:"value"` + ValueTwo uint64 `json:"valueTwo"` + Op Operator `json:"op"` +} + +// Syscall is used to match a syscall in Seccomp +type Syscall struct { + Name string `json:"name"` + Action Action `json:"action"` + Args []Arg `json:"args,omitempty"` +} diff --git a/components/engine/vendor/src/github.com/opencontainers/specs/specs-go/state.go b/components/engine/vendor/src/github.com/opencontainers/specs/specs-go/state.go new file mode 100644 index 0000000000..d3ad79d9c2 --- /dev/null +++ b/components/engine/vendor/src/github.com/opencontainers/specs/specs-go/state.go @@ -0,0 +1,13 @@ +package specs + +// State holds information about the runtime state of the container. +type State struct { + // Version is the version of the specification that is supported. + Version string `json:"version"` + // ID is the container ID + ID string `json:"id"` + // Pid is the process id for the container's main process. + Pid int `json:"pid"` + // BundlePath is the path to the container's bundle directory. + BundlePath string `json:"bundlePath"` +} diff --git a/components/engine/vendor/src/github.com/opencontainers/specs/specs-go/version.go b/components/engine/vendor/src/github.com/opencontainers/specs/specs-go/version.go new file mode 100644 index 0000000000..f11c897896 --- /dev/null +++ b/components/engine/vendor/src/github.com/opencontainers/specs/specs-go/version.go @@ -0,0 +1,18 @@ +package specs + +import "fmt" + +const ( + // VersionMajor is for an API incompatible changes + VersionMajor = 0 + // VersionMinor is for functionality in a backwards-compatible manner + VersionMinor = 4 + // VersionPatch is for backwards-compatible bug fixes + VersionPatch = 0 + + // VersionDev indicates development branch. Releases will be empty string. + VersionDev = "" +) + +// Version is the specification version that the package types support. +var Version = fmt.Sprintf("%d.%d.%d%s", VersionMajor, VersionMinor, VersionPatch, VersionDev) diff --git a/components/engine/vendor/src/google.golang.org/grpc/Makefile b/components/engine/vendor/src/google.golang.org/grpc/Makefile index 5bc38be209..12e84e4e5b 100644 --- a/components/engine/vendor/src/google.golang.org/grpc/Makefile +++ b/components/engine/vendor/src/google.golang.org/grpc/Makefile @@ -47,4 +47,4 @@ clean: go clean google.golang.org/grpc/... coverage: testdeps - goveralls -v google.golang.org/grpc/... + ./coverage.sh --coveralls diff --git a/components/engine/vendor/src/google.golang.org/grpc/clientconn.go b/components/engine/vendor/src/google.golang.org/grpc/clientconn.go index 4729bbd6ab..bf66914c11 100644 --- a/components/engine/vendor/src/google.golang.org/grpc/clientconn.go +++ b/components/engine/vendor/src/google.golang.org/grpc/clientconn.go @@ -89,6 +89,12 @@ func WithCodec(c Codec) DialOption { } } +func WithPicker(p Picker) DialOption { + return func(o *dialOptions) { + o.picker = p + } +} + // WithBlock returns a DialOption which makes caller of Dial blocks until the underlying // connection is up. Without this, Dial returns immediately and connecting the server // happens in background. @@ -154,7 +160,9 @@ func Dial(target string, opts ...DialOption) (*ClientConn, error) { cc.dopts.codec = protoCodec{} } if cc.dopts.picker == nil { - cc.dopts.picker = &unicastPicker{} + cc.dopts.picker = &unicastPicker{ + target: target, + } } if err := cc.dopts.picker.Init(cc); err != nil { return nil, err @@ -209,15 +217,15 @@ type ClientConn struct { // State returns the connectivity state of cc. // This is EXPERIMENTAL API. -func (cc *ClientConn) State() ConnectivityState { +func (cc *ClientConn) State() (ConnectivityState, error) { return cc.dopts.picker.State() } -// WaitForStateChange blocks until the state changes to something other than the sourceState -// or timeout fires on cc. It returns false if timeout fires, and true otherwise. +// WaitForStateChange blocks until the state changes to something other than the sourceState. +// It returns the new state or error. // This is EXPERIMENTAL API. -func (cc *ClientConn) WaitForStateChange(timeout time.Duration, sourceState ConnectivityState) bool { - return cc.dopts.picker.WaitForStateChange(timeout, sourceState) +func (cc *ClientConn) WaitForStateChange(ctx context.Context, sourceState ConnectivityState) (ConnectivityState, error) { + return cc.dopts.picker.WaitForStateChange(ctx, sourceState) } // Close starts to tear down the ClientConn. @@ -229,6 +237,7 @@ func (cc *ClientConn) Close() error { type Conn struct { target string dopts dialOptions + resetChan chan int shutdownChan chan struct{} events trace.EventLog @@ -249,6 +258,7 @@ func NewConn(cc *ClientConn) (*Conn, error) { c := &Conn{ target: cc.target, dopts: cc.dopts, + resetChan: make(chan int, 1), shutdownChan: make(chan struct{}), } if EnableTracing { @@ -317,26 +327,20 @@ func (cc *Conn) State() ConnectivityState { return cc.state } -// WaitForStateChange blocks until the state changes to something other than the sourceState -// or timeout fires. It returns false if timeout fires and true otherwise. -// TODO(zhaoq): Rewrite for complex Picker. -func (cc *Conn) WaitForStateChange(timeout time.Duration, sourceState ConnectivityState) bool { - start := time.Now() +// WaitForStateChange blocks until the state changes to something other than the sourceState. +func (cc *Conn) WaitForStateChange(ctx context.Context, sourceState ConnectivityState) (ConnectivityState, error) { cc.mu.Lock() defer cc.mu.Unlock() if sourceState != cc.state { - return true - } - expired := timeout <= time.Since(start) - if expired { - return false + return cc.state, nil } done := make(chan struct{}) + var err error go func() { select { - case <-time.After(timeout - time.Since(start)): + case <-ctx.Done(): cc.mu.Lock() - expired = true + err = ctx.Err() cc.stateCV.Broadcast() cc.mu.Unlock() case <-done: @@ -345,11 +349,20 @@ func (cc *Conn) WaitForStateChange(timeout time.Duration, sourceState Connectivi defer close(done) for sourceState == cc.state { cc.stateCV.Wait() - if expired { - return false + if err != nil { + return cc.state, err } } - return true + return cc.state, nil +} + +// NotifyReset tries to signal the underlying transport needs to be reset due to +// for example a name resolution change in flight. +func (cc *Conn) NotifyReset() { + select { + case cc.resetChan <- 0: + default: + } } func (cc *Conn) resetTransport(closeTransport bool) error { @@ -359,6 +372,7 @@ func (cc *Conn) resetTransport(closeTransport bool) error { cc.mu.Lock() cc.printf("connecting") if cc.state == Shutdown { + // cc.Close() has been invoked. cc.mu.Unlock() return ErrClientConnClosing } @@ -390,9 +404,18 @@ func (cc *Conn) resetTransport(closeTransport bool) error { copts.Timeout = timeout } connectTime := time.Now() - newTransport, err := transport.NewClientTransport(cc.target, &copts) + addr, err := cc.dopts.picker.PickAddr() + var newTransport transport.ClientTransport + if err == nil { + newTransport, err = transport.NewClientTransport(addr, &copts) + } if err != nil { cc.mu.Lock() + if cc.state == Shutdown { + // cc.Close() has been invoked. + cc.mu.Unlock() + return ErrClientConnClosing + } cc.errorf("transient failure: %v", err) cc.state = TransientFailure cc.stateCV.Broadcast() @@ -416,7 +439,7 @@ func (cc *Conn) resetTransport(closeTransport bool) error { closeTransport = false time.Sleep(sleepTime) retries++ - grpclog.Printf("grpc: ClientConn.resetTransport failed to create client transport: %v; Reconnecting to %q", err, cc.target) + grpclog.Printf("grpc: Conn.resetTransport failed to create client transport: %v; Reconnecting to %q", err, cc.target) continue } cc.mu.Lock() @@ -439,6 +462,27 @@ func (cc *Conn) resetTransport(closeTransport bool) error { } } +func (cc *Conn) reconnect() bool { + cc.mu.Lock() + if cc.state == Shutdown { + // cc.Close() has been invoked. + cc.mu.Unlock() + return false + } + cc.state = TransientFailure + cc.stateCV.Broadcast() + cc.mu.Unlock() + if err := cc.resetTransport(true); err != nil { + // The ClientConn is closing. + cc.mu.Lock() + cc.printf("transport exiting: %v", err) + cc.mu.Unlock() + grpclog.Printf("grpc: Conn.transportMonitor exits due to: %v", err) + return false + } + return true +} + // Run in a goroutine to track the error in transport and create the // new transport if an error happens. It returns when the channel is closing. func (cc *Conn) transportMonitor() { @@ -448,20 +492,19 @@ func (cc *Conn) transportMonitor() { // the ClientConn is idle (i.e., no RPC in flight). case <-cc.shutdownChan: return - case <-cc.transport.Error(): - cc.mu.Lock() - cc.state = TransientFailure - cc.stateCV.Broadcast() - cc.mu.Unlock() - if err := cc.resetTransport(true); err != nil { - // The ClientConn is closing. - cc.mu.Lock() - cc.printf("transport exiting: %v", err) - cc.mu.Unlock() - grpclog.Printf("grpc: ClientConn.transportMonitor exits due to: %v", err) + case <-cc.resetChan: + if !cc.reconnect() { return } - continue + case <-cc.transport.Error(): + if !cc.reconnect() { + return + } + // Tries to drain reset signal if there is any since it is out-dated. + select { + case <-cc.resetChan: + default: + } } } } diff --git a/components/engine/vendor/src/google.golang.org/grpc/coverage.sh b/components/engine/vendor/src/google.golang.org/grpc/coverage.sh new file mode 100755 index 0000000000..9de8c918df --- /dev/null +++ b/components/engine/vendor/src/google.golang.org/grpc/coverage.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +set -e + +workdir=.cover +profile="$workdir/cover.out" +mode=count + +generate_cover_data() { + rm -rf "$workdir" + mkdir "$workdir" + + for pkg in "$@"; do + f="$workdir/$(echo $pkg | tr / -).cover" + go test -covermode="$mode" -coverprofile="$f" "$pkg" + done + + echo "mode: $mode" >"$profile" + grep -h -v "^mode:" "$workdir"/*.cover >>"$profile" +} + +show_cover_report() { + go tool cover -${1}="$profile" +} + +push_to_coveralls() { + goveralls -coverprofile="$profile" +} + +generate_cover_data $(go list ./...) +show_cover_report func +case "$1" in +"") + ;; +--coveralls) + push_to_coveralls ;; +*) + echo >&2 "error: invalid option: $1" ;; +esac +rm -rf "$workdir" diff --git a/components/engine/vendor/src/google.golang.org/grpc/metadata/metadata.go b/components/engine/vendor/src/google.golang.org/grpc/metadata/metadata.go index adebc38f8e..52070dbeca 100644 --- a/components/engine/vendor/src/google.golang.org/grpc/metadata/metadata.go +++ b/components/engine/vendor/src/google.golang.org/grpc/metadata/metadata.go @@ -46,27 +46,16 @@ const ( binHdrSuffix = "-bin" ) -// grpc-http2 requires ASCII header key and value (more detail can be found in -// "Requests" subsection in go/grpc-http2). -func isASCII(s string) bool { - for _, c := range s { - if c > 127 { - return false - } - } - return true -} - // encodeKeyValue encodes key and value qualified for transmission via gRPC. // Transmitting binary headers violates HTTP/2 spec. // TODO(zhaoq): Maybe check if k is ASCII also. func encodeKeyValue(k, v string) (string, string) { - if isASCII(v) { - return k, v + k = strings.ToLower(k) + if strings.HasSuffix(k, binHdrSuffix) { + val := base64.StdEncoding.EncodeToString([]byte(v)) + v = string(val) } - key := strings.ToLower(k + binHdrSuffix) - val := base64.StdEncoding.EncodeToString([]byte(v)) - return key, string(val) + return k, v } // DecodeKeyValue returns the original key and value corresponding to the @@ -75,12 +64,11 @@ func DecodeKeyValue(k, v string) (string, string, error) { if !strings.HasSuffix(k, binHdrSuffix) { return k, v, nil } - key := k[:len(k)-len(binHdrSuffix)] val, err := base64.StdEncoding.DecodeString(v) if err != nil { return "", "", err } - return key, string(val), nil + return k, string(val), nil } // MD is a mapping from metadata keys to values. Users should use the following diff --git a/components/engine/vendor/src/google.golang.org/grpc/naming/naming.go b/components/engine/vendor/src/google.golang.org/grpc/naming/naming.go new file mode 100644 index 0000000000..06605607c3 --- /dev/null +++ b/components/engine/vendor/src/google.golang.org/grpc/naming/naming.go @@ -0,0 +1,73 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +// Package naming defines the naming API and related data structures for gRPC. +// The interface is EXPERIMENTAL and may be suject to change. +package naming + +// Operation defines the corresponding operations for a name resolution change. +type Operation uint8 + +const ( + // Add indicates a new address is added. + Add Operation = iota + // Delete indicates an exisiting address is deleted. + Delete +) + +// Update defines a name resolution update. Notice that it is not valid having both +// empty string Addr and nil Metadata in an Update. +type Update struct { + // Op indicates the operation of the update. + Op Operation + // Addr is the updated address. It is empty string if there is no address update. + Addr string + // Metadata is the updated metadata. It is nil if there is no metadata update. + // Metadata is not required for a custom naming implementation. + Metadata interface{} +} + +// Resolver creates a Watcher for a target to track its resolution changes. +type Resolver interface { + // Resolve creates a Watcher for target. + Resolve(target string) (Watcher, error) +} + +// Watcher watches for the updates on the specified target. +type Watcher interface { + // Next blocks until an update or error happens. It may return one or more + // updates. The first call should get the full set of the results. + Next() ([]*Update, error) + // Close closes the Watcher. + Close() +} diff --git a/components/engine/vendor/src/google.golang.org/grpc/picker.go b/components/engine/vendor/src/google.golang.org/grpc/picker.go index bc48573a41..b83c859b43 100644 --- a/components/engine/vendor/src/google.golang.org/grpc/picker.go +++ b/components/engine/vendor/src/google.golang.org/grpc/picker.go @@ -34,9 +34,13 @@ package grpc import ( - "time" + "container/list" + "fmt" + "sync" "golang.org/x/net/context" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/naming" "google.golang.org/grpc/transport" ) @@ -48,12 +52,14 @@ type Picker interface { // Pick blocks until either a transport.ClientTransport is ready for the upcoming RPC // or some error happens. Pick(ctx context.Context) (transport.ClientTransport, error) + // PickAddr picks a peer address for connecting. This will be called repeated for + // connecting/reconnecting. + PickAddr() (string, error) // State returns the connectivity state of the underlying connections. - State() ConnectivityState + State() (ConnectivityState, error) // WaitForStateChange blocks until the state changes to something other than - // the sourceState or timeout fires on cc. It returns false if timeout fires, - // and true otherwise. - WaitForStateChange(timeout time.Duration, sourceState ConnectivityState) bool + // the sourceState. It returns the new state or error. + WaitForStateChange(ctx context.Context, sourceState ConnectivityState) (ConnectivityState, error) // Close closes all the Conn's owned by this Picker. Close() error } @@ -61,7 +67,8 @@ type Picker interface { // unicastPicker is the default Picker which is used when there is no custom Picker // specified by users. It always picks the same Conn. type unicastPicker struct { - conn *Conn + target string + conn *Conn } func (p *unicastPicker) Init(cc *ClientConn) error { @@ -77,12 +84,16 @@ func (p *unicastPicker) Pick(ctx context.Context) (transport.ClientTransport, er return p.conn.Wait(ctx) } -func (p *unicastPicker) State() ConnectivityState { - return p.conn.State() +func (p *unicastPicker) PickAddr() (string, error) { + return p.target, nil } -func (p *unicastPicker) WaitForStateChange(timeout time.Duration, sourceState ConnectivityState) bool { - return p.conn.WaitForStateChange(timeout, sourceState) +func (p *unicastPicker) State() (ConnectivityState, error) { + return p.conn.State(), nil +} + +func (p *unicastPicker) WaitForStateChange(ctx context.Context, sourceState ConnectivityState) (ConnectivityState, error) { + return p.conn.WaitForStateChange(ctx, sourceState) } func (p *unicastPicker) Close() error { @@ -91,3 +102,142 @@ func (p *unicastPicker) Close() error { } return nil } + +// unicastNamingPicker picks an address from a name resolver to set up the connection. +type unicastNamingPicker struct { + cc *ClientConn + resolver naming.Resolver + watcher naming.Watcher + mu sync.Mutex + // The list of the addresses are obtained from watcher. + addrs *list.List + // It tracks the current picked addr by PickAddr(). The next PickAddr may + // push it forward on addrs. + pickedAddr *list.Element + conn *Conn +} + +// NewUnicastNamingPicker creates a Picker to pick addresses from a name resolver +// to connect. +func NewUnicastNamingPicker(r naming.Resolver) Picker { + return &unicastNamingPicker{ + resolver: r, + addrs: list.New(), + } +} + +type addrInfo struct { + addr string + // Set to true if this addrInfo needs to be deleted in the next PickAddrr() call. + deleting bool +} + +// processUpdates calls Watcher.Next() once and processes the obtained updates. +func (p *unicastNamingPicker) processUpdates() error { + updates, err := p.watcher.Next() + if err != nil { + return err + } + for _, update := range updates { + switch update.Op { + case naming.Add: + p.mu.Lock() + p.addrs.PushBack(&addrInfo{ + addr: update.Addr, + }) + p.mu.Unlock() + // Initial connection setup + if p.conn == nil { + conn, err := NewConn(p.cc) + if err != nil { + return err + } + p.conn = conn + } + case naming.Delete: + p.mu.Lock() + for e := p.addrs.Front(); e != nil; e = e.Next() { + if update.Addr == e.Value.(*addrInfo).addr { + if e == p.pickedAddr { + // Do not remove the element now if it is the current picked + // one. We leave the deletion to the next PickAddr() call. + e.Value.(*addrInfo).deleting = true + // Notify Conn to close it. All the live RPCs on this connection + // will be aborted. + p.conn.NotifyReset() + } else { + p.addrs.Remove(e) + } + } + } + p.mu.Unlock() + default: + grpclog.Println("Unknown update.Op %d", update.Op) + } + } + return nil +} + +// monitor runs in a standalone goroutine to keep watching name resolution updates until the watcher +// is closed. +func (p *unicastNamingPicker) monitor() { + for { + if err := p.processUpdates(); err != nil { + return + } + } +} + +func (p *unicastNamingPicker) Init(cc *ClientConn) error { + w, err := p.resolver.Resolve(cc.target) + if err != nil { + return err + } + p.watcher = w + p.cc = cc + // Get the initial name resolution. + if err := p.processUpdates(); err != nil { + return err + } + go p.monitor() + return nil +} + +func (p *unicastNamingPicker) Pick(ctx context.Context) (transport.ClientTransport, error) { + return p.conn.Wait(ctx) +} + +func (p *unicastNamingPicker) PickAddr() (string, error) { + p.mu.Lock() + defer p.mu.Unlock() + if p.pickedAddr == nil { + p.pickedAddr = p.addrs.Front() + } else { + pa := p.pickedAddr + p.pickedAddr = pa.Next() + if pa.Value.(*addrInfo).deleting { + p.addrs.Remove(pa) + } + if p.pickedAddr == nil { + p.pickedAddr = p.addrs.Front() + } + } + if p.pickedAddr == nil { + return "", fmt.Errorf("there is no address available to pick") + } + return p.pickedAddr.Value.(*addrInfo).addr, nil +} + +func (p *unicastNamingPicker) State() (ConnectivityState, error) { + return 0, fmt.Errorf("State() is not supported for unicastNamingPicker") +} + +func (p *unicastNamingPicker) WaitForStateChange(ctx context.Context, sourceState ConnectivityState) (ConnectivityState, error) { + return 0, fmt.Errorf("WaitForStateChange is not supported for unicastNamingPciker") +} + +func (p *unicastNamingPicker) Close() error { + p.watcher.Close() + p.conn.Close() + return nil +} diff --git a/components/engine/vendor/src/google.golang.org/grpc/server.go b/components/engine/vendor/src/google.golang.org/grpc/server.go index 487a75c5ee..655e7d865e 100644 --- a/components/engine/vendor/src/google.golang.org/grpc/server.go +++ b/components/engine/vendor/src/google.golang.org/grpc/server.go @@ -410,11 +410,11 @@ func (s *Server) processStreamingRPC(t transport.ServerTransport, stream *transp defer func() { ss.mu.Lock() if err != nil && err != io.EOF { - trInfo.tr.LazyLog(&fmtStringer{"%v", []interface{}{err}}, true) - trInfo.tr.SetError() + ss.trInfo.tr.LazyLog(&fmtStringer{"%v", []interface{}{err}}, true) + ss.trInfo.tr.SetError() } - trInfo.tr.Finish() - trInfo.tr = nil + ss.trInfo.tr.Finish() + ss.trInfo.tr = nil ss.mu.Unlock() }() } @@ -430,10 +430,10 @@ func (s *Server) processStreamingRPC(t transport.ServerTransport, stream *transp if trInfo != nil { ss.mu.Lock() if ss.statusCode != codes.OK { - trInfo.tr.LazyLog(stringer(ss.statusDesc), true) - trInfo.tr.SetError() + ss.trInfo.tr.LazyLog(stringer(ss.statusDesc), true) + ss.trInfo.tr.SetError() } else { - trInfo.tr.LazyLog(stringer("OK"), false) + ss.trInfo.tr.LazyLog(stringer("OK"), false) } ss.mu.Unlock() } @@ -448,18 +448,40 @@ func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Str } pos := strings.LastIndex(sm, "/") if pos == -1 { + if trInfo != nil { + trInfo.tr.LazyLog(&fmtStringer{"Malformed method name %q", []interface{}{sm}}, true) + trInfo.tr.SetError() + } if err := t.WriteStatus(stream, codes.InvalidArgument, fmt.Sprintf("malformed method name: %q", stream.Method())); err != nil { + if trInfo != nil { + trInfo.tr.LazyLog(&fmtStringer{"%v", []interface{}{err}}, true) + trInfo.tr.SetError() + } grpclog.Printf("grpc: Server.handleStream failed to write status: %v", err) } + if trInfo != nil { + trInfo.tr.Finish() + } return } service := sm[:pos] method := sm[pos+1:] srv, ok := s.m[service] if !ok { + if trInfo != nil { + trInfo.tr.LazyLog(&fmtStringer{"Unknown service %v", []interface{}{service}}, true) + trInfo.tr.SetError() + } if err := t.WriteStatus(stream, codes.Unimplemented, fmt.Sprintf("unknown service %v", service)); err != nil { + if trInfo != nil { + trInfo.tr.LazyLog(&fmtStringer{"%v", []interface{}{err}}, true) + trInfo.tr.SetError() + } grpclog.Printf("grpc: Server.handleStream failed to write status: %v", err) } + if trInfo != nil { + trInfo.tr.Finish() + } return } // Unary RPC or Streaming RPC? @@ -471,9 +493,20 @@ func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Str s.processStreamingRPC(t, stream, srv, sd, trInfo) return } + if trInfo != nil { + trInfo.tr.LazyLog(&fmtStringer{"Unknown method %v", []interface{}{method}}, true) + trInfo.tr.SetError() + } if err := t.WriteStatus(stream, codes.Unimplemented, fmt.Sprintf("unknown method %v", method)); err != nil { + if trInfo != nil { + trInfo.tr.LazyLog(&fmtStringer{"%v", []interface{}{err}}, true) + trInfo.tr.SetError() + } grpclog.Printf("grpc: Server.handleStream failed to write status: %v", err) } + if trInfo != nil { + trInfo.tr.Finish() + } } // Stop stops the gRPC server. Once Stop returns, the server stops accepting diff --git a/components/engine/vendor/src/google.golang.org/grpc/stream.go b/components/engine/vendor/src/google.golang.org/grpc/stream.go index 2370dd0e9d..0ee572c27b 100644 --- a/components/engine/vendor/src/google.golang.org/grpc/stream.go +++ b/components/engine/vendor/src/google.golang.org/grpc/stream.go @@ -149,7 +149,7 @@ type clientStream struct { tracing bool // set to EnableTracing when the clientStream is created. - mu sync.Mutex + mu sync.Mutex closed bool // trInfo.tr is set when the clientStream is created (if EnableTracing is true), // and is set to nil when the clientStream's finish method is called. diff --git a/components/engine/vendor/src/google.golang.org/grpc/transport/control.go b/components/engine/vendor/src/google.golang.org/grpc/transport/control.go index 6b5201e7a6..f6b38a5a6d 100644 --- a/components/engine/vendor/src/google.golang.org/grpc/transport/control.go +++ b/components/engine/vendor/src/google.golang.org/grpc/transport/control.go @@ -86,7 +86,8 @@ func (flushIO) isItem() bool { } type ping struct { - ack bool + ack bool + data [8]byte } func (ping) isItem() bool { diff --git a/components/engine/vendor/src/google.golang.org/grpc/transport/http2_client.go b/components/engine/vendor/src/google.golang.org/grpc/transport/http2_client.go index b13fb78c4c..07b0c11710 100644 --- a/components/engine/vendor/src/google.golang.org/grpc/transport/http2_client.go +++ b/components/engine/vendor/src/google.golang.org/grpc/transport/http2_client.go @@ -646,7 +646,9 @@ func (t *http2Client) handleSettings(f *http2.SettingsFrame) { } func (t *http2Client) handlePing(f *http2.PingFrame) { - t.controlBuf.put(&ping{true}) + pingAck := &ping{ack: true} + copy(pingAck.data[:], f.Data[:]) + t.controlBuf.put(pingAck) } func (t *http2Client) handleGoAway(f *http2.GoAwayFrame) { @@ -751,7 +753,7 @@ func (t *http2Client) reader() { endStream := frame.Header().Flags.Has(http2.FlagHeadersEndStream) curStream = t.operateHeaders(hDec, curStream, frame, endStream) case *http2.ContinuationFrame: - curStream = t.operateHeaders(hDec, curStream, frame, false) + curStream = t.operateHeaders(hDec, curStream, frame, frame.HeadersEnded()) case *http2.DataFrame: t.handleData(frame) case *http2.RSTStreamFrame: @@ -827,9 +829,7 @@ func (t *http2Client) controller() { case *flushIO: t.framer.flushWrite() case *ping: - // TODO(zhaoq): Ack with all-0 data now. will change to some - // meaningful content when this is actually in use. - t.framer.writePing(true, i.ack, [8]byte{}) + t.framer.writePing(true, i.ack, i.data) default: grpclog.Printf("transport: http2Client.controller got unexpected item type %v\n", i) } diff --git a/components/engine/vendor/src/google.golang.org/grpc/transport/http2_server.go b/components/engine/vendor/src/google.golang.org/grpc/transport/http2_server.go index f3488f83dc..e16c63cc93 100644 --- a/components/engine/vendor/src/google.golang.org/grpc/transport/http2_server.go +++ b/components/engine/vendor/src/google.golang.org/grpc/transport/http2_server.go @@ -163,22 +163,6 @@ func (t *http2Server) operateHeaders(hDec *hpackDecoder, s *Stream, frame header if !endHeaders { return s } - t.mu.Lock() - if t.state != reachable { - t.mu.Unlock() - return nil - } - if uint32(len(t.activeStreams)) >= t.maxStreams { - t.mu.Unlock() - t.controlBuf.put(&resetStream{s.id, http2.ErrCodeRefusedStream}) - return nil - } - s.sendQuotaPool = newQuotaPool(int(t.streamSendQuota)) - t.activeStreams[s.id] = s - t.mu.Unlock() - s.windowHandler = func(n int) { - t.updateWindow(s, uint32(n)) - } if hDec.state.timeoutSet { s.ctx, s.cancel = context.WithTimeout(context.TODO(), hDec.state.timeout) } else { @@ -202,6 +186,22 @@ func (t *http2Server) operateHeaders(hDec *hpackDecoder, s *Stream, frame header recv: s.buf, } s.method = hDec.state.method + t.mu.Lock() + if t.state != reachable { + t.mu.Unlock() + return nil + } + if uint32(len(t.activeStreams)) >= t.maxStreams { + t.mu.Unlock() + t.controlBuf.put(&resetStream{s.id, http2.ErrCodeRefusedStream}) + return nil + } + s.sendQuotaPool = newQuotaPool(int(t.streamSendQuota)) + t.activeStreams[s.id] = s + t.mu.Unlock() + s.windowHandler = func(n int) { + t.updateWindow(s, uint32(n)) + } handle(s) return nil } @@ -268,7 +268,7 @@ func (t *http2Server) HandleStreams(handle func(*Stream)) { endStream := frame.Header().Flags.Has(http2.FlagHeadersEndStream) curStream = t.operateHeaders(hDec, curStream, frame, endStream, handle) case *http2.ContinuationFrame: - curStream = t.operateHeaders(hDec, curStream, frame, false, handle) + curStream = t.operateHeaders(hDec, curStream, frame, frame.HeadersEnded(), handle) case *http2.DataFrame: t.handleData(frame) case *http2.RSTStreamFrame: @@ -377,7 +377,9 @@ func (t *http2Server) handleSettings(f *http2.SettingsFrame) { } func (t *http2Server) handlePing(f *http2.PingFrame) { - t.controlBuf.put(&ping{true}) + pingAck := &ping{ack: true} + copy(pingAck.data[:], f.Data[:]) + t.controlBuf.put(pingAck) } func (t *http2Server) handleWindowUpdate(f *http2.WindowUpdateFrame) { @@ -628,9 +630,7 @@ func (t *http2Server) controller() { case *flushIO: t.framer.flushWrite() case *ping: - // TODO(zhaoq): Ack with all-0 data now. will change to some - // meaningful content when this is actually in use. - t.framer.writePing(true, i.ack, [8]byte{}) + t.framer.writePing(true, i.ack, i.data) default: grpclog.Printf("transport: http2Server.controller got unexpected item type %v\n", i) } @@ -660,9 +660,9 @@ func (t *http2Server) Close() (err error) { t.mu.Unlock() close(t.shutdownChan) err = t.conn.Close() - // Notify all active streams. + // Cancel all active streams. for _, s := range streams { - s.write(recvMsg{err: ErrConnClosing}) + s.cancel() } return } @@ -684,9 +684,8 @@ func (t *http2Server) closeStream(s *Stream) { s.state = streamDone s.mu.Unlock() // In case stream sending and receiving are invoked in separate - // goroutines (e.g., bi-directional streaming), the caller needs - // to call cancel on the stream to interrupt the blocking on - // other goroutines. + // goroutines (e.g., bi-directional streaming), cancel needs to be + // called to interrupt the potential blocking on other goroutines. s.cancel() }