commit 6138d366d1f73d71e840c47d81649c5ec31e36ca Author: overweight <5324761+overweight@user.noreply.gitee.com> Date: Mon Sep 30 10:37:25 2019 -0400 Package init diff --git a/VERSION-openeuler b/VERSION-openeuler new file mode 100644 index 0000000..32c6fec --- /dev/null +++ b/VERSION-openeuler @@ -0,0 +1 @@ +18.09.0.100 diff --git a/apply-patches b/apply-patches new file mode 100755 index 0000000..d398bf2 --- /dev/null +++ b/apply-patches @@ -0,0 +1,44 @@ +#!/bin/bash + +# Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. +# Description: This shell script is used to apply patches for the project +# Author: jingrui@huawei.com +# Create: 2019-03-02 + +set -ex + +pkg=docker-ce-18.09.0 +cwd=$PWD +src=$cwd/$pkg + +tar -xzvf $pkg.tar.gz + +cd $src +git init +git add . +git config user.name 'build' +git config user.email 'build@obs.com' +git commit -m 'init build' +cd $cwd + +series=$cwd/series.conf +while IPF= read -r line +do + if [[ "$line" =~ ^patch* ]]; then + echo git apply $cwd/$line + cd $src && git apply $cwd/$line + fi +done <"$series" + +cd $cwd + +if [ "$1" == "selinux" ]; then + cp -rf $src/components/engine/contrib/selinux-euleros/docker-engine-selinux/* . +else + cp -rf $src/* . + cp -rf VERSION-openeuler $cwd/components/cli/ + cp -rf VERSION-openeuler $cwd/components/engine/ +fi +mv $src/.git $src/git + + diff --git a/docker-ce-18.09.0.tar.gz b/docker-ce-18.09.0.tar.gz new file mode 100755 index 0000000..c7ee353 Binary files /dev/null and b/docker-ce-18.09.0.tar.gz differ diff --git a/docker-engine-openeuler.spec b/docker-engine-openeuler.spec new file mode 100644 index 0000000..1196743 --- /dev/null +++ b/docker-engine-openeuler.spec @@ -0,0 +1,202 @@ +Name: docker-engine +Version: 18.09.0 +Release: 100 +Summary: The open-source application container engine +Group: Tools/Docker + +License: ASL 2.0 +Source: %{name}.tar.gz + +URL: https://mobyproject.org + +%global is_systemd 1 +%global debug_package %{nil} + +# required packages for build +# most are already in the container (see contrib/builder/rpm/ARCH/generate.sh) +BuildRequires: pkgconfig(systemd) golang >= 1.8.3 go-md2man btrfs-progs-devel device-mapper-devel glibc-static libseccomp-devel +BuildRequires: libselinux-devel libtool-ltdl-devel pkgconfig selinux-policy selinux-policy-devel sqlite-devel systemd-devel +BuildRequires: tar containerd docker-runc docker-proxy + +# required packages on install +Requires: /bin/sh iptables libcgroup tar xz device-mapper-libs >= 1.02.90-1 systemd-units + +# conflicting packages +Provides: docker +Conflicts: docker-io +Conflicts: docker-engine-cs + +%description +Docker is an open source project to build, ship and run any application as a +lightweight container. + +Docker containers are both hardware-agnostic and platform-agnostic. This means +they can run anywhere, from your laptop to the largest EC2 compute instance and +everything in between - and they don't require you to use a particular +language, framework or packaging system. That makes them great building blocks +for deploying and scaling web apps, databases, and backend services without +depending on a particular stack or provider. + +%prep +%autosetup -c -n %{name} + +%build + +./apply-patches + +# build docker engine +WORKDIR=$(pwd) +GITCOMMIT=$(git rev-parse --short HEAD) +export VERSION=$(cat VERSION) +export DOCKER_GITCOMMIT=${GITCOMMIT} +export AUTO_GOPATH=1 +export DOCKER_BUILDTAGS="pkcs11 seccomp selinux" +cd ${WORKDIR}/components/engine +./hack/make.sh dynbinary + +# buid docker cli +cd ${WORKDIR}/components/cli +mkdir -p .gopath/src/github.com/docker +export GOPATH=`pwd`/.gopath +ln -sf `pwd` .gopath/src/github.com/docker/cli +ln -sf ${WORKDIR}/components/engine .gopath/src/github.com/docker/docker +cd .gopath/src/github.com/docker/cli +make dynbinary + +# ./man/md2man-all.sh runs outside the build container (if at all), since we don't have go-md2man here +./man/md2man-all.sh -q + +rm -rf .gopath +cd ${WORKDIR} + +%check +./components/engine/bundles/dynbinary-daemon/dockerd -v +./components/cli/build/docker -v + +%install +# install binary +install -d $RPM_BUILD_ROOT/%{_bindir} +install -p -m 755 components/engine/bundles/dynbinary-daemon/dockerd $RPM_BUILD_ROOT/%{_bindir}/dockerd + +# install cli +install -p -m 755 components/cli/build/docker $RPM_BUILD_ROOT/%{_bindir}/docker + +# install proxy +install -p -m 755 /usr/bin/docker-proxy $RPM_BUILD_ROOT/%{_bindir}/docker-proxy + +# install containerd +install -p -m 755 /usr/bin/containerd $RPM_BUILD_ROOT/%{_bindir}/containerd +install -p -m 755 /usr/bin/containerd-shim $RPM_BUILD_ROOT/%{_bindir}/containerd-shim + +# install runc +install -p -m 755 /usr/local/bin/runc $RPM_BUILD_ROOT/%{_bindir}/runc + +# install udev rules +install -d $RPM_BUILD_ROOT/%{_sysconfdir}/udev/rules.d +install -p -m 644 components/engine/contrib/udev/80-docker.rules $RPM_BUILD_ROOT/%{_sysconfdir}/udev/rules.d/80-docker.rules + +# add init scripts +install -d $RPM_BUILD_ROOT/etc/sysconfig +install -d $RPM_BUILD_ROOT/%{_initddir} +install -p -m 644 components/engine/contrib/init/sysvinit-redhat/docker.sysconfig $RPM_BUILD_ROOT/etc/sysconfig/docker +install -p -m 644 components/engine/contrib/init/sysvinit-redhat/docker-network $RPM_BUILD_ROOT/etc/sysconfig/docker-network +install -p -m 644 components/engine/contrib/init/sysvinit-redhat/docker-storage $RPM_BUILD_ROOT/etc/sysconfig/docker-storage + +install -d $RPM_BUILD_ROOT/%{_unitdir} +install -p -m 644 components/engine/contrib/init/systemd/docker.service $RPM_BUILD_ROOT/%{_unitdir}/docker.service +# add bash, zsh, and fish completions +install -d $RPM_BUILD_ROOT/usr/share/bash-completion/completions +install -d $RPM_BUILD_ROOT/usr/share/zsh/vendor-completions +install -d $RPM_BUILD_ROOT/usr/share/fish/vendor_completions.d +install -p -m 644 components/cli/contrib/completion/bash/docker $RPM_BUILD_ROOT/usr/share/bash-completion/completions/docker +install -p -m 644 components/cli/contrib/completion/zsh/_docker $RPM_BUILD_ROOT/usr/share/zsh/vendor-completions/_docker +install -p -m 644 components/cli/contrib/completion/fish/docker.fish $RPM_BUILD_ROOT/usr/share/fish/vendor_completions.d/docker.fish + +# install manpages +install -d %{buildroot}%{_mandir}/man1 +install -p -m 644 components/cli/man/man1/*.1 $RPM_BUILD_ROOT/%{_mandir}/man1 +install -d %{buildroot}%{_mandir}/man5 +install -p -m 644 components/cli/man/man5/*.5 $RPM_BUILD_ROOT/%{_mandir}/man5 +install -d %{buildroot}%{_mandir}/man8 +install -p -m 644 components/cli/man/man8/*.8 $RPM_BUILD_ROOT/%{_mandir}/man8 + +# add vimfiles +install -d $RPM_BUILD_ROOT/usr/share/vim/vimfiles/doc +install -d $RPM_BUILD_ROOT/usr/share/vim/vimfiles/ftdetect +install -d $RPM_BUILD_ROOT/usr/share/vim/vimfiles/syntax +install -p -m 644 components/engine/contrib/syntax/vim/doc/dockerfile.txt $RPM_BUILD_ROOT/usr/share/vim/vimfiles/doc/dockerfile.txt +install -p -m 644 components/engine/contrib/syntax/vim/ftdetect/dockerfile.vim $RPM_BUILD_ROOT/usr/share/vim/vimfiles/ftdetect/dockerfile.vim +install -p -m 644 components/engine/contrib/syntax/vim/syntax/dockerfile.vim $RPM_BUILD_ROOT/usr/share/vim/vimfiles/syntax/dockerfile.vim + +# add nano +install -d $RPM_BUILD_ROOT/usr/share/nano +install -p -m 644 components/engine/contrib/syntax/nano/Dockerfile.nanorc $RPM_BUILD_ROOT/usr/share/nano/Dockerfile.nanorc + +# list files owned by the package here +%files +%doc components/engine/AUTHORS components/engine/CHANGELOG.md components/engine/CONTRIBUTING.md components/engine/LICENSE components/engine/MAINTAINERS components/engine/NOTICE components/engine/README.md +/%{_bindir}/docker +/%{_bindir}/dockerd +/%{_bindir}/containerd +/%{_bindir}/docker-proxy +/%{_bindir}/containerd-shim +/%{_bindir}/runc +/%{_sysconfdir}/udev/rules.d/80-docker.rules +%if 0%{?is_systemd} +/%{_unitdir}/docker.service +%else +/%{_initddir}/docker +%endif +/usr/share/bash-completion/completions/docker +/usr/share/zsh/vendor-completions/_docker +/usr/share/fish/vendor_completions.d/docker.fish +%doc +/%{_mandir}/man1/* +/%{_mandir}/man5/* +/%{_mandir}/man8/* + +%config(noreplace,missingok) /etc/sysconfig/docker +%config(noreplace,missingok) /etc/sysconfig/docker-storage +%config(noreplace,missingok) /etc/sysconfig/docker-network + +/usr/share/vim/vimfiles/doc/dockerfile.txt +/usr/share/vim/vimfiles/ftdetect/dockerfile.vim +/usr/share/vim/vimfiles/syntax/dockerfile.vim +/usr/share/nano/Dockerfile.nanorc + +%post +if ! getent group docker > /dev/null; then + groupadd --system docker +fi +%if 0%{?is_systemd} +#%systemd_post docker +systemctl enable docker +systemctl start docker +%else +# This adds the proper /etc/rc*.d links for the script +/sbin/chkconfig --add docker +%endif +if ! getent group docker > /dev/null; then + groupadd --system docker +fi + +%preun +%if 0%{?is_systemd} +%systemd_preun docker +%else +if [ $1 -eq 0 ] ; then + /sbin/service docker stop >/dev/null 2>&1 + /sbin/chkconfig --del docker +fi +%endif + +%postun +%if 0%{?is_systemd} +%systemd_postun_with_restart docker +%else +if [ "$1" -ge "1" ] ; then + /sbin/service docker condrestart >/dev/null 2>&1 || : +fi +%endif + +%changelog diff --git a/patch/0001-pause-move-pause-function-to-docker.patch b/patch/0001-pause-move-pause-function-to-docker.patch new file mode 100644 index 0000000..6702df8 --- /dev/null +++ b/patch/0001-pause-move-pause-function-to-docker.patch @@ -0,0 +1,616 @@ +From 7e41c5cf67a5deaa542d3907a257adf6ae6c976b Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Sat, 19 Jan 2019 11:07:09 +0800 +Subject: [PATCH 001/111] pause: move pause function to docker + +reason: The origin pause function has a long callstack from +docker->containerd->runc,it is waste a lot of time. +Now we move this function to docker, docker will update freeze +cgroup directly. + +Change-Id: I8c26d5b4ed71fb30563db0d4e77167b5b68ccad9 +Signed-off-by: Wentao Zhang +Signed-off-by: zhangyu235 +Signed-off-by: lujingxiao +--- + components/engine/container/container.go | 1 + + components/engine/daemon/daemon.go | 53 ++++++ + components/engine/daemon/freezer/cgroup_fs.go | 94 +++++++++ + .../engine/daemon/freezer/cgroup_systemd.go | 59 ++++++ + components/engine/daemon/freezer/freezer.go | 179 ++++++++++++++++++ + components/engine/daemon/oci_linux.go | 1 + + components/engine/daemon/pause.go | 19 +- + components/engine/daemon/unpause.go | 17 +- + .../engine/libcontainerd/utils_linux.go | 25 +++ + .../engine/libcontainerd/utils_windows.go | 7 +- + 10 files changed, 449 insertions(+), 6 deletions(-) + create mode 100644 components/engine/daemon/freezer/cgroup_fs.go + create mode 100644 components/engine/daemon/freezer/cgroup_systemd.go + create mode 100644 components/engine/daemon/freezer/freezer.go + create mode 100644 components/engine/libcontainerd/utils_linux.go + +diff --git a/components/engine/container/container.go b/components/engine/container/container.go +index 6a5907c34b..f74676f7ee 100644 +--- a/components/engine/container/container.go ++++ b/components/engine/container/container.go +@@ -106,6 +106,7 @@ type Container struct { + // Fields here are specific to Windows + NetworkSharedContainerID string `json:"-"` + SharedEndpointList []string `json:"-"` ++ CgroupParent string + } + + // NewBaseContainer creates a new container with its +diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go +index a307863017..8d6b4d8546 100644 +--- a/components/engine/daemon/daemon.go ++++ b/components/engine/daemon/daemon.go +@@ -35,6 +35,7 @@ import ( + "github.com/docker/docker/daemon/discovery" + "github.com/docker/docker/daemon/events" + "github.com/docker/docker/daemon/exec" ++ "github.com/docker/docker/daemon/freezer" + "github.com/docker/docker/daemon/images" + "github.com/docker/docker/daemon/logger" + "github.com/docker/docker/daemon/network" +@@ -197,6 +198,53 @@ func (daemon *Daemon) NewResolveOptionsFunc() resolver.ResolveOptionsFunc { + } + } + ++func (daemon *Daemon) updatePauseStatus(c *container.Container) error { ++ if !daemon.IsNativeRuntime(c.HostConfig.Runtime) { ++ return nil ++ } ++ ++ // update docker pause status. ++ // for old container, CgroupParent may be empty. ++ if c.CgroupParent == "" { ++ spec, err := libcontainerd.LoadContainerSpec(filepath.Join(daemon.configStore.ExecRoot, "libcontainerd"), c.ID) ++ if err != nil { ++ return err ++ } ++ c.CgroupParent = spec.Linux.CgroupsPath ++ } ++ ++ if !c.IsRunning() { ++ c.Paused = false ++ return nil ++ } ++ ++ useSystemd := UsingSystemd(daemon.configStore) ++ freeze, err := freezer.New(c.ID, c.CgroupParent, useSystemd) ++ if err != nil { ++ return err ++ } ++ ++ paused, err := freeze.IsPaused() ++ if err != nil { ++ return err ++ } ++ c.Paused = paused ++ return nil ++} ++ ++func (daemon *Daemon) IsNativeRuntime(runtime string) bool { ++ // For the containers which created by old docker (do not support multi-runtime) ++ // c.HostConfig.Runtime may be empty. just use the default runtime. ++ if runtime == "" { ++ runtime = daemon.configStore.GetDefaultRuntimeName() ++ } ++ rt := daemon.configStore.GetRuntime(runtime) ++ if rt != nil && filepath.Base(rt.Path) == DefaultRuntimeBinary { ++ return true ++ } ++ return false ++} ++ + func (daemon *Daemon) restore() error { + containers := make(map[string]*container.Container) + +@@ -244,6 +292,11 @@ func (daemon *Daemon) restore() error { + delete(containers, id) + continue + } ++ ++ if err := daemon.updatePauseStatus(c); err != nil { ++ logrus.Errorf("Failed to update pause status for container %s: %s", c.ID, err) ++ } ++ + if err := daemon.Register(c); err != nil { + logrus.Errorf("Failed to register container %s: %s", c.ID, err) + delete(containers, id) +diff --git a/components/engine/daemon/freezer/cgroup_fs.go b/components/engine/daemon/freezer/cgroup_fs.go +new file mode 100644 +index 0000000000..5822c3a659 +--- /dev/null ++++ b/components/engine/daemon/freezer/cgroup_fs.go +@@ -0,0 +1,94 @@ ++package freezer ++ ++import ( ++ "fmt" ++ "os" ++ "path/filepath" ++ "sync" ++ ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++ "github.com/opencontainers/runc/libcontainer/configs" ++ "github.com/opencontainers/runc/libcontainer/utils" ++) ++ ++// The absolute path to the root of the cgroup hierarchies. ++var cgroupRootLock sync.Mutex ++var cgroupRoot string ++ ++func fsCgroupPath(subsystem string, c *configs.Cgroup) (string, error) { ++ rawRoot, err := getCgroupRoot() ++ if err != nil { ++ return "", err ++ } ++ ++ if (c.Name != "" || c.Parent != "") && c.Path != "" { ++ return "", fmt.Errorf("cgroup: either Path or Name and Parent should be used") ++ } ++ ++ // XXX: Do not remove this code. Path safety is important! -- cyphar ++ cgPath := utils.CleanPath(c.Path) ++ cgParent := utils.CleanPath(c.Parent) ++ cgName := utils.CleanPath(c.Name) ++ ++ innerPath := cgPath ++ if innerPath == "" { ++ innerPath = filepath.Join(cgParent, cgName) ++ } ++ ++ mnt, root, err := cgroups.FindCgroupMountpointAndRoot(subsystem) ++ // If we didn't mount the subsystem, there is no point we make the path. ++ if err != nil { ++ return "", err ++ } ++ ++ // If the cgroup name/path is absolute do not look relative to the cgroup of the init process. ++ if filepath.IsAbs(innerPath) { ++ // Sometimes subsystems can be mounted together as 'cpu,cpuacct'. ++ return filepath.Join(rawRoot, filepath.Base(mnt), innerPath), nil ++ } ++ ++ parentPath, err := parentPath(subsystem, mnt, root) ++ if err != nil { ++ return "", err ++ } ++ ++ return filepath.Join(parentPath, innerPath), nil ++} ++ ++func parentPath(subsystem, mountpoint, root string) (string, error) { ++ // Use GetThisCgroupDir instead of GetInitCgroupDir, because the creating ++ // process could in container and shared pid namespace with host, and ++ // /proc/1/cgroup could point to whole other world of cgroups. ++ initPath, err := cgroups.GetOwnCgroup(subsystem) ++ if err != nil { ++ return "", err ++ } ++ // This is needed for nested containers, because in /proc/self/cgroup we ++ // see pathes from host, which don't exist in container. ++ relDir, err := filepath.Rel(root, initPath) ++ if err != nil { ++ return "", err ++ } ++ return filepath.Join(mountpoint, relDir), nil ++} ++ ++func getCgroupRoot() (string, error) { ++ cgroupRootLock.Lock() ++ defer cgroupRootLock.Unlock() ++ ++ if cgroupRoot != "" { ++ return cgroupRoot, nil ++ } ++ ++ root, err := cgroups.FindCgroupMountpointDir() ++ if err != nil { ++ return "", err ++ } ++ ++ if _, err := os.Stat(root); err != nil { ++ return "", err ++ } ++ ++ cgroupRoot = root ++ return cgroupRoot, nil ++} +diff --git a/components/engine/daemon/freezer/cgroup_systemd.go b/components/engine/daemon/freezer/cgroup_systemd.go +new file mode 100644 +index 0000000000..4a05d04910 +--- /dev/null ++++ b/components/engine/daemon/freezer/cgroup_systemd.go +@@ -0,0 +1,59 @@ ++package freezer ++ ++import ( ++ "fmt" ++ "path/filepath" ++ "strings" ++ ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++ "github.com/opencontainers/runc/libcontainer/cgroups/systemd" ++ "github.com/opencontainers/runc/libcontainer/configs" ++) ++ ++var ( ++ systemdEnabledChecked = false ++ systemdEnabled = false ++) ++ ++func SystemdEnabled() bool { ++ if systemdEnabledChecked { ++ return systemdEnabled ++ } ++ systemdEnabledChecked = true ++ systemdEnabled = systemd.UseSystemd() ++ return systemdEnabled ++} ++ ++func systemdCgroupPath(subsystem string, c *configs.Cgroup) (string, error) { ++ mountpoint, err := cgroups.FindCgroupMountpoint(subsystem) ++ if err != nil { ++ return "", err ++ } ++ ++ initPath, err := cgroups.GetInitCgroup(subsystem) ++ if err != nil { ++ return "", err ++ } ++ // if pid 1 is systemd 226 or later, it will be in init.scope, not the root ++ initPath = strings.TrimSuffix(filepath.Clean(initPath), "init.scope") ++ ++ slice := "system.slice" ++ if c.Parent != "" { ++ slice = c.Parent ++ } ++ ++ slice, err = systemd.ExpandSlice(slice) ++ if err != nil { ++ return "", err ++ } ++ ++ return filepath.Join(mountpoint, initPath, slice, getUnitName(c)), nil ++} ++ ++func getUnitName(c *configs.Cgroup) string { ++ // by default, we create a scope unless the user explicitly asks for a slice. ++ if !strings.HasSuffix(c.Name, ".slice") { ++ return fmt.Sprintf("%s-%s.scope", c.ScopePrefix, c.Name) ++ } ++ return c.Name ++} +diff --git a/components/engine/daemon/freezer/freezer.go b/components/engine/daemon/freezer/freezer.go +new file mode 100644 +index 0000000000..774f5c21ed +--- /dev/null ++++ b/components/engine/daemon/freezer/freezer.go +@@ -0,0 +1,179 @@ ++package freezer ++ ++import ( ++ "bytes" ++ "fmt" ++ "io/ioutil" ++ "os" ++ "path/filepath" ++ "strings" ++ "sync" ++ "time" ++ ++ "github.com/opencontainers/runc/libcontainer/configs" ++ "github.com/opencontainers/runc/libcontainer/utils" ++) ++ ++// Freezer is the interface which could be used to pause/resume container, ++// And it could be used to get the real container paused status of a container too. ++type Freezer interface { ++ // Pause will set the container to pause state by writing freeze cgroup. ++ Pause() error ++ ++ // Resume will set the container to running state by writing freeze cgroup. ++ Resume() error ++ ++ // IsPaused will return if the container is paused or not by reading cgroup information. ++ IsPaused() (bool, error) ++} ++ ++func writeFile(dir, file, data string) error { ++ // Normally dir should not be empty, one case is that cgroup subsystem ++ // is not mounted, we will get empty dir, and we want it fail here. ++ if dir == "" { ++ return fmt.Errorf("no such directory for %s", file) ++ } ++ if err := ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700); err != nil { ++ return fmt.Errorf("failed to write %v to %v: %v", data, file, err) ++ } ++ return nil ++} ++ ++func readFile(dir, file string) (string, error) { ++ data, err := ioutil.ReadFile(filepath.Join(dir, file)) ++ return string(data), err ++} ++ ++// New will create a Freezer interface for caller ++func New(cid, cgroupParent string, useSystemdCgroup bool) (Freezer, error) { ++ if useSystemdCgroup && !SystemdEnabled() { ++ return nil, fmt.Errorf("systemd cgroup flag passed, but systemd support for managing cgroups is not available") ++ } ++ cgroupConfig, err := prepareCgroupConfig(cid, cgroupParent, useSystemdCgroup) ++ if err != nil { ++ return nil, err ++ } ++ ++ return newFreezer(useSystemdCgroup, cgroupConfig) ++} ++ ++func prepareCgroupConfig(cid, cgroupsPath string, useSystemdCgroup bool) (*configs.Cgroup, error) { ++ var myCgroupPath string ++ c := &configs.Cgroup{ ++ Resources: &configs.Resources{}, ++ } ++ if cgroupsPath != "" { ++ myCgroupPath = utils.CleanPath(cgroupsPath) ++ if useSystemdCgroup { ++ myCgroupPath = cgroupsPath ++ } ++ } ++ ++ if useSystemdCgroup { ++ if myCgroupPath == "" { ++ c.Parent = "system.slice" ++ c.ScopePrefix = "runc" ++ c.Name = cid ++ } else { ++ // Parse the path from expected "slice:prefix:name" ++ // for e.g. "system.slice:docker:1234" ++ parts := strings.Split(myCgroupPath, ":") ++ if len(parts) != 3 { ++ return nil, fmt.Errorf("expected cgroupsPath to be of format \"slice:prefix:name\" for systemd cgroups") ++ } ++ c.Parent = parts[0] ++ c.ScopePrefix = parts[1] ++ c.Name = parts[2] ++ } ++ } else { ++ if myCgroupPath == "" { ++ c.Name = cid ++ } ++ c.Path = myCgroupPath ++ } ++ return c, nil ++} ++ ++func newFreezer(useSystemdCgroup bool, cgroup *configs.Cgroup) (Freezer, error) { ++ var err error ++ var path string ++ ++ if useSystemdCgroup { ++ path, err = systemdCgroupPath("freezer", cgroup) ++ if err != nil { ++ return nil, err ++ } ++ } else { ++ path, err = fsCgroupPath("freezer", cgroup) ++ if err != nil { ++ return nil, err ++ } ++ } ++ return &freezer{path: path}, nil ++} ++ ++type freezer struct { ++ sync.Mutex ++ path string ++} ++ ++// Pause will set the container to pause state by writing freeze cgroup. ++func (f *freezer) Pause() error { ++ f.Lock() ++ defer f.Unlock() ++ ++ if err := f.updateCgroup(string(configs.Frozen)); err != nil { ++ return err ++ } ++ ++ tasks, err := readFile(f.path, "tasks") ++ if err != nil { ++ return fmt.Errorf("failed to check container cgroup task status: %v", err) ++ } ++ ++ if strings.TrimSpace(tasks) == "" { ++ return fmt.Errorf("error: no tasks running in freeze cgroup") ++ } ++ return nil ++} ++ ++// Resume will set the container to running state by writing freeze cgroup. ++func (f *freezer) Resume() error { ++ f.Lock() ++ defer f.Unlock() ++ return f.updateCgroup(string(configs.Thawed)) ++} ++ ++// IsPaused will return if the container is paused or not by reading cgroup information. ++func (f *freezer) IsPaused() (bool, error) { ++ f.Lock() ++ defer f.Unlock() ++ ++ data, err := readFile(f.path, "freezer.state") ++ if err != nil { ++ // If freezer cgroup is not mounted, the container would just be not paused. ++ if os.IsNotExist(err) { ++ return false, nil ++ } ++ return false, fmt.Errorf("failed to check container status: %v", err) ++ } ++ return bytes.Equal(bytes.TrimSpace([]byte(data)), []byte("FROZEN")), nil ++} ++ ++func (f *freezer) updateCgroup(state string) error { ++ if err := writeFile(f.path, "freezer.state", state); err != nil { ++ return err ++ } ++ ++ for { ++ newState, err := readFile(f.path, "freezer.state") ++ if err != nil { ++ return err ++ } ++ if strings.TrimSpace(newState) == state { ++ break ++ } ++ time.Sleep(1 * time.Millisecond) ++ } ++ return nil ++} +diff --git a/components/engine/daemon/oci_linux.go b/components/engine/daemon/oci_linux.go +index 7611fc054d..864d22fbcb 100644 +--- a/components/engine/daemon/oci_linux.go ++++ b/components/engine/daemon/oci_linux.go +@@ -711,6 +711,7 @@ func (daemon *Daemon) createSpec(c *container.Container) (retSpec *specs.Spec, e + cgroupsPath = filepath.Join(parent, c.ID) + } + s.Linux.CgroupsPath = cgroupsPath ++ c.CgroupParent = cgroupsPath + + if err := setResources(&s, c.HostConfig.Resources); err != nil { + return nil, fmt.Errorf("linux runtime spec resources: %v", err) +diff --git a/components/engine/daemon/pause.go b/components/engine/daemon/pause.go +index be6ec1b92a..972baa961f 100644 +--- a/components/engine/daemon/pause.go ++++ b/components/engine/daemon/pause.go +@@ -5,6 +5,7 @@ import ( + "fmt" + + "github.com/docker/docker/container" ++ "github.com/docker/docker/daemon/freezer" + "github.com/sirupsen/logrus" + ) + +@@ -38,8 +39,22 @@ func (daemon *Daemon) containerPause(container *container.Container) error { + return errContainerIsRestarting(container.ID) + } + +- if err := daemon.containerd.Pause(context.Background(), container.ID); err != nil { +- return fmt.Errorf("Cannot pause container %s: %s", container.ID, err) ++ if daemon.IsNativeRuntime(container.HostConfig.Runtime) { ++ freezer, err := freezer.New(container.ID, container.CgroupParent, UsingSystemd(daemon.configStore)) ++ if err != nil { ++ return fmt.Errorf("Failed to create freezer for container %s: %v", container.ID, err) ++ } ++ ++ if err := freezer.Pause(); err != nil { ++ return fmt.Errorf("Cannot pause container %s: %v", container.ID, err) ++ } ++ ++ container.Paused = true ++ daemon.LogContainerEvent(container, "pause") ++ } else { ++ if err := daemon.containerd.Pause(context.Background(), container.ID); err != nil { ++ return fmt.Errorf("Cannot pause container %s: %s", container.ID, err) ++ } + } + + container.Paused = true +diff --git a/components/engine/daemon/unpause.go b/components/engine/daemon/unpause.go +index 27648ae72e..4a8225258f 100644 +--- a/components/engine/daemon/unpause.go ++++ b/components/engine/daemon/unpause.go +@@ -5,6 +5,7 @@ import ( + "fmt" + + "github.com/docker/docker/container" ++ "github.com/docker/docker/daemon/freezer" + "github.com/sirupsen/logrus" + ) + +@@ -27,8 +28,20 @@ func (daemon *Daemon) containerUnpause(container *container.Container) error { + return fmt.Errorf("Container %s is not paused", container.ID) + } + +- if err := daemon.containerd.Resume(context.Background(), container.ID); err != nil { +- return fmt.Errorf("Cannot unpause container %s: %s", container.ID, err) ++ if daemon.IsNativeRuntime(container.HostConfig.Runtime) { ++ freezer, err := freezer.New(container.ID, container.CgroupParent, UsingSystemd(daemon.configStore)) ++ if err != nil { ++ return fmt.Errorf("Failed to create freezer for container %s: %v", container.ID, err) ++ } ++ if err := freezer.Resume(); err != nil { ++ return fmt.Errorf("Cannot unpause container %s: %s", container.ID, err) ++ } ++ container.Paused = false ++ daemon.LogContainerEvent(container, "unpause") ++ } else { ++ if err := daemon.containerd.Resume(context.Background(), container.ID); err != nil { ++ return fmt.Errorf("Cannot unpause container %s: %s", container.ID, err) ++ } + } + + container.Paused = false +diff --git a/components/engine/libcontainerd/utils_linux.go b/components/engine/libcontainerd/utils_linux.go +new file mode 100644 +index 0000000000..f9b3e64db9 +--- /dev/null ++++ b/components/engine/libcontainerd/utils_linux.go +@@ -0,0 +1,25 @@ ++package libcontainerd ++ ++import ( ++ "encoding/json" ++ "io/ioutil" ++ "path/filepath" ++ ++ "github.com/opencontainers/runtime-spec/specs-go" ++) ++ ++func LoadContainerSpec(stateDir, id string) (*specs.Spec, error) { ++ var spec specs.Spec ++ dir, err := filepath.Abs(stateDir) ++ if err != nil { ++ return nil, err ++ } ++ dt, err := ioutil.ReadFile(filepath.Join(dir, id, "config.json")) ++ if err != nil { ++ return nil, err ++ } ++ if err := json.Unmarshal(dt, &spec); err != nil { ++ return nil, err ++ } ++ return &spec, nil ++} +diff --git a/components/engine/libcontainerd/utils_windows.go b/components/engine/libcontainerd/utils_windows.go +index aabb9aeaaa..a8ba3629a3 100644 +--- a/components/engine/libcontainerd/utils_windows.go ++++ b/components/engine/libcontainerd/utils_windows.go +@@ -1,6 +1,7 @@ + package libcontainerd // import "github.com/docker/docker/libcontainerd" + + import ( ++ "fmt" + "strings" + + opengcs "github.com/Microsoft/opengcs/client" +@@ -19,10 +20,12 @@ func setupEnvironmentVariables(a []string) map[string]string { + return r + } + ++func LoadContainerSpec(stateDir, id string) (*specs.Spec, error) { ++ return nil, fmt.Errorf("not supported") ++} ++ + // Apply for the LCOW option is a no-op. + func (s *LCOWOption) Apply(interface{}) error { +- return nil +-} + + // debugGCS is a dirty hack for debugging for Linux Utility VMs. It simply + // runs a bunch of commands inside the UVM, but seriously aides in advanced debugging. +-- +2.17.1 + diff --git a/patch/0002-pause-docker-pause-set-timeout-30s.patch b/patch/0002-pause-docker-pause-set-timeout-30s.patch new file mode 100644 index 0000000..a961427 --- /dev/null +++ b/patch/0002-pause-docker-pause-set-timeout-30s.patch @@ -0,0 +1,76 @@ +From fb74bc5ce2a510e38b9a8a83d4524876f1881759 Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Sat, 19 Jan 2019 11:11:25 +0800 +Subject: [PATCH 002/111] pause: docker pause set timeout 30s + +reason:fix docker pause in infinite loop case,when the process in D status + libcontainer: cgroups: Write freezer state after every state check + This commit ensures we write the expected freezer cgroup state after + every state check, in case the state check does not give the expected + result. This can happen when a new task is created and prevents the + whole cgroup to be FROZEN, leaving the state into FREEZING instead. + This patch prevents the case of an infinite loop to happen. + Cherry-pick from https://github.com/opencontainers/runc/pull/1610 + +Change-Id: Ib5355b9d928c491e120439780c1f03c18aa68b73 +Signed-off-by: panwenxiang +Signed-off-by: zhangyu235 +Signed-off-by: lujingxiao +--- + components/engine/daemon/freezer/freezer.go | 37 +++++++++++++++------ + 1 file changed, 27 insertions(+), 10 deletions(-) + +diff --git a/components/engine/daemon/freezer/freezer.go b/components/engine/daemon/freezer/freezer.go +index 774f5c21ed..cd8b3513d7 100644 +--- a/components/engine/daemon/freezer/freezer.go ++++ b/components/engine/daemon/freezer/freezer.go +@@ -161,19 +161,36 @@ func (f *freezer) IsPaused() (bool, error) { + } + + func (f *freezer) updateCgroup(state string) error { +- if err := writeFile(f.path, "freezer.state", state); err != nil { +- return err ++ curState, err := readFile(f.path, "freezer.state") ++ if err != nil { ++ return fmt.Errorf("read current state failed for %#v", err) + } ++ curState = strings.TrimSpace(curState) + ++ timeout := time.After(30 * time.Second) ++ tick := time.Tick(1 * time.Millisecond) + for { +- newState, err := readFile(f.path, "freezer.state") +- if err != nil { +- return err +- } +- if strings.TrimSpace(newState) == state { +- break ++ select { ++ case <-timeout: ++ if err := writeFile(f.path, "freezer.state", curState); err != nil { ++ return fmt.Errorf("cannot write %s to freezer for %#v", curState, err) ++ } ++ return fmt.Errorf("update freezer cgroup timeout for 30s") ++ case <-tick: ++ // In case this loop does not exit because it doesn't get the expected ++ // state, let's write again this state, hoping it's going to be properly ++ // set this time. Otherwise, this loop could run infinitely, waiting for ++ // a state change that would never happen. ++ if err := writeFile(f.path, "freezer.state", state); err != nil { ++ return fmt.Errorf("cannot write freezer.state for %#v", err) ++ } ++ newState, err := readFile(f.path, "freezer.state") ++ if err != nil { ++ return err ++ } ++ if strings.TrimSpace(newState) == state { ++ return nil ++ } + } +- time.Sleep(1 * time.Millisecond) + } +- return nil + } +-- +2.17.1 + diff --git a/patch/0003-pause-fix-integration-testing-faile-about-doc.patch b/patch/0003-pause-fix-integration-testing-faile-about-doc.patch new file mode 100644 index 0000000..a3d8bfb --- /dev/null +++ b/patch/0003-pause-fix-integration-testing-faile-about-doc.patch @@ -0,0 +1,50 @@ +From 8a897fcad4bf6d2f4be05bb3075640d65e98ac8e Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Sat, 19 Jan 2019 11:13:24 +0800 +Subject: [PATCH 003/111] pause: fix integration testing faile about + `docker pause` status display + +reason:fix integration testing faile about `docker pause` status display + +Change-Id: I851b29171a33f5eb278800fb0f1e061bebb3745c +Signed-off-by: leizhongkai +Signed-off-by: zhangyu235 +Signed-off-by: lujingxiao +--- + components/engine/daemon/pause.go | 4 ++++ + components/engine/daemon/unpause.go | 4 ++++ + 2 files changed, 8 insertions(+) + +diff --git a/components/engine/daemon/pause.go b/components/engine/daemon/pause.go +index 972baa961f..6f9d8b0f70 100644 +--- a/components/engine/daemon/pause.go ++++ b/components/engine/daemon/pause.go +@@ -50,6 +50,10 @@ func (daemon *Daemon) containerPause(container *container.Container) error { + } + + container.Paused = true ++ daemon.setStateCounter(container) ++ if err := container.CheckpointTo(daemon.containersReplica); err != nil { ++ return err ++ } + daemon.LogContainerEvent(container, "pause") + } else { + if err := daemon.containerd.Pause(context.Background(), container.ID); err != nil { +diff --git a/components/engine/daemon/unpause.go b/components/engine/daemon/unpause.go +index 4a8225258f..290d2b1d0c 100644 +--- a/components/engine/daemon/unpause.go ++++ b/components/engine/daemon/unpause.go +@@ -37,6 +37,10 @@ func (daemon *Daemon) containerUnpause(container *container.Container) error { + return fmt.Errorf("Cannot unpause container %s: %s", container.ID, err) + } + container.Paused = false ++ daemon.setStateCounter(container) ++ if err := container.CheckpointTo(daemon.containersReplica); err != nil { ++ return err ++ } + daemon.LogContainerEvent(container, "unpause") + } else { + if err := daemon.containerd.Resume(context.Background(), container.ID); err != nil { +-- +2.17.1 + diff --git a/patch/0004-prjquota-support-set-filesystem-quot.patch b/patch/0004-prjquota-support-set-filesystem-quot.patch new file mode 100644 index 0000000..5e1fa61 --- /dev/null +++ b/patch/0004-prjquota-support-set-filesystem-quot.patch @@ -0,0 +1,398 @@ +From a8f05692638bf50826ed9533f2a5282e2cde359d Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Sat, 19 Jan 2019 11:13:41 +0800 +Subject: [PATCH 004/111] prjquota: support set filesystem + quota if ext4 support project quota + +reason:Support set filesystem quota if ext4 support project quota + +Change-Id: I99d28f248e758837cbf8b615e673ee7d8e36be7b +Signed-off-by: Lei Jitang +Signed-off-by: zhangyu235 +Signed-off-by: lujingxiao +--- + components/engine/daemon/daemon_unix.go | 19 ++ + .../daemon/graphdriver/overlay2/overlay.go | 10 +- + .../daemon/graphdriver/quota/projectquota.go | 176 +++++++++++++++--- + .../daemon/graphdriver/vfs/quota_linux.go | 13 +- + 4 files changed, 191 insertions(+), 27 deletions(-) + +diff --git a/components/engine/daemon/daemon_unix.go b/components/engine/daemon/daemon_unix.go +index b69eede21c..1b35df4950 100644 +--- a/components/engine/daemon/daemon_unix.go ++++ b/components/engine/daemon/daemon_unix.go +@@ -633,6 +633,25 @@ func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes. + } + } + ++ if hostConfig.StorageOpt != nil && daemon.imageService.GraphDriverForOS(runtime.GOOS) == "overlay2" { ++ _, exist := hostConfig.StorageOpt["size"] ++ if exist { ++ status := daemon.imageService.LayerStoreStatus()[runtime.GOOS] ++ if status[0][0] == "Backing Filesystem" && status[0][1] == "extfs" { ++ if hostConfig.Privileged { ++ warnings = append(warnings, "filesystem quota for overlay2 over ext4 can't take affect with privileged container") ++ } else { ++ for _, cap := range hostConfig.CapAdd { ++ if cap == "SYS_RESOURCE" { ++ warnings = append(warnings, "filesystem quota for overlay2 over ext4 can't take affect with CAP_SYS_RESOURCE") ++ break ++ } ++ } ++ } ++ } ++ } ++ } ++ + return warnings, nil + } + +diff --git a/components/engine/daemon/graphdriver/overlay2/overlay.go b/components/engine/daemon/graphdriver/overlay2/overlay.go +index 6b3236f8f3..36ae182bcd 100644 +--- a/components/engine/daemon/graphdriver/overlay2/overlay.go ++++ b/components/engine/daemon/graphdriver/overlay2/overlay.go +@@ -216,16 +216,16 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap + + d.naiveDiff = graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps) + +- if backingFs == "xfs" { +- // Try to enable project quota support over xfs. +- if d.quotaCtl, err = quota.NewControl(home); err == nil { ++ if backingFs == "xfs" || backingFs == "extfs" { ++ // Try to enable project quota support over xfs and extfs. ++ if d.quotaCtl, err = quota.NewControl(home, backingFs); err == nil { + projectQuotaSupported = true + } else if opts.quota.Size > 0 { + return nil, fmt.Errorf("Storage option overlay2.size not supported. Filesystem does not support Project Quota: %v", err) + } + } else if opts.quota.Size > 0 { + // if xfs is not the backing fs then error out if the storage-opt overlay2.size is used. +- return nil, fmt.Errorf("Storage Option overlay2.size only supported for backingFS XFS. Found %v", backingFs) ++ return nil, fmt.Errorf("Storage Option overlay2.size only supported for backingFS XFS or ext4. Found %v", backingFs) + } + + logger.Debugf("backingFs=%s, projectQuotaSupported=%v", backingFs, projectQuotaSupported) +@@ -341,7 +341,7 @@ func (d *Driver) Cleanup() error { + // file system. + func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error { + if opts != nil && len(opts.StorageOpt) != 0 && !projectQuotaSupported { +- return fmt.Errorf("--storage-opt is supported only for overlay over xfs with 'pquota' mount option") ++ return fmt.Errorf("--storage-opt is supported only for overlay over xfs or ext4 with 'pquota' mount option") + } + + if opts == nil { +diff --git a/components/engine/daemon/graphdriver/quota/projectquota.go b/components/engine/daemon/graphdriver/quota/projectquota.go +index 93e85823af..7d879eb81d 100644 +--- a/components/engine/daemon/graphdriver/quota/projectquota.go ++++ b/components/engine/daemon/graphdriver/quota/projectquota.go +@@ -38,8 +38,8 @@ struct fsxattr { + #ifndef PRJQUOTA + #define PRJQUOTA 2 + #endif +-#ifndef XFS_PROJ_QUOTA +-#define XFS_PROJ_QUOTA 2 ++#ifndef PROJ_QUOTA ++#define PROJ_QUOTA 2 + #endif + #ifndef Q_XSETPQLIM + #define Q_XSETPQLIM QCMD(Q_XSETQLIM, PRJQUOTA) +@@ -49,6 +49,28 @@ struct fsxattr { + #endif + + const int Q_XGETQSTAT_PRJQUOTA = QCMD(Q_XGETQSTAT, PRJQUOTA); ++ ++#ifndef Q_XGETPQSTAT ++#define Q_XGETPQSTAT QCMD(Q_XGETQSTAT, PRJQUOTA) ++#endif ++ ++#ifndef Q_SETPQUOTA ++#define Q_SETPQUOTA (unsigned int)QCMD(Q_SETQUOTA, PRJQUOTA) ++#endif ++ ++#ifndef Q_GETPQUOTA ++#define Q_GETPQUOTA (unsigned int)QCMD(Q_GETQUOTA, PRJQUOTA) ++#endif ++ ++#define PDQ_ACCT_BIT 4 ++#define PDQ_ENFD_BIT 5 ++ ++#ifndef QUOTA_PDQ_ACCT ++#define QUOTA_PDQ_ACCT (1<>C.PDQ_ACCT_BIT + (info.qs_flags&C.QUOTA_PDQ_ENFD)>>C.PDQ_ENFD_BIT), nil ++} ++ ++// GetQuota - get the quota limits of a directory that was configured with SetQuota ++func (q *Control) GetQuota(targetPath string, quota *Quota) error { ++ q.lock.Lock() ++ projectID, ok := q.quotas[targetPath] ++ q.lock.Unlock() ++ if !ok { ++ return fmt.Errorf("quota not found for path : %s", targetPath) ++ } ++ ++ // ++ // get the quota limit for the container's project id ++ // ++ ++ return q.quotaOps.GetProjectQuota(q.backingFsBlockDev, projectID, quota) ++} ++ + // getProjectID - get the project id of path on xfs + func getProjectID(targetPath string) (uint32, error) { + dir, err := openDir(targetPath) +diff --git a/components/engine/daemon/graphdriver/vfs/quota_linux.go b/components/engine/daemon/graphdriver/vfs/quota_linux.go +index 0d5c3a7b98..bb2f571834 100644 +--- a/components/engine/daemon/graphdriver/vfs/quota_linux.go ++++ b/components/engine/daemon/graphdriver/vfs/quota_linux.go +@@ -1,6 +1,7 @@ + package vfs // import "github.com/docker/docker/daemon/graphdriver/vfs" + + import ( ++ "github.com/docker/docker/daemon/graphdriver" + "github.com/docker/docker/daemon/graphdriver/quota" + "github.com/sirupsen/logrus" + ) +@@ -10,7 +11,17 @@ type driverQuota struct { + } + + func setupDriverQuota(driver *Driver) { +- if quotaCtl, err := quota.NewControl(driver.home); err == nil { ++ // Probe fs type before setting quota, now only supports xfs and extfs ++ fsMagic, err := graphdriver.GetFSMagic(driver.home) ++ if err != nil { ++ return ++ } ++ fsName, ok := graphdriver.FsNames[fsMagic] ++ if !ok { ++ return ++ } ++ ++ if quotaCtl, err := quota.NewControl(driver.home, fsName); err == nil { + driver.quotaCtl = quotaCtl + } else if err != quota.ErrQuotaNotSupported { + logrus.Warnf("Unable to setup quota: %v\n", err) +-- +2.17.1 + diff --git a/patch/0005-prjquota-fix-few-overlay2-quota-problems.patch b/patch/0005-prjquota-fix-few-overlay2-quota-problems.patch new file mode 100644 index 0000000..605fb81 --- /dev/null +++ b/patch/0005-prjquota-fix-few-overlay2-quota-problems.patch @@ -0,0 +1,37 @@ +From b5e205b8d1ea0a62ea8847dc5510a10c65a2573e Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Sat, 19 Jan 2019 11:13:57 +0800 +Subject: [PATCH 005/111] prjquota: fix few overlay2 quota problems + +reason:fix few overlay2 quota problems + +Change-Id: Id3a7915747c415d56684c291fb0498d04b762c8c +Signed-off-by: dengguangxing +Signed-off-by: zhangyu235 +Signed-off-by: lujingxiao +--- + components/engine/daemon/graphdriver/overlay2/overlay.go | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/components/engine/daemon/graphdriver/overlay2/overlay.go b/components/engine/daemon/graphdriver/overlay2/overlay.go +index 36ae182bcd..96f44ba9a1 100644 +--- a/components/engine/daemon/graphdriver/overlay2/overlay.go ++++ b/components/engine/daemon/graphdriver/overlay2/overlay.go +@@ -455,6 +455,14 @@ func (d *Driver) parseStorageOpt(storageOpt map[string]string, driver *Driver) e + if err != nil { + return err + } ++ // deal with negative and super large number ++ if size < 0 { ++ return fmt.Errorf("Illegal storage size(%s): numerical result out of range", val) ++ } ++ // for overlay (0-1024) means no limit ++ if size < 1024 && size > 0 { ++ return fmt.Errorf("Illegal storage size:%d, 1024 at least", size) ++ } + driver.options.quota.Size = uint64(size) + default: + return fmt.Errorf("Unknown option %s", key) +-- +2.17.1 + diff --git a/patch/0006-prjquota-overlay2-quota-control-backward-comp.patch b/patch/0006-prjquota-overlay2-quota-control-backward-comp.patch new file mode 100644 index 0000000..3fb5ce9 --- /dev/null +++ b/patch/0006-prjquota-overlay2-quota-control-backward-comp.patch @@ -0,0 +1,53 @@ +From c5a18b46152c4c7016c0c2f0054e276a53f25e1f Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Sat, 19 Jan 2019 11:14:09 +0800 +Subject: [PATCH 006/111] prjquota: overlay2 quota control backward + compability + +reason: In Euleros docker we support default quota control limit +for daemon with commit + docker: Add options to surport default limit for daemon +However in mainstream they have similar commit + 35903110 Add overlay2.size daemon storage-opt +But mainstream uses different api `overlay2.size` compares with +`overlay2.basesize`, so adding this backward compability. + +Change-Id: I36a548bd7f1ce6fab6cad24cfb49faa56f7a1fd1 +Signed-off-by: lujingxiao +--- + components/engine/daemon/graphdriver/overlay2/overlay.go | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/components/engine/daemon/graphdriver/overlay2/overlay.go b/components/engine/daemon/graphdriver/overlay2/overlay.go +index 96f44ba9a1..b969582eb3 100644 +--- a/components/engine/daemon/graphdriver/overlay2/overlay.go ++++ b/components/engine/daemon/graphdriver/overlay2/overlay.go +@@ -248,6 +248,7 @@ func parseOptions(options []string) (*overlayOptions, error) { + return nil, err + } + case "overlay2.size": ++ case "overlay2.basesize": + size, err := units.RAMInBytes(val) + if err != nil { + return nil, err +@@ -394,12 +395,16 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts) (retErr + } + }() + +- if opts != nil && len(opts.StorageOpt) > 0 { ++ if (opts != nil && len(opts.StorageOpt) > 0) || d.options.quota.Size > 0 { + driver := &Driver{} + if err := d.parseStorageOpt(opts.StorageOpt, driver); err != nil { + return err + } + ++ if driver.options.quota.Size == 0 && d.options.quota.Size > 0 { ++ driver.options.quota.Size = d.options.quota.Size ++ } ++ + if driver.options.quota.Size > 0 { + // Set container disk quota limit + if err := d.quotaCtl.SetQuota(dir, driver.options.quota); err != nil { +-- +2.17.1 + diff --git a/patch/0007-filelimit-Add-file-fds-limit.patch b/patch/0007-filelimit-Add-file-fds-limit.patch new file mode 100644 index 0000000..ce38212 --- /dev/null +++ b/patch/0007-filelimit-Add-file-fds-limit.patch @@ -0,0 +1,319 @@ +From 1e26a8c5d18eb93c2786eff9eeede77f3b8162df Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Sat, 19 Jan 2019 11:14:23 +0800 +Subject: [PATCH 007/111] filelimit: Add file fds limit + +Change-Id: I4255fc648ad71dcba78fe38fae9d26454e2e41d8 +Signed-off-by: yangshukui +Signed-off-by: lujingxiao +--- + components/cli/cli/command/container/opts.go | 3 +++ + components/cli/contrib/completion/bash/docker | 1 + + components/cli/contrib/completion/zsh/_docker | 1 + + .../cli/docs/reference/commandline/create.md | 1 + + components/cli/docs/reference/commandline/run.md | 1 + + components/cli/man/docker-run.1.md | 4 ++++ + .../vendor/github.com/docker/docker/api/Checklist | 1 + + .../docker/api/types/container/host_config.go | 1 + + .../engine/api/types/container/host_config.go | 1 + + components/engine/daemon/daemon_unix.go | 6 ++++++ + components/engine/daemon/oci_linux.go | 3 +++ + .../integration-cli/docker_cli_run_unix_test.go | 10 ++++++++++ + .../integration-cli/requirements_unix_test.go | 4 ++++ + components/engine/pkg/sysinfo/sysinfo.go | 6 ++++++ + components/engine/pkg/sysinfo/sysinfo_linux.go | 15 +++++++++++++++ + .../opencontainers/runtime-spec/Checklist | 1 + + .../runtime-spec/specs-go/config.go | 8 ++++++++ + 17 files changed, 67 insertions(+) + create mode 100644 components/cli/vendor/github.com/docker/docker/api/Checklist + create mode 100644 components/engine/vendor/github.com/opencontainers/runtime-spec/Checklist + +diff --git a/components/cli/cli/command/container/opts.go b/components/cli/cli/command/container/opts.go +index 97906b6722..efb28a2cdf 100644 +--- a/components/cli/cli/command/container/opts.go ++++ b/components/cli/cli/command/container/opts.go +@@ -100,6 +100,7 @@ type containerOptions struct { + ipv6Address string + ipcMode string + pidsLimit int64 ++ filesLimit int64 + restartPolicy string + readonlyRootfs bool + loggingDriver string +@@ -271,6 +272,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions { + flags.BoolVar(&copts.oomKillDisable, "oom-kill-disable", false, "Disable OOM Killer") + flags.IntVar(&copts.oomScoreAdj, "oom-score-adj", 0, "Tune host's OOM preferences (-1000 to 1000)") + flags.Int64Var(&copts.pidsLimit, "pids-limit", 0, "Tune container pids limit (set -1 for unlimited)") ++ flags.Int64Var(&copts.filesLimit, "files-limit", 0, "Tune container files limit (set -1 for unlimited)") + + // Low-level execution (cgroups, namespaces, ...) + flags.StringVar(&copts.cgroupParent, "cgroup-parent", "", "Optional parent cgroup for the container") +@@ -531,6 +533,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err + CPURealtimePeriod: copts.cpuRealtimePeriod, + CPURealtimeRuntime: copts.cpuRealtimeRuntime, + PidsLimit: copts.pidsLimit, ++ FilesLimit: copts.filesLimit, + BlkioWeight: copts.blkioWeight, + BlkioWeightDevice: copts.blkioWeightDevice.GetList(), + BlkioDeviceReadBps: copts.deviceReadBps.GetList(), +diff --git a/components/cli/contrib/completion/bash/docker b/components/cli/contrib/completion/bash/docker +index 44ac8f3e0e..2e2c8cb04f 100644 +--- a/components/cli/contrib/completion/bash/docker ++++ b/components/cli/contrib/completion/bash/docker +@@ -1789,6 +1789,7 @@ _docker_container_run_and_create() { + --env -e + --env-file + --expose ++ --files-limit + --group-add + --health-cmd + --health-interval +diff --git a/components/cli/contrib/completion/zsh/_docker b/components/cli/contrib/completion/zsh/_docker +index 94f042204d..cbbdfdb798 100644 +--- a/components/cli/contrib/completion/zsh/_docker ++++ b/components/cli/contrib/completion/zsh/_docker +@@ -621,6 +621,7 @@ __docker_container_subcommand() { + "($help)--entrypoint=[Overwrite the default entrypoint of the image]:entry point: " + "($help)*--env-file=[Read environment variables from a file]:environment file:_files" + "($help)*--expose=[Expose a port from the container without publishing it]: " ++ "($help)--files-limit[Tune container files limit (set -1 for max)]" + "($help)*--group=[Set one or more supplementary user groups for the container]:group:_groups" + "($help -h --hostname)"{-h=,--hostname=}"[Container host name]:hostname:_hosts" + "($help -i --interactive)"{-i,--interactive}"[Keep stdin open even if not attached]" +diff --git a/components/cli/docs/reference/commandline/create.md b/components/cli/docs/reference/commandline/create.md +index d585da40ae..5d888183b3 100644 +--- a/components/cli/docs/reference/commandline/create.md ++++ b/components/cli/docs/reference/commandline/create.md +@@ -57,6 +57,7 @@ Options: + -e, --env value Set environment variables (default []) + --env-file value Read in a file of environment variables (default []) + --expose value Expose a port or a range of ports (default []) ++ --files-limit int Tune container files limit (set -1 for unlimited) + --group-add value Add additional groups to join (default []) + --health-cmd string Command to run to check health + --health-interval duration Time between running the check (ns|us|ms|s|m|h) (default 0s) +diff --git a/components/cli/docs/reference/commandline/run.md b/components/cli/docs/reference/commandline/run.md +index 08b9f18d68..21b4fdf261 100644 +--- a/components/cli/docs/reference/commandline/run.md ++++ b/components/cli/docs/reference/commandline/run.md +@@ -61,6 +61,7 @@ Options: + -e, --env value Set environment variables (default []) + --env-file value Read in a file of environment variables (default []) + --expose value Expose a port or a range of ports (default []) ++ --files-limit int Tune container files limit (set -1 for unlimited) + --group-add value Add additional groups to join (default []) + --health-cmd string Command to run to check health + --health-interval duration Time between running the check (ns|us|ms|s|m|h) (default 0s) +diff --git a/components/cli/man/docker-run.1.md b/components/cli/man/docker-run.1.md +index e03377001d..41f501d5b9 100644 +--- a/components/cli/man/docker-run.1.md ++++ b/components/cli/man/docker-run.1.md +@@ -39,6 +39,7 @@ docker-run - Run a command in a new container + [**--entrypoint**[=*ENTRYPOINT*]] + [**--env-file**[=*[]*]] + [**--expose**[=*[]*]] ++[**--files-limit**[=*FILES_LIMIT*]] + [**--group-add**[=*[]*]] + [**-h**|**--hostname**[=*HOSTNAME*]] + [**--help**] +@@ -315,6 +316,9 @@ that the container listens on the specified network ports at runtime. Docker + uses this information to interconnect containers using links and to set up port + redirection on the host system. + ++**--files-limit**="" ++ Tune the container's files limit. Set `-1` to have max files for the container. ++ + **--group-add**=[] + Add additional groups to run as + +diff --git a/components/cli/vendor/github.com/docker/docker/api/Checklist b/components/cli/vendor/github.com/docker/docker/api/Checklist +new file mode 100644 +index 0000000000..9594f235e4 +--- /dev/null ++++ b/components/cli/vendor/github.com/docker/docker/api/Checklist +@@ -0,0 +1 @@ ++Add FilesLimit to components/cli/vendor/github.com/docker/docker/api/types/container/host_config.go for supporting --files-limit +diff --git a/components/cli/vendor/github.com/docker/docker/api/types/container/host_config.go b/components/cli/vendor/github.com/docker/docker/api/types/container/host_config.go +index 4ef26fa6c8..1565b5e091 100644 +--- a/components/cli/vendor/github.com/docker/docker/api/types/container/host_config.go ++++ b/components/cli/vendor/github.com/docker/docker/api/types/container/host_config.go +@@ -334,6 +334,7 @@ type Resources struct { + MemorySwappiness *int64 // Tuning container memory swappiness behaviour + OomKillDisable *bool // Whether to disable OOM Killer or not + PidsLimit int64 // Setting pids limit for a container ++ FilesLimit int64 // Setting files limit for a container + Ulimits []*units.Ulimit // List of ulimits to be set in the container + + // Applicable to Windows +diff --git a/components/engine/api/types/container/host_config.go b/components/engine/api/types/container/host_config.go +index 4ef26fa6c8..1565b5e091 100644 +--- a/components/engine/api/types/container/host_config.go ++++ b/components/engine/api/types/container/host_config.go +@@ -334,6 +334,7 @@ type Resources struct { + MemorySwappiness *int64 // Tuning container memory swappiness behaviour + OomKillDisable *bool // Whether to disable OOM Killer or not + PidsLimit int64 // Setting pids limit for a container ++ FilesLimit int64 // Setting files limit for a container + Ulimits []*units.Ulimit // List of ulimits to be set in the container + + // Applicable to Windows +diff --git a/components/engine/daemon/daemon_unix.go b/components/engine/daemon/daemon_unix.go +index 1b35df4950..138b8ac544 100644 +--- a/components/engine/daemon/daemon_unix.go ++++ b/components/engine/daemon/daemon_unix.go +@@ -425,6 +425,12 @@ func verifyContainerResources(resources *containertypes.Resources, sysInfo *sysi + resources.PidsLimit = 0 + } + ++ if resources.FilesLimit != 0 && !sysInfo.FilesLimit { ++ warnings = append(warnings, "Your kernel does not support files limit capabilities, files limit discarded.") ++ logrus.Warnf("Your kernel does not support files limit capabilities, files limit discarded.") ++ resources.FilesLimit = 0 ++ } ++ + // cpu subsystem checks and adjustments + if resources.NanoCPUs > 0 && resources.CPUPeriod > 0 { + return warnings, fmt.Errorf("Conflicting options: Nano CPUs and CPU Period cannot both be set") +diff --git a/components/engine/daemon/oci_linux.go b/components/engine/daemon/oci_linux.go +index 864d22fbcb..210d2ad3f6 100644 +--- a/components/engine/daemon/oci_linux.go ++++ b/components/engine/daemon/oci_linux.go +@@ -70,6 +70,9 @@ func setResources(s *specs.Spec, r containertypes.Resources) error { + Pids: &specs.LinuxPids{ + Limit: r.PidsLimit, + }, ++ Files: &specs.Files{ ++ Limit: &r.FilesLimit, ++ }, + } + + if s.Linux.Resources != nil && len(s.Linux.Resources.Devices) > 0 { +diff --git a/components/engine/integration-cli/docker_cli_run_unix_test.go b/components/engine/integration-cli/docker_cli_run_unix_test.go +index 5f782ee530..a618316d4b 100644 +--- a/components/engine/integration-cli/docker_cli_run_unix_test.go ++++ b/components/engine/integration-cli/docker_cli_run_unix_test.go +@@ -1414,6 +1414,16 @@ func (s *DockerSuite) TestRunPIDsLimit(c *check.C) { + c.Assert(out, checker.Equals, "4", check.Commentf("setting the pids limit failed")) + } + ++// TestRunFilesLimit makes sure the files cgroup is set with --files-limit ++func (s *DockerSuite) TestRunFilesLimit(c *check.C) { ++ testRequires(c, filesLimit) ++ file := "/sys/fs/cgroup/files/files.limit" ++ out, _ := dockerCmd(c, "run", "--name", "fileslimit", "--files-limit", "32", "busybox", "cat", file) ++ c.Assert(strings.TrimSpace(out), checker.Equals, "32") ++ out = inspectField(c, "fileslimit", "HostConfig.FilesLimit") ++ c.Assert(out, checker.Equals, "32", check.Commentf("setting the files limit failed")) ++} ++ + func (s *DockerSuite) TestRunPrivilegedAllowedDevices(c *check.C) { + testRequires(c, DaemonIsLinux, NotUserNamespace) + +diff --git a/components/engine/integration-cli/requirements_unix_test.go b/components/engine/integration-cli/requirements_unix_test.go +index 7c594f7db4..873c1fbfe2 100644 +--- a/components/engine/integration-cli/requirements_unix_test.go ++++ b/components/engine/integration-cli/requirements_unix_test.go +@@ -37,6 +37,10 @@ func pidsLimit() bool { + return SysInfo.PidsLimit + } + ++func filesLimit() bool { ++ return SysInfo.FilesLimit ++} ++ + func kernelMemorySupport() bool { + return testEnv.DaemonInfo.KernelMemory + } +diff --git a/components/engine/pkg/sysinfo/sysinfo.go b/components/engine/pkg/sysinfo/sysinfo.go +index 0f327d5068..5d9320218c 100644 +--- a/components/engine/pkg/sysinfo/sysinfo.go ++++ b/components/engine/pkg/sysinfo/sysinfo.go +@@ -15,6 +15,7 @@ type SysInfo struct { + cgroupBlkioInfo + cgroupCpusetInfo + cgroupPids ++ cgroupFiles + + // Whether IPv4 forwarding is supported or not, if this was disabled, networking will not work + IPv4ForwardingDisabled bool +@@ -102,6 +103,11 @@ type cgroupPids struct { + PidsLimit bool + } + ++type cgroupFiles struct { ++ // Whether Files Limit is supported or not ++ FilesLimit bool ++} ++ + // IsCpusetCpusAvailable returns `true` if the provided string set is contained + // in cgroup's cpuset.cpus set, `false` otherwise. + // If error is not nil a parsing error occurred. +diff --git a/components/engine/pkg/sysinfo/sysinfo_linux.go b/components/engine/pkg/sysinfo/sysinfo_linux.go +index dde5be19bc..c0bf280412 100644 +--- a/components/engine/pkg/sysinfo/sysinfo_linux.go ++++ b/components/engine/pkg/sysinfo/sysinfo_linux.go +@@ -40,6 +40,7 @@ func New(quiet bool) *SysInfo { + sysInfo.cgroupBlkioInfo = checkCgroupBlkioInfo(cgMounts, quiet) + sysInfo.cgroupCpusetInfo = checkCgroupCpusetInfo(cgMounts, quiet) + sysInfo.cgroupPids = checkCgroupPids(quiet) ++ sysInfo.cgroupFiles = checkCgroupFiles(quiet) + } + + _, ok := cgMounts["devices"] +@@ -240,6 +241,20 @@ func checkCgroupPids(quiet bool) cgroupPids { + } + } + ++// checkCgroupPids reads the files information from the pids cgroup mount point. ++func checkCgroupFiles(quiet bool) cgroupFiles { ++ _, err := cgroups.FindCgroupMountpoint("files") ++ if err != nil { ++ if !quiet { ++ logrus.Warn(err) ++ } ++ return cgroupFiles{} ++ } ++ return cgroupFiles{ ++ FilesLimit: true, ++ } ++} ++ + func cgroupEnabled(mountPoint, name string) bool { + _, err := os.Stat(path.Join(mountPoint, name)) + return err == nil +diff --git a/components/engine/vendor/github.com/opencontainers/runtime-spec/Checklist b/components/engine/vendor/github.com/opencontainers/runtime-spec/Checklist +new file mode 100644 +index 0000000000..5b7ba2fab9 +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runtime-spec/Checklist +@@ -0,0 +1 @@ ++Add struct LinuxFiles to components/engine/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go for supporting --files-limit +diff --git a/components/engine/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go b/components/engine/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go +index f32698cab2..46049b3bfa 100644 +--- a/components/engine/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go ++++ b/components/engine/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go +@@ -314,6 +314,12 @@ type LinuxPids struct { + Limit int64 `json:"limit"` + } + ++// Files for Linux cgroup 'files' resource management (https://lwn.net/Articles/604129/) ++type Files struct { ++ // Maximum number of open files. Default is "no limit". ++ Limit *int64 `json:"limit,omitempty"` ++} ++ + // LinuxNetwork identification and priority configuration + type LinuxNetwork struct { + // Set class identifier for container's network packets +@@ -340,6 +346,8 @@ type LinuxResources struct { + CPU *LinuxCPU `json:"cpu,omitempty"` + // Task resource restriction configuration. + Pids *LinuxPids `json:"pids,omitempty"` ++ // Files resource restriction configuration. ++ Files *Files `json:"files,omitempty"` + // BlockIO restriction configuration + BlockIO *LinuxBlockIO `json:"blockIO,omitempty"` + // Hugetlb limit (in bytes) +-- +2.17.1 + diff --git a/patch/0008-filelimit-Ignore-host-kernel-whether-suport-f.patch b/patch/0008-filelimit-Ignore-host-kernel-whether-suport-f.patch new file mode 100644 index 0000000..db1f0d6 --- /dev/null +++ b/patch/0008-filelimit-Ignore-host-kernel-whether-suport-f.patch @@ -0,0 +1,104 @@ +From 7a965f7491e5f60e27ce1f6c052aa139f80e0744 Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Sat, 19 Jan 2019 11:14:42 +0800 +Subject: [PATCH 008/111] filelimit: Ignore host kernel whether suport + files limit when run a secure container + +reason:Ignore host kernel whether suport files limit when run a secure +container. + +Change-Id: Iabd2c54492465a8df53375206d5ff600d9da7f6e +Signed-off-by: yangshukui +Signed-off-by: lujingxiao +--- + components/engine/daemon/container.go | 2 +- + components/engine/daemon/daemon_unix.go | 15 +++++++++------ + components/engine/daemon/daemon_windows.go | 6 +++--- + 3 files changed, 13 insertions(+), 10 deletions(-) + +diff --git a/components/engine/daemon/container.go b/components/engine/daemon/container.go +index c8e2053970..bd96de2571 100644 +--- a/components/engine/daemon/container.go ++++ b/components/engine/daemon/container.go +@@ -348,7 +348,7 @@ func (daemon *Daemon) verifyContainerSettings(platform string, hostConfig *conta + warnings []string + ) + // Now do platform-specific verification +- if warnings, err = verifyPlatformContainerSettings(daemon, hostConfig, config, update); err != nil { ++ if warnings, err = daemon.verifyPlatformContainerSettings(hostConfig, config, update); err != nil { + return warnings, err + } + if hostConfig.NetworkMode.IsHost() && len(hostConfig.PortBindings) > 0 { +diff --git a/components/engine/daemon/daemon_unix.go b/components/engine/daemon/daemon_unix.go +index 138b8ac544..f4b75055f5 100644 +--- a/components/engine/daemon/daemon_unix.go ++++ b/components/engine/daemon/daemon_unix.go +@@ -350,8 +350,9 @@ func adaptSharedNamespaceContainer(daemon containerGetter, hostConfig *container + } + } + +-func verifyContainerResources(resources *containertypes.Resources, sysInfo *sysinfo.SysInfo, update bool) ([]string, error) { ++func (daemon *Daemon) verifyContainerResources(hostConfig *containertypes.HostConfig, sysInfo *sysinfo.SysInfo, update bool) ([]string, error) { + warnings := []string{} ++ resources := &hostConfig.Resources + fixMemorySwappiness(resources) + + // memory subsystem checks and adjustments +@@ -426,9 +427,11 @@ func verifyContainerResources(resources *containertypes.Resources, sysInfo *sysi + } + + if resources.FilesLimit != 0 && !sysInfo.FilesLimit { +- warnings = append(warnings, "Your kernel does not support files limit capabilities, files limit discarded.") +- logrus.Warnf("Your kernel does not support files limit capabilities, files limit discarded.") +- resources.FilesLimit = 0 ++ if daemon.IsNativeRuntime(hostConfig.Runtime) { ++ warnings = append(warnings, "Your kernel does not support files limit capabilities, files limit discarded.") ++ logrus.Warnf("Your kernel does not support files limit capabilities, files limit discarded.") ++ resources.FilesLimit = 0 ++ } + } + + // cpu subsystem checks and adjustments +@@ -580,11 +583,11 @@ func UsingSystemd(config *config.Config) bool { + + // verifyPlatformContainerSettings performs platform-specific validation of the + // hostconfig and config structures. +-func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes.HostConfig, config *containertypes.Config, update bool) ([]string, error) { ++func (daemon *Daemon) verifyPlatformContainerSettings(hostConfig *containertypes.HostConfig, config *containertypes.Config, update bool) ([]string, error) { + var warnings []string + sysInfo := sysinfo.New(true) + +- w, err := verifyContainerResources(&hostConfig.Resources, sysInfo, update) ++ w, err := daemon.verifyContainerResources(hostConfig, sysInfo, update) + + // no matter err is nil or not, w could have data in itself. + warnings = append(warnings, w...) +diff --git a/components/engine/daemon/daemon_windows.go b/components/engine/daemon/daemon_windows.go +index 04d3de9924..4812236bc2 100644 +--- a/components/engine/daemon/daemon_windows.go ++++ b/components/engine/daemon/daemon_windows.go +@@ -75,7 +75,7 @@ func (daemon *Daemon) adaptContainerSettings(hostConfig *containertypes.HostConf + return nil + } + +-func verifyContainerResources(resources *containertypes.Resources, isHyperv bool) ([]string, error) { ++func (daemon *Daemon) verifyContainerResources(resources *containertypes.Resources, isHyperv bool) ([]string, error) { + warnings := []string{} + fixMemorySwappiness(resources) + if !isHyperv { +@@ -191,10 +191,10 @@ func verifyContainerResources(resources *containertypes.Resources, isHyperv bool + + // verifyPlatformContainerSettings performs platform-specific validation of the + // hostconfig and config structures. +-func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes.HostConfig, config *containertypes.Config, update bool) ([]string, error) { ++func (daemon *Daemon) verifyPlatformContainerSettings(hostConfig *containertypes.HostConfig, config *containertypes.Config, update bool) ([]string, error) { + warnings := []string{} + +- hyperv := daemon.runAsHyperVContainer(hostConfig) ++ hyperv := daemon.daemon.runAsHyperVContainer(hostConfig) + if !hyperv && system.IsWindowsClient() && !system.IsIoTCore() { + // @engine maintainers. This block should not be removed. It partially enforces licensing + // restrictions on Windows. Ping @jhowardmsft if there are concerns or PRs to change this. +-- +2.17.1 + diff --git a/patch/0009-healthycheck-add-healthycheck-in-_ping-add-se.patch b/patch/0009-healthycheck-add-healthycheck-in-_ping-add-se.patch new file mode 100644 index 0000000..a07f0cb --- /dev/null +++ b/patch/0009-healthycheck-add-healthycheck-in-_ping-add-se.patch @@ -0,0 +1,128 @@ +From c03a609c9bb837d8f361888460c7a605dc1219d6 Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Sat, 19 Jan 2019 11:15:11 +0800 +Subject: [PATCH 009/111] healthycheck: add healthycheck in _ping & + add semaphore set info in docker info + +reason: add healthycheck in _ping & add semaphore set info in docker info + +Change-Id: I90b3def5fa8bcf0e21090471c43c6309e58c26aa +Signed-off-by: liruilin4 +Signed-off-by: zhangyu235 +Signed-off-by: lujingxiao +--- + .../api/server/router/system/backend.go | 1 + + .../api/server/router/system/system_routes.go | 2 ++ + .../daemon/graphdriver/devmapper/driver.go | 7 ++++++ + components/engine/daemon/info.go | 10 ++++++++ + components/engine/utils/utils.go | 23 +++++++++++++++++++ + 5 files changed, 43 insertions(+) + create mode 100644 components/engine/utils/utils.go + +diff --git a/components/engine/api/server/router/system/backend.go b/components/engine/api/server/router/system/backend.go +index f5d2d98101..2ba5ab774b 100644 +--- a/components/engine/api/server/router/system/backend.go ++++ b/components/engine/api/server/router/system/backend.go +@@ -19,6 +19,7 @@ type Backend interface { + SubscribeToEvents(since, until time.Time, ef filters.Args) ([]events.Message, chan interface{}) + UnsubscribeFromEvents(chan interface{}) + AuthenticateToRegistry(ctx context.Context, authConfig *types.AuthConfig) (string, string, error) ++ HealthyCheck() + } + + // ClusterBackend is all the methods that need to be implemented +diff --git a/components/engine/api/server/router/system/system_routes.go b/components/engine/api/server/router/system/system_routes.go +index a2ff692de3..f235acc657 100644 +--- a/components/engine/api/server/router/system/system_routes.go ++++ b/components/engine/api/server/router/system/system_routes.go +@@ -31,6 +31,8 @@ func (s *systemRouter) pingHandler(ctx context.Context, w http.ResponseWriter, r + if bv := builderVersion; bv != "" { + w.Header().Set("Builder-Version", string(bv)) + } ++ ++ s.backend.HealthyCheck() + _, err := w.Write([]byte{'O', 'K'}) + return err + } +diff --git a/components/engine/daemon/graphdriver/devmapper/driver.go b/components/engine/daemon/graphdriver/devmapper/driver.go +index 899b1f8670..623843f852 100644 +--- a/components/engine/daemon/graphdriver/devmapper/driver.go ++++ b/components/engine/daemon/graphdriver/devmapper/driver.go +@@ -15,6 +15,7 @@ import ( + "github.com/docker/docker/pkg/idtools" + "github.com/docker/docker/pkg/locker" + "github.com/docker/docker/pkg/mount" ++ "github.com/docker/docker/utils" + "github.com/docker/go-units" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +@@ -101,6 +102,12 @@ func (d *Driver) Status() [][2]string { + if vStr, err := devicemapper.GetLibraryVersion(); err == nil { + status = append(status, [2]string{"Library Version", vStr}) + } ++ usz, mni, err := utils.CheckSemSetStat() ++ status = append(status, [2]string{"Semaphore Set Used", fmt.Sprintf("%d", usz)}) ++ status = append(status, [2]string{"Semaphore Set Total", fmt.Sprintf("%d", mni)}) ++ if err != nil { ++ status = append(status, [2]string{"WARNING", fmt.Sprintf("%v", err)}) ++ } + return status + } + +diff --git a/components/engine/daemon/info.go b/components/engine/daemon/info.go +index bf84342b54..262719d9d1 100644 +--- a/components/engine/daemon/info.go ++++ b/components/engine/daemon/info.go +@@ -20,6 +20,7 @@ import ( + "github.com/docker/docker/pkg/sysinfo" + "github.com/docker/docker/pkg/system" + "github.com/docker/docker/registry" ++ "github.com/docker/docker/utils" + "github.com/docker/go-connections/sockets" + "github.com/sirupsen/logrus" + ) +@@ -256,3 +257,12 @@ func maskCredentials(rawURL string) string { + maskedURL := parsedURL.String() + return maskedURL + } ++ ++func (daemon *Daemon) HealthyCheck() { ++ if daemon.imageService.GraphDriverForOS(runtime.GOOS) == "devicemapper" { ++ _, _, err := utils.CheckSemSetStat() ++ if err != nil { ++ logrus.Warn(err) ++ } ++ } ++} +diff --git a/components/engine/utils/utils.go b/components/engine/utils/utils.go +new file mode 100644 +index 0000000000..75fd409399 +--- /dev/null ++++ b/components/engine/utils/utils.go +@@ -0,0 +1,23 @@ ++package utils ++ ++/* ++#include ++#include ++int mysemctl(int cmd, struct seminfo *p){ ++ return semctl(0, 0, cmd, p); ++} ++*/ ++import "C" ++import ( ++ "fmt" ++) ++ ++func CheckSemSetStat() (int, int, error) { ++ var seminfo *C.struct_seminfo = new(C.struct_seminfo) ++ C.mysemctl(C.SEM_INFO, seminfo) ++ var err error = nil ++ if seminfo.semusz == seminfo.semmni { ++ err = fmt.Errorf("system semaphore nums has attached limit: %d", int(seminfo.semusz)) ++ } ++ return int(seminfo.semusz), int(seminfo.semmni), err ++} +-- +2.17.1 + diff --git a/patch/0010-annotation-add-annotation-into-cli-flag.patch b/patch/0010-annotation-add-annotation-into-cli-flag.patch new file mode 100644 index 0000000..2b3238f --- /dev/null +++ b/patch/0010-annotation-add-annotation-into-cli-flag.patch @@ -0,0 +1,149 @@ +From 961d5e98090e4725dca298dde8afb8df54f99f2e Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Sat, 19 Jan 2019 11:15:25 +0800 +Subject: [PATCH 010/111] annotation: add annotation into cli flag + +reason: add annotation into cli flag + +Change-Id: Ibca64819e6f390c70e8516a1462d8e465fcfe080 +Signed-off-by: caihaomin +Signed-off-by: lujingxiao +--- + components/cli/cli/command/container/opts.go | 7 +++++ + .../docker/api/types/container/config.go | 1 + + .../engine/api/types/container/config.go | 1 + + components/engine/api/types/types.go | 28 ++++++++++--------- + components/engine/daemon/oci_linux.go | 1 + + 5 files changed, 25 insertions(+), 13 deletions(-) + +diff --git a/components/cli/cli/command/container/opts.go b/components/cli/cli/command/container/opts.go +index efb28a2cdf..af30dfcbf2 100644 +--- a/components/cli/cli/command/container/opts.go ++++ b/components/cli/cli/command/container/opts.go +@@ -43,6 +43,7 @@ type containerOptions struct { + deviceWriteIOps opts.ThrottledeviceOpt + env opts.ListOpts + labels opts.ListOpts ++ annotation opts.ListOpts + deviceCgroupRules opts.ListOpts + devices opts.ListOpts + ulimits *opts.UlimitOpt +@@ -148,6 +149,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions { + groupAdd: opts.NewListOpts(nil), + labels: opts.NewListOpts(nil), + labelsFile: opts.NewListOpts(nil), ++ annotation: opts.NewListOpts(opts.ValidateEnv), + linkLocalIPs: opts.NewListOpts(nil), + links: opts.NewListOpts(opts.ValidateLink), + loggingOpts: opts.NewListOpts(nil), +@@ -173,6 +175,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions { + flags.BoolVarP(&copts.stdin, "interactive", "i", false, "Keep STDIN open even if not attached") + flags.VarP(&copts.labels, "label", "l", "Set meta data on a container") + flags.Var(&copts.labelsFile, "label-file", "Read in a line delimited file of labels") ++ flags.Var(&copts.annotation, "annotation", "Set annotations on a container") + flags.BoolVar(&copts.readonlyRootfs, "read-only", false, "Mount the container's root filesystem as read only") + flags.StringVar(&copts.restartPolicy, "restart", "no", "Restart policy to apply when a container exits") + flags.StringVar(&copts.stopSignal, "stop-signal", signal.DefaultStopSignal, "Signal to stop a container") +@@ -438,6 +441,9 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err + return nil, err + } + ++ // collect all the annotations for the container ++ annotations := copts.annotation.GetAll() ++ + pidMode := container.PidMode(copts.pidMode) + if !pidMode.Valid() { + return nil, errors.Errorf("--pid: invalid PID mode") +@@ -568,6 +574,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err + Entrypoint: entrypoint, + WorkingDir: copts.workingDir, + Labels: opts.ConvertKVStringsToMap(labels), ++ Annotations: opts.ConvertKVStringsToMap(annotations), + Healthcheck: healthConfig, + } + if flags.Changed("stop-signal") { +diff --git a/components/cli/vendor/github.com/docker/docker/api/types/container/config.go b/components/cli/vendor/github.com/docker/docker/api/types/container/config.go +index 89ad08c234..c28f0b101e 100644 +--- a/components/cli/vendor/github.com/docker/docker/api/types/container/config.go ++++ b/components/cli/vendor/github.com/docker/docker/api/types/container/config.go +@@ -63,6 +63,7 @@ type Config struct { + MacAddress string `json:",omitempty"` // Mac Address of the container + OnBuild []string // ONBUILD metadata that were defined on the image Dockerfile + Labels map[string]string // List of labels set to this container ++ Annotations map[string]string // List of annotations set to this container + StopSignal string `json:",omitempty"` // Signal to stop a container + StopTimeout *int `json:",omitempty"` // Timeout (in seconds) to stop a container + Shell strslice.StrSlice `json:",omitempty"` // Shell for shell-form of RUN, CMD, ENTRYPOINT +diff --git a/components/engine/api/types/container/config.go b/components/engine/api/types/container/config.go +index 89ad08c234..c28f0b101e 100644 +--- a/components/engine/api/types/container/config.go ++++ b/components/engine/api/types/container/config.go +@@ -63,6 +63,7 @@ type Config struct { + MacAddress string `json:",omitempty"` // Mac Address of the container + OnBuild []string // ONBUILD metadata that were defined on the image Dockerfile + Labels map[string]string // List of labels set to this container ++ Annotations map[string]string // List of annotations set to this container + StopSignal string `json:",omitempty"` // Signal to stop a container + StopTimeout *int `json:",omitempty"` // Timeout (in seconds) to stop a container + Shell strslice.StrSlice `json:",omitempty"` // Shell for shell-form of RUN, CMD, ENTRYPOINT +diff --git a/components/engine/api/types/types.go b/components/engine/api/types/types.go +index a8fae3ba32..959e9eb447 100644 +--- a/components/engine/api/types/types.go ++++ b/components/engine/api/types/types.go +@@ -56,19 +56,20 @@ type ImageMetadata struct { + // Container contains response of Engine API: + // GET "/containers/json" + type Container struct { +- ID string `json:"Id"` +- Names []string +- Image string +- ImageID string +- Command string +- Created int64 +- Ports []Port +- SizeRw int64 `json:",omitempty"` +- SizeRootFs int64 `json:",omitempty"` +- Labels map[string]string +- State string +- Status string +- HostConfig struct { ++ ID string `json:"Id"` ++ Names []string ++ Image string ++ ImageID string ++ Command string ++ Created int64 ++ Ports []Port ++ SizeRw int64 `json:",omitempty"` ++ SizeRootFs int64 `json:",omitempty"` ++ Labels map[string]string ++ Annotaitons map[string]string ++ State string ++ Status string ++ HostConfig struct { + NetworkMode string `json:",omitempty"` + } + NetworkSettings *SummaryNetworkSettings +@@ -188,6 +189,7 @@ type Info struct { + NoProxy string + Name string + Labels []string ++ Annotations []string + ExperimentalBuild bool + ServerVersion string + ClusterStore string +diff --git a/components/engine/daemon/oci_linux.go b/components/engine/daemon/oci_linux.go +index 210d2ad3f6..5018b21f0d 100644 +--- a/components/engine/daemon/oci_linux.go ++++ b/components/engine/daemon/oci_linux.go +@@ -846,6 +846,7 @@ func (daemon *Daemon) createSpec(c *container.Container) (retSpec *specs.Spec, e + s.Process.NoNewPrivileges = c.NoNewPrivileges + s.Process.OOMScoreAdj = &c.HostConfig.OomScoreAdj + s.Linux.MountLabel = c.MountLabel ++ s.Annotations = c.Config.Annotations + + // Set the masked and readonly paths with regard to the host config options if they are set. + if c.HostConfig.MaskedPaths != nil { +-- +2.17.1 + diff --git a/patch/0011-hookspec-Allow-adding-custom-hooks.patch b/patch/0011-hookspec-Allow-adding-custom-hooks.patch new file mode 100644 index 0000000..0b14502 --- /dev/null +++ b/patch/0011-hookspec-Allow-adding-custom-hooks.patch @@ -0,0 +1,288 @@ +From 65c782bb9b0b159f5644395cb291b8741b4400f4 Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Sat, 19 Jan 2019 11:15:40 +0800 +Subject: [PATCH 011/111] hookspec: Allow adding custom hooks + +reason: Add new flag "--hook-spec" which accept a file containing custom hook +definitions, custom hooks will be appended to system hooks which means +docker will execute its own hook first(libnetwork prestart hook) to make +sure everything predefined is working normally, user custom programme +can be executed afterwards + +One example hook spec file can be of format: +``` +{ + "prestart": [ + { + "path": "/bin/ls", + "args": ["ls"], + "env": [] + } + ], + "poststart":[], + "poststop":[] +} +``` + +Change-Id: Iee6f4e5b56ebf0647304c08c2948d599356192e6 +Signed-off-by: Zhang Wei +Signed-off-by: xiadanni +Signed-off-by: lujingxiao +--- + components/cli/cli/command/container/opts.go | 3 ++ + .../cli/docs/reference/commandline/create.md | 1 + + .../cli/docs/reference/commandline/run.md | 1 + + components/cli/man/docker-run.1.md | 28 +++++++++++++++++++ + .../docker/api/types/container/host_config.go | 1 + + .../engine/api/types/container/host_config.go | 1 + + components/engine/container/container.go | 4 ++- + components/engine/daemon/container.go | 24 ++++++++++++++++ + components/engine/daemon/daemon_unix.go | 16 +++++++++++ + components/engine/daemon/oci_linux.go | 6 ++++ + 10 files changed, 84 insertions(+), 1 deletion(-) + +diff --git a/components/cli/cli/command/container/opts.go b/components/cli/cli/command/container/opts.go +index af30dfcbf2..8e07aa77cb 100644 +--- a/components/cli/cli/command/container/opts.go ++++ b/components/cli/cli/command/container/opts.go +@@ -111,6 +111,7 @@ type containerOptions struct { + stopTimeout int + isolation string + shmSize opts.MemBytes ++ hookSpec string + noHealthcheck bool + healthCmd string + healthInterval time.Duration +@@ -283,6 +284,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions { + flags.StringVar(&copts.isolation, "isolation", "", "Container isolation technology") + flags.StringVar(&copts.pidMode, "pid", "", "PID namespace to use") + flags.Var(&copts.shmSize, "shm-size", "Size of /dev/shm") ++ flags.StringVar(&copts.hookSpec, "hook-spec", "", "file containing hook definition(prestart, poststart, poststop)") + flags.StringVar(&copts.utsMode, "uts", "", "UTS namespace to use") + flags.StringVar(&copts.runtime, "runtime", "", "Runtime to use for this container") + +@@ -619,6 +621,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err + VolumeDriver: copts.volumeDriver, + Isolation: container.Isolation(copts.isolation), + ShmSize: copts.shmSize.Value(), ++ HookSpec: copts.hookSpec, + Resources: resources, + Tmpfs: tmpfs, + Sysctls: copts.sysctls.GetAll(), +diff --git a/components/cli/docs/reference/commandline/create.md b/components/cli/docs/reference/commandline/create.md +index 5d888183b3..34cb79a679 100644 +--- a/components/cli/docs/reference/commandline/create.md ++++ b/components/cli/docs/reference/commandline/create.md +@@ -66,6 +66,7 @@ Options: + --health-start-period duration Start period for the container to initialize before counting retries towards unstable (ns|us|ms|s|m|h) (default 0s) + --help Print usage + -h, --hostname string Container host name ++ --hook-spec File containing hook definition(prestart, poststart, poststop) + --init Run an init inside the container that forwards signals and reaps processes + -i, --interactive Keep STDIN open even if not attached + --io-maxbandwidth string Maximum IO bandwidth limit for the system drive (Windows only) +diff --git a/components/cli/docs/reference/commandline/run.md b/components/cli/docs/reference/commandline/run.md +index 21b4fdf261..1dc43ddcd9 100644 +--- a/components/cli/docs/reference/commandline/run.md ++++ b/components/cli/docs/reference/commandline/run.md +@@ -70,6 +70,7 @@ Options: + --health-start-period duration Start period for the container to initialize before counting retries towards unstable (ns|us|ms|s|m|h) (default 0s) + --help Print usage + -h, --hostname string Container host name ++ --hook-spec File containing hook definition(prestart, poststart, poststop) + --init Run an init inside the container that forwards signals and reaps processes + -i, --interactive Keep STDIN open even if not attached + --io-maxbandwidth string Maximum IO bandwidth limit for the system drive (Windows only) +diff --git a/components/cli/man/docker-run.1.md b/components/cli/man/docker-run.1.md +index 41f501d5b9..b0cbcd2e87 100644 +--- a/components/cli/man/docker-run.1.md ++++ b/components/cli/man/docker-run.1.md +@@ -43,6 +43,7 @@ docker-run - Run a command in a new container + [**--group-add**[=*[]*]] + [**-h**|**--hostname**[=*HOSTNAME*]] + [**--help**] ++[**--hook-spec**[=*HOOKFILE*]] + [**--init**] + [**-i**|**--interactive**] + [**--ip**[=*IPv4-ADDRESS*]] +@@ -330,6 +331,33 @@ redirection on the host system. + **--help** + Print usage statement + ++**--hook-spec**="" ++ Add custom hooks for container ++ ++ With this flag, user can specify a file containing custom hook, an example hook file can be like this: ++ ++``` ++{ ++ "prestart": [ ++ { ++ "path": "/usr/libexec/oci/hooks.d/oci-systemd-hook", ++ "args": ["oci-systemd-hook", "prestart"], ++ "env": ["container=runc"] ++ } ++ ], ++ "poststop":[ ++ { ++ "path": "/usr/libexec/oci/hooks.d/oci-systemd-hook", ++ "args": ["oci-systemd-hook", "poststop"], ++ "env": ["container=runc"] ++ } ++ ] ++} ++``` ++ ++ currently it supports three hooks: "prestart", "poststart", "poststop". ++ See OCI spec definition for more information about "hooks". ++ + **--init** + Run an init inside the container that forwards signals and reaps processes + +diff --git a/components/cli/vendor/github.com/docker/docker/api/types/container/host_config.go b/components/cli/vendor/github.com/docker/docker/api/types/container/host_config.go +index 1565b5e091..701cae55f1 100644 +--- a/components/cli/vendor/github.com/docker/docker/api/types/container/host_config.go ++++ b/components/cli/vendor/github.com/docker/docker/api/types/container/host_config.go +@@ -395,6 +395,7 @@ type HostConfig struct { + // Applicable to Windows + ConsoleSize [2]uint // Initial console size (height,width) + Isolation Isolation // Isolation technology of the container (e.g. default, hyperv) ++ HookSpec string // specification file containing custom hook definition + + // Contains container's resources (cgroups, ulimits) + Resources +diff --git a/components/engine/api/types/container/host_config.go b/components/engine/api/types/container/host_config.go +index 1565b5e091..701cae55f1 100644 +--- a/components/engine/api/types/container/host_config.go ++++ b/components/engine/api/types/container/host_config.go +@@ -395,6 +395,7 @@ type HostConfig struct { + // Applicable to Windows + ConsoleSize [2]uint // Initial console size (height,width) + Isolation Isolation // Isolation technology of the container (e.g. default, hyperv) ++ HookSpec string // specification file containing custom hook definition + + // Contains container's resources (cgroups, ulimits) + Resources +diff --git a/components/engine/container/container.go b/components/engine/container/container.go +index f74676f7ee..02adc2019a 100644 +--- a/components/engine/container/container.go ++++ b/components/engine/container/container.go +@@ -38,6 +38,7 @@ import ( + volumemounts "github.com/docker/docker/volume/mounts" + "github.com/docker/go-units" + agentexec "github.com/docker/swarmkit/agent/exec" ++ "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + ) +@@ -93,6 +94,8 @@ type Container struct { + LogCopier *logger.Copier `json:"-"` + restartManager restartmanager.RestartManager + attachContext *attachContext ++ Hooks specs.Hooks ++ CgroupParent string + + // Fields here are specific to Unix platforms + AppArmorProfile string +@@ -106,7 +109,6 @@ type Container struct { + // Fields here are specific to Windows + NetworkSharedContainerID string `json:"-"` + SharedEndpointList []string `json:"-"` +- CgroupParent string + } + + // NewBaseContainer creates a new container with its +diff --git a/components/engine/daemon/container.go b/components/engine/daemon/container.go +index bd96de2571..8e68904b16 100644 +--- a/components/engine/daemon/container.go ++++ b/components/engine/daemon/container.go +@@ -1,6 +1,7 @@ + package daemon // import "github.com/docker/docker/daemon" + + import ( ++ "encoding/json" + "fmt" + "os" + "path" +@@ -224,11 +225,34 @@ func (daemon *Daemon) setHostConfig(container *container.Container, hostConfig * + return err + } + ++ // register hooks to container ++ if err := daemon.registerHooks(container, hostConfig); err != nil { ++ return err ++ } ++ + runconfig.SetDefaultNetModeIfBlank(hostConfig) + container.HostConfig = hostConfig + return container.CheckpointTo(daemon.containersReplica) + } + ++ ++func (daemon *Daemon) registerHooks(container *container.Container, hostConfig *containertypes.HostConfig) error { ++ if hostConfig.HookSpec == "" { ++ return nil ++ } ++ // the hook spec has already been sanitized, so no need for validation again ++ f, err := os.Open(hostConfig.HookSpec) ++ if err != nil { ++ return fmt.Errorf("open hook spec file error: %v", err) ++ } ++ defer f.Close() ++ ++ if err = json.NewDecoder(f).Decode(&container.Hooks); err != nil { ++ return fmt.Errorf("malformed hook spec, is your spec file in json format? error: %v", err) ++ } ++ return nil ++} ++ + // verifyContainerSettings performs validation of the hostconfig and config + // structures. + func (daemon *Daemon) verifyContainerSettings(platform string, hostConfig *containertypes.HostConfig, config *containertypes.Config, update bool) ([]string, error) { +diff --git a/components/engine/daemon/daemon_unix.go b/components/engine/daemon/daemon_unix.go +index f4b75055f5..ebf4e067fb 100644 +--- a/components/engine/daemon/daemon_unix.go ++++ b/components/engine/daemon/daemon_unix.go +@@ -627,6 +627,22 @@ func (daemon *Daemon) verifyPlatformContainerSettings(hostConfig *containertypes + return warnings, fmt.Errorf("cgroup-parent for systemd cgroup should be a valid slice named as \"xxx.slice\"") + } + } ++ ++ if hostConfig.HookSpec != "" { ++ hostConfig.HookSpec = filepath.Clean(hostConfig.HookSpec) ++ if !filepath.IsAbs(hostConfig.HookSpec) { ++ return warnings, fmt.Errorf("Hook spec file must be an absolute path") ++ } ++ fi, err := os.Stat(hostConfig.HookSpec) ++ if err != nil { ++ return warnings, fmt.Errorf("stat hook spec file failed: %v", err) ++ } ++ if !fi.Mode().IsRegular() { ++ return warnings, fmt.Errorf("Hook spec file must be a regular text file") ++ } ++ } ++ ++ + if hostConfig.Runtime == "" { + hostConfig.Runtime = daemon.configStore.GetDefaultRuntimeName() + } +diff --git a/components/engine/daemon/oci_linux.go b/components/engine/daemon/oci_linux.go +index 5018b21f0d..884739c07e 100644 +--- a/components/engine/daemon/oci_linux.go ++++ b/components/engine/daemon/oci_linux.go +@@ -818,6 +818,12 @@ func (daemon *Daemon) createSpec(c *container.Container) (retSpec *specs.Spec, e + } + } + ++ // apppend user custom hooks after system hooks ++ // make sure docker's predefined hooks are executed before custom hooks ++ s.Hooks.Prestart = append(s.Hooks.Prestart, c.Hooks.Prestart...) ++ s.Hooks.Poststart = append(s.Hooks.Poststart, c.Hooks.Poststart...) ++ s.Hooks.Poststop = append(s.Hooks.Poststop, c.Hooks.Poststop...) ++ + if apparmor.IsEnabled() { + var appArmorProfile string + if c.AppArmorProfile != "" { +-- +2.17.1 + diff --git a/patch/0012-hookspec-Security-enhancement-for-hooks.patch b/patch/0012-hookspec-Security-enhancement-for-hooks.patch new file mode 100644 index 0000000..3bce41d --- /dev/null +++ b/patch/0012-hookspec-Security-enhancement-for-hooks.patch @@ -0,0 +1,82 @@ +From e81d103000ae3213b91ed54410ddb20d911ddc1a Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Sat, 19 Jan 2019 11:15:56 +0800 +Subject: [PATCH 012/111] hookspec: Security enhancement for hooks + +reason: Currently docker support running hooks with any path, this is insecure. +To solve this, we need to restrict path to specified path, e.g. +"/var/lib/docker/hooks" or "/var/lib/docker/1000.1000/hooks" if user +remap enabled for user ns. + +Change-Id: I9cff78f1a1105dcb4bc0b00c8e6e715904dfb778 +Signed-off-by: Zhang Wei +Signed-off-by: xiadanni +Signed-off-by: lujingxiao +--- + components/engine/daemon/container.go | 37 +++++++++++++++++++++++++++ + components/engine/daemon/daemon.go | 1 + + 2 files changed, 38 insertions(+) + +diff --git a/components/engine/daemon/container.go b/components/engine/daemon/container.go +index 8e68904b16..0864443513 100644 +--- a/components/engine/daemon/container.go ++++ b/components/engine/daemon/container.go +@@ -250,6 +250,43 @@ func (daemon *Daemon) registerHooks(container *container.Container, hostConfig * + if err = json.NewDecoder(f).Decode(&container.Hooks); err != nil { + return fmt.Errorf("malformed hook spec, is your spec file in json format? error: %v", err) + } ++ ++ // hook path must be absolute and must be subdir of XXX ++ if err = daemon.validateHook(container); err != nil { ++ return err ++ } ++ return nil ++} ++ ++func (daemon *Daemon) validateHook(container *container.Container) error { ++ for _, v := range container.Hooks.Prestart { ++ if err := daemon.validateHookPath(v.Path); err != nil { ++ return err ++ } ++ } ++ for _, v := range container.Hooks.Poststart { ++ if err := daemon.validateHookPath(v.Path); err != nil { ++ return err ++ } ++ } ++ for _, v := range container.Hooks.Poststop { ++ if err := daemon.validateHookPath(v.Path); err != nil { ++ return err ++ } ++ } ++ return nil ++} ++ ++func (daemon *Daemon) validateHookPath(path string) error { ++ // hook path must be absolute and must be subdir of XXX ++ path = filepath.Clean(path) ++ if !filepath.IsAbs(path) { ++ return fmt.Errorf("Hook path %q must be an absolute path", path) ++ } ++ ++ if !filepath.HasPrefix(path, daemon.hookStore) { ++ return fmt.Errorf("hook program must be put under %q", daemon.hookStore) ++ } + return nil + } + +diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go +index 8d6b4d8546..d1f3131c4f 100644 +--- a/components/engine/daemon/daemon.go ++++ b/components/engine/daemon/daemon.go +@@ -95,6 +95,7 @@ type Daemon struct { + volumes *volumesservice.VolumesService + discoveryWatcher discovery.Reloader + root string ++ hookStore string + seccompEnabled bool + apparmorEnabled bool + shutdown bool +-- +2.17.1 + diff --git a/patch/0013-hookspec-Add-bash-completion-for-hook-spec.patch b/patch/0013-hookspec-Add-bash-completion-for-hook-spec.patch new file mode 100644 index 0000000..60cfac0 --- /dev/null +++ b/patch/0013-hookspec-Add-bash-completion-for-hook-spec.patch @@ -0,0 +1,31 @@ +From e261cd646bd656db1eae5a3bcb4af59af3423a46 Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Sat, 19 Jan 2019 11:16:09 +0800 +Subject: [PATCH 013/111] hookspec: Add bash completion for + --hook-spec + +reason: Add bash completion for --hook-spec + +Change-Id: Ic2cf226576a5437aa69b48edb73737c59f116ae8 +Signed-off-by: Zhang Wei +Signed-off-by: xiadanni +Signed-off-by: lujingxiao +--- + components/cli/contrib/completion/bash/docker | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/components/cli/contrib/completion/bash/docker b/components/cli/contrib/completion/bash/docker +index 2e2c8cb04f..9012988075 100644 +--- a/components/cli/contrib/completion/bash/docker ++++ b/components/cli/contrib/completion/bash/docker +@@ -1791,6 +1791,7 @@ _docker_container_run_and_create() { + --expose + --files-limit + --group-add ++ --hook-spec + --health-cmd + --health-interval + --health-retries +-- +2.17.1 + diff --git a/patch/0014-hookspec-Add-default-hooks-for-all-containers.patch b/patch/0014-hookspec-Add-default-hooks-for-all-containers.patch new file mode 100644 index 0000000..e253458 --- /dev/null +++ b/patch/0014-hookspec-Add-default-hooks-for-all-containers.patch @@ -0,0 +1,367 @@ +From d6725e951ee958b61af8c32d5c71d79d2e708432 Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Sat, 19 Jan 2019 11:16:48 +0800 +Subject: [PATCH 014/111] hookspec: Add default hooks for all + containers + +reason: Add new flag `--hook-spec` for daemon, so that we can specify one json +file containing hooks definition for all containers. + +You can also add this into `/etc/docker/daemon.json` daemon config file: + +``` +{ + "hook-spec": "/tmp/hookspec.json" +} +``` + +Change-Id: I9263d5a912daeb04621e7d2ec991204333c2b931 +Signed-off-by: Zhang Wei +Signed-off-by: xiadanni +Signed-off-by: lujingxiao +--- + components/cli/cli/command/system/info.go | 4 + + components/cli/contrib/completion/bash/docker | 1 + + .../cli/docs/reference/commandline/dockerd.md | 1 + + components/cli/man/dockerd.8.md | 25 ++++- + .../docker/docker/api/types/types.go | 1 + + components/engine/api/types/types.go | 1 + + components/engine/cmd/dockerd/config.go | 1 + + components/engine/daemon/config/config.go | 1 + + components/engine/daemon/container.go | 94 +++++++++++++++++-- + components/engine/daemon/daemon.go | 7 ++ + components/engine/daemon/info.go | 1 + + 11 files changed, 126 insertions(+), 11 deletions(-) + +diff --git a/components/cli/cli/command/system/info.go b/components/cli/cli/command/system/info.go +index 92fc2cd3e7..17ccc14aec 100644 +--- a/components/cli/cli/command/system/info.go ++++ b/components/cli/cli/command/system/info.go +@@ -204,6 +204,10 @@ func prettyPrintInfo(dockerCli command.Cli, info types.Info) error { + } + + fmt.Fprintln(dockerCli.Out(), "Live Restore Enabled:", info.LiveRestoreEnabled) ++ if info.HookSpec != "" { ++ fmt.Fprintf(dockerCli.Out(), "Default hook spec file: %s", info.HookSpec) ++ } ++ + if info.ProductLicense != "" { + fmt.Fprintln(dockerCli.Out(), "Product License:", info.ProductLicense) + } +diff --git a/components/cli/contrib/completion/bash/docker b/components/cli/contrib/completion/bash/docker +index 9012988075..64f7fe08dd 100644 +--- a/components/cli/contrib/completion/bash/docker ++++ b/components/cli/contrib/completion/bash/docker +@@ -2274,6 +2274,7 @@ _docker_daemon() { + --fixed-cidr + --fixed-cidr-v6 + --group -G ++ --hook-spec + --init-path + --insecure-registry + --ip +diff --git a/components/cli/docs/reference/commandline/dockerd.md b/components/cli/docs/reference/commandline/dockerd.md +index 4b50b78b19..bbf6908af3 100644 +--- a/components/cli/docs/reference/commandline/dockerd.md ++++ b/components/cli/docs/reference/commandline/dockerd.md +@@ -53,6 +53,7 @@ Options: + --fixed-cidr-v6 string IPv6 subnet for fixed IPs + -G, --group string Group for the unix socket (default "docker") + --help Print usage ++ --hook-spec Default hook spec file applied to all containers + -H, --host list Daemon socket(s) to connect to (default []) + --icc Enable inter-container communication (default true) + --init Run an init in the container to forward signals and reap processes +diff --git a/components/cli/man/dockerd.8.md b/components/cli/man/dockerd.8.md +index 0224035970..d075080e78 100644 +--- a/components/cli/man/dockerd.8.md ++++ b/components/cli/man/dockerd.8.md +@@ -38,6 +38,7 @@ dockerd - Enable daemon mode + [**-G**|**--group**[=*docker*]] + [**-H**|**--host**[=*[]*]] + [**--help**] ++[**--hook-spec**[=*HOOKFILE*]] + [**--icc**[=*true*]] + [**--init**[=*false*]] + [**--init-path**[=*""*]] +@@ -239,7 +240,29 @@ unix://[/path/to/socket] to use. + + **--help** + Print usage statement +- ++ ++**--hook-spec**="" ++ Add default hooks for all containers. ++ ++ With this flag, user can specify a file containing custom hook, an example hook file can be like this: ++ ++ ``` ++ { ++ "prestart": [ ++ { ++ "path": "/var/lib/docker/hooks/myhook", ++ "args": ["myhook", "prestart"], ++ "env": ["container=runc"] ++ } ++ ] ++ } ++ ``` ++ ++ Then all the containers will run the default hook `myhook` when start. ++ ++ currently it supports three hooks: "prestart", "poststart", "poststop". ++ See OCI spec definition for more information about "hooks". ++ + **--icc**=*true*|*false* + Allow unrestricted inter\-container and Docker daemon host communication. If + disabled, containers can still be linked together using the **--link** option +diff --git a/components/cli/vendor/github.com/docker/docker/api/types/types.go b/components/cli/vendor/github.com/docker/docker/api/types/types.go +index a8fae3ba32..2fb6c5478b 100644 +--- a/components/cli/vendor/github.com/docker/docker/api/types/types.go ++++ b/components/cli/vendor/github.com/docker/docker/api/types/types.go +@@ -192,6 +192,7 @@ type Info struct { + ServerVersion string + ClusterStore string + ClusterAdvertise string ++ HookSpec string + Runtimes map[string]Runtime + DefaultRuntime string + Swarm swarm.Info +diff --git a/components/engine/api/types/types.go b/components/engine/api/types/types.go +index 959e9eb447..820d513cbb 100644 +--- a/components/engine/api/types/types.go ++++ b/components/engine/api/types/types.go +@@ -194,6 +194,7 @@ type Info struct { + ServerVersion string + ClusterStore string + ClusterAdvertise string ++ HookSpec string + Runtimes map[string]Runtime + DefaultRuntime string + Swarm swarm.Info +diff --git a/components/engine/cmd/dockerd/config.go b/components/engine/cmd/dockerd/config.go +index 2c8ed8edb4..6f62b97da8 100644 +--- a/components/engine/cmd/dockerd/config.go ++++ b/components/engine/cmd/dockerd/config.go +@@ -56,6 +56,7 @@ func installCommonConfigFlags(conf *config.Config, flags *pflag.FlagSet) { + flags.StringVar(&conf.ClusterAdvertise, "cluster-advertise", "", "Address or interface name to advertise") + flags.StringVar(&conf.ClusterStore, "cluster-store", "", "URL of the distributed storage backend") + flags.Var(opts.NewNamedMapOpts("cluster-store-opts", conf.ClusterOpts, nil), "cluster-store-opt", "Set cluster store options") ++ flags.StringVar(&conf.HookSpec, "hook-spec", "", "Default hook spec file applied to all containers") + flags.StringVar(&conf.CorsHeaders, "api-cors-header", "", "Set CORS headers in the Engine API") + flags.IntVar(&maxConcurrentDownloads, "max-concurrent-downloads", config.DefaultMaxConcurrentDownloads, "Set the max concurrent downloads for each pull") + flags.IntVar(&maxConcurrentUploads, "max-concurrent-uploads", config.DefaultMaxConcurrentUploads, "Set the max concurrent uploads for each push") +diff --git a/components/engine/daemon/config/config.go b/components/engine/daemon/config/config.go +index 8b2c844a57..2141ce8c54 100644 +--- a/components/engine/daemon/config/config.go ++++ b/components/engine/daemon/config/config.go +@@ -124,6 +124,7 @@ type CommonConfig struct { + ExecOptions []string `json:"exec-opts,omitempty"` + GraphDriver string `json:"storage-driver,omitempty"` + GraphOptions []string `json:"storage-opts,omitempty"` ++ HookSpec string `json:"hook-spec,omitempty"` + Labels []string `json:"labels,omitempty"` + Mtu int `json:"mtu,omitempty"` + NetworkDiagnosticPort int `json:"network-diagnostic-port,omitempty"` +diff --git a/components/engine/daemon/container.go b/components/engine/daemon/container.go +index 0864443513..8f9f6baf25 100644 +--- a/components/engine/daemon/container.go ++++ b/components/engine/daemon/container.go +@@ -13,16 +13,19 @@ import ( + containertypes "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/strslice" + "github.com/docker/docker/container" ++ "github.com/docker/docker/daemon/config" + "github.com/docker/docker/daemon/network" + "github.com/docker/docker/errdefs" + "github.com/docker/docker/image" + "github.com/docker/docker/opts" ++ "github.com/docker/docker/pkg/idtools" + "github.com/docker/docker/pkg/signal" + "github.com/docker/docker/pkg/system" + "github.com/docker/docker/pkg/truncindex" + "github.com/docker/docker/runconfig" + volumemounts "github.com/docker/docker/volume/mounts" + "github.com/docker/go-connections/nat" ++ "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/selinux/go-selinux/label" + "github.com/pkg/errors" + ) +@@ -226,7 +229,7 @@ func (daemon *Daemon) setHostConfig(container *container.Container, hostConfig * + } + + // register hooks to container +- if err := daemon.registerHooks(container, hostConfig); err != nil { ++ if err := daemon.registerHooks(container, hostConfig.HookSpec); err != nil { + return err + } + +@@ -235,41 +238,112 @@ func (daemon *Daemon) setHostConfig(container *container.Container, hostConfig * + return container.CheckpointTo(daemon.containersReplica) + } + ++func (daemon *Daemon) sanitizeHookSpec(spec string) (string, error) { ++ if spec != "" { ++ spec = filepath.Clean(spec) ++ if !filepath.IsAbs(spec) { ++ return "", fmt.Errorf("hook spec file must be an absolute path") ++ } ++ fi, err := os.Stat(spec) ++ if err != nil { ++ return "", fmt.Errorf("stat hook spec file failed: %v", err) ++ } ++ if !fi.Mode().IsRegular() { ++ return "", fmt.Errorf("hook spec file must be a regular text file") ++ } ++ } ++ return spec, nil ++} ++ ++func (daemon *Daemon) initHooks(config *config.Config, rootIdentity idtools.Identity) error { ++ // create hook store dir ++ var err error ++ hookDir := filepath.Join(config.Root, "hooks") ++ if err = idtools.MkdirAllAndChown(hookDir, 0700, rootIdentity); err != nil && !os.IsExist(err) { ++ return err ++ } ++ daemon.hookStore = hookDir ++ ++ if config.HookSpec, err = daemon.sanitizeHookSpec(config.HookSpec); err != nil { ++ return err ++ } ++ ++ // setup default hooks ++ if err := daemon.registerDaemonHooks(config.HookSpec); err != nil { ++ return err ++ } ++ ++ return nil ++} + +-func (daemon *Daemon) registerHooks(container *container.Container, hostConfig *containertypes.HostConfig) error { +- if hostConfig.HookSpec == "" { ++func (daemon *Daemon) registerDaemonHooks(hookspec string) error { ++ if hookspec == "" { + return nil + } ++ + // the hook spec has already been sanitized, so no need for validation again +- f, err := os.Open(hostConfig.HookSpec) ++ f, err := os.Open(hookspec) + if err != nil { + return fmt.Errorf("open hook spec file error: %v", err) + } + defer f.Close() + +- if err = json.NewDecoder(f).Decode(&container.Hooks); err != nil { ++ if err = json.NewDecoder(f).Decode(&daemon.Hooks); err != nil { + return fmt.Errorf("malformed hook spec, is your spec file in json format? error: %v", err) + } + + // hook path must be absolute and must be subdir of XXX +- if err = daemon.validateHook(container); err != nil { ++ if err = daemon.validateHook(&daemon.Hooks); err != nil { + return err + } ++ ++ return nil ++} ++ ++func (daemon *Daemon) registerHooks(container *container.Container, hookspec string) error { ++ container.Hooks.Prestart = append(container.Hooks.Prestart, daemon.Hooks.Prestart...) ++ container.Hooks.Poststart = append(container.Hooks.Poststart, daemon.Hooks.Poststart...) ++ container.Hooks.Poststop = append(container.Hooks.Poststop, daemon.Hooks.Poststop...) ++ if hookspec == "" { ++ return nil ++ } ++ ++ // the hook spec has already been sanitized, so no need for validation again ++ f, err := os.Open(hookspec) ++ if err != nil { ++ return fmt.Errorf("open hook spec file error: %v", err) ++ } ++ defer f.Close() ++ ++ var hooks specs.Hooks ++ if err = json.NewDecoder(f).Decode(&hooks); err != nil { ++ return fmt.Errorf("malformed hook spec, is your spec file in json format? error: %v", err) ++ } ++ ++ container.Hooks.Prestart = append(container.Hooks.Prestart, hooks.Prestart...) ++ container.Hooks.Poststart = append(container.Hooks.Poststart, hooks.Poststart...) ++ container.Hooks.Poststop = append(container.Hooks.Poststop, hooks.Poststop...) ++ ++ // hook path must be absolute and must be subdir of XXX ++ if err = daemon.validateHook(&container.Hooks); err != nil { ++ return err ++ } ++ + return nil + } + +-func (daemon *Daemon) validateHook(container *container.Container) error { +- for _, v := range container.Hooks.Prestart { ++func (daemon *Daemon) validateHook(hooks *specs.Hooks) error { ++ for _, v := range hooks.Prestart { + if err := daemon.validateHookPath(v.Path); err != nil { + return err + } + } +- for _, v := range container.Hooks.Poststart { ++ for _, v := range hooks.Poststart { + if err := daemon.validateHookPath(v.Path); err != nil { + return err + } + } +- for _, v := range container.Hooks.Poststop { ++ for _, v := range hooks.Poststop { + if err := daemon.validateHookPath(v.Path); err != nil { + return err + } +diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go +index d1f3131c4f..f7635f27cc 100644 +--- a/components/engine/daemon/daemon.go ++++ b/components/engine/daemon/daemon.go +@@ -67,6 +67,7 @@ import ( + "github.com/docker/libnetwork" + "github.com/docker/libnetwork/cluster" + nwconfig "github.com/docker/libnetwork/config" ++ "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" + ) + +@@ -109,6 +110,7 @@ type Daemon struct { + containerdCli *containerd.Client + containerd libcontainerd.Client + defaultIsolation containertypes.Isolation // Default isolation mode on Windows ++ Hooks specs.Hooks + clusterProvider cluster.Provider + cluster Cluster + genericResources []swarm.GenericResource +@@ -997,6 +999,11 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S + return nil, errors.New("Devices cgroup isn't mounted") + } + ++ // setup hooks environment ++ if err := d.initHooks(config, rootIDs); err != nil { ++ return nil, fmt.Errorf("Failed to register default hooks: %v", err) ++ } ++ + d.ID = trustKey.PublicKey().KeyID() + d.repository = daemonRepo + d.containers = container.NewMemoryStore() +diff --git a/components/engine/daemon/info.go b/components/engine/daemon/info.go +index 262719d9d1..523a396643 100644 +--- a/components/engine/daemon/info.go ++++ b/components/engine/daemon/info.go +@@ -67,6 +67,7 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) { + HTTPSProxy: maskCredentials(sockets.GetProxyEnv("https_proxy")), + NoProxy: sockets.GetProxyEnv("no_proxy"), + LiveRestoreEnabled: daemon.configStore.LiveRestoreEnabled, ++ HookSpec: daemon.configStore.HookSpec, + Isolation: daemon.defaultIsolation, + } + +-- +2.17.1 + diff --git a/patch/0015-hookspec-add-limit-of-hook-spec-file.patch b/patch/0015-hookspec-add-limit-of-hook-spec-file.patch new file mode 100644 index 0000000..9fce5b7 --- /dev/null +++ b/patch/0015-hookspec-add-limit-of-hook-spec-file.patch @@ -0,0 +1,72 @@ +From a6c5e3824b6b8d3a443e1a14136360cc73779296 Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Sat, 19 Jan 2019 11:17:06 +0800 +Subject: [PATCH 015/111] hookspec: add limit of hook spec file + +reason: add limit of hook spec file, to prevent docker daemon OOM. + +Change-Id: I11afebf163de3c401ed4f9b8f30c403f1d15de77 +Signed-off-by: xiadanni +Signed-off-by: lujingxiao +--- + components/engine/daemon/container.go | 8 ++++++++ + components/engine/daemon/daemon_unix.go | 15 ++------------- + 2 files changed, 10 insertions(+), 13 deletions(-) + +diff --git a/components/engine/daemon/container.go b/components/engine/daemon/container.go +index 8f9f6baf25..a8cb950f44 100644 +--- a/components/engine/daemon/container.go ++++ b/components/engine/daemon/container.go +@@ -30,6 +30,11 @@ import ( + "github.com/pkg/errors" + ) + ++const ( ++ // hook spec file size limit (in bytes) ++ hookSpecSizeLimit = (10 * 1024 * 1024) ++) ++ + // GetContainer looks for a container using the provided information, which could be + // one of the following inputs from the caller: + // - A full container ID, which will exact match a container in daemon's list +@@ -251,6 +256,9 @@ func (daemon *Daemon) sanitizeHookSpec(spec string) (string, error) { + if !fi.Mode().IsRegular() { + return "", fmt.Errorf("hook spec file must be a regular text file") + } ++ if fi.Size() > hookSpecSizeLimit { ++ return "", fmt.Errorf("Hook spec file size must not exceed %d bytes", hookSpecSizeLimit) ++ } + } + return spec, nil + } +diff --git a/components/engine/daemon/daemon_unix.go b/components/engine/daemon/daemon_unix.go +index ebf4e067fb..5b390d2db1 100644 +--- a/components/engine/daemon/daemon_unix.go ++++ b/components/engine/daemon/daemon_unix.go +@@ -628,21 +628,10 @@ func (daemon *Daemon) verifyPlatformContainerSettings(hostConfig *containertypes + } + } + +- if hostConfig.HookSpec != "" { +- hostConfig.HookSpec = filepath.Clean(hostConfig.HookSpec) +- if !filepath.IsAbs(hostConfig.HookSpec) { +- return warnings, fmt.Errorf("Hook spec file must be an absolute path") +- } +- fi, err := os.Stat(hostConfig.HookSpec) +- if err != nil { +- return warnings, fmt.Errorf("stat hook spec file failed: %v", err) +- } +- if !fi.Mode().IsRegular() { +- return warnings, fmt.Errorf("Hook spec file must be a regular text file") +- } ++ if hostConfig.HookSpec, err = daemon.sanitizeHookSpec(hostConfig.HookSpec); err != nil { ++ return warnings, err + } + +- + if hostConfig.Runtime == "" { + hostConfig.Runtime = daemon.configStore.GetDefaultRuntimeName() + } +-- +2.17.1 + diff --git a/patch/0016-hookspec-fix-hooks-nil-pointer-dereference.patch b/patch/0016-hookspec-fix-hooks-nil-pointer-dereference.patch new file mode 100644 index 0000000..b406e09 --- /dev/null +++ b/patch/0016-hookspec-fix-hooks-nil-pointer-dereference.patch @@ -0,0 +1,31 @@ +From 8dd367f460008bbe1715117610b1eb48cb5b20bf Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Sat, 19 Jan 2019 11:17:23 +0800 +Subject: [PATCH 016/111] hookspec: fix hooks nil pointer dereference + +reason: merge containerd and runc into one version for docker-1.11.2 and +docker-17.06 + +Change-Id: I1b0899cf18f2734bb205026b3094f1a3264a695e +Signed-off-by: jingrui +Signed-off-by: xiadanni +Signed-off-by: lujingxiao +--- + components/engine/oci/defaults.go | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/components/engine/oci/defaults.go b/components/engine/oci/defaults.go +index 992157b0f5..5e17ea3f38 100644 +--- a/components/engine/oci/defaults.go ++++ b/components/engine/oci/defaults.go +@@ -66,6 +66,7 @@ func DefaultLinuxSpec() specs.Spec { + }, + }, + Root: &specs.Root{}, ++ Hooks: &specs.Hooks{}, + } + s.Mounts = []specs.Mount{ + { +-- +2.17.1 + diff --git a/patch/0017-hookspec-canonicalize-hook-path-before-valida.patch b/patch/0017-hookspec-canonicalize-hook-path-before-valida.patch new file mode 100644 index 0000000..85c5191 --- /dev/null +++ b/patch/0017-hookspec-canonicalize-hook-path-before-valida.patch @@ -0,0 +1,45 @@ +From fe67259fd158b8193db31fad83f2dc3d3d3bc5c4 Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Sat, 19 Jan 2019 11:17:38 +0800 +Subject: [PATCH 017/111] hookspec: canonicalize hook path before + validation + +reason:hook programs must put under hook directory, but if we refer + them with a symbolic link path in hookspecs, docker daemon refuse + to run. so we just canonicalize path first before check. + +Change-Id: I68d3757f26d7df05eb048e686368eca061cb06a9 +Signed-off-by: zhangyuyun +Signed-off-by: xiadanni +Signed-off-by: lujingxiao +--- + components/engine/daemon/container.go | 13 +++++++++++-- + 1 file changed, 11 insertions(+), 2 deletions(-) + +diff --git a/components/engine/daemon/container.go b/components/engine/daemon/container.go +index a8cb950f44..06a19bb4c8 100644 +--- a/components/engine/daemon/container.go ++++ b/components/engine/daemon/container.go +@@ -366,8 +366,17 @@ func (daemon *Daemon) validateHookPath(path string) error { + return fmt.Errorf("Hook path %q must be an absolute path", path) + } + +- if !filepath.HasPrefix(path, daemon.hookStore) { +- return fmt.Errorf("hook program must be put under %q", daemon.hookStore) ++ realPath, err := filepath.EvalSymlinks(path) ++ if err != nil { ++ if !strings.Contains(err.Error(), "no such file or directory") { ++ return fmt.Errorf("failed to canonicalise path for %s: %s", path, err) ++ } ++ // for backward compatibility ++ realPath = path ++ } ++ ++ if !filepath.HasPrefix(realPath, daemon.hookStore) { ++ return fmt.Errorf("hook path %q isn't right, hook program must be put under %q", path, daemon.hookStore) + } + return nil + } +-- +2.17.1 + diff --git a/patch/0018-dfx-trylock-add-trylock-and-trylocktimeout-fo.patch b/patch/0018-dfx-trylock-add-trylock-and-trylocktimeout-fo.patch new file mode 100644 index 0000000..b2137f0 --- /dev/null +++ b/patch/0018-dfx-trylock-add-trylock-and-trylocktimeout-fo.patch @@ -0,0 +1,362 @@ +From 6a9e68763da72ebc0b9a7e45cd08ce57fe11998f Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Sat, 19 Jan 2019 11:21:56 +0800 +Subject: [PATCH 018/111] dfx/trylock: add trylock and trylocktimeout + for docker inspect + +reason:In order to avoid deadlocks, add trylock and trylocktimeout + for docker inspect commmand. The -t/--time is a parameter of + docker inspect command, set by user, and the default value + is 120s. + +Change-Id: Ie30ff28941624cc595e2a30d453d6f90b265d803 +Signed-off-by: zhangyu235 +Signed-off-by: lujingxiao +--- + .../cli/cli/command/container/inspect.go | 4 +- + components/cli/cli/command/system/inspect.go | 12 ++-- + .../github.com/docker/docker/client/Checklist | 1 + + .../docker/docker/client/container_inspect.go | 4 +- + .../docker/docker/client/interface.go | 2 +- + .../api/server/router/container/backend.go | 2 +- + .../api/server/router/container/inspect.go | 4 +- + components/engine/container/state.go | 4 +- + .../engine/daemon/cluster/executor/backend.go | 2 +- + .../cluster/executor/container/adapter.go | 3 +- + components/engine/daemon/inspect.go | 12 ++-- + components/engine/pkg/trylock/mutex.go | 61 +++++++++++++++++++ + 12 files changed, 93 insertions(+), 18 deletions(-) + create mode 100644 components/cli/vendor/github.com/docker/docker/client/Checklist + create mode 100644 components/engine/pkg/trylock/mutex.go + +diff --git a/components/cli/cli/command/container/inspect.go b/components/cli/cli/command/container/inspect.go +index 4f50e2a080..b77994e896 100644 +--- a/components/cli/cli/command/container/inspect.go ++++ b/components/cli/cli/command/container/inspect.go +@@ -13,6 +13,7 @@ type inspectOptions struct { + format string + size bool + refs []string ++ time int + } + + // newInspectCommand creates a new cobra.Command for `docker container inspect` +@@ -32,6 +33,7 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command { + flags := cmd.Flags() + flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template") + flags.BoolVarP(&opts.size, "size", "s", false, "Display total file sizes") ++ flags.IntVarP(&opts.time, "time", "t", 120, "Seconds to wait for inspect timeout") + + return cmd + } +@@ -41,7 +43,7 @@ func runInspect(dockerCli command.Cli, opts inspectOptions) error { + ctx := context.Background() + + getRefFunc := func(ref string) (interface{}, []byte, error) { +- return client.ContainerInspectWithRaw(ctx, ref, opts.size) ++ return client.ContainerInspectWithRaw(ctx, ref, opts.size, opts.time) + } + return inspect.Inspect(dockerCli.Out(), opts.refs, opts.format, getRefFunc) + } +diff --git a/components/cli/cli/command/system/inspect.go b/components/cli/cli/command/system/inspect.go +index b49b4b33d3..248f9caad2 100644 +--- a/components/cli/cli/command/system/inspect.go ++++ b/components/cli/cli/command/system/inspect.go +@@ -19,6 +19,7 @@ type inspectOptions struct { + inspectType string + size bool + ids []string ++ time int + } + + // NewInspectCommand creates a new cobra.Command for `docker inspect` +@@ -39,6 +40,7 @@ func NewInspectCommand(dockerCli command.Cli) *cobra.Command { + flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template") + flags.StringVar(&opts.inspectType, "type", "", "Return JSON for specified type") + flags.BoolVarP(&opts.size, "size", "s", false, "Display total file sizes if the type is container") ++ flags.IntVarP(&opts.time, "time", "t", 120, "Seconds to wait for inspect timeout") + + return cmd + } +@@ -47,16 +49,16 @@ func runInspect(dockerCli command.Cli, opts inspectOptions) error { + var elementSearcher inspect.GetRefFunc + switch opts.inspectType { + case "", "container", "image", "node", "network", "service", "volume", "task", "plugin", "secret": +- elementSearcher = inspectAll(context.Background(), dockerCli, opts.size, opts.inspectType) ++ elementSearcher = inspectAll(context.Background(), dockerCli, opts.size, opts.inspectType, opts.time) + default: + return errors.Errorf("%q is not a valid value for --type", opts.inspectType) + } + return inspect.Inspect(dockerCli.Out(), opts.ids, opts.format, elementSearcher) + } + +-func inspectContainers(ctx context.Context, dockerCli command.Cli, getSize bool) inspect.GetRefFunc { ++func inspectContainers(ctx context.Context, dockerCli command.Cli, getSize bool, timeout int) inspect.GetRefFunc { + return func(ref string) (interface{}, []byte, error) { +- return dockerCli.Client().ContainerInspectWithRaw(ctx, ref, getSize) ++ return dockerCli.Client().ContainerInspectWithRaw(ctx, ref, getSize, timeout) + } + } + +@@ -109,7 +111,7 @@ func inspectSecret(ctx context.Context, dockerCli command.Cli) inspect.GetRefFun + } + } + +-func inspectAll(ctx context.Context, dockerCli command.Cli, getSize bool, typeConstraint string) inspect.GetRefFunc { ++func inspectAll(ctx context.Context, dockerCli command.Cli, getSize bool, typeConstraint string, timeout int) inspect.GetRefFunc { + var inspectAutodetect = []struct { + objectType string + isSizeSupported bool +@@ -119,7 +121,7 @@ func inspectAll(ctx context.Context, dockerCli command.Cli, getSize bool, typeCo + { + objectType: "container", + isSizeSupported: true, +- objectInspector: inspectContainers(ctx, dockerCli, getSize), ++ objectInspector: inspectContainers(ctx, dockerCli, getSize, timeout), + }, + { + objectType: "image", +diff --git a/components/cli/vendor/github.com/docker/docker/client/Checklist b/components/cli/vendor/github.com/docker/docker/client/Checklist +new file mode 100644 +index 0000000000..9b1682dc41 +--- /dev/null ++++ b/components/cli/vendor/github.com/docker/docker/client/Checklist +@@ -0,0 +1 @@ ++Add trylcok and trylocktimeout for docker inspect commmand. +diff --git a/components/cli/vendor/github.com/docker/docker/client/container_inspect.go b/components/cli/vendor/github.com/docker/docker/client/container_inspect.go +index f453064cf8..b8573fd40b 100644 +--- a/components/cli/vendor/github.com/docker/docker/client/container_inspect.go ++++ b/components/cli/vendor/github.com/docker/docker/client/container_inspect.go +@@ -6,6 +6,7 @@ import ( + "encoding/json" + "io/ioutil" + "net/url" ++ "strconv" + + "github.com/docker/docker/api/types" + ) +@@ -27,7 +28,7 @@ func (cli *Client) ContainerInspect(ctx context.Context, containerID string) (ty + } + + // ContainerInspectWithRaw returns the container information and its raw representation. +-func (cli *Client) ContainerInspectWithRaw(ctx context.Context, containerID string, getSize bool) (types.ContainerJSON, []byte, error) { ++func (cli *Client) ContainerInspectWithRaw(ctx context.Context, containerID string, getSize bool, timeout int) (types.ContainerJSON, []byte, error) { + if containerID == "" { + return types.ContainerJSON{}, nil, objectNotFoundError{object: "container", id: containerID} + } +@@ -35,6 +36,7 @@ func (cli *Client) ContainerInspectWithRaw(ctx context.Context, containerID stri + if getSize { + query.Set("size", "1") + } ++ query.Set("t", strconv.Itoa(timeout)) + serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", query, nil) + if err != nil { + return types.ContainerJSON{}, nil, wrapResponseError(err, serverResp, "container", containerID) +diff --git a/components/cli/vendor/github.com/docker/docker/client/interface.go b/components/cli/vendor/github.com/docker/docker/client/interface.go +index d190f8e58d..b2d5d7bb72 100644 +--- a/components/cli/vendor/github.com/docker/docker/client/interface.go ++++ b/components/cli/vendor/github.com/docker/docker/client/interface.go +@@ -56,7 +56,7 @@ type ContainerAPIClient interface { + ContainerExecStart(ctx context.Context, execID string, config types.ExecStartCheck) error + ContainerExport(ctx context.Context, container string) (io.ReadCloser, error) + ContainerInspect(ctx context.Context, container string) (types.ContainerJSON, error) +- ContainerInspectWithRaw(ctx context.Context, container string, getSize bool) (types.ContainerJSON, []byte, error) ++ ContainerInspectWithRaw(ctx context.Context, container string, getSize bool, timeout int) (types.ContainerJSON, []byte, error) + ContainerKill(ctx context.Context, container, signal string) error + ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) + ContainerLogs(ctx context.Context, container string, options types.ContainerLogsOptions) (io.ReadCloser, error) +diff --git a/components/engine/api/server/router/container/backend.go b/components/engine/api/server/router/container/backend.go +index 75ea1d82b7..88fbe71a88 100644 +--- a/components/engine/api/server/router/container/backend.go ++++ b/components/engine/api/server/router/container/backend.go +@@ -49,7 +49,7 @@ type stateBackend interface { + // monitorBackend includes functions to implement to provide containers monitoring functionality. + type monitorBackend interface { + ContainerChanges(name string) ([]archive.Change, error) +- ContainerInspect(name string, size bool, version string) (interface{}, error) ++ ContainerInspect(name string, size bool, version string, timeout int) (interface{}, error) + ContainerLogs(ctx context.Context, name string, config *types.ContainerLogsOptions) (msgs <-chan *backend.LogMessage, tty bool, err error) + ContainerStats(ctx context.Context, name string, config *backend.ContainerStatsConfig) error + ContainerTop(name string, psArgs string) (*container.ContainerTopOKBody, error) +diff --git a/components/engine/api/server/router/container/inspect.go b/components/engine/api/server/router/container/inspect.go +index 5c78d15bc9..cb6eb50251 100644 +--- a/components/engine/api/server/router/container/inspect.go ++++ b/components/engine/api/server/router/container/inspect.go +@@ -3,6 +3,7 @@ package container // import "github.com/docker/docker/api/server/router/containe + import ( + "context" + "net/http" ++ "strconv" + + "github.com/docker/docker/api/server/httputils" + ) +@@ -12,7 +13,8 @@ func (s *containerRouter) getContainersByName(ctx context.Context, w http.Respon + displaySize := httputils.BoolValue(r, "size") + + version := httputils.VersionFromContext(ctx) +- json, err := s.backend.ContainerInspect(vars["name"], displaySize, version) ++ timeout, _ := strconv.Atoi(r.Form.Get("t")) ++ json, err := s.backend.ContainerInspect(vars["name"], displaySize, version, timeout) + if err != nil { + return err + } +diff --git a/components/engine/container/state.go b/components/engine/container/state.go +index 7c2a1ec81c..91ea30a76e 100644 +--- a/components/engine/container/state.go ++++ b/components/engine/container/state.go +@@ -4,10 +4,10 @@ import ( + "context" + "errors" + "fmt" +- "sync" + "time" + + "github.com/docker/docker/api/types" ++ "github.com/docker/docker/pkg/trylock" + "github.com/docker/go-units" + ) + +@@ -15,7 +15,7 @@ import ( + // set the state. Container has an embed, which allows all of the + // functions defined against State to run against Container. + type State struct { +- sync.Mutex ++ trylock.TryMutex + // Note that `Running` and `Paused` are not mutually exclusive: + // When pausing a container (on Linux), the cgroups freezer is used to suspend + // all processes in the container. Freezing the process requires the process to +diff --git a/components/engine/daemon/cluster/executor/backend.go b/components/engine/daemon/cluster/executor/backend.go +index cfbc86ce36..c9ff4503bc 100644 +--- a/components/engine/daemon/cluster/executor/backend.go ++++ b/components/engine/daemon/cluster/executor/backend.go +@@ -41,7 +41,7 @@ type Backend interface { + ActivateContainerServiceBinding(containerName string) error + DeactivateContainerServiceBinding(containerName string) error + UpdateContainerServiceConfig(containerName string, serviceConfig *clustertypes.ServiceConfig) error +- ContainerInspectCurrent(name string, size bool) (*types.ContainerJSON, error) ++ ContainerInspectCurrent(name string, size bool, timeout int) (*types.ContainerJSON, error) + ContainerWait(ctx context.Context, name string, condition containerpkg.WaitCondition) (<-chan containerpkg.StateStatus, error) + ContainerRm(name string, config *types.ContainerRmConfig) error + ContainerKill(name string, sig uint64) error +diff --git a/components/engine/daemon/cluster/executor/container/adapter.go b/components/engine/daemon/cluster/executor/container/adapter.go +index 720b8447fc..3743cb6418 100644 +--- a/components/engine/daemon/cluster/executor/container/adapter.go ++++ b/components/engine/daemon/cluster/executor/container/adapter.go +@@ -351,7 +351,8 @@ func (c *containerAdapter) start(ctx context.Context) error { + } + + func (c *containerAdapter) inspect(ctx context.Context) (types.ContainerJSON, error) { +- cs, err := c.backend.ContainerInspectCurrent(c.container.name(), false) ++ timeout := -1 ++ cs, err := c.backend.ContainerInspectCurrent(c.container.name(), false, timeout) + if ctx.Err() != nil { + return types.ContainerJSON{}, ctx.Err() + } +diff --git a/components/engine/daemon/inspect.go b/components/engine/daemon/inspect.go +index 45a2154254..be8f6eff71 100644 +--- a/components/engine/daemon/inspect.go ++++ b/components/engine/daemon/inspect.go +@@ -19,25 +19,29 @@ import ( + // ContainerInspect returns low-level information about a + // container. Returns an error if the container cannot be found, or if + // there is an error getting the data. +-func (daemon *Daemon) ContainerInspect(name string, size bool, version string) (interface{}, error) { ++func (daemon *Daemon) ContainerInspect(name string, size bool, version string, timeout int) (interface{}, error) { + switch { + case versions.LessThan(version, "1.20"): + return daemon.containerInspectPre120(name) + case versions.Equal(version, "1.20"): + return daemon.containerInspect120(name) + } +- return daemon.ContainerInspectCurrent(name, size) ++ return daemon.ContainerInspectCurrent(name, size, timeout) + } + + // ContainerInspectCurrent returns low-level information about a + // container in a most recent api version. +-func (daemon *Daemon) ContainerInspectCurrent(name string, size bool) (*types.ContainerJSON, error) { ++func (daemon *Daemon) ContainerInspectCurrent(name string, size bool, timeout int) (*types.ContainerJSON, error) { + container, err := daemon.GetContainer(name) + if err != nil { + return nil, err + } + +- container.Lock() ++ // The unit of timeout is seconds, and the unit of frequency is milliseconds. ++ lockTimeoutSuccess := container.TryLockTimeout(timeout, 100) ++ if lockTimeoutSuccess == false { ++ return nil, fmt.Errorf("Container %s inspect failed due to trylock timeout for %ds.", name, timeout) ++ } + + base, err := daemon.getInspectData(container) + if err != nil { +diff --git a/components/engine/pkg/trylock/mutex.go b/components/engine/pkg/trylock/mutex.go +new file mode 100644 +index 0000000000..18b3c3cca7 +--- /dev/null ++++ b/components/engine/pkg/trylock/mutex.go +@@ -0,0 +1,61 @@ ++package trylock ++ ++import ( ++ "sync" ++ "sync/atomic" ++ "time" ++ "unsafe" ++) ++ ++const mutexLocked = 1 << iota ++ ++// Mutex is simple sync.Mutex + ability to try to Lock. ++type TryMutex struct { ++ in sync.Mutex ++} ++ ++// Lock locks m. ++// If the lock is already in use, the calling goroutine ++// blocks until the mutex is available. ++func (m *TryMutex) Lock() { ++ m.in.Lock() ++} ++ ++// Unlock unlocks m. ++// It is a run-time error if m is not locked on entry to Unlock. ++// ++// A locked Mutex is not associated with a particular goroutine. ++// It is allowed for one goroutine to lock a Mutex and then ++// arrange for another goroutine to unlock it. ++func (m *TryMutex) Unlock() { ++ m.in.Unlock() ++} ++ ++// TryLock tries to lock m. It returns true in case of success, false otherwise. ++func (m *TryMutex) TryLock() bool { ++ return atomic.CompareAndSwapInt32((*int32)(unsafe.Pointer(&m.in)), 0, mutexLocked) ++} ++ ++// TryLockTimeout tries to lock m at a certain frequency for a certain period of time. ++// It returns true in case of success, false otherwise. ++func (m *TryMutex) TryLockTimeout(timeout int, frequency int) bool { ++ if timeout <= 0 { ++ m.Lock() ++ return true ++ } else { ++ timer := time.After(time.Second * time.Duration(timeout)) ++ result := false ++ for { ++ select { ++ case <-timer: ++ return result ++ default: ++ } ++ result = atomic.CompareAndSwapInt32((*int32)(unsafe.Pointer(&m.in)), 0, mutexLocked) ++ if result { ++ return result ++ } ++ time.Sleep(time.Millisecond * time.Duration(frequency)) ++ } ++ } ++} +-- +2.17.1 + diff --git a/patch/0019-dfx-print-container-name-and-id-when-create-d.patch b/patch/0019-dfx-print-container-name-and-id-when-create-d.patch new file mode 100644 index 0000000..c7fbdb4 --- /dev/null +++ b/patch/0019-dfx-print-container-name-and-id-when-create-d.patch @@ -0,0 +1,31 @@ +From 9e05ad46f060ab47559cd1566a95ec579fbc08ac Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Sat, 19 Jan 2019 11:18:35 +0800 +Subject: [PATCH 019/111] dfx: print container name and id when create + done + +reason:print container name and id when create done + +Change-Id: I62932133c2a28e24adb54a40d16e2792a0772185 +Signed-off-by: dengguangxing +Signed-off-by: zhangsong34 +Signed-off-by: lujingxiao +--- + components/engine/daemon/create.go | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/components/engine/daemon/create.go b/components/engine/daemon/create.go +index 1afb1bebea..565e9dc022 100644 +--- a/components/engine/daemon/create.go ++++ b/components/engine/daemon/create.go +@@ -74,6 +74,7 @@ func (daemon *Daemon) containerCreate(params types.ContainerCreateConfig, manage + return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, err + } + containerActions.WithValues("create").UpdateSince(start) ++ logrus.Infof("Container create done(Name: %s ID: %s)", container.Name, container.ID) + + return containertypes.ContainerCreateCreatedBody{ID: container.ID, Warnings: warnings}, nil + } +-- +2.17.1 + diff --git a/patch/0020-cleanup-remove-redundant-files-in-graphdriver.patch b/patch/0020-cleanup-remove-redundant-files-in-graphdriver.patch new file mode 100644 index 0000000..e2e3a4d --- /dev/null +++ b/patch/0020-cleanup-remove-redundant-files-in-graphdriver.patch @@ -0,0 +1,441 @@ +From b555ed1bb121b0740665fb0db9f7fea3b339f98c Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Sat, 19 Jan 2019 11:22:22 +0800 +Subject: [PATCH 020/111] cleanup: remove redundant files in + graphdriver and mount dir + +reason:remove redundant files in graphdriver and mount dir + +Change-Id: Ie75b78ac1e288d2c909dcd446636d16b1dd60363 +Signed-off-by: yangshukui +Signed-off-by: zhangsong34 +Signed-off-by: lujingxiao +--- + components/engine/daemon/daemon.go | 28 ++++++- + .../engine/daemon/graphdriver/aufs/aufs.go | 5 ++ + .../engine/daemon/graphdriver/btrfs/btrfs.go | 5 ++ + .../daemon/graphdriver/devmapper/driver.go | 5 ++ + .../engine/daemon/graphdriver/driver.go | 2 + + .../daemon/graphdriver/overlay/overlay.go | 5 ++ + .../daemon/graphdriver/overlay2/overlay.go | 19 +++++ + components/engine/daemon/graphdriver/proxy.go | 5 ++ + .../engine/daemon/graphdriver/vfs/driver.go | 5 ++ + .../engine/daemon/graphdriver/zfs/zfs.go | 5 ++ + components/engine/daemon/images/service.go | 4 + + .../engine/distribution/xfer/download_test.go | 4 + + components/engine/layer/layer.go | 1 + + components/engine/layer/layer_store.go | 73 +++++++++++++++++++ + components/engine/pkg/ioutils/fswriters.go | 36 +++++++++ + components/engine/reference/store.go | 1 + + 16 files changed, 201 insertions(+), 2 deletions(-) + +diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go +index f7635f27cc..4546587369 100644 +--- a/components/engine/daemon/daemon.go ++++ b/components/engine/daemon/daemon.go +@@ -262,7 +262,15 @@ func (daemon *Daemon) restore() error { + id := v.Name() + container, err := daemon.load(id) + if err != nil { +- logrus.Errorf("Failed to load container %v: %v", id, err) ++ logrus.Errorf("Failed to load container %v: %v. Try to remove it", id, err) ++ cdir := filepath.Join(daemon.repository, id) ++ // to make sure we are not in fd exhausted state ++ if !strings.Contains(err.Error(), "too many open files") { ++ logrus.Warnf("remove invalid container data: %s", cdir) ++ if err := system.EnsureRemoveAll(cdir); err != nil { ++ logrus.Warnf("remove %s error: %v", cdir, err) ++ } ++ } + continue + } + if !system.IsOSSupported(container.OS) { +@@ -274,7 +282,12 @@ func (daemon *Daemon) restore() error { + if (container.Driver == "" && currentDriverForContainerOS == "aufs") || container.Driver == currentDriverForContainerOS { + rwlayer, err := daemon.imageService.GetLayerByID(container.ID, container.OS) + if err != nil { +- logrus.Errorf("Failed to load container mount %v: %v", id, err) ++ logrus.Errorf("Failed to load container mount %v: %v. Try to remove it", id, err) ++ cdir := filepath.Join(daemon.repository, id) ++ logrus.Warnf("remove invalid container data: %s", cdir) ++ if err := system.EnsureRemoveAll(cdir); err != nil { ++ logrus.Warnf("remove %s error: %v", cdir, err) ++ } + continue + } + container.RWLayer = rwlayer +@@ -472,6 +485,17 @@ func (daemon *Daemon) restore() error { + }(c) + } + wg.Wait() ++ ++ containerIDs := make(map[string]struct{}) ++ for cid, _ := range containers { ++ containerIDs[cid] = struct{}{} ++ } ++ ++ err = daemon.imageService.LayerStoreForOS(runtime.GOOS).CleanupRedundant(containerIDs) ++ if err != nil { ++ logrus.Errorf("cleanup redundant IDs in layerStore failed %s", err) ++ } ++ + daemon.netController, err = daemon.initNetworkController(daemon.configStore, activeSandboxes) + if err != nil { + return fmt.Errorf("Error initializing network controller: %v", err) +diff --git a/components/engine/daemon/graphdriver/aufs/aufs.go b/components/engine/daemon/graphdriver/aufs/aufs.go +index 114aa9a615..303138d48a 100644 +--- a/components/engine/daemon/graphdriver/aufs/aufs.go ++++ b/components/engine/daemon/graphdriver/aufs/aufs.go +@@ -230,6 +230,11 @@ func (a *Driver) Exists(id string) bool { + return true + } + ++// GetAll not implemented ++func (a *Driver) GetAll() []string { ++ return []string{} ++} ++ + // CreateReadWrite creates a layer that is writable for use as a container + // file system. + func (a *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error { +diff --git a/components/engine/daemon/graphdriver/btrfs/btrfs.go b/components/engine/daemon/graphdriver/btrfs/btrfs.go +index 7ce7edef36..d04ce10be9 100644 +--- a/components/engine/daemon/graphdriver/btrfs/btrfs.go ++++ b/components/engine/daemon/graphdriver/btrfs/btrfs.go +@@ -167,6 +167,11 @@ func (d *Driver) GetMetadata(id string) (map[string]string, error) { + return nil, nil + } + ++// GetAll not implemented ++func (a *Driver) GetAll() []string { ++ return []string{} ++} ++ + // Cleanup unmounts the home directory. + func (d *Driver) Cleanup() error { + err := d.subvolDisableQuota() +diff --git a/components/engine/daemon/graphdriver/devmapper/driver.go b/components/engine/daemon/graphdriver/devmapper/driver.go +index 623843f852..a56b26bc8f 100644 +--- a/components/engine/daemon/graphdriver/devmapper/driver.go ++++ b/components/engine/daemon/graphdriver/devmapper/driver.go +@@ -126,6 +126,11 @@ func (d *Driver) GetMetadata(id string) (map[string]string, error) { + return metadata, nil + } + ++// GetAll not implemented ++func (a *Driver) GetAll() []string { ++ return []string{} ++} ++ + // Cleanup unmounts a device. + func (d *Driver) Cleanup() error { + err := d.DeviceSet.Shutdown(d.home) +diff --git a/components/engine/daemon/graphdriver/driver.go b/components/engine/daemon/graphdriver/driver.go +index a9e1957393..672257a9b5 100644 +--- a/components/engine/daemon/graphdriver/driver.go ++++ b/components/engine/daemon/graphdriver/driver.go +@@ -78,6 +78,8 @@ type ProtoDriver interface { + // held by the driver, e.g., unmounting all layered filesystems + // known to this driver. + Cleanup() error ++ // GetAll returns all the mountid exists in this driver ++ GetAll() []string + } + + // DiffDriver is the interface to use to implement graph diffs +diff --git a/components/engine/daemon/graphdriver/overlay/overlay.go b/components/engine/daemon/graphdriver/overlay/overlay.go +index 08c05e192f..d59a6dfc83 100644 +--- a/components/engine/daemon/graphdriver/overlay/overlay.go ++++ b/components/engine/daemon/graphdriver/overlay/overlay.go +@@ -264,6 +264,11 @@ func (d *Driver) GetMetadata(id string) (map[string]string, error) { + return metadata, nil + } + ++// GetAll not implemented ++func (a *Driver) GetAll() []string { ++ return []string{} ++} ++ + // Cleanup any state created by overlay which should be cleaned when daemon + // is being shutdown. For now, we just have to unmount the bind mounted + // we had created. +diff --git a/components/engine/daemon/graphdriver/overlay2/overlay.go b/components/engine/daemon/graphdriver/overlay2/overlay.go +index b969582eb3..7290616bae 100644 +--- a/components/engine/daemon/graphdriver/overlay2/overlay.go ++++ b/components/engine/daemon/graphdriver/overlay2/overlay.go +@@ -507,6 +507,25 @@ func (d *Driver) dir(id string) string { + return path.Join(d.home, id) + } + ++func (d *Driver) GetAll() []string { ++ var ids []string ++ ++ fs, err := ioutil.ReadDir(d.home) ++ if err != nil { ++ logrus.Errorf("open directory(%s) failed: %s", d.home, err) ++ return ids ++ } ++ ++ for _, f := range fs { ++ if len(f.Name()) >= 64 { ++ ids = append(ids, f.Name()) ++ } ++ } ++ ++ return ids ++ ++} ++ + func (d *Driver) getLowerDirs(id string) ([]string, error) { + var lowersArray []string + lowers, err := ioutil.ReadFile(path.Join(d.dir(id), lowerFile)) +diff --git a/components/engine/daemon/graphdriver/proxy.go b/components/engine/daemon/graphdriver/proxy.go +index cb350d8074..6c132d40a8 100644 +--- a/components/engine/daemon/graphdriver/proxy.go ++++ b/components/engine/daemon/graphdriver/proxy.go +@@ -91,6 +91,11 @@ func (d *graphDriverProxy) Capabilities() Capabilities { + return d.caps + } + ++// GetAll not implemented ++func (d *graphDriverProxy) GetAll() []string { ++ return []string{} ++} ++ + func (d *graphDriverProxy) CreateReadWrite(id, parent string, opts *CreateOpts) error { + return d.create("GraphDriver.CreateReadWrite", id, parent, opts) + } +diff --git a/components/engine/daemon/graphdriver/vfs/driver.go b/components/engine/daemon/graphdriver/vfs/driver.go +index 33e6bf6cc9..8c2947d9ee 100644 +--- a/components/engine/daemon/graphdriver/vfs/driver.go ++++ b/components/engine/daemon/graphdriver/vfs/driver.go +@@ -64,6 +64,11 @@ func (d *Driver) GetMetadata(id string) (map[string]string, error) { + return nil, nil + } + ++// GetAll not implemented ++func (a *Driver) GetAll() []string { ++ return []string{} ++} ++ + // Cleanup is used to implement graphdriver.ProtoDriver. There is no cleanup required for this driver. + func (d *Driver) Cleanup() error { + return nil +diff --git a/components/engine/daemon/graphdriver/zfs/zfs.go b/components/engine/daemon/graphdriver/zfs/zfs.go +index 8a798778d2..e1e2d0d823 100644 +--- a/components/engine/daemon/graphdriver/zfs/zfs.go ++++ b/components/engine/daemon/graphdriver/zfs/zfs.go +@@ -227,6 +227,11 @@ func (d *Driver) GetMetadata(id string) (map[string]string, error) { + }, nil + } + ++// GetAll not implemented ++func (a *Driver) GetAll() []string { ++ return []string{} ++} ++ + func (d *Driver) cloneFilesystem(name, parentName string) error { + snapshotName := fmt.Sprintf("%d", time.Now().Nanosecond()) + parentDataset := zfs.Dataset{Name: parentName} +diff --git a/components/engine/daemon/images/service.go b/components/engine/daemon/images/service.go +index e8df5cb649..8d187e2603 100644 +--- a/components/engine/daemon/images/service.go ++++ b/components/engine/daemon/images/service.go +@@ -177,6 +177,10 @@ func (i *ImageService) GraphDriverForOS(os string) string { + return i.layerStores[os].DriverName() + } + ++func (i *ImageService) LayerStoreForOS(os string) layer.Store { ++ return i.layerStores[os] ++} ++ + // ReleaseLayer releases a layer allowing it to be removed + // called from delete.go Daemon.cleanupContainer(), and Daemon.containerExport() + func (i *ImageService) ReleaseLayer(rwlayer layer.RWLayer, containerOS string) error { +diff --git a/components/engine/distribution/xfer/download_test.go b/components/engine/distribution/xfer/download_test.go +index 4ab3705af6..91153591ed 100644 +--- a/components/engine/distribution/xfer/download_test.go ++++ b/components/engine/distribution/xfer/download_test.go +@@ -150,6 +150,10 @@ func (ls *mockLayerStore) DriverStatus() [][2]string { + return [][2]string{} + } + ++func (ls *mockLayerStore) CleanupRedundant(cids map[string]struct{}) error { ++ return nil ++} ++ + func (ls *mockLayerStore) DriverName() string { + return "mock" + } +diff --git a/components/engine/layer/layer.go b/components/engine/layer/layer.go +index d0c7fa8608..425006854d 100644 +--- a/components/engine/layer/layer.go ++++ b/components/engine/layer/layer.go +@@ -193,6 +193,7 @@ type Store interface { + Cleanup() error + DriverStatus() [][2]string + DriverName() string ++ CleanupRedundant(map[string]struct{}) error + } + + // DescribableStore represents a layer store capable of storing +diff --git a/components/engine/layer/layer_store.go b/components/engine/layer/layer_store.go +index bc3e8719fc..6a568e9d9b 100644 +--- a/components/engine/layer/layer_store.go ++++ b/components/engine/layer/layer_store.go +@@ -5,6 +5,7 @@ import ( + "fmt" + "io" + "io/ioutil" ++ "strings" + "sync" + + "github.com/docker/distribution" +@@ -460,6 +461,78 @@ func (ls *layerStore) releaseLayer(l *roLayer) ([]Metadata, error) { + } + } + ++// CleanupRedundant will cleanup useless dirs in image/mount and driver ++func (ls *layerStore) CleanupRedundant(cs map[string]struct{}) error { ++ cids, err := ls.getAndCleanCacheIDs(cs) ++ if err != nil { ++ return fmt.Errorf("get cacheIDs failed: %s", err) ++ } ++ ++ ids := ls.driver.GetAll() ++ ++ for _, id := range ids { ++ if _, ok := cids[id]; !ok { ++ logrus.Warnf("remove redundant cacheID: %s", id) ++ ls.driver.Remove(id) ++ } ++ } ++ return nil ++} ++ ++func (ls *layerStore) getAndCleanCacheIDs(cs map[string]struct{}) (map[string]struct{}, error) { ++ cids := make(map[string]struct{}) ++ ++ ids, mountids, err := ls.store.List() ++ if err != nil { ++ return cids, fmt.Errorf("failed to get mount list from store: %s", err) ++ } ++ ++ for _, id := range ids { ++ cid, err := ls.store.GetCacheID(id) ++ if err != nil { ++ logrus.Errorf("failed to get cache id for %s: %s", id, err) ++ // just return if we are not in fd exhausted state ++ if strings.Contains(err.Error(), "too many open files") { ++ return cids, err ++ } ++ continue ++ } ++ cids[cid] = struct{}{} ++ } ++ ++ for _, mid := range mountids { ++ // if mid not exist in current container list, just remove it ++ if _, ok := cs[mid]; !ok { ++ logrus.Warnf("remove redundant mountID: %s", mid) ++ ls.store.RemoveMount(mid) ++ continue ++ } ++ mountID, err := ls.store.GetMountID(mid) ++ if err != nil { ++ logrus.Errorf("failed to get mount id for %s: %s", mid, err) ++ // just return if we are not in fd exhausted state ++ if strings.Contains(err.Error(), "too many open files") { ++ return cids, err ++ } ++ } else { ++ cids[mountID] = struct{}{} ++ } ++ ++ initID, err := ls.store.GetInitID(mid) ++ if err != nil { ++ logrus.Errorf("failed to get init id for %s: %s", mid, err) ++ // just return if we are not in fd exhausted state ++ if strings.Contains(err.Error(), "too many open files") { ++ return cids, err ++ } ++ continue ++ } ++ cids[initID] = struct{}{} ++ } ++ ++ return cids, nil ++} ++ + func (ls *layerStore) Release(l Layer) ([]Metadata, error) { + ls.layerL.Lock() + defer ls.layerL.Unlock() +diff --git a/components/engine/pkg/ioutils/fswriters.go b/components/engine/pkg/ioutils/fswriters.go +index 534d66ac26..093f11ad8e 100644 +--- a/components/engine/pkg/ioutils/fswriters.go ++++ b/components/engine/pkg/ioutils/fswriters.go +@@ -5,6 +5,9 @@ import ( + "io/ioutil" + "os" + "path/filepath" ++ "strings" ++ ++ "github.com/sirupsen/logrus" + ) + + // NewAtomicFileWriter returns WriteCloser so that writing to it writes to a +@@ -27,6 +30,39 @@ func NewAtomicFileWriter(filename string, perm os.FileMode) (io.WriteCloser, err + }, nil + } + ++func CleanupTmpFilesRecursive(rootDir string) { ++ var removals []string ++ filepath.Walk(rootDir, func(path string, f os.FileInfo, err error) error { ++ if strings.HasPrefix(f.Name(), ".tmp-") { ++ removals = append(removals, path) ++ } ++ return nil ++ }) ++ ++ for _, r := range removals { ++ os.RemoveAll(r) ++ } ++} ++ ++// CleanupAtomicFile cleanup redundant atomic files ++func CleanupAtomicFile(filename string) error { ++ baseName := ".tmp-" + filepath.Base(filename) ++ dir := filepath.Dir(filename) ++ fs, err := ioutil.ReadDir(dir) ++ if err != nil { ++ logrus.Errorf("open directory(%s) failed: %s", dir, err) ++ return err ++ } ++ ++ for _, f := range fs { ++ if strings.Contains(f.Name(), baseName) { ++ logrus.Warnf("Remove temporary file: %s", filepath.Join(dir, f.Name())) ++ os.RemoveAll(filepath.Join(dir, f.Name())) ++ } ++ } ++ return nil ++} ++ + // AtomicWriteFile atomically writes data to a file named by filename. + func AtomicWriteFile(filename string, data []byte, perm os.FileMode) error { + f, err := NewAtomicFileWriter(filename, perm) +diff --git a/components/engine/reference/store.go b/components/engine/reference/store.go +index b01051bf58..e54f772b5e 100644 +--- a/components/engine/reference/store.go ++++ b/components/engine/reference/store.go +@@ -81,6 +81,7 @@ func NewReferenceStore(jsonPath string) (Store, error) { + Repositories: make(map[string]repository), + referencesByIDCache: make(map[digest.Digest]map[string]reference.Named), + } ++ ioutils.CleanupAtomicFile(abspath) + // Load the json file if it exists, otherwise create it. + if err := store.reload(); os.IsNotExist(err) { + if err := store.save(); err != nil { +-- +2.17.1 + diff --git a/patch/0021-umask-support-specify-umask.patch b/patch/0021-umask-support-specify-umask.patch new file mode 100644 index 0000000..ee22ec4 --- /dev/null +++ b/patch/0021-umask-support-specify-umask.patch @@ -0,0 +1,125 @@ +From 79b46d05b185bf8df96cabb2a121186cd2f121c3 Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Sat, 19 Jan 2019 11:22:35 +0800 +Subject: [PATCH 021/111] umask: support specify umask + +reason: support specify umask. +Umask can be 0022 or 0027(default) by specify umask when +start container by command `docker create/run` or start +daemon by command `dockerd`. For example: +$ dockerd --annotation native.umask=normal +$ dockerd --annotation native.umask=secure +$ docker run --exec-opt native.umask=normal +$ docker run --exec-opt native.umask=secure +`normal` reparent umask is 0022, `secure` +reparent umask is 0027. + +Change-Id: Iba07a884b733b411e5268d7ecaa22b9aa327ac3c +Signed-off-by: wangfengtu +Signed-off-by: lujingxiao +--- + components/engine/daemon/create.go | 21 +++++++++++++++- + components/engine/daemon/daemon_unix.go | 33 +++++++++++++++++++++++++ + 2 files changed, 53 insertions(+), 1 deletion(-) + +diff --git a/components/engine/daemon/create.go b/components/engine/daemon/create.go +index 565e9dc022..fa000c2208 100644 +--- a/components/engine/daemon/create.go ++++ b/components/engine/daemon/create.go +@@ -79,6 +79,22 @@ func (daemon *Daemon) containerCreate(params types.ContainerCreateConfig, manage + return containertypes.ContainerCreateCreatedBody{ID: container.ID, Warnings: warnings}, nil + } + ++func (daemon *Daemon) setUmask(c *containertypes.Config) error { ++ // Use option native.umask passed by command create/run if specified, ++ // otherwise use daemon's native.umask option. ++ if val, ok := c.Annotations["native.umask"]; ok { ++ if val != umaskNormal && val != umaskSecure { ++ return fmt.Errorf("native.umask option %s not supported", val) ++ } ++ } else if UsingNormalUmask(daemon.configStore) { ++ c.Annotations["native.umask"] = umaskNormal ++ } else { ++ c.Annotations["native.umask"] = umaskSecure ++ } ++ ++ return nil ++} ++ + // Create creates a new container from the given configuration with a given name. + func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (retC *container.Container, retErr error) { + var ( +@@ -162,8 +178,11 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) ( + } + container.RWLayer = rwLayer + +- rootIDs := daemon.idMapping.RootPair() ++ if err := daemon.setUmask(params.Config); err != nil { ++ return nil, err ++ } + ++ rootIDs := daemon.idMapping.RootPair() + if err := idtools.MkdirAndChown(container.Root, 0700, rootIDs); err != nil { + return nil, err + } +diff --git a/components/engine/daemon/daemon_unix.go b/components/engine/daemon/daemon_unix.go +index 5b390d2db1..8ffdd0009a 100644 +--- a/components/engine/daemon/daemon_unix.go ++++ b/components/engine/daemon/daemon_unix.go +@@ -77,6 +77,10 @@ const ( + // DefaultRuntimeName is the default runtime to be used by + // containerd if none is specified + DefaultRuntimeName = "runc" ++ ++ // constant for umasks in containers. normal: 0022, secure(default): 0027 ++ umaskNormal = "normal" ++ umaskSecure = "secure" + ) + + type containerGetter interface { +@@ -581,6 +585,32 @@ func UsingSystemd(config *config.Config) bool { + return getCD(config) == cgroupSystemdDriver + } + ++// getUmask gets the raw value of the native.umask option, if set. ++func getUmask(config *config.Config) string { ++ for _, option := range config.ExecOptions { ++ key, val, err := parsers.ParseKeyValueOpt(option) ++ if err != nil || !strings.EqualFold(key, "native.umask") { ++ continue ++ } ++ return val ++ } ++ return "" ++} ++ ++// VerifyNativeUmask validates native.umask ++func VerifyNativeUmask(config *config.Config) error { ++ umask := getUmask(config) ++ if umask == "" || umask == umaskNormal || umask == umaskSecure { ++ return nil ++ } ++ return fmt.Errorf("native.umask option %s not supported", umask) ++} ++ ++// UsingNormalUmask returns true if cli option includes native.umask=normal ++func UsingNormalUmask(config *config.Config) bool { ++ return getUmask(config) == umaskNormal ++} ++ + // verifyPlatformContainerSettings performs platform-specific validation of the + // hostconfig and config structures. + func (daemon *Daemon) verifyPlatformContainerSettings(hostConfig *containertypes.HostConfig, config *containertypes.Config, update bool) ([]string, error) { +@@ -737,6 +767,9 @@ func verifyDaemonSettings(conf *config.Config) error { + return fmt.Errorf("cgroup-parent for systemd cgroup should be a valid slice named as \"xxx.slice\"") + } + } ++ if err := VerifyNativeUmask(conf); err != nil { ++ return err ++ } + + if conf.DefaultRuntime == "" { + conf.DefaultRuntime = config.StockRuntimeName +-- +2.17.1 + diff --git a/patch/0022-umask-fix-nil-pointer-on-c.Annotations-in-set.patch b/patch/0022-umask-fix-nil-pointer-on-c.Annotations-in-set.patch new file mode 100644 index 0000000..c0b5da0 --- /dev/null +++ b/patch/0022-umask-fix-nil-pointer-on-c.Annotations-in-set.patch @@ -0,0 +1,42 @@ +From 412b8a34a2aa40ddf6f9b507142c4793922cedf5 Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Sat, 19 Jan 2019 11:22:46 +0800 +Subject: [PATCH 022/111] umask: fix nil pointer on c.Annotations in + setUmask + +reason: c.Annotations should be check before using in setUmask(). +When "/create" request is sent via restful api, the c.Annotations +is nil, so **nil map** happens in setUmask. + +Change-Id: Idafa2d8d1f54c1ebc34a6380d64c1cd7efad0266 +Signed-off-by: lujingxiao +--- + components/engine/daemon/create.go | 11 ++++++++--- + 1 file changed, 8 insertions(+), 3 deletions(-) + +diff --git a/components/engine/daemon/create.go b/components/engine/daemon/create.go +index fa000c2208..b57b01eacc 100644 +--- a/components/engine/daemon/create.go ++++ b/components/engine/daemon/create.go +@@ -86,10 +86,15 @@ func (daemon *Daemon) setUmask(c *containertypes.Config) error { + if val != umaskNormal && val != umaskSecure { + return fmt.Errorf("native.umask option %s not supported", val) + } +- } else if UsingNormalUmask(daemon.configStore) { +- c.Annotations["native.umask"] = umaskNormal + } else { +- c.Annotations["native.umask"] = umaskSecure ++ if c.Annotations == nil { ++ c.Annotations = make(map[string]string) ++ } ++ if UsingNormalUmask(daemon.configStore) { ++ c.Annotations["native.umask"] = umaskNormal ++ } else { ++ c.Annotations["native.umask"] = umaskSecure ++ } + } + + return nil +-- +2.17.1 + diff --git a/patch/0023-prjquota-fix-syscall-bugs-in-projectquota.patch b/patch/0023-prjquota-fix-syscall-bugs-in-projectquota.patch new file mode 100644 index 0000000..ef984df --- /dev/null +++ b/patch/0023-prjquota-fix-syscall-bugs-in-projectquota.patch @@ -0,0 +1,65 @@ +From d864d32460063a25ef5a408c596b40555a062646 Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Sat, 19 Jan 2019 15:02:39 +0800 +Subject: [PATCH 023/111] prjquota: fix syscall bugs in projectquota + +reason: fix syscall bugs in projectquota, which is introduced +when cherry-picked. + +Change-Id: I4496f2b8fcdcd16eb34584b435a9ef9434639cee +Signed-off-by: lujingxiao +--- + .../engine/daemon/graphdriver/quota/projectquota.go | 9 ++++----- + 1 file changed, 4 insertions(+), 5 deletions(-) + +diff --git a/components/engine/daemon/graphdriver/quota/projectquota.go b/components/engine/daemon/graphdriver/quota/projectquota.go +index 7d879eb81d..7f2fa2fe70 100644 +--- a/components/engine/daemon/graphdriver/quota/projectquota.go ++++ b/components/engine/daemon/graphdriver/quota/projectquota.go +@@ -79,7 +79,6 @@ import ( + "path" + "path/filepath" + "sync" +- "syscall" + "unsafe" + + rsystem "github.com/opencontainers/runc/libcontainer/system" +@@ -234,7 +233,7 @@ func (q *Control) SetQuota(targetPath string, quota Quota) error { + // + err := setProjectID(targetPath, projectID) + if err != nil { +- q.lock.Lock() ++ q.lock.Unlock() + return err + } + +@@ -312,7 +311,7 @@ func (q *Ext4Quota) SetProjectQuota(backingFsBlockDev string, projectID uint32, + var cs = C.CString(backingFsBlockDev) + defer C.free(unsafe.Pointer(cs)) + +- _, _, errno := syscall.Syscall6(syscall.SYS_QUOTACTL, C.Q_SETPQUOTA, ++ _, _, errno := unix.Syscall6(unix.SYS_QUOTACTL, C.Q_SETPQUOTA, + uintptr(unsafe.Pointer(cs)), uintptr(C.__u32(projectID)), + uintptr(unsafe.Pointer(&d)), 0, 0) + if errno != 0 { +@@ -330,7 +329,7 @@ func (q *Ext4Quota) GetProjectQuota(backingFsBlockDev string, projectID uint32, + var cs = C.CString(backingFsBlockDev) + defer C.free(unsafe.Pointer(cs)) + +- _, _, errno := syscall.Syscall6(syscall.SYS_QUOTACTL, C.Q_SETPQUOTA, ++ _, _, errno := unix.Syscall6(unix.SYS_QUOTACTL, C.Q_GETPQUOTA, + uintptr(unsafe.Pointer(cs)), uintptr(C.__u32(projectID)), + uintptr(unsafe.Pointer(&d)), 0, 0) + if errno != 0 { +@@ -350,7 +349,7 @@ func getQuotaStat(backingFsBlockDev string) (int, error) { + + var cs = C.CString(backingFsBlockDev) + defer C.free(unsafe.Pointer(cs)) +- _, _, errno := syscall.Syscall6(syscall.SYS_QUOTACTL, C.Q_XGETPQSTAT, ++ _, _, errno := unix.Syscall6(unix.SYS_QUOTACTL, C.Q_XGETPQSTAT, + uintptr(unsafe.Pointer(cs)), 0, + uintptr(unsafe.Pointer(&info)), 0, 0) + if errno != 0 { +-- +2.17.1 + diff --git a/patch/0024-runtime-spec-Compatibility-modifications-fo.patch b/patch/0024-runtime-spec-Compatibility-modifications-fo.patch new file mode 100644 index 0000000..cc3f8c8 --- /dev/null +++ b/patch/0024-runtime-spec-Compatibility-modifications-fo.patch @@ -0,0 +1,51 @@ +From 3de51170031133bcd8d6aefe022d3fd26287c8c0 Mon Sep 17 00:00:00 2001 +From: leizhongkai +Date: Sat, 19 Jan 2019 16:45:43 +0800 +Subject: [PATCH 024/111] runtime-spec: Compatibility modifications + for runc-1.0.0-rc3 about `struct LinuxBlockIO` + +reason:Compatibility modifications for runc-1.0.0-rc3 about `struct LinuxBlockIO` + +Change-Id: If2389709d4639b5e9d61a9b853a8f220ef6e3884 +Signed-off-by: leizhongkai +--- + .../runtime-spec/specs-go/config.go | 18 +++++++++--------- + 2 files changed, 10 insertions(+), 9 deletions(-) + +diff --git a/components/engine/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go b/components/engine/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go +index 46049b3bfa..aab7b8a098 100644 +--- a/components/engine/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go ++++ b/components/engine/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go +@@ -256,20 +256,20 @@ type LinuxThrottleDevice struct { + + // LinuxBlockIO for Linux cgroup 'blkio' resource management + type LinuxBlockIO struct { +- // Specifies per cgroup weight +- Weight *uint16 `json:"weight,omitempty"` +- // Specifies tasks' weight in the given cgroup while competing with the cgroup's child cgroups, CFQ scheduler only +- LeafWeight *uint16 `json:"leafWeight,omitempty"` ++ // 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 []LinuxWeightDevice `json:"weightDevice,omitempty"` ++ WeightDevice []LinuxWeightDevice `json:"blkioWeightDevice,omitempty"` + // IO read rate limit per cgroup per device, bytes per second +- ThrottleReadBpsDevice []LinuxThrottleDevice `json:"throttleReadBpsDevice,omitempty"` ++ ThrottleReadBpsDevice []LinuxThrottleDevice `json:"blkioThrottleReadBpsDevice,omitempty"` + // IO write rate limit per cgroup per device, bytes per second +- ThrottleWriteBpsDevice []LinuxThrottleDevice `json:"throttleWriteBpsDevice,omitempty"` ++ ThrottleWriteBpsDevice []LinuxThrottleDevice `json:"blkioThrottleWriteBpsDevice,omitempty"` + // IO read rate limit per cgroup per device, IO per second +- ThrottleReadIOPSDevice []LinuxThrottleDevice `json:"throttleReadIOPSDevice,omitempty"` ++ ThrottleReadIOPSDevice []LinuxThrottleDevice `json:"blkioThrottleReadIOPSDevice,omitempty"` + // IO write rate limit per cgroup per device, IO per second +- ThrottleWriteIOPSDevice []LinuxThrottleDevice `json:"throttleWriteIOPSDevice,omitempty"` ++ ThrottleWriteIOPSDevice []LinuxThrottleDevice `json:"blkioThrottleWriteIOPSDevice,omitempty"` + } + + // LinuxMemory for Linux cgroup 'memory' resource management +-- +2.17.1 + diff --git a/patch/0025-resource-limit-enable-unlimited-usage-of-memo.patch b/patch/0025-resource-limit-enable-unlimited-usage-of-memo.patch new file mode 100644 index 0000000..998b1ef --- /dev/null +++ b/patch/0025-resource-limit-enable-unlimited-usage-of-memo.patch @@ -0,0 +1,44 @@ +From d0625f253c9cfd62e043f1799b9f9c2a0e2fdb21 Mon Sep 17 00:00:00 2001 +From: caihaomin +Date: Sun, 20 Jan 2019 13:59:45 +0800 +Subject: [PATCH 025/111] resource-limit: enable unlimited usage of + memory compoents + +reason:enable unlimited usage of memory compoents + +Change-Id: I24871d1b2ef1fe20277d5e5c721d1297fa5fd6cf +Signed-off-by: caihaomin +--- + components/engine/container/container_unix.go | 2 +- + components/engine/daemon/update_linux.go | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/components/engine/container/container_unix.go b/components/engine/container/container_unix.go +index ed664f3eec..5a21b8c483 100644 +--- a/components/engine/container/container_unix.go ++++ b/components/engine/container/container_unix.go +@@ -323,7 +323,7 @@ func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfi + if resources.Memory != 0 { + // if memory limit smaller than already set memoryswap limit and doesn't + // update the memoryswap limit, then error out. +- if resources.Memory > cResources.MemorySwap && resources.MemorySwap == 0 { ++ if cResources.MemorySwap > 0 && resources.Memory > cResources.MemorySwap && resources.MemorySwap == 0 { + return conflictingUpdateOptions("Memory limit should be smaller than already set memoryswap limit, update the memoryswap at the same time") + } + cResources.Memory = resources.Memory +diff --git a/components/engine/daemon/update_linux.go b/components/engine/daemon/update_linux.go +index 6a307eabc5..1a6f070b82 100644 +--- a/components/engine/daemon/update_linux.go ++++ b/components/engine/daemon/update_linux.go +@@ -46,7 +46,7 @@ func toContainerdResources(resources container.Resources) *libcontainerd.Resourc + Kernel: &resources.KernelMemory, + } + +- if resources.MemorySwap > 0 { ++ if resources.MemorySwap != 0 { + r.Memory.Swap = &resources.MemorySwap + } + +-- +2.17.1 + diff --git a/patch/0026-prjquota-use-dockerd-quota-size-when-docker.patch b/patch/0026-prjquota-use-dockerd-quota-size-when-docker.patch new file mode 100644 index 0000000..f7801e0 --- /dev/null +++ b/patch/0026-prjquota-use-dockerd-quota-size-when-docker.patch @@ -0,0 +1,37 @@ +From 0ff4db1fc19ecb6f9dedfaa8d0645a638bb7ab20 Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Sat, 19 Jan 2019 17:53:59 +0800 +Subject: [PATCH 026/111] prjquota: use dockerd quota size when + docker not specifies + +reason: if docker run not specifies quota size but dockerd has default +size, we should use the default value in dockerd. +EulerOS docker 1.11.2 and 17.06 has the same logic. But now, if docker +create/run not give --storage-opt, it will panic during parseStorageOpt, so +updating the handling logic. + +Change-Id: I52141c7f1caf5a2a4cbd9c00fbe709f09f7a412b +Signed-off-by: lujingxiao +--- + .../engine/daemon/graphdriver/overlay2/overlay.go | 11 ++++++----- + 1 file changed, 6 insertions(+), 5 deletions(-) + +diff --git a/components/engine/daemon/graphdriver/overlay2/overlay.go b/components/engine/daemon/graphdriver/overlay2/overlay.go +index 7290616bae..722d65b11a 100644 +--- a/components/engine/daemon/graphdriver/overlay2/overlay.go ++++ b/components/engine/daemon/graphdriver/overlay2/overlay.go +@@ -406,6 +406,11 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts) (retErr + return err + } + } ++ } else if d.options.quota.Size > 0 { ++ // docker run not specified quota size, but dockerd does, so limits it also ++ if err := d.quotaCtl.SetQuota(dir, d.options.quota); err != nil { ++ return err ++ } + } + + if err := idtools.MkdirAndChown(path.Join(dir, "diff"), 0755, root); err != nil { +-- +2.17.1 + diff --git a/patch/0027-oci-add-files_panic_enable-to-masked-path.patch b/patch/0027-oci-add-files_panic_enable-to-masked-path.patch new file mode 100644 index 0000000..a526dea --- /dev/null +++ b/patch/0027-oci-add-files_panic_enable-to-masked-path.patch @@ -0,0 +1,30 @@ +From 5370b3b9f2f4adfc10bbc3db5903b1a23e13f6c2 Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Wed, 9 Jan 2019 00:37:17 +0800 +Subject: [PATCH 027/111] oci: add files_panic_enable to masked path + +reason: cherry-pick commits to docker-18.09 + +reason:add files_panic_enable to masked path + +Change-Id: I3d6fb04a1063f64ad52e40cb5debf21c301d829c +Signed-off-by: xiadanni +--- + components/engine/oci/defaults.go | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/components/engine/oci/defaults.go b/components/engine/oci/defaults.go +index 5e17ea3f38..e39140b47f 100644 +--- a/components/engine/oci/defaults.go ++++ b/components/engine/oci/defaults.go +@@ -123,6 +123,7 @@ func DefaultLinuxSpec() specs.Spec { + "/proc/timer_stats", + "/proc/sched_debug", + "/proc/scsi", ++ "/proc/files_panic_enable", + "/sys/firmware", + }, + ReadonlyPaths: []string{ +-- +2.17.1 + diff --git a/patch/0028-oci-add-fdenable-fdstat-and-fdthreshold-to-ma.patch b/patch/0028-oci-add-fdenable-fdstat-and-fdthreshold-to-ma.patch new file mode 100644 index 0000000..4b444cb --- /dev/null +++ b/patch/0028-oci-add-fdenable-fdstat-and-fdthreshold-to-ma.patch @@ -0,0 +1,34 @@ +From f81a87edbeb1ddc58d48d36c963f76f0ed6cc04f Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Wed, 9 Jan 2019 00:40:29 +0800 +Subject: [PATCH 028/111] oci: add fdenable fdstat and fdthreshold to + masked path + +reason: cherry-pick commits to docker-18.09 + +reason:add fdenable fdstat and fdthreshold to masked path + +Change-Id: I7b39a4ad9b989ef4b8185e386aa70c2d638efcd9 +Signed-off-by: caihaomin +Signed-off-by: xiadanni +--- + components/engine/oci/defaults.go | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/components/engine/oci/defaults.go b/components/engine/oci/defaults.go +index e39140b47f..de0088c4bb 100644 +--- a/components/engine/oci/defaults.go ++++ b/components/engine/oci/defaults.go +@@ -124,6 +124,9 @@ func DefaultLinuxSpec() specs.Spec { + "/proc/sched_debug", + "/proc/scsi", + "/proc/files_panic_enable", ++ "/proc/fdthreshold", ++ "/proc/fdstat", ++ "/proc/fdenable", + "/sys/firmware", + }, + ReadonlyPaths: []string{ +-- +2.17.1 + diff --git a/patch/0029-oci-add-oom_extend-to-proc-masked-path.patch b/patch/0029-oci-add-oom_extend-to-proc-masked-path.patch new file mode 100644 index 0000000..b92787c --- /dev/null +++ b/patch/0029-oci-add-oom_extend-to-proc-masked-path.patch @@ -0,0 +1,43 @@ +From fe4522af7f1c5dadfeaf8513dd584c66854d612b Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Wed, 9 Jan 2019 00:43:55 +0800 +Subject: [PATCH 029/111] oci: add oom_extend to proc masked path + +reason: cherry-pick commits to docker-18.09 + +reason:add oom_extend to proc masked path + +Change-Id: I4b9c3dd94b6d68d753e9aad0879949b310fe7180 +Signed-off-by: dengguangxing +Signed-off-by: Haomin +Signed-off-by: xiadanni +--- + components/engine/oci/defaults.go | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/components/engine/oci/defaults.go b/components/engine/oci/defaults.go +index de0088c4bb..74d3fdb2d8 100644 +--- a/components/engine/oci/defaults.go ++++ b/components/engine/oci/defaults.go +@@ -116,6 +116,7 @@ func DefaultLinuxSpec() specs.Spec { + s.Linux = &specs.Linux{ + MaskedPaths: []string{ + "/proc/acpi", ++ "/proc/config.gz", + "/proc/kcore", + "/proc/keys", + "/proc/latency_stats", +@@ -127,6 +128,10 @@ func DefaultLinuxSpec() specs.Spec { + "/proc/fdthreshold", + "/proc/fdstat", + "/proc/fdenable", ++ "/proc/signo", ++ "/proc/sig_catch", ++ "/proc/kbox", ++ "/proc/oom_extend", + "/sys/firmware", + }, + ReadonlyPaths: []string{ +-- +2.17.1 + diff --git a/patch/0030-restart-fix-docker-stats-blocked-while-docker.patch b/patch/0030-restart-fix-docker-stats-blocked-while-docker.patch new file mode 100644 index 0000000..70052ad --- /dev/null +++ b/patch/0030-restart-fix-docker-stats-blocked-while-docker.patch @@ -0,0 +1,34 @@ +From 1e9ad6a48e283bcc9850fb1439bf0be6be805010 Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Wed, 9 Jan 2019 00:49:35 +0800 +Subject: [PATCH 030/111] restart: fix docker stats blocked while + docker daemon restart + +reason: cherry-pick commits to docker-18.09 + +change closeChan channel from non-buffered channel to 2-buffered channel, + because non-blocked channel will be blocked while docker daemon restart + +Change-Id: Ica5dbefc85e463836b55e5d96da522a64a259f64 +Signed-off-by: jiangpengfei9 +Signed-off-by: xiadanni +--- + components/cli/cli/command/container/stats.go | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/components/cli/cli/command/container/stats.go b/components/cli/cli/command/container/stats.go +index 4efcb19e65..1f9e1b8556 100644 +--- a/components/cli/cli/command/container/stats.go ++++ b/components/cli/cli/command/container/stats.go +@@ -53,7 +53,7 @@ func NewStatsCommand(dockerCli command.Cli) *cobra.Command { + // nolint: gocyclo + func runStats(dockerCli command.Cli, opts *statsOptions) error { + showAll := len(opts.containers) == 0 +- closeChan := make(chan error) ++ closeChan := make(chan error, 2) + + ctx := context.Background() + +-- +2.17.1 + diff --git a/patch/0031-restart-Incase-deadlock-when-kill-the-docker-.patch b/patch/0031-restart-Incase-deadlock-when-kill-the-docker-.patch new file mode 100644 index 0000000..0360efa --- /dev/null +++ b/patch/0031-restart-Incase-deadlock-when-kill-the-docker-.patch @@ -0,0 +1,66 @@ +From 6da7401bff6b59e59f04323fcf2f88115f38ede8 Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Wed, 9 Jan 2019 16:33:11 +0800 +Subject: [PATCH 031/111] restart: Incase deadlock when kill the docker + CLI and restart + +reason: cherry-pick commits to docker-18.09 + +the docker daemon is in created stage and last long time, then kill +the docker cli and restart docker at the same time, the restart stage will trylock, +because the created stage already locked. + +cherry-pick from 1.11.2: 3082432 + +the testcase testCE_container_resourced_cpuiso_ABN.023.sh + + current_dir=$(cd `dirname $0` && pwd) + source ${COMMON_DIR}/commonlib.sh + source ${current_dir}/../common/container_resourced_commonlib.sh + resource_managerd_env + + work_dir=`mktemp -d /tmp/testCE_container_resourced_cpuiso_ABN_005.XXXX` + bind_cpu=1 + resource_pid=`ps axu |grep resource-managerd |grep -v grep|awk '{print $2}'` + service container-resourced stop + service container-resourced start & kill -9 $! + container_id=`timeout 10 docker run -itd --hook-spec /etc/docker/resource-hook.json --cpuset-cpus $bind_cpu -e ISOLATION_CORES=$bind_cpu $ubuntu_image bash &` + timeout 10 docker restart $container_id + fn_check_result_noeq $? 0 + resource_managerd_env + docker restart $container_id + fn_check_result $? 0 + test_cpuset $container_id + docker rm -f $container_id + rm -rf $work_dir + kill_resource_managerd + exit $exit_flag +Conflicts: + daemon/restart.go + +Change-Id: I571f109dc07a7e76df40c544008a97cd497ef8bb +Signed-off-by: panwenxiang +Signed-off-by: lixiang172 +--- + components/engine/daemon/restart.go | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/components/engine/daemon/restart.go b/components/engine/daemon/restart.go +index 0f06dea267..bae665f76b 100644 +--- a/components/engine/daemon/restart.go ++++ b/components/engine/daemon/restart.go +@@ -41,7 +41,10 @@ func (daemon *Daemon) containerRestart(container *container.Container, seconds i + defer daemon.Unmount(container) + } + +- if container.IsRunning() { ++ if container.RemovalInProgress || container.Dead { ++ return fmt.Errorf("can't restart a container which is dead or marked for removal") ++ } ++ if container.Running { + // set AutoRemove flag to false before stop so the container won't be + // removed during restart process + autoRemove := container.HostConfig.AutoRemove +-- +2.17.1 + diff --git a/patch/0032-devmapper-Add-udev-event-time-out-to-fix-dock.patch b/patch/0032-devmapper-Add-udev-event-time-out-to-fix-dock.patch new file mode 100644 index 0000000..b2c8c04 --- /dev/null +++ b/patch/0032-devmapper-Add-udev-event-time-out-to-fix-dock.patch @@ -0,0 +1,254 @@ +From e22807e3972f1c168e596fa95a97e2730024824c Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Thu, 3 Jan 2019 10:20:44 +0800 +Subject: [PATCH 032/111] devmapper: Add udev event time out to fix + docker stuck on udev wait + +reason: cherry-pick commits to docker-18.09 + +Add udev event time out to fix docker stuck on udev wait + +Change-Id: I47505140699c0a51f37f4127b761f2a99930466d +Signed-off-by: jingrui +--- + .../daemon/graphdriver/devmapper/deviceset.go | 28 ++++++--- + .../engine/pkg/devicemapper/devmapper.go | 57 +++++++++++++------ + .../pkg/devicemapper/devmapper_wrapper.go | 5 ++ + 3 files changed, 66 insertions(+), 24 deletions(-) + +diff --git a/components/engine/daemon/graphdriver/devmapper/deviceset.go b/components/engine/daemon/graphdriver/devmapper/deviceset.go +index 5dc01d71d9..af53cf83e6 100644 +--- a/components/engine/daemon/graphdriver/devmapper/deviceset.go ++++ b/components/engine/daemon/graphdriver/devmapper/deviceset.go +@@ -35,13 +35,16 @@ import ( + ) + + var ( +- defaultDataLoopbackSize int64 = 100 * 1024 * 1024 * 1024 +- defaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024 +- defaultBaseFsSize uint64 = 10 * 1024 * 1024 * 1024 +- defaultThinpBlockSize uint32 = 128 // 64K = 128 512b sectors +- defaultUdevSyncOverride = false +- maxDeviceID = 0xffffff // 24 bit, pool limit +- deviceIDMapSz = (maxDeviceID + 1) / 8 ++ defaultDataLoopbackSize int64 = 100 * 1024 * 1024 * 1024 ++ defaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024 ++ defaultBaseFsSize uint64 = 10 * 1024 * 1024 * 1024 ++ defaultThinpBlockSize uint32 = 128 // 64K = 128 512b sectors ++ defaultUdevSyncOverride = false ++ maxDeviceID = 0xffffff // 24 bit, pool limit ++ deviceIDMapSz = (maxDeviceID + 1) / 8 ++ // The default timeout is 30s from `man systemd-udevd`, we use 35 ++ // just to make sure the timeout really happened in systemd-udevd ++ defaultUdevWaitTimeout = 35 + driverDeferredRemovalSupport = false + enableDeferredRemoval = false + enableDeferredDeletion = false +@@ -2059,7 +2062,9 @@ func (devices *DeviceSet) issueDiscard(info *devInfo) error { + // Should be called with devices.Lock() held. + func (devices *DeviceSet) deleteDevice(info *devInfo, syncDelete bool) error { + if devices.doBlkDiscard { +- devices.issueDiscard(info) ++ if err := devices.issueDiscard(info); err != nil { ++ return err ++ } + } + + // Try to deactivate device in case it is active. +@@ -2651,6 +2656,7 @@ func NewDeviceSet(root string, doInit bool, options []string, uidMaps, gidMaps [ + + foundBlkDiscard := false + var lvmSetupConfig directLVMConfig ++ udevWaitTimeout := int64(defaultUdevWaitTimeout) + for _, option := range options { + key, val, err := parsers.ParseKeyValueOpt(option) + if err != nil { +@@ -2800,10 +2806,16 @@ func NewDeviceSet(root string, doInit bool, options []string, uidMaps, gidMaps [ + devicemapper.LogInit(devicemapper.DefaultLogger{ + Level: int(level), + }) ++ case "dm.udev_wait_timeout": ++ udevWaitTimeout, err = strconv.ParseInt(val, 10, 32) ++ if err != nil { ++ return nil, err ++ } + default: + return nil, fmt.Errorf("devmapper: Unknown option %s", key) + } + } ++ devicemapper.SetUdevWaitTimtout(udevWaitTimeout) + + if err := validateLVMConfig(lvmSetupConfig); err != nil { + return nil, err +diff --git a/components/engine/pkg/devicemapper/devmapper.go b/components/engine/pkg/devicemapper/devmapper.go +index 63243637a7..b384a27f8f 100644 +--- a/components/engine/pkg/devicemapper/devmapper.go ++++ b/components/engine/pkg/devicemapper/devmapper.go +@@ -7,6 +7,7 @@ import ( + "fmt" + "os" + "runtime" ++ "time" + "unsafe" + + "github.com/sirupsen/logrus" +@@ -59,6 +60,7 @@ var ( + ErrNilCookie = errors.New("cookie ptr can't be nil") + ErrGetBlockSize = errors.New("Can't get block size") + ErrUdevWait = errors.New("wait on udev cookie failed") ++ ErrUdevWaitTimeout = errors.New("wait on udev cookie time out") + ErrSetDevDir = errors.New("dm_set_dev_dir failed") + ErrGetLibraryVersion = errors.New("dm_get_library_version failed") + ErrCreateRemoveTask = errors.New("Can't create task of type deviceRemove") +@@ -71,10 +73,11 @@ var ( + ) + + var ( +- dmSawBusy bool +- dmSawExist bool +- dmSawEnxio bool // No Such Device or Address +- dmSawEnoData bool // No data available ++ dmSawBusy bool ++ dmSawExist bool ++ dmSawEnxio bool // No Such Device or Address ++ dmSawEnoData bool // No data available ++ dmUdevWaitTimeout int64 + ) + + type ( +@@ -256,13 +259,36 @@ func (t *Task) getNextTarget(next unsafe.Pointer) (nextPtr unsafe.Pointer, start + + // UdevWait waits for any processes that are waiting for udev to complete the specified cookie. + func UdevWait(cookie *uint) error { +- if res := DmUdevWait(*cookie); res != 1 { +- logrus.Debugf("devicemapper: Failed to wait on udev cookie %d, %d", *cookie, res) +- return ErrUdevWait ++ chError := make(chan error) ++ go func() { ++ if res := DmUdevWait(*cookie); res != 1 { ++ logrus.Debugf("Failed to wait on udev cookie %d", *cookie) ++ chError <- ErrUdevWait ++ } ++ chError <- nil ++ }() ++ select { ++ case err := <-chError: ++ return err ++ case <-time.After(time.Second * time.Duration(dmUdevWaitTimeout)): ++ logrus.Errorf("Failed to wait on udev cookie %d: timeout %v", *cookie, dmUdevWaitTimeout) ++ if res := DmUdevComplete(*cookie); res != 1 { ++ // This is bad to return here ++ logrus.Errorf("Failed to complete udev cookie %d on udev wait timeout", *cookie) ++ return ErrUdevWaitTimeout ++ } ++ // wait DmUdevWait return after DmUdevComplete ++ <-chError ++ return ErrUdevWaitTimeout + } + return nil + } + ++// SetUdevWaitTimtout sets udev wait timeout ++func SetUdevWaitTimtout(t int64) { ++ dmUdevWaitTimeout = t ++} ++ + // SetDevDir sets the dev folder for the device mapper library (usually /dev). + func SetDevDir(dir string) error { + if res := DmSetDevDir(dir); res != 1 { +@@ -319,11 +345,11 @@ func RemoveDevice(name string) error { + if err := task.setCookie(cookie, 0); err != nil { + return fmt.Errorf("devicemapper: Can not set cookie: %s", err) + } +- defer UdevWait(cookie) + + dmSawBusy = false // reset before the task is run + dmSawEnxio = false + if err = task.run(); err != nil { ++ UdevWait(cookie) + if dmSawBusy { + return ErrBusy + } +@@ -333,7 +359,7 @@ func RemoveDevice(name string) error { + return fmt.Errorf("devicemapper: Error running RemoveDevice %s", err) + } + +- return nil ++ return UdevWait(cookie) + } + + // RemoveDeviceDeferred is a useful helper for cleaning up a device, but deferred. +@@ -470,13 +496,13 @@ func CreatePool(poolName string, dataFile, metadataFile *os.File, poolBlockSize + if err := task.setCookie(cookie, flags); err != nil { + return fmt.Errorf("devicemapper: Can't set cookie %s", err) + } +- defer UdevWait(cookie) + + if err := task.run(); err != nil { ++ UdevWait(cookie) + return fmt.Errorf("devicemapper: Error running deviceCreate (CreatePool) %s", err) + } + +- return nil ++ return UdevWait(cookie) + } + + // ReloadPool is the programmatic example of "dmsetup reload". +@@ -656,13 +682,13 @@ func ResumeDevice(name string) error { + if err := task.setCookie(cookie, 0); err != nil { + return fmt.Errorf("devicemapper: Can't set cookie %s", err) + } +- defer UdevWait(cookie) + + if err := task.run(); err != nil { ++ UdevWait(cookie) + return fmt.Errorf("devicemapper: Error running deviceResume %s", err) + } + +- return nil ++ return UdevWait(cookie) + } + + // CreateDevice creates a device with the specified poolName with the specified device id. +@@ -760,13 +786,12 @@ func activateDevice(poolName string, name string, deviceID int, size uint64, ext + return fmt.Errorf("devicemapper: Can't set cookie %s", err) + } + +- defer UdevWait(cookie) +- + if err := task.run(); err != nil { ++ UdevWait(cookie) + return fmt.Errorf("devicemapper: Error running deviceCreate (ActivateDevice) %s", err) + } + +- return nil ++ return UdevWait(cookie) + } + + // CreateSnapDeviceRaw creates a snapshot device. Caller needs to suspend and resume the origin device if it is active. +diff --git a/components/engine/pkg/devicemapper/devmapper_wrapper.go b/components/engine/pkg/devicemapper/devmapper_wrapper.go +index 0b88f49695..77cd674a09 100644 +--- a/components/engine/pkg/devicemapper/devmapper_wrapper.go ++++ b/components/engine/pkg/devicemapper/devmapper_wrapper.go +@@ -77,6 +77,7 @@ var ( + DmTaskSetRo = dmTaskSetRoFct + DmTaskSetSector = dmTaskSetSectorFct + DmUdevWait = dmUdevWaitFct ++ DmUdevComplete = dmUdevCompleteFct + DmUdevSetSyncSupport = dmUdevSetSyncSupportFct + DmUdevGetSyncSupport = dmUdevGetSyncSupportFct + DmCookieSupported = dmCookieSupportedFct +@@ -227,6 +228,10 @@ func dmUdevWaitFct(cookie uint) int { + return int(C.dm_udev_wait(C.uint32_t(cookie))) + } + ++func dmUdevCompleteFct(cookie uint) int { ++ return int(C.dm_udev_complete(C.uint32_t(cookie))) ++} ++ + func dmCookieSupportedFct() int { + return int(C.dm_cookie_supported()) + } +-- +2.17.1 + diff --git a/patch/0033-devmapper-Fix-devicemapper-issue-power-off-th.patch b/patch/0033-devmapper-Fix-devicemapper-issue-power-off-th.patch new file mode 100644 index 0000000..5e263bd --- /dev/null +++ b/patch/0033-devmapper-Fix-devicemapper-issue-power-off-th.patch @@ -0,0 +1,224 @@ +From 72f9e0c4d0d907a036d25c9dfeea9c0baddddc3e Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Thu, 3 Jan 2019 10:37:50 +0800 +Subject: [PATCH 033/111] devmapper: Fix devicemapper issue: power off + the VM while loading a image, couldn't load it after VM bootup + +reason: cherry-pick commits to docker-18.09 + +cherry-pick from 28991928c4 | * Fix devicemapper issue: power off the VM while +loading a image, couldn't load it after VM bootup + +Issue Description: +While running loading image test, power off or restart the VM, and then +there are +some chances that we can not load the image. And "Error running +deviceCreate (createSnapDevice) dm_task_run failed" will be reported. + +Reproduce Steps: +> 1. run `docker load -i xxx.tar` +> 2. virsh restart VM; # restart the VM. +> 3. After startup, run `docker load -i xxx.tar`, will fail to import +> the image + +Analysis: +From syslog, we found that docker was executing "Umount Device" then VM +powered restart. And found +two failure reasons: +> 1. Rollback operation only remove the device on DM thin pool, not +> remove the device in memory +> 2. TransactionData or metadata not flushed to Disk. + +Solution: +> 1. Rollback operation for DM, should remove the devices cache in DM +> driver. +> 2. When restore layers and images, check whether the device exists in +> graphdriver. + If desen't, remove the layer( and the metadata) and do not load the +image. + +Issue link: + #203 + +Signed-off-by: Wentao Zhang + +Conflicts: + image/store.go + layer/layer_store.go + +Change-Id: If3bfbf0d0ed8f950cfd5934fc25fac892481275c +Signed-off-by: jingrui +--- + .../daemon/graphdriver/devmapper/deviceset.go | 38 +++++++------------ + components/engine/image/store.go | 3 +- + components/engine/layer/layer_store.go | 15 +++++++- + 3 files changed, 28 insertions(+), 28 deletions(-) + +diff --git a/components/engine/daemon/graphdriver/devmapper/deviceset.go b/components/engine/daemon/graphdriver/devmapper/deviceset.go +index af53cf83e6..0675b2eacd 100644 +--- a/components/engine/daemon/graphdriver/devmapper/deviceset.go ++++ b/components/engine/daemon/graphdriver/devmapper/deviceset.go +@@ -6,7 +6,6 @@ import ( + "bufio" + "encoding/json" + "fmt" +- "io" + "io/ioutil" + "os" + "os/exec" +@@ -23,6 +22,7 @@ import ( + "github.com/docker/docker/pkg/devicemapper" + "github.com/docker/docker/pkg/dmesg" + "github.com/docker/docker/pkg/idtools" ++ "github.com/docker/docker/pkg/ioutils" + "github.com/docker/docker/pkg/loopback" + "github.com/docker/docker/pkg/mount" + "github.com/docker/docker/pkg/parsers" +@@ -306,6 +306,10 @@ func (devices *DeviceSet) ensureImage(name string, size int64) (string, error) { + return filename, nil + } + ++func (devices *DeviceSet) removeDeviceMap(hash string) { ++ delete(devices.Devices, hash) ++} ++ + func (devices *DeviceSet) allocateTransactionID() uint64 { + devices.OpenTransactionID = devices.TransactionID + 1 + return devices.OpenTransactionID +@@ -328,28 +332,9 @@ func (devices *DeviceSet) removeMetadata(info *devInfo) error { + + // Given json data and file path, write it to disk + func (devices *DeviceSet) writeMetaFile(jsonData []byte, filePath string) error { +- tmpFile, err := ioutil.TempFile(devices.metadataDir(), ".tmp") +- if err != nil { +- return fmt.Errorf("devmapper: Error creating metadata file: %s", err) +- } +- +- n, err := tmpFile.Write(jsonData) +- if err != nil { +- return fmt.Errorf("devmapper: Error writing metadata to %s: %s", tmpFile.Name(), err) +- } +- if n < len(jsonData) { +- return io.ErrShortWrite ++ if err := ioutils.AtomicWriteFile(filePath, jsonData, 0600); err != nil { ++ return fmt.Errorf("devmapper: Error writing metadata to %s: %s", filePath, err) + } +- if err := tmpFile.Sync(); err != nil { +- return fmt.Errorf("devmapper: Error syncing metadata file %s: %s", tmpFile.Name(), err) +- } +- if err := tmpFile.Close(); err != nil { +- return fmt.Errorf("devmapper: Error closing metadata file %s: %s", tmpFile.Name(), err) +- } +- if err := os.Rename(tmpFile.Name(), filePath); err != nil { +- return fmt.Errorf("devmapper: Error committing metadata file %s: %s", tmpFile.Name(), err) +- } +- + return nil + } + +@@ -483,7 +468,7 @@ func (devices *DeviceSet) unregisterDevice(hash string) error { + Hash: hash, + } + +- delete(devices.Devices, hash) ++ devices.removeDeviceMap(hash) + + if err := devices.removeMetadata(info); err != nil { + logrus.WithField("storage-driver", "devicemapper").Debugf("Error removing metadata: %s", err) +@@ -509,7 +494,7 @@ func (devices *DeviceSet) registerDevice(id int, hash string, size uint64, trans + + if err := devices.saveMetadata(info); err != nil { + // Try to remove unused device +- delete(devices.Devices, hash) ++ devices.removeDeviceMap(hash) + return nil, err + } + +@@ -1173,7 +1158,7 @@ func (devices *DeviceSet) checkGrowBaseDeviceFS(info *devInfo) error { + + if err := devices.saveMetadata(info); err != nil { + // Try to remove unused device +- delete(devices.Devices, info.Hash) ++ devices.removeDeviceMap(info.Hash) + return err + } + +@@ -1401,6 +1386,8 @@ func (devices *DeviceSet) rollbackTransaction() error { + devices.markDeviceIDFree(devices.DeviceID) + } + ++ devices.removeDeviceMap(dinfo.Hash) ++ + if err := devices.removeTransactionMetaData(); err != nil { + logger.Errorf("Unable to remove transaction meta file %s: %s", devices.transactionMetaFile(), err) + } +@@ -1482,6 +1469,7 @@ func (devices *DeviceSet) closeTransaction() error { + logrus.WithField("storage-driver", "devicemapper").Debug("Failed to close Transaction") + return err + } ++ + return nil + } + +diff --git a/components/engine/image/store.go b/components/engine/image/store.go +index 1a8a8a2451..b078a2627a 100644 +--- a/components/engine/image/store.go ++++ b/components/engine/image/store.go +@@ -81,8 +81,9 @@ func (is *store) restore() error { + } + l, err = is.lss[img.OperatingSystem()].Get(chainID) + if err != nil { ++ logrus.Errorf("layer does not exist, not restoring image %v, %v, %s", dgst, chainID, img.OperatingSystem()) ++ // If the layer doesn't exist, return nil to ignore this image. + if err == layer.ErrLayerDoesNotExist { +- logrus.Errorf("layer does not exist, not restoring image %v, %v, %s", dgst, chainID, img.OperatingSystem()) + return nil + } + return err +diff --git a/components/engine/layer/layer_store.go b/components/engine/layer/layer_store.go +index 6a568e9d9b..351f787b87 100644 +--- a/components/engine/layer/layer_store.go ++++ b/components/engine/layer/layer_store.go +@@ -105,7 +105,7 @@ func newStoreFromGraphDriver(root string, driver graphdriver.Driver, os string) + for _, id := range ids { + l, err := ls.loadLayer(id) + if err != nil { +- logrus.Debugf("Failed to load layer %s: %s", id, err) ++ logrus.Warnf("Failed to load layer %s: %s", id, err) + continue + } + if l.parent != nil { +@@ -126,11 +126,17 @@ func (ls *layerStore) Driver() graphdriver.Driver { + return ls.driver + } + +-func (ls *layerStore) loadLayer(layer ChainID) (*roLayer, error) { ++func (ls *layerStore) loadLayer(layer ChainID) (l *roLayer, err error) { + cl, ok := ls.layerMap[layer] + if ok { + return cl, nil + } ++ defer func() { ++ // If failed to load the layer, remove the layer metadata. ++ if err != nil { ++ ls.store.Remove(layer) ++ } ++ }() + + diff, err := ls.store.GetDiffID(layer) + if err != nil { +@@ -147,6 +153,11 @@ func (ls *layerStore) loadLayer(layer ChainID) (*roLayer, error) { + return nil, fmt.Errorf("failed to get cache id for %s: %s", layer, err) + } + ++ // Check whether the layer exists in graphdriver here. ++ if exist := ls.driver.Exists(cacheID); !exist { ++ return nil, fmt.Errorf("cacheID %s for layer %s does not exists in graphdriver", cacheID, layer) ++ } ++ + parent, err := ls.store.GetParent(layer) + if err != nil { + return nil, fmt.Errorf("failed to get parent for %s: %s", layer, err) +-- +2.17.1 + diff --git a/patch/0035-restart-reject-to-restart-container-when-remo.patch b/patch/0035-restart-reject-to-restart-container-when-remo.patch new file mode 100644 index 0000000..5caad6e --- /dev/null +++ b/patch/0035-restart-reject-to-restart-container-when-remo.patch @@ -0,0 +1,42 @@ +From 2e0fda9de03efe4f399a35c52cac2444b5518709 Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Wed, 9 Jan 2019 16:43:39 +0800 +Subject: [PATCH 035/111] restart: reject to restart container when + removing it + +reason: cherry-pick commits to docker-18.09 + +concurrence of restart and remove a container may cause +that container info is still saved in memdb while container is actually +already removed. + +cherry-pick from 1.11.2: f7eeafa +Conflicts: + daemon/restart.go + +Change-Id: Ic9be384c908f49697c87f6b51ffcb1968f38d6cc +Signed-off-by: liruilin4 +Signed-off-by: lixiang172 +--- + components/engine/daemon/restart.go | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/components/engine/daemon/restart.go b/components/engine/daemon/restart.go +index bae665f76b..2c441de873 100644 +--- a/components/engine/daemon/restart.go ++++ b/components/engine/daemon/restart.go +@@ -55,6 +55,11 @@ func (daemon *Daemon) containerRestart(container *container.Container, seconds i + container.HostConfig.AutoRemove = autoRemove + // containerStop will write HostConfig to disk, we shall restore AutoRemove + // in disk too ++ // if rm running concurrently, the container may be already removed now. we should not ++ // save it to disk ++ if container.RemovalInProgress || container.Dead { ++ return fmt.Errorf("can't restart a container which is dead or marked for removal") ++ } + if toDiskErr := daemon.checkpointAndSave(container); toDiskErr != nil { + logrus.Errorf("Write container to disk error: %v", toDiskErr) + } +-- +2.17.1 + diff --git a/patch/0036-restart-do-not-reset-container-restartCount-o.patch b/patch/0036-restart-do-not-reset-container-restartCount-o.patch new file mode 100644 index 0000000..5c047ab --- /dev/null +++ b/patch/0036-restart-do-not-reset-container-restartCount-o.patch @@ -0,0 +1,41 @@ +From af5045eb5d9cc84afeace447e2bf3e1571866f4d Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Wed, 9 Jan 2019 16:52:39 +0800 +Subject: [PATCH 036/111] restart: do not reset container restartCount + on boot + +reason: cherry-pick commits to docker-18.09 + +when daemon restart, container's RestartCount will be reseted to 0 + this is cherry-picked from docker + commit:3bffccc719bf8237ce78af8b88aaa7757f43795a + Signed-off-by: dengguangxing + +cherry-pick from 1.11.2: d5292e3 + +Conflicts: + daemon/daemon.go + +Change-Id: I2276e9d075b593b74e4b17c0f8e48a2dec3e6f4c +Signed-off-by: xueshaojia +Signed-off-by: lixiang172 +--- + components/engine/daemon/daemon.go | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go +index 4546587369..a058688dd3 100644 +--- a/components/engine/daemon/daemon.go ++++ b/components/engine/daemon/daemon.go +@@ -532,7 +532,7 @@ func (daemon *Daemon) restore() error { + + // Make sure networks are available before starting + daemon.waitForNetworks(c) +- if err := daemon.containerStart(c, "", "", true); err != nil { ++ if err := daemon.containerStart(c, "", "", false); err != nil { + logrus.Errorf("Failed to start container %s: %s", c.ID, err) + } + close(chNotify) +-- +2.17.1 + diff --git a/patch/0037-config-Add-liver-restore-to-OPTIION-to-enable.patch b/patch/0037-config-Add-liver-restore-to-OPTIION-to-enable.patch new file mode 100644 index 0000000..de43188 --- /dev/null +++ b/patch/0037-config-Add-liver-restore-to-OPTIION-to-enable.patch @@ -0,0 +1,50 @@ +From 5f82c47f5f08655e325610a9430a4e891c71857a Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Thu, 3 Jan 2019 13:58:17 +0800 +Subject: [PATCH 037/111] config: Add --liver-restore to OPTIION to + enable live restore by default + +reason: cherry-pick commits to docker-18.09 + +this commit copy latest file from docker-1.11.2 + +cherry-pick from +c9846ccea8 | * Add --liver-restore to OPTIION to enable live restore by +default + +Change-Id: I9c52a81d1bb06f2abb090bbd048cbf2dbc44956a +Signed-off-by: Lei Jitang +Signed-off-by: jingrui +--- + .../init/sysvinit-redhat/docker.sysconfig | 19 ++++++++++++++----- + 1 file changed, 14 insertions(+), 5 deletions(-) + +diff --git a/components/engine/contrib/init/sysvinit-redhat/docker.sysconfig b/components/engine/contrib/init/sysvinit-redhat/docker.sysconfig +index 0864b3d77f..234acad1dd 100644 +--- a/components/engine/contrib/init/sysvinit-redhat/docker.sysconfig ++++ b/components/engine/contrib/init/sysvinit-redhat/docker.sysconfig +@@ -1,7 +1,16 @@ + # /etc/sysconfig/docker +-# +-# Other arguments to pass to the docker daemon process +-# These will be parsed by the sysv initscript and appended +-# to the arguments list passed to docker daemon + +-other_args="" ++# Modify these options if you want to change the way the docker daemon runs ++OPTIONS='--live-restore' ++ ++DOCKER_CERT_PATH=/etc/docker ++ ++# If you have a registry secured with https but do not have proper certs ++# distributed, you can tell docker to not look for full authorization by ++# adding the registry to the INSECURE_REGISTRY line and uncommenting it. ++# INSECURE_REGISTRY='--insecure-registry' ++ ++# Location used for temporary files, such as those created by ++# docker load and build operations. Default is /var/lib/docker/tmp ++# Can be overridden by setting the following environment variable. ++# DOCKER_TMPDIR=/var/tmp +-- +2.17.1 + diff --git a/patch/0038-devmapper-devicemapper-ignore-error-when-remo.patch b/patch/0038-devmapper-devicemapper-ignore-error-when-remo.patch new file mode 100644 index 0000000..f26b15e --- /dev/null +++ b/patch/0038-devmapper-devicemapper-ignore-error-when-remo.patch @@ -0,0 +1,44 @@ +From 42da0fd120db6f5b34c523212b54b88e8837efa6 Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Thu, 3 Jan 2019 14:09:59 +0800 +Subject: [PATCH 038/111] devmapper: devicemapper: ignore error when + remove a device which does not exist + +reason: cherry-pick commits to docker-18.09 + +cherry-pick from +e91268fda7 | * devicemapper: ignore error when remove a device which +does not exist + +Conflicts: + daemon/graphdriver/devmapper/deviceset.go + pkg/devicemapper/devmapper.go + +Change-Id: Ia1d33d78e4d7000c6e4bd54ec203800a0234cae5 +Signed-off-by: Liu Hua +Signed-off-by: jingrui +--- + .../engine/daemon/graphdriver/devmapper/deviceset.go | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/components/engine/daemon/graphdriver/devmapper/deviceset.go b/components/engine/daemon/graphdriver/devmapper/deviceset.go +index 0675b2eacd..be48d92e8d 100644 +--- a/components/engine/daemon/graphdriver/devmapper/deviceset.go ++++ b/components/engine/daemon/graphdriver/devmapper/deviceset.go +@@ -2137,6 +2137,13 @@ func (devices *DeviceSet) deactivateDeviceMode(info *devInfo, deferredRemove boo + + if deferredRemove { + err = devicemapper.RemoveDeviceDeferred(info.Name()) ++ if err != nil { ++ if err == devicemapper.ErrEnxio { ++ logrus.Warnf("devmapper: device %s has gone", info.Name()) ++ return nil ++ } ++ return err ++ } + } else { + err = devices.removeDevice(info.Name()) + } +-- +2.17.1 + diff --git a/patch/0039-restart-Remove-redundant-Mounts-when-start-do.patch b/patch/0039-restart-Remove-redundant-Mounts-when-start-do.patch new file mode 100644 index 0000000..f5d27e9 --- /dev/null +++ b/patch/0039-restart-Remove-redundant-Mounts-when-start-do.patch @@ -0,0 +1,361 @@ +From 49a3cd1ceb6956bd7787135a7938543d64d30638 Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Thu, 3 Jan 2019 14:02:32 +0800 +Subject: [PATCH 039/111] restart: -- Remove redundant Mounts when + start docker daemon + +reason: cherry-pick commits to docker-18.09 + +merge from +b192ce147d * Remove redundant Mounts when start docker daemon +8b031c9488 * devicemapper: remove redundant mountpoint when docker +restart +9a3c14633e * Revert "devicemapper: remove redundant mountpoint when +docker restart" +7bbda93537 * devicemapper: remove redundant mountpoint when docker +restart +0be4e475d7 * remove redundant containers/xxx/shm mountpoint +d4adbe2bf7 * Remove redundant overlay2 mountpoints + +--- +Remove redundant Mounts when start docker daemon + + +Signed-off-by: yangshukui +--- +devicemapper: remove redundant mountpoint when docker restart + +Modify the regexp patterns in `getIdPatterns` in order to match +`/var/lib/docker/devicemapper/mnt/xxx-init`. + +Fix #254 + +Signed-off-by: Yuanhong Peng +--- +Revert "devicemapper: remove redundant mountpoint when docker restart" + +With this commit, daemon will restart with error: +``` +level=error msg="devmapper: Error unmounting device +0019325b1b09c75da1c9c97dcffdc024498d318b2c148e4ebe67b1cda4ac446e: +devmapper: Unknown device +0019325b1b09c75da1c9c97dcffdc024498d318b2c148e4ebe67b1cda4ac446e" +``` +Here, the redundant device is `xxx-init` not `xxx`. + +This reverts commit 8b031c9488236a9a808be7aed4cce69786dec010. + +Signed-off-by: Yuanhong Peng +--- +devicemapper: remove redundant mountpoint when docker restart + +Modify the regexp patterns in `getIdPatterns` in order to match +`/var/lib/docker/devicemapper/mnt/xxx-init`. + +Fix #254 + +Signed-off-by: Yuanhong Peng +--- +remove redundant containers/xxx/shm mountpoint + +fix problems caused by killing docker daemon when running a container +once restarted, docker ps shows container in created status but failed +to rm it with `container/xxx/shm device or resource busy` + + +Signed-off-by: Deng Guangxing +--- +Remove redundant overlay2 mountpoints + +Modify the regexp patterns in `getIdPatterns` in order to match +/var/lib/docker/overlay2/xxx/merged. + +Signed-off-by: Yuanhong Peng +--- +Signed-off-by: jingrui + +Change-Id: I4859d9cca731477fc64be6772d8c4b89a000f1b5 +--- + components/engine/daemon/daemon.go | 6 + + components/engine/daemon/daemon_unix.go | 132 ++++++++++++++++++ + components/engine/daemon/daemon_unix_test.go | 29 ++++ + components/engine/daemon/daemon_windows.go | 4 + + components/engine/daemon/images/service.go | 4 + + .../engine/distribution/xfer/download_test.go | 4 + + components/engine/layer/layer.go | 1 + + components/engine/layer/layer_store.go | 4 + + 8 files changed, 184 insertions(+) + +diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go +index a058688dd3..b207709f7c 100644 +--- a/components/engine/daemon/daemon.go ++++ b/components/engine/daemon/daemon.go +@@ -486,6 +486,12 @@ func (daemon *Daemon) restore() error { + } + wg.Wait() + ++ err = daemon.removeRedundantMounts(containers) ++ if err != nil { ++ // just print error info ++ logrus.Errorf("removeRedundantMounts failed %v", err) ++ } ++ + containerIDs := make(map[string]struct{}) + for cid, _ := range containers { + containerIDs[cid] = struct{}{} +diff --git a/components/engine/daemon/daemon_unix.go b/components/engine/daemon/daemon_unix.go +index 8ffdd0009a..d4a32a0b25 100644 +--- a/components/engine/daemon/daemon_unix.go ++++ b/components/engine/daemon/daemon_unix.go +@@ -10,6 +10,7 @@ import ( + "net" + "os" + "path/filepath" ++ "regexp" + "runtime" + "runtime/debug" + "strconv" +@@ -1587,3 +1588,134 @@ func (daemon *Daemon) setupSeccompProfile() error { + } + return nil + } ++ ++func getIdPatterns(id string) (regexps []*regexp.Regexp) { ++ var patterns []string ++ if id == "" { ++ id = "(?P[0-9a-f]{64})" ++ } ++ patterns = append(patterns, "aufs/mnt/"+id+"(-init)?"+"$", "overlay/"+id+"(-init)?"+"/merged$", "overlay2/"+id+"(-init)?"+"/merged$", "zfs/graph/"+id+"(-init)?"+"$", "devicemapper/mnt/"+id+"(-init)?"+"$") ++ for _, p := range patterns { ++ r, err := regexp.Compile(p) ++ if err == nil { ++ regexps = append(regexps, r) ++ } ++ } ++ return ++} ++ ++func getContainerMountId(path string) (bool, string) { ++ regs := getIdPatterns("") ++ for _, reg := range regs { ++ ret := reg.FindStringSubmatch(path) ++ if len(ret) == 3 { ++ if ret[2] == "" { ++ return false, ret[1] ++ } else { ++ return true, ret[1] ++ } ++ } ++ } ++ return false, "" ++} ++ ++func isContainerMount(path string) (bool, string) { ++ var regs []*regexp.Regexp ++ var patterns []string ++ var id = "(?P[0-9a-f]{64})" ++ ++ // TODO: fill in patterns with other mounts info ++ patterns = append(patterns, "containers/"+id+"/shm$") ++ for _, p := range patterns { ++ r, err := regexp.Compile(p) ++ if err == nil { ++ regs = append(regs, r) ++ } ++ } ++ ++ for _, reg := range regs { ++ ret := reg.FindStringSubmatch(path) ++ if len(ret) == 2 { ++ return true, ret[1] ++ } ++ } ++ return false, "" ++} ++ ++func (daemon *Daemon) removeRedundantMounts(containers map[string]*container.Container) error { ++ var ( ++ isShmMount, isInitdev bool ++ id string ++ redundantMounts = map[string]bool{} ++ ) ++ ++ // Get redundant Mounts ++ f, err := os.Open("/proc/self/mountinfo") ++ if err != nil { ++ return err ++ } ++ defer f.Close() ++ ++ activeContainers := map[string]string{} ++ for _, c := range containers { ++ if c.IsRunning() && !c.IsRestarting() { ++ activeContainers[c.ID] = c.ID ++ if mountid, err := daemon.imageService.GetLayerMountID(c.ID, c.OS); err == nil { ++ activeContainers[mountid] = c.ID ++ logrus.Debugf("removeRedundantMounts, mountid %s, containerID %s\n", mountid, c.ID) ++ } ++ } ++ } ++ root := filepath.Join(daemon.root, daemon.imageService.GraphDriverForOS(runtime.GOOS)) ++ scanner := bufio.NewScanner(f) ++ for scanner.Scan() { ++ text := scanner.Text() ++ fields := strings.Split(text, " ") ++ if len(fields) < 5 { ++ return fmt.Errorf("%s", "/proc/self/mountinfo format err") ++ } ++ path := fields[4] ++ if !strings.HasPrefix(path, daemon.root) || path == root { ++ continue ++ } ++ ++ isShmMount, id = isContainerMount(path) ++ if !isShmMount { ++ isInitdev, id = getContainerMountId(path) ++ } ++ if id == "" { ++ continue ++ } ++ ++ if _, ok := activeContainers[id]; !ok { ++ if isShmMount { ++ redundantMounts[path] = true ++ } else { ++ if isInitdev { ++ id = fmt.Sprintf("%s-init", id) ++ } ++ redundantMounts[id] = false ++ } ++ } ++ } ++ if err := scanner.Err(); err != nil { ++ return err ++ } ++ ++ // Remove redundant Mounts ++ for path, shm := range redundantMounts { ++ var ( ++ err error ++ ) ++ logrus.Debugf("Umount legacy mountpoint [%s]", path) ++ if shm { ++ err = mount.Unmount(path) ++ } else { ++ err = daemon.imageService.DriverPut(runtime.GOOS, path) ++ } ++ if err != nil { ++ logrus.Debugf("Umount legacy mountpoint: %s failed with %s", path, err) ++ } ++ } ++ return err ++} +diff --git a/components/engine/daemon/daemon_unix_test.go b/components/engine/daemon/daemon_unix_test.go +index 36c6030988..d9bba54a93 100644 +--- a/components/engine/daemon/daemon_unix_test.go ++++ b/components/engine/daemon/daemon_unix_test.go +@@ -266,3 +266,32 @@ func TestNetworkOptions(t *testing.T) { + t.Fatal("Expected networkOptions error, got nil") + } + } ++ ++func TestGetContainerMountId(t *testing.T) { ++ id := "56e143922c405419a38b23bfbccc92284f35525e3f2ad7011ea904501ccd1219" ++ ++ id1 := getContainerMountId("/var/lib/docker/aufs/mnt/" + id) ++ if id1 != id { ++ t.Fatalf("Expected container mount id [%s], but got [%s]", id, id1) ++ } ++ ++ id1 = getContainerMountId("/var/lib/docker/devicemapper/mnt/" + id) ++ if id1 != id { ++ t.Fatalf("Expected container mount id [%s], but got [%s]", id, id1) ++ } ++ ++ id1 = getContainerMountId("/var/lib/docker/overlay/" + id + "/merged") ++ if id1 != id { ++ t.Fatalf("Expected container mount id [%s], but got [%s]", id, id1) ++ } ++ ++ id1 = getContainerMountId("/var/lib/docker/zfs/graph/" + id) ++ if id1 != id { ++ t.Fatalf("Expected container mount id [%s], but got [%s]", id, id1) ++ } ++ ++ id1 = getContainerMountId("/var/lib/docker/devicemapper_err/mnt" + id) ++ if id1 != "" { ++ t.Fatalf("Expected a empty container mount id, but got [%s]", id1) ++ } ++} +diff --git a/components/engine/daemon/daemon_windows.go b/components/engine/daemon/daemon_windows.go +index 4812236bc2..9d895b3dfa 100644 +--- a/components/engine/daemon/daemon_windows.go ++++ b/components/engine/daemon/daemon_windows.go +@@ -657,3 +657,7 @@ func (daemon *Daemon) initRuntimes(_ map[string]types.Runtime) error { + + func setupResolvConf(config *config.Config) { + } ++ ++func (daemon *Daemon) removeRedundantMounts(containers map[string]*container.Container) error { ++ return nil ++} +diff --git a/components/engine/daemon/images/service.go b/components/engine/daemon/images/service.go +index 8d187e2603..e43a4c6651 100644 +--- a/components/engine/daemon/images/service.go ++++ b/components/engine/daemon/images/service.go +@@ -193,6 +193,10 @@ func (i *ImageService) ReleaseLayer(rwlayer layer.RWLayer, containerOS string) e + return nil + } + ++func (i *ImageService) DriverPut(os string, path string) error { ++ return i.layerStores[os].DriverPut(path) ++} ++ + // LayerDiskUsage returns the number of bytes used by layer stores + // called from disk_usage.go + func (i *ImageService) LayerDiskUsage(ctx context.Context) (int64, error) { +diff --git a/components/engine/distribution/xfer/download_test.go b/components/engine/distribution/xfer/download_test.go +index 91153591ed..c4c4aefba9 100644 +--- a/components/engine/distribution/xfer/download_test.go ++++ b/components/engine/distribution/xfer/download_test.go +@@ -146,6 +146,10 @@ func (ls *mockLayerStore) Cleanup() error { + return nil + } + ++func (ls *mockLayerStore) DriverPut(id string) error { ++ return nil ++} ++ + func (ls *mockLayerStore) DriverStatus() [][2]string { + return [][2]string{} + } +diff --git a/components/engine/layer/layer.go b/components/engine/layer/layer.go +index 425006854d..cb13c98d0b 100644 +--- a/components/engine/layer/layer.go ++++ b/components/engine/layer/layer.go +@@ -191,6 +191,7 @@ type Store interface { + ReleaseRWLayer(RWLayer) ([]Metadata, error) + + Cleanup() error ++ DriverPut(id string) error + DriverStatus() [][2]string + DriverName() string + CleanupRedundant(map[string]struct{}) error +diff --git a/components/engine/layer/layer_store.go b/components/engine/layer/layer_store.go +index 351f787b87..5decb0bdce 100644 +--- a/components/engine/layer/layer_store.go ++++ b/components/engine/layer/layer_store.go +@@ -807,6 +807,10 @@ func (ls *layerStore) Cleanup() error { + return ls.driver.Cleanup() + } + ++func (ls *layerStore) DriverPut(id string) error { ++ return ls.driver.Put(id) ++} ++ + func (ls *layerStore) DriverStatus() [][2]string { + return ls.driver.Status() + } +-- +2.17.1 + diff --git a/patch/0040-devmapper-devicemapper-add-API-GetDeviceList.patch b/patch/0040-devmapper-devicemapper-add-API-GetDeviceList.patch new file mode 100644 index 0000000..b4a744c --- /dev/null +++ b/patch/0040-devmapper-devicemapper-add-API-GetDeviceList.patch @@ -0,0 +1,166 @@ +From 9df99e9c4b27a3ccfae5f9b5b285784aeaaf1ac9 Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Mon, 7 Jan 2019 13:36:04 +0800 +Subject: [PATCH 040/111] devmapper: devicemapper: add API + GetDeviceList + +reason: cherry-pick commits to docker-18.09 + +cherry-pick from 58fa445402 * devicemapper: add API GetDeviceList + +Change-Id: Ia52611b75f12179bbe2e718e1b8575f0825d5dd7 +Signed-off-by: Wang Long +Signed-off-by: jingrui +--- + .../engine/pkg/devicemapper/devmapper.go | 21 ++++++ + .../pkg/devicemapper/devmapper_wrapper.go | 67 +++++++++++++++++++ + 2 files changed, 88 insertions(+) + +diff --git a/components/engine/pkg/devicemapper/devmapper.go b/components/engine/pkg/devicemapper/devmapper.go +index b384a27f8f..06ddc3e96b 100644 +--- a/components/engine/pkg/devicemapper/devmapper.go ++++ b/components/engine/pkg/devicemapper/devmapper.go +@@ -55,6 +55,7 @@ var ( + ErrTaskGetDeps = errors.New("dm_task_get_deps failed") + ErrTaskGetInfo = errors.New("dm_task_get_info failed") + ErrTaskGetDriverVersion = errors.New("dm_task_get_driver_version failed") ++ ErrTaskGetNames = errors.New("dm_task_get_names failed") + ErrTaskDeferredRemove = errors.New("dm_task_deferred_remove failed") + ErrTaskSetCookie = errors.New("dm_task_set_cookie failed") + ErrNilCookie = errors.New("cookie ptr can't be nil") +@@ -241,6 +242,14 @@ func (t *Task) getInfoWithDeferred() (*Info, error) { + return info, nil + } + ++func (t *Task) getDeviceList() ([]string, error) { ++ res := DmTaskGetNames(t.unmanaged) ++ if res == nil { ++ return nil, ErrTaskGetNames ++ } ++ return res, nil ++} ++ + func (t *Task) getDriverVersion() (string, error) { + res := DmTaskGetDriverVersion(t.unmanaged) + if res == "" { +@@ -569,6 +578,18 @@ func GetInfoWithDeferred(name string) (*Info, error) { + return task.getInfoWithDeferred() + } + ++// GetDevices get all device name ++func GetDeviceList() ([]string, error) { ++ task := TaskCreate(deviceList) ++ if task == nil { ++ return nil, fmt.Errorf("devicemapper: Can't create deviceList task") ++ } ++ if err := task.run(); err != nil { ++ return nil, err ++ } ++ return task.getDeviceList() ++} ++ + // GetDriverVersion is the programmatic example of "dmsetup version". + // It outputs version information of the driver. + func GetDriverVersion() (string, error) { +diff --git a/components/engine/pkg/devicemapper/devmapper_wrapper.go b/components/engine/pkg/devicemapper/devmapper_wrapper.go +index 77cd674a09..3b00a3b54b 100644 +--- a/components/engine/pkg/devicemapper/devmapper_wrapper.go ++++ b/components/engine/pkg/devicemapper/devmapper_wrapper.go +@@ -6,6 +6,9 @@ package devicemapper // import "github.com/docker/docker/pkg/devicemapper" + #define _GNU_SOURCE + #include + #include // FIXME: present only for BLKGETSIZE64, maybe we can remove it? ++#include ++#include ++ + + // FIXME: Can't we find a way to do the logging in pure Go? + extern void DevmapperLogCallback(int level, char *file, int line, int dm_errno_or_class, char *str); +@@ -32,6 +35,51 @@ static void log_with_errno_init() + { + dm_log_with_errno_init(log_cb); + } ++ ++// FIXME: how to use dm_task_get_names directly ++static char **local_dm_task_get_names(struct dm_task *dmt, unsigned int *size) { ++ struct dm_names *ns, *ns1; ++ unsigned next = 0; ++ char **result; ++ int i = 0; ++ ++ if (!(ns = dm_task_get_names(dmt))) ++ return NULL; ++ ++ // No devices found ++ if (!ns->dev) ++ return NULL; ++ ++ // calucate the total devices ++ ns1 = ns; ++ *size = 0; ++ do { ++ ns1 = (struct dm_names *)((char *) ns1 + next); ++ (*size)++; ++ next = ns1->next; ++ } while (next); ++ ++ result = malloc(sizeof(char *)* (*size)); ++ if (!result) ++ return NULL; ++ ++ next = 0; ++ do { ++ ns = (struct dm_names *)((char *) ns + next); ++ result[i++] = strdup(ns->name); ++ next = ns->next; ++ } while (next); ++ ++ return result; ++} ++ ++void free_devices_names(char **names, unsigned int size) { ++ int i; ++ ++ for (i = 0; i < size; i++) ++ free(names[i]); ++ free(names); ++} + */ + import "C" + +@@ -69,6 +117,7 @@ var ( + DmTaskGetDeps = dmTaskGetDepsFct + DmTaskGetInfo = dmTaskGetInfoFct + DmTaskGetDriverVersion = dmTaskGetDriverVersionFct ++ DmTaskGetNames = dmTaskGetNamesFct + DmTaskRun = dmTaskRunFct + DmTaskSetAddNode = dmTaskSetAddNodeFct + DmTaskSetCookie = dmTaskSetCookieFct +@@ -190,6 +239,24 @@ func dmTaskGetInfoFct(task *cdmTask, info *Info) int { + return int(C.dm_task_get_info((*C.struct_dm_task)(task), &Cinfo)) + } + ++func dmTaskGetNamesFct(task *cdmTask) []string { ++ var res []string ++ var names []*C.char ++ len := C.uint(0) ++ Cnames := C.local_dm_task_get_names((*C.struct_dm_task)(task), &len) ++ defer C.free_devices_names(Cnames, len) ++ ++ hdr := (*reflect.SliceHeader)(unsafe.Pointer(&names)) ++ hdr.Cap = int(len) ++ hdr.Len = int(len) ++ hdr.Data = uintptr(unsafe.Pointer(Cnames)) ++ ++ for _, name := range names { ++ res = append(res, C.GoString(name)) ++ } ++ return res ++} ++ + func dmTaskGetDriverVersionFct(task *cdmTask) string { + buffer := C.malloc(128) + defer C.free(buffer) +-- +2.17.1 + diff --git a/patch/0041-devmapper-devmapper-remove-broken-device-when.patch b/patch/0041-devmapper-devmapper-remove-broken-device-when.patch new file mode 100644 index 0000000..fe40752 --- /dev/null +++ b/patch/0041-devmapper-devmapper-remove-broken-device-when.patch @@ -0,0 +1,69 @@ +From 544b5be235f9a34a5c75a936549bf6d6f2280339 Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Thu, 3 Jan 2019 15:22:26 +0800 +Subject: [PATCH 041/111] devmapper: devmapper: remove broken device + when start daemon + +reason: cherry-pick commits to docker-18.09 + +cherry-pick from +6104c8e237 | * devmapper: remove broken device when start daemon + +When use `kill -9 docker-daemon-pid` to stop docker, It may casue that +the status of devicemapper devices inconsisitent. eg, we can see the +device info use `dmsetup status` command, but the device does not exist +in `/dev/mapper` directory. + +At next time docker start, it will start failed or load container +failed. + +This patch add a check when docker start in `initDevmapper` function. +First, we find all devmapper devices in the system. Then If the device +name has a prefix with `docker-{major}:{minor}-{inode}` and the length +is equal to zero, we use `devicemapper.RemoveDevice` remove the device. + + +Change-Id: I7eccf4e9539751d615b953abf94951b7e94153d1 +Signed-off-by: Wang Long +Signed-off-by: jingrui +--- + .../daemon/graphdriver/devmapper/deviceset.go | 23 +++++++++++++++++++ + 1 file changed, 23 insertions(+) + +diff --git a/components/engine/daemon/graphdriver/devmapper/deviceset.go b/components/engine/daemon/graphdriver/devmapper/deviceset.go +index be48d92e8d..b3e142e2ba 100644 +--- a/components/engine/daemon/graphdriver/devmapper/deviceset.go ++++ b/components/engine/daemon/graphdriver/devmapper/deviceset.go +@@ -1732,6 +1732,29 @@ func (devices *DeviceSet) initDevmapper(doInit bool) (retErr error) { + devices.devicePrefix = fmt.Sprintf("docker-%d:%d-%d", major(st.Dev), minor(st.Dev), st.Ino) + logger.Debugf("Generated prefix: %s", devices.devicePrefix) + ++ deviceNames, err := devicemapper.GetDeviceList() ++ if err != nil { ++ logrus.Debugf("devmapper: Failed to get device list: %s", err) ++ } ++ ++ for _, name := range deviceNames { ++ if !strings.HasPrefix(name, devices.devicePrefix) { ++ continue ++ } ++ _, length, _, _, err := devicemapper.GetStatus(name) ++ if err != nil { ++ logrus.Warnf("devmapper: get device status(%s): %s", name, err) ++ continue ++ } ++ // remove broken device ++ if length == 0 { ++ if err := devicemapper.RemoveDevice(name); err != nil { ++ logrus.Warnf("devmapper: remove broken device(%s): %s", name, err) ++ } ++ logrus.Debugf("devmapper: remove broken device: %s", name) ++ } ++ } ++ + // Check for the existence of the thin-pool device + poolExists, err := devices.thinPoolExists(devices.getPoolName()) + if err != nil { +-- +2.17.1 + diff --git a/patch/0042-oci-Cannot-join-own-pid-ipc-namespace.patch b/patch/0042-oci-Cannot-join-own-pid-ipc-namespace.patch new file mode 100644 index 0000000..4e4647a --- /dev/null +++ b/patch/0042-oci-Cannot-join-own-pid-ipc-namespace.patch @@ -0,0 +1,137 @@ +From 50c51324293064e12e99da9dfbb3554ab1255f51 Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Thu, 3 Jan 2019 15:30:42 +0800 +Subject: [PATCH 042/111] oci: Cannot join own pid/ipc namespace + +reason: cherry-pick commits to docker-18.09 + +Change-Id: I34a0d97a4196d509d80d20f262f3fbac555e0745 +Signed-off-by: Yuanhong Peng +Signed-off-by: jingrui +--- + .../daemon/container_operations_unix.go | 20 +++++++++----- + components/engine/daemon/oci_linux.go | 2 +- + .../integration-cli/docker_cli_run_test.go | 27 +++++++++++++++++++ + 3 files changed, 42 insertions(+), 7 deletions(-) + +diff --git a/components/engine/daemon/container_operations_unix.go b/components/engine/daemon/container_operations_unix.go +index 9953c7f3fd..2cc2b2e3cd 100644 +--- a/components/engine/daemon/container_operations_unix.go ++++ b/components/engine/daemon/container_operations_unix.go +@@ -58,13 +58,17 @@ func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]s + return env, nil + } + +-func (daemon *Daemon) getIpcContainer(id string) (*container.Container, error) { +- errMsg := "can't join IPC of container " + id ++func (daemon *Daemon) getIpcContainer(cc *container.Container) (*container.Container, error) { ++ containerID := cc.HostConfig.IpcMode.Container() ++ errMsg := "can't join IPC of container " + containerID + // Check the container exists +- container, err := daemon.GetContainer(id) ++ container, err := daemon.GetContainer(containerID) + if err != nil { + return nil, errors.Wrap(err, errMsg) + } ++ if container.ID == cc.ID { ++ return nil, fmt.Errorf("cannot join own ipc namespace") ++ } + // Check the container is running and not restarting + if err := daemon.checkContainer(container, containerIsRunning, containerIsNotRestarting); err != nil { + return nil, errors.Wrap(err, errMsg) +@@ -81,12 +85,16 @@ func (daemon *Daemon) getIpcContainer(id string) (*container.Container, error) { + return container, nil + } + +-func (daemon *Daemon) getPidContainer(container *container.Container) (*container.Container, error) { +- containerID := container.HostConfig.PidMode.Container() ++func (daemon *Daemon) getPidContainer(cc *container.Container) (*container.Container, error) { ++ containerID := cc.HostConfig.PidMode.Container() + container, err := daemon.GetContainer(containerID) + if err != nil { + return nil, errors.Wrapf(err, "cannot join PID of a non running container: %s", containerID) + } ++ if container.ID == cc.ID { ++ return nil, fmt.Errorf("cannot join own pid namespace") ++ } ++ + return container, daemon.checkContainer(container, containerIsRunning, containerIsNotRestarting) + } + +@@ -109,7 +117,7 @@ func (daemon *Daemon) setupIpcDirs(c *container.Container) error { + + switch { + case ipcMode.IsContainer(): +- ic, err := daemon.getIpcContainer(ipcMode.Container()) ++ ic, err := daemon.getIpcContainer(c) + if err != nil { + return err + } +diff --git a/components/engine/daemon/oci_linux.go b/components/engine/daemon/oci_linux.go +index 884739c07e..f5270bd545 100644 +--- a/components/engine/daemon/oci_linux.go ++++ b/components/engine/daemon/oci_linux.go +@@ -256,7 +256,7 @@ func setNamespaces(daemon *Daemon, s *specs.Spec, c *container.Container) error + switch { + case ipcMode.IsContainer(): + ns := specs.LinuxNamespace{Type: "ipc"} +- ic, err := daemon.getIpcContainer(ipcMode.Container()) ++ ic, err := daemon.getIpcContainer(c) + if err != nil { + return err + } +diff --git a/components/engine/integration-cli/docker_cli_run_test.go b/components/engine/integration-cli/docker_cli_run_test.go +index 4f55c05aeb..4a137c6159 100644 +--- a/components/engine/integration-cli/docker_cli_run_test.go ++++ b/components/engine/integration-cli/docker_cli_run_test.go +@@ -2323,6 +2323,15 @@ func (s *DockerSuite) TestRunModeIpcContainerNotExists(c *check.C) { + } + } + ++func (s *DockerSuite) TestJoinOwnIpcNamespace(c *check.C) { ++ // Not applicable on Windows as uses Unix-specific capabilities ++ testRequires(c, DaemonIsLinux, NotUserNamespace) ++ _, _, err := dockerCmdWithError("run", "-d", "--name", "testipc", "--ipc", "container:testipc", "busybox", "top") ++ if err == nil { ++ c.Fatalf("Join own ipc namespace is not permitted") ++ } ++} ++ + func (s *DockerSuite) TestRunModeIpcContainerNotRunning(c *check.C) { + // Not applicable on Windows as uses Unix-specific capabilities + testRequires(c, SameHostDaemon, DaemonIsLinux) +@@ -2383,6 +2392,15 @@ func (s *DockerSuite) TestRunModePIDContainerNotRunning(c *check.C) { + } + } + ++func (s *DockerSuite) TestJoinOwnPidNamespace(c *check.C) { ++ // Not applicable on Windows as uses Unix-specific capabilities ++ testRequires(c, DaemonIsLinux) ++ _, _, err := dockerCmdWithError("run", "-d", "--name", "testpid", "--pid", "container:testpid", "busybox", "top") ++ if err == nil { ++ c.Fatalf("Join own pid namespace is not permitted") ++ } ++} ++ + func (s *DockerSuite) TestRunMountShmMqueueFromHost(c *check.C) { + // Not applicable on Windows as uses Unix-specific capabilities + testRequires(c, SameHostDaemon, DaemonIsLinux, NotUserNamespace) +@@ -2621,6 +2639,15 @@ func (s *DockerSuite) TestRunNetContainerWhichHost(c *check.C) { + } + } + ++func (s *DockerSuite) TestJoinOwnNetNamespace(c *check.C) { ++ // Not applicable on Windows as uses Unix-specific capabilities ++ testRequires(c, DaemonIsLinux, NotUserNamespace) ++ _, _, err := dockerCmdWithError("run", "-d", "--name", "testnet", "--net", "container:testnet", "busybox", "top") ++ if err == nil { ++ c.Fatalf("Join own net namespace is not permitted") ++ } ++} ++ + func (s *DockerSuite) TestRunAllowPortRangeThroughPublish(c *check.C) { + // TODO Windows. This may be possible to enable in the future. However, + // Windows does not currently support --expose, or populate the network +-- +2.17.1 + diff --git a/patch/0043-docker-Make-sure-the-pid-exist-in-pidfile-is-.patch b/patch/0043-docker-Make-sure-the-pid-exist-in-pidfile-is-.patch new file mode 100644 index 0000000..d449664 --- /dev/null +++ b/patch/0043-docker-Make-sure-the-pid-exist-in-pidfile-is-.patch @@ -0,0 +1,81 @@ +From f70a2648621ab1463299eabecda8e8c7584831c3 Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Thu, 3 Jan 2019 16:01:36 +0800 +Subject: [PATCH 043/111] docker: Make sure the pid exist in pidfile + is docker + +reason: cherry-pick commits to docker-18.09. + +cherry-pick from +b4714e3321 | * Make sure the pid exist in pidfile is docker + + +Because of the recycling of used PIDs policy in kernel, +it could be possible that the process died with pidfile existing +and when the process boot next time, the pid in pidfile has been assigned +to another process, this would make the process can't be boot again without +removing the pidfile manually. + +Change-Id: I237566682716733174900cd4dc76ce74ff9f4195 +Signed-off-by: Lei Jitang +Signed-off-by: jingrui +--- + components/engine/pkg/pidfile/pidfile.go | 30 +++++++++++++++++++++++- + 1 file changed, 29 insertions(+), 1 deletion(-) + +diff --git a/components/engine/pkg/pidfile/pidfile.go b/components/engine/pkg/pidfile/pidfile.go +index 0617a89e5f..485c00138b 100644 +--- a/components/engine/pkg/pidfile/pidfile.go ++++ b/components/engine/pkg/pidfile/pidfile.go +@@ -4,6 +4,7 @@ + package pidfile // import "github.com/docker/docker/pkg/pidfile" + + import ( ++ "bufio" + "fmt" + "io/ioutil" + "os" +@@ -19,12 +20,39 @@ type PIDFile struct { + path string + } + ++// isSameApplication check whether the pid exist in pidfile ++// is the the same application we are going to run. ++func isSameApplication(pid int) (bool, error) { ++ path := filepath.Join("/proc", strconv.Itoa(pid), "status") ++ file, err := os.Open(path) ++ if err != nil { ++ return false, err ++ } ++ defer file.Close() ++ sc := bufio.NewScanner(file) ++ for sc.Scan() { ++ lens := strings.Split(sc.Text(), ":") ++ if len(lens) == 2 && strings.TrimSpace(lens[0]) == "Name" { ++ if strings.TrimSpace(lens[1]) == os.Args[0] { ++ return true, nil ++ } ++ return false, nil ++ } ++ } ++ if err := sc.Err(); err != nil { ++ return false, err ++ } ++ return false, nil ++} ++ + func checkPIDFileAlreadyExists(path string) error { + if pidByte, err := ioutil.ReadFile(path); err == nil { + pidString := strings.TrimSpace(string(pidByte)) + if pid, err := strconv.Atoi(pidString); err == nil { + if processExists(pid) { +- return fmt.Errorf("pid file found, ensure docker is not running or delete %s", path) ++ if same, err := isSameApplication(pid); same || (err != nil && !os.IsNotExist(err)) { ++ return fmt.Errorf("pid file found, ensure docker is not running or delete %s", path) ++ } + } + } + } +-- +2.17.1 + diff --git a/patch/0044-plugin-Fix-plugin-security-bug-caused-by-unch.patch b/patch/0044-plugin-Fix-plugin-security-bug-caused-by-unch.patch new file mode 100644 index 0000000..d3bffe0 --- /dev/null +++ b/patch/0044-plugin-Fix-plugin-security-bug-caused-by-unch.patch @@ -0,0 +1,99 @@ +From 75d53c469ea6115db0386155262565a8aa15556d Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Thu, 3 Jan 2019 16:04:37 +0800 +Subject: [PATCH 044/111] plugin: Fix plugin security bug caused by + unchecked plugin name + +reason: cherry-pick commits to docker-18.09 + +cherry-pick from 48c9622f82 | * plugin,bugfix: Fix plugin security bug +caused by unchecked plugin name + +Docker may activate plugins outside plugin directory if plugin name +contains string like "../../". This patch fix this bug by checking the +combined plugin path before use it. + +fix issue docker/docker#268 + +Change-Id: Icff8b24e50fc92721149267bc8c29a8652046d8a +Signed-off-by: majiuyue +Signed-off-by: jingrui +--- + components/engine/pkg/plugins/discovery.go | 27 ++++++++++++++++------ + components/engine/pkg/plugins/plugins.go | 2 +- + 2 files changed, 21 insertions(+), 8 deletions(-) + +diff --git a/components/engine/pkg/plugins/discovery.go b/components/engine/pkg/plugins/discovery.go +index 4b79bd29ad..51f1d8ebea 100644 +--- a/components/engine/pkg/plugins/discovery.go ++++ b/components/engine/pkg/plugins/discovery.go +@@ -14,6 +14,8 @@ import ( + ) + + var ( ++ // ErrForbidden plugin sock/spec outside allowed location ++ ErrForbidden = errors.New("plugin outside allowed location") + // ErrNotFound plugin not found + ErrNotFound = errors.New("plugin not found") + socketsPath = "/run/docker/plugins" +@@ -82,7 +84,10 @@ func Scan() ([]string, error) { + + // Plugin returns the plugin registered with the given name (or returns an error). + func (l *localRegistry) Plugin(name string) (*Plugin, error) { +- socketpaths := pluginPaths(socketsPath, name, ".sock") ++ socketpaths, err := pluginPaths(socketsPath, name, ".sock") ++ if err != nil { ++ return nil, ErrForbidden ++ } + + for _, p := range socketpaths { + if fi, err := os.Stat(p); err == nil && fi.Mode()&os.ModeSocket != 0 { +@@ -92,8 +97,10 @@ func (l *localRegistry) Plugin(name string) (*Plugin, error) { + + var txtspecpaths []string + for _, p := range specsPaths { +- txtspecpaths = append(txtspecpaths, pluginPaths(p, name, ".spec")...) +- txtspecpaths = append(txtspecpaths, pluginPaths(p, name, ".json")...) ++ for _, ext := range []string{".spec", ".json"} { ++ paths, _ := pluginPaths(p, name, ext) ++ txtspecpaths = append(txtspecpaths, paths...) ++ } + } + + for _, p := range txtspecpaths { +@@ -146,9 +153,15 @@ func readPluginJSONInfo(name, path string) (*Plugin, error) { + return &p, nil + } + +-func pluginPaths(base, name, ext string) []string { +- return []string{ +- filepath.Join(base, name+ext), +- filepath.Join(base, name, name+ext), ++func pluginPaths(base, name, ext string) ([]string, error) { ++ paths := []string{ ++ filepath.Clean(filepath.Join(base, name+ext)), ++ filepath.Clean(filepath.Join(base, name, name+ext)), ++ } ++ for _, p := range paths { ++ if !strings.HasPrefix(p, base) { ++ return nil, ErrForbidden ++ } + } ++ return paths, nil + } +diff --git a/components/engine/pkg/plugins/plugins.go b/components/engine/pkg/plugins/plugins.go +index 6962079df9..8a6fbeda29 100644 +--- a/components/engine/pkg/plugins/plugins.go ++++ b/components/engine/pkg/plugins/plugins.go +@@ -208,7 +208,7 @@ func loadWithRetry(name string, retry bool) (*Plugin, error) { + for { + pl, err := registry.Plugin(name) + if err != nil { +- if !retry { ++ if !retry || err == ErrForbidden { + return nil, err + } + +-- +2.17.1 + diff --git a/patch/0045-overlay-safely-remove-overlay-layer-directory.patch b/patch/0045-overlay-safely-remove-overlay-layer-directory.patch new file mode 100644 index 0000000..c639ac3 --- /dev/null +++ b/patch/0045-overlay-safely-remove-overlay-layer-directory.patch @@ -0,0 +1,122 @@ +From d2901f9efd4b1b26c995ea1c61663a14bc9c55d6 Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Thu, 3 Jan 2019 16:25:23 +0800 +Subject: [PATCH 045/111] overlay: safely remove overlay layer + directory + +reason: cherry-pick commits to docker-18.09 + +merge from +0af3bf355a * safely remove overlay layer directory +e2b1d6827b * docker: add link string validation + +--- +safely remove overlay layer directory + +do not recover link if the format is illegal +do not remove illegal link string + +Signed-off-by: Deng Guangxing +--- +docker: add link string validation + +validate link string with restrict reqirements, not just stringLen. +Shukui Yang + +Signed-off-by: Deng Guangxing +Signed-off-by: yangshukui + +Change-Id: Ie4f47b942c7e89bd6632d310c1cb34533ed5726b +Signed-off-by: jingrui +--- + .../daemon/graphdriver/overlay2/overlay.go | 30 +++++++++++++++++-- + .../daemon/graphdriver/overlay2/randomid.go | 7 +++++ + 2 files changed, 34 insertions(+), 3 deletions(-) + +diff --git a/components/engine/daemon/graphdriver/overlay2/overlay.go b/components/engine/daemon/graphdriver/overlay2/overlay.go +index 722d65b11a..773d5232cc 100644 +--- a/components/engine/daemon/graphdriver/overlay2/overlay.go ++++ b/components/engine/daemon/graphdriver/overlay2/overlay.go +@@ -419,6 +419,10 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts) (retErr + } + + lid := generateID(idLength) ++ if !verifyID(lid, idLength) { ++ // this should never happen ++ return fmt.Errorf("[overlay2], generated link string(%s) illegal", lid) ++ } + if err := os.Symlink(path.Join("..", id, "diff"), path.Join(d.home, linkDir, lid)); err != nil { + return err + } +@@ -491,6 +495,9 @@ func (d *Driver) getLower(parent string) (string, error) { + if err != nil { + return "", err + } ++ if !verifyID(string(parentLink), idLength) { ++ return "", fmt.Errorf("illegal link string: %s", parentLink) ++ } + lowers := []string{path.Join(linkDir, string(parentLink))} + + parentLower, err := ioutil.ReadFile(path.Join(parentDir, lowerFile)) +@@ -570,7 +577,7 @@ func (d *Driver) Remove(id string) error { + dir := d.dir(id) + lid, err := ioutil.ReadFile(path.Join(dir, "link")) + if err == nil { +- if len(lid) == 0 { ++ if !verifyID(string(lid), idLength) { + logrus.WithField("storage-driver", "overlay2").Errorf("refusing to remove empty link for layer %v", id) + } else if err := os.RemoveAll(path.Join(d.home, linkDir, string(lid))); err != nil { + logrus.WithField("storage-driver", "overlay2").Debugf("Failed to remove link: %v", err) +@@ -703,8 +710,25 @@ func (d *Driver) Put(id string) error { + + // Exists checks to see if the id is already mounted. + func (d *Driver) Exists(id string) bool { +- _, err := os.Stat(d.dir(id)) +- return err == nil ++ _, rerr := os.Stat(d.dir(id)) ++ if rerr == nil { ++ lstr, err := ioutil.ReadFile(path.Join(d.dir(id), "link")) ++ // link is valid ++ if err == nil && verifyID(string(lstr), idLength) { ++ // check symlink ++ _, rerr = os.Stat(path.Join(d.home, linkDir, string(lstr))) ++ if rerr != nil { ++ os.RemoveAll(path.Join(d.home, linkDir, string(lstr))) ++ ++ logrus.Infof("[overlay2]: symlink (%s) is missing, create a new one", lstr) ++ if rerr = os.Symlink(path.Join("..", id, "diff"), path.Join(d.home, linkDir, string(lstr))); rerr != nil { ++ return false ++ } ++ } ++ return true ++ } ++ } ++ return false + } + + // isParent determines whether the given parent is the direct parent of the +diff --git a/components/engine/daemon/graphdriver/overlay2/randomid.go b/components/engine/daemon/graphdriver/overlay2/randomid.go +index 842c06127f..933d9fccb6 100644 +--- a/components/engine/daemon/graphdriver/overlay2/randomid.go ++++ b/components/engine/daemon/graphdriver/overlay2/randomid.go +@@ -8,6 +8,7 @@ import ( + "fmt" + "io" + "os" ++ "regexp" + "syscall" + "time" + +@@ -79,3 +80,9 @@ func retryOnError(err error) bool { + + return false + } ++ ++func verifyID(id string, l int) bool { ++ regstr := fmt.Sprintf("^[A-Z0-9]{%d}$", l) ++ rgxp := regexp.MustCompile(regstr) ++ return rgxp.MatchString(id) ++} +-- +2.17.1 + diff --git a/patch/0046-debug-add-more-error-info-when-dial-docker.so.patch b/patch/0046-debug-add-more-error-info-when-dial-docker.so.patch new file mode 100644 index 0000000..fd26143 --- /dev/null +++ b/patch/0046-debug-add-more-error-info-when-dial-docker.so.patch @@ -0,0 +1,57 @@ +From 024a67b1d7ccfa85bba14318cd4fbbe78ecc8b7e Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Thu, 10 Jan 2019 00:17:01 +0800 +Subject: [PATCH 046/111] debug: add more error info when dial + docker.sock fail + +reason: cherry-pick commits to docker-18.09 + +add more error info when dial docker.sock fail + +cherry-pick from 1.11.2: 91c7491 + +Change-Id: I3c7219d44be752ecde92479f03c0e2cee3ccb4a0 +Signed-off-by: lujingxiao +Signed-off-by: xiadanni +--- + components/engine/client/request.go | 15 ++++++++++++--- + 1 file changed, 12 insertions(+), 3 deletions(-) + +diff --git a/components/engine/client/request.go b/components/engine/client/request.go +index a19d62aa52..855b84d6ac 100644 +--- a/components/engine/client/request.go ++++ b/components/engine/client/request.go +@@ -10,6 +10,7 @@ import ( + "net" + "net/http" + "net/url" ++ "regexp" + "os" + "strings" + +@@ -156,11 +157,19 @@ func (cli *Client) doRequest(ctx context.Context, req *http.Request) (serverResp + + if err, ok := err.(net.Error); ok { + if err.Timeout() { +- return serverResp, ErrorConnectionFailed(cli.host) ++ return serverResp, fmt.Errorf("Cannot connect to the Docker daemon failed for timeout.") + } + if !err.Temporary() { +- if strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "dial unix") { +- return serverResp, ErrorConnectionFailed(cli.host) ++ if strings.Contains(err.Error(), "dial unix") { ++ var err2 error ++ r, rerr := regexp.Compile("dial unix.*") ++ if rerr != nil { ++ err2 = fmt.Errorf("Cannot connect to the Docker daemon failed for dial unix.") ++ } else { ++ rbytes := r.Find([]byte(err.Error())) ++ err2 = fmt.Errorf("Cannot connect to the Docker daemon failed for %s", string(rbytes)) ++ } ++ return serverResp, err2 + } + } + } +-- +2.17.1 + diff --git a/patch/0047-docker-fix-panic-slice-bounds-out-of-range.patch b/patch/0047-docker-fix-panic-slice-bounds-out-of-range.patch new file mode 100644 index 0000000..975fac0 --- /dev/null +++ b/patch/0047-docker-fix-panic-slice-bounds-out-of-range.patch @@ -0,0 +1,59 @@ +From 8483caa076b11f33e9a4c578b8aefce127468e66 Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Fri, 11 Jan 2019 12:18:27 +0800 +Subject: [PATCH 047/111] docker: fix panic slice bounds out of range + +reason: fix panic + +panic: runtime error: slice bounds out of range + +goroutine 1 [running]: +github.com/docker/docker/daemon.parseInitVersion(0xc4209da600, 0x22, 0x22, 0x600, 0xc4209da600, 0x22, 0x28, 0x0) + /go/src/github.com/docker/docker/daemon/info_unix.go:163 +0x3ce +github.com/docker/docker/daemon.(*Daemon).fillPlatformInfo(0xc4206e21e0, 0xc42009cc00, 0xc42081d220) + /go/src/github.com/docker/docker/daemon/info_unix.go:68 +0x115f +github.com/docker/docker/daemon.(*Daemon).SystemInfo(0xc4206e21e0, 0x0, 0x0, 0xc42020d3c0) + /go/src/github.com/docker/docker/daemon/info.go:75 +0x7e8 +github.com/docker/docker/daemon.NewDaemon(0x55b515ace080, 0xc4201ab400, 0xc4207c5400, 0xc4207602d0, 0x0, 0x0, 0x0) + /go/src/github.com/docker/docker/daemon/daemon.go:1080 +0x2c87 +main.(*DaemonCli).start(0xc420727260, 0xc42016f6e0, 0x0, 0x0) + /go/src/github.com/docker/docker/cmd/dockerd/daemon.go:180 +0x74f +main.runDaemon(0xc42016f6e0, 0xc420194600, 0x0) + /go/src/github.com/docker/docker/cmd/dockerd/docker_unix.go:7 +0x47 +main.newDaemonCommand.func1(0xc42076d680, 0xc420190d80, 0x0, 0x8, 0x0, 0x0) + /go/src/github.com/docker/docker/cmd/dockerd/docker.go:29 +0x5d +github.com/docker/docker/vendor/github.com/spf13/cobra.(*Command).execute(0xc42076d680, 0xc42003a0a0, 0x8, 0x8, 0xc42076d680, 0xc42003a0a0) + /go/src/github.com/docker/docker/vendor/github.com/spf13/cobra/command.go:762 +0x46a +github.com/docker/docker/vendor/github.com/spf13/cobra.(*Command).ExecuteC(0xc42076d680, 0x55b515aa3350, 0x55b51565f0c0, 0x55b515aa3360) + /go/src/github.com/docker/docker/vendor/github.com/spf13/cobra/command.go:852 +0x30c +github.com/docker/docker/vendor/github.com/spf13/cobra.(*Command).Execute(0xc42076d680, 0xc42000e020, 0x55b5138f336f) + /go/src/github.com/docker/docker/vendor/github.com/spf13/cobra/command.go:800 +0x2d +main.main() + /go/src/github.com/docker/docker/cmd/dockerd/docker.go:70 +0xa2 + +Change-Id: Iafadb0c9215e1840c084637ebc96f5ef4d004cbd +Signed-off-by: jingrui +--- + components/engine/daemon/info_unix.go | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/components/engine/daemon/info_unix.go b/components/engine/daemon/info_unix.go +index 55b6c6e79b..c53804edec 100644 +--- a/components/engine/daemon/info_unix.go ++++ b/components/engine/daemon/info_unix.go +@@ -160,7 +160,11 @@ func parseInitVersion(v string) (types.Commit, error) { + gitParts := strings.Split(parts[1], ".") + if len(gitParts) == 2 && gitParts[0] == "git" { + version.ID = gitParts[1] +- version.Expected = dockerversion.InitCommitID[0:len(version.ID)] ++ n := len(dockerversion.InitCommitID) ++ if n > len(version.ID) { ++ n = len(version.ID) ++ } ++ version.Expected = dockerversion.InitCommitID[0:n] + } + } + if version.ID == "" && strings.HasPrefix(parts[0], "tini version ") { +-- +2.17.1 + diff --git a/patch/0048-docker-check-metadata-when-load-layer.patch b/patch/0048-docker-check-metadata-when-load-layer.patch new file mode 100644 index 0000000..a4ded6f --- /dev/null +++ b/patch/0048-docker-check-metadata-when-load-layer.patch @@ -0,0 +1,36 @@ +From 7eb6d78447ed19a19c57331cb63e58097d29caeb Mon Sep 17 00:00:00 2001 +From: zhangyu235 +Date: Tue, 8 Jan 2019 18:19:16 +0800 +Subject: [PATCH 048/111] docker: check metadata when load layer + +reason:check metadata when load layer + +Cherry-pick from docker 1.11.2: +- 41795d7 check metadata when load layer + +Change-Id: Ia1734172a33d0ebe29ddb4b76d207da2981a137a +Signed-off-by: Liu Hua +Signed-off-by: zhangyu235 +--- + components/engine/layer/layer_store.go | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/components/engine/layer/layer_store.go b/components/engine/layer/layer_store.go +index 5decb0bdce..f22e9c666c 100644 +--- a/components/engine/layer/layer_store.go ++++ b/components/engine/layer/layer_store.go +@@ -193,6 +193,11 @@ func (ls *layerStore) loadLayer(layer ChainID) (l *roLayer, err error) { + return nil, err + } + cl.parent = p ++ } else { ++ _, err := ls.driver.GetMetadata(cacheID) ++ if err != nil { ++ return nil, fmt.Errorf("cacheID %s for layer %s does not have metadata ", cacheID, layer) ++ } + } + + ls.layerMap[cl.chainID] = cl +-- +2.17.1 + diff --git a/patch/0049-cleanup-cleanup-useless-netns-file-in-var-run.patch b/patch/0049-cleanup-cleanup-useless-netns-file-in-var-run.patch new file mode 100644 index 0000000..df09f7a --- /dev/null +++ b/patch/0049-cleanup-cleanup-useless-netns-file-in-var-run.patch @@ -0,0 +1,81 @@ +From 652be7c8c259c25adaed10f6f121a7d18283daa0 Mon Sep 17 00:00:00 2001 +From: zhangyu235 +Date: Tue, 8 Jan 2019 19:08:25 +0800 +Subject: [PATCH 049/111] cleanup: cleanup useless netns file in + /var/run/docker/netns + +reason:killing daemon would left useless netns file. +try to clean them up during daemon restore according to +activesandboxes + +cherrt-pick from docker 1.11.2: +- e657d09 cleanup useless netns file in /var/run/docker/netns + +Change-Id: I4bfff028a1ad2df9a42456b9f181db87d63c7cd7 +Signed-off-by: dengguangxing +Signed-off-by: zhangyu235 +--- + .../docker/libnetwork/controller.go | 2 ++ + .../docker/libnetwork/osl/namespace_linux.go | 33 +++++++++++++++++++ + 2 files changed, 35 insertions(+) + +diff --git a/components/engine/vendor/github.com/docker/libnetwork/controller.go b/components/engine/vendor/github.com/docker/libnetwork/controller.go +index 2896011dbf..95013d31d3 100644 +--- a/components/engine/vendor/github.com/docker/libnetwork/controller.go ++++ b/components/engine/vendor/github.com/docker/libnetwork/controller.go +@@ -247,6 +247,8 @@ func New(cfgOptions ...config.Option) (NetworkController, error) { + c.sandboxCleanup(c.cfg.ActiveSandboxes) + c.cleanupLocalEndpoints() + c.networkCleanup() ++ osl.NetnsFileCleanup(c.cfg.ActiveSandboxes) ++ + + if err := c.startExternalKeyListener(); err != nil { + return nil, err +diff --git a/components/engine/vendor/github.com/docker/libnetwork/osl/namespace_linux.go b/components/engine/vendor/github.com/docker/libnetwork/osl/namespace_linux.go +index bfc5d31a53..f97b286bcd 100644 +--- a/components/engine/vendor/github.com/docker/libnetwork/osl/namespace_linux.go ++++ b/components/engine/vendor/github.com/docker/libnetwork/osl/namespace_linux.go +@@ -586,6 +586,39 @@ func (n *networkNamespace) Restore(ifsopt map[string][]IfaceOption, routes []*ty + return nil + } + ++func NetnsFileCleanup(activeSandboxes map[string]interface{}) { ++ maxLen := 12 ++ dir, err := ioutil.ReadDir(prefix) ++ if err != nil { ++ logrus.Warnf("failed to open %s for netns cleanup: %s", prefix, err) ++ return ++ } ++ ++ activeSandboxesMap := make(map[string]string) ++ // shorten active sandboxes id to 12 char ++ for ac, _ := range activeSandboxes { ++ shortid := ac[:maxLen] ++ activeSandboxesMap[shortid] = shortid ++ } ++ ++ for _, v := range dir { ++ id := v.Name() ++ // skip if id length is not 12, like default ++ if len(id) != maxLen { ++ continue ++ } ++ ++ if _, ok := activeSandboxesMap[id]; !ok { ++ path := filepath.Join(prefix, id) ++ // cleanup netns file if not active ++ syscall.Unmount(path, syscall.MNT_DETACH) ++ if err := os.Remove(path); err != nil { ++ logrus.Warnf("Failed to cleanup netns file %s: %s", path, err) ++ } ++ } ++ } ++} ++ + // Checks whether IPv6 needs to be enabled/disabled on the loopback interface + func (n *networkNamespace) checkLoV6() { + var ( +-- +2.17.1 + diff --git a/patch/0050-volume-Make-v.opts-to-nil-if-opts.json-is-nul.patch b/patch/0050-volume-Make-v.opts-to-nil-if-opts.json-is-nul.patch new file mode 100644 index 0000000..26e2262 --- /dev/null +++ b/patch/0050-volume-Make-v.opts-to-nil-if-opts.json-is-nul.patch @@ -0,0 +1,39 @@ +From 415f93f45004d4082d3a7bcfdee122b584cd2e57 Mon Sep 17 00:00:00 2001 +From: zhangyu235 +Date: Tue, 8 Jan 2019 19:34:41 +0800 +Subject: [PATCH 050/111] volume: Make v.opts to nil if opts.json is + null + +reason:Make v.opts to nil if opts.json is null + +cherry-pick from docker 1.11.2: +- 1b9344b Make v.opts to nil if opts.json is null + +Change-Id: I59d6ebb41cbb908d72beb4f9f6c645cb7ae2c4ba +Signed-off-by: Lei Jitang +Signed-off-by: zhangyu235 +--- + components/engine/volume/local/local.go | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/components/engine/volume/local/local.go b/components/engine/volume/local/local.go +index 7190de9ed6..2e865815a9 100644 +--- a/components/engine/volume/local/local.go ++++ b/components/engine/volume/local/local.go +@@ -89,6 +89,13 @@ func New(scope string, rootIdentity idtools.Identity) (*Root, error) { + v.opts = &opts + } + ++ // For the local volumes created without options, ++ // the opts.json is null, so all the value in v.opts is null ++ // We should make v.opts to nil to avoid v.mount ++ if v.opts != nil && v.opts.MountType == "" && v.opts.MountOpts == "" && v.opts.MountDevice == "" { ++ v.opts = nil ++ } ++ + // unmount anything that may still be mounted (for example, from an unclean shutdown) + mount.Unmount(v.path) + } +-- +2.17.1 + diff --git a/patch/0051-docker-fix-panic-when-load-maliciously-image.patch b/patch/0051-docker-fix-panic-when-load-maliciously-image.patch new file mode 100644 index 0000000..5e372e2 --- /dev/null +++ b/patch/0051-docker-fix-panic-when-load-maliciously-image.patch @@ -0,0 +1,102 @@ +From 1fe0a4bfad5d7fa418f4e726211f4f037e59cfee Mon Sep 17 00:00:00 2001 +From: zhangyu235 +Date: Tue, 8 Jan 2019 19:47:28 +0800 +Subject: [PATCH 051/111] docker: fix panic when load maliciously + image + +reason:fix oom panic when load maliciously modified image with +huge size manifest files + +cherry-pick from docker 1.11.2: +- bbe29c3 fix panic when load maliciously image + +Change-Id: I2525e492fac31c33d3ba7275c95b570322a05025 +Signed-off-by: leizhongkai +Signed-off-by: zhangyu235 +--- + components/engine/image/tarexport/load.go | 28 +++++++++++++++++++ + .../engine/image/tarexport/tarexport.go | 1 + + 2 files changed, 29 insertions(+) + +diff --git a/components/engine/image/tarexport/load.go b/components/engine/image/tarexport/load.go +index 786214383e..b9f8f7e3ac 100644 +--- a/components/engine/image/tarexport/load.go ++++ b/components/engine/image/tarexport/load.go +@@ -49,6 +49,11 @@ func (l *tarexporter) Load(inTar io.ReadCloser, outStream io.Writer, quiet bool) + if err != nil { + return err + } ++ ++ if err = checkJsonFileSize(manifestPath); err != nil { ++ return err ++ } ++ + manifestFile, err := os.Open(manifestPath) + if err != nil { + if os.IsNotExist(err) { +@@ -72,6 +77,9 @@ func (l *tarexporter) Load(inTar io.ReadCloser, outStream io.Writer, quiet bool) + if err != nil { + return err + } ++ if err = checkJsonFileSize(configPath); err != nil { ++ return err ++ } + config, err := ioutil.ReadFile(configPath) + if err != nil { + return err +@@ -246,6 +254,11 @@ func (l *tarexporter) legacyLoad(tmpDir string, outStream io.Writer, progressOut + if err != nil { + return err + } ++ ++ if err = checkJsonFileSize(repositoriesPath); err != nil { ++ return err ++ } ++ + repositoriesFile, err := os.Open(repositoriesPath) + if err != nil { + return err +@@ -286,6 +299,9 @@ func (l *tarexporter) legacyLoadImage(oldID, sourceDir string, loadedMap map[str + if err != nil { + return err + } ++ if err = checkJsonFileSize(configPath); err != nil { ++ return err ++ } + imageJSON, err := ioutil.ReadFile(configPath) + if err != nil { + logrus.Debugf("Error reading json: %v", err) +@@ -413,6 +429,18 @@ func checkValidParent(img, parent *image.Image) bool { + return true + } + ++func checkJsonFileSize(path string) error { ++ fileInfo, err := os.Stat(path) ++ if err != nil { ++ return err ++ } ++ fileSize := fileInfo.Size() ++ if fileSize > maxJsonFileSize { ++ return fmt.Errorf("%s is too large", filepath.Base(path)) ++ } ++ return nil ++} ++ + func checkCompatibleOS(imageOS string) error { + // always compatible if the images OS matches the host OS; also match an empty image OS + if imageOS == runtime.GOOS || imageOS == "" { +diff --git a/components/engine/image/tarexport/tarexport.go b/components/engine/image/tarexport/tarexport.go +index beff668cd8..f23fe6f8bb 100644 +--- a/components/engine/image/tarexport/tarexport.go ++++ b/components/engine/image/tarexport/tarexport.go +@@ -13,6 +13,7 @@ const ( + legacyConfigFileName = "json" + legacyVersionFileName = "VERSION" + legacyRepositoriesFileName = "repositories" ++ maxJsonFileSize = (10 * 1024 * 1024) + ) + + type manifestItem struct { +-- +2.17.1 + diff --git a/patch/0052-docker-Lock-the-RWLayer-while-committing-expo.patch b/patch/0052-docker-Lock-the-RWLayer-while-committing-expo.patch new file mode 100644 index 0000000..17ebbc2 --- /dev/null +++ b/patch/0052-docker-Lock-the-RWLayer-while-committing-expo.patch @@ -0,0 +1,194 @@ +From 568c9501d56ccff1806253f9e2289fdd5c002231 Mon Sep 17 00:00:00 2001 +From: zhangyu235 +Date: Thu, 17 Jan 2019 20:03:14 +0800 +Subject: [PATCH 052/111] docker: Lock the RWLayer while + committing/exporting + +reason:Add a rw mutex to `mountLayer/roLayer/emptyLayer` and introduce +new methods `RLockRWLayer()` and `RUnlockRWLayer()` to `RWLayer`. +Now the rw layer of the container would be locked to block removal of +that layer. + +Cherry-pick from docker 1.11.2: +- 2923a77c Lock the RWLayer while committing/exporting + +Change-Id: I11d6dcb60a23fe3516cebaed36d19aabaa863769 +Signed-off-by: Yuanhong Peng +Signed-off-by: zhangyu235 +--- + components/engine/daemon/commit.go | 2 ++ + components/engine/daemon/export.go | 2 ++ + components/engine/layer/empty.go | 13 ++++++++++++- + components/engine/layer/layer.go | 7 +++++++ + components/engine/layer/layer_store.go | 2 ++ + components/engine/layer/mounted_layer.go | 10 ++++++++++ + components/engine/layer/ro_layer.go | 10 ++++++++++ + 7 files changed, 45 insertions(+), 1 deletion(-) + +diff --git a/components/engine/daemon/commit.go b/components/engine/daemon/commit.go +index 0f6f440514..fc7d2782ef 100644 +--- a/components/engine/daemon/commit.go ++++ b/components/engine/daemon/commit.go +@@ -155,6 +155,8 @@ func (daemon *Daemon) CreateImageFromContainer(name string, c *backend.CreateIma + return "", err + } + ++ container.RWLayer.RLockRWLayer() ++ defer container.RWLayer.RUnlockRWLayer() + id, err := daemon.imageService.CommitImage(backend.CommitConfig{ + Author: c.Author, + Comment: c.Comment, +diff --git a/components/engine/daemon/export.go b/components/engine/daemon/export.go +index 27bc35967d..ebd2d75f40 100644 +--- a/components/engine/daemon/export.go ++++ b/components/engine/daemon/export.go +@@ -34,6 +34,8 @@ func (daemon *Daemon) ContainerExport(name string, out io.Writer) error { + return errdefs.Conflict(err) + } + ++ container.RWLayer.RLockRWLayer() ++ defer container.RWLayer.RUnlockRWLayer() + data, err := daemon.containerExport(container) + if err != nil { + return fmt.Errorf("Error exporting container %s: %v", name, err) +diff --git a/components/engine/layer/empty.go b/components/engine/layer/empty.go +index c81c702140..16a49a7abd 100644 +--- a/components/engine/layer/empty.go ++++ b/components/engine/layer/empty.go +@@ -6,13 +6,16 @@ import ( + "fmt" + "io" + "io/ioutil" ++ "sync" + ) + + // DigestSHA256EmptyTar is the canonical sha256 digest of empty tar file - + // (1024 NULL bytes) + const DigestSHA256EmptyTar = DiffID("sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef") + +-type emptyLayer struct{} ++type emptyLayer struct { ++ sync.RWMutex ++} + + // EmptyLayer is a layer that corresponds to empty tar. + var EmptyLayer = &emptyLayer{} +@@ -55,6 +58,14 @@ func (el *emptyLayer) Metadata() (map[string]string, error) { + return make(map[string]string), nil + } + ++func (el *emptyLayer) RLockRWLayer() { ++ el.RLock() ++} ++ ++func (el *emptyLayer) RUnlockRWLayer() { ++ el.RUnlock() ++} ++ + // IsEmpty returns true if the layer is an EmptyLayer + func IsEmpty(diffID DiffID) bool { + return diffID == DigestSHA256EmptyTar +diff --git a/components/engine/layer/layer.go b/components/engine/layer/layer.go +index cb13c98d0b..e35a13135b 100644 +--- a/components/engine/layer/layer.go ++++ b/components/engine/layer/layer.go +@@ -145,6 +145,13 @@ type RWLayer interface { + + // Metadata returns the low level metadata for the mutable layer + Metadata() (map[string]string, error) ++ ++ // RLockRWLayer locks the RWLayer to block unmounting/removal ++ // of that layer ++ RLockRWLayer() ++ ++ // RUnlockRWLayer unlocks the RWLayer ++ RUnlockRWLayer() + } + + // Metadata holds information about a +diff --git a/components/engine/layer/layer_store.go b/components/engine/layer/layer_store.go +index f22e9c666c..7c80a29645 100644 +--- a/components/engine/layer/layer_store.go ++++ b/components/engine/layer/layer_store.go +@@ -666,6 +666,8 @@ func (ls *layerStore) ReleaseRWLayer(l RWLayer) ([]Metadata, error) { + return []Metadata{}, nil + } + ++ m.Lock() ++ defer m.Unlock() + if err := m.deleteReference(l); err != nil { + return nil, err + } +diff --git a/components/engine/layer/mounted_layer.go b/components/engine/layer/mounted_layer.go +index d6858c662c..66711d6cf7 100644 +--- a/components/engine/layer/mounted_layer.go ++++ b/components/engine/layer/mounted_layer.go +@@ -2,6 +2,7 @@ package layer // import "github.com/docker/docker/layer" + + import ( + "io" ++ "sync" + + "github.com/docker/docker/pkg/archive" + "github.com/docker/docker/pkg/containerfs" +@@ -16,6 +17,7 @@ type mountedLayer struct { + layerStore *layerStore + + references map[RWLayer]*referencedRWLayer ++ sync.RWMutex + } + + func (ml *mountedLayer) cacheParent() string { +@@ -58,6 +60,14 @@ func (ml *mountedLayer) Metadata() (map[string]string, error) { + return ml.layerStore.driver.GetMetadata(ml.mountID) + } + ++func (ml *mountedLayer) RLockRWLayer() { ++ ml.RLock() ++} ++ ++func (ml *mountedLayer) RUnlockRWLayer() { ++ ml.RUnlock() ++} ++ + func (ml *mountedLayer) getReference() RWLayer { + ref := &referencedRWLayer{ + mountedLayer: ml, +diff --git a/components/engine/layer/ro_layer.go b/components/engine/layer/ro_layer.go +index 3555e8b027..59bcf17d18 100644 +--- a/components/engine/layer/ro_layer.go ++++ b/components/engine/layer/ro_layer.go +@@ -3,6 +3,7 @@ package layer // import "github.com/docker/docker/layer" + import ( + "fmt" + "io" ++ "sync" + + "github.com/docker/distribution" + "github.com/opencontainers/go-digest" +@@ -19,6 +20,7 @@ type roLayer struct { + + referenceCount int + references map[Layer]struct{} ++ sync.RWMutex + } + + // TarStream for roLayer guarantees that the data that is produced is the exact +@@ -92,6 +94,14 @@ func (rl *roLayer) Metadata() (map[string]string, error) { + return rl.layerStore.driver.GetMetadata(rl.cacheID) + } + ++func (rl *roLayer) RLockRWLayer() { ++ rl.RLock() ++} ++ ++func (rl *roLayer) RUnlockRWLayer() { ++ rl.RUnlock() ++} ++ + type referencedCacheLayer struct { + *roLayer + } +-- +2.17.1 + diff --git a/patch/0053-docker-remove-init-layer-when-no-space-to-cre.patch b/patch/0053-docker-remove-init-layer-when-no-space-to-cre.patch new file mode 100644 index 0000000..b2443f8 --- /dev/null +++ b/patch/0053-docker-remove-init-layer-when-no-space-to-cre.patch @@ -0,0 +1,46 @@ +From 2afc35ae4a34d70007bccb971d1f074574a3e282 Mon Sep 17 00:00:00 2001 +From: zhangyu235 +Date: Thu, 17 Jan 2019 20:13:18 +0800 +Subject: [PATCH 053/111] docker: remove init layer when no space to + create RW layer + +reason:remove init layer when no space to create RW layer + +Cherry-pick from docker 1.11.2: +- 24d00cf remove init layer when no space to create RW layer + +Change-Id: I3ef9fa1073c12242f5f56997883fe831d9497a9a +Signed-off-by: lujingxiao +Signed-off-by: zhangyu235 +--- + components/engine/layer/layer_store.go | 14 ++++++++++++++ + 1 file changed, 14 insertions(+) + +diff --git a/components/engine/layer/layer_store.go b/components/engine/layer/layer_store.go +index 7c80a29645..20c0195b79 100644 +--- a/components/engine/layer/layer_store.go ++++ b/components/engine/layer/layer_store.go +@@ -598,6 +598,20 @@ func (ls *layerStore) CreateRWLayer(name string, parent ChainID, opts *CreateRWL + // Release parent chain if error + defer func() { + if err != nil { ++ m.Lock() ++ if err := ls.driver.Remove(m.mountID); err != nil { ++ logrus.Errorf("Error removing mounted layer during create rw layer %s: %s", m.name, err) ++ } ++ if m.initID != "" { ++ if err := ls.driver.Remove(m.initID); err != nil { ++ logrus.Errorf("Error removing init layer during create rw layer %s: %s", m.name, err) ++ } ++ } ++ if err := ls.store.RemoveMount(m.name); err != nil { ++ logrus.Errorf("Error removing mount metadata during create rw layer %s: %s", m.name, err) ++ } ++ m.Unlock() ++ + ls.layerL.Lock() + ls.releaseLayer(p) + ls.layerL.Unlock() +-- +2.17.1 + diff --git a/patch/0054-docker-remove-init-layer-if-fails-in-initMoun.patch b/patch/0054-docker-remove-init-layer-if-fails-in-initMoun.patch new file mode 100644 index 0000000..0d8d65e --- /dev/null +++ b/patch/0054-docker-remove-init-layer-if-fails-in-initMoun.patch @@ -0,0 +1,78 @@ +From 1fb61b2addb6043e8403a613b4229c20070f8bfe Mon Sep 17 00:00:00 2001 +From: zhangyu235 +Date: Fri, 18 Jan 2019 17:38:47 +0800 +Subject: [PATCH 054/111] docker: remove init layer if fails in + initMount + +reason:remove init layer if fails in initMount + +Cherry-pick from docker 1.11.2: +- 673c733 remove init layer if fails in initMount +during initMount, operation could fails after device +created, it should be removed during rollback +Signed-off-by: lujingxiao +------- +- 424970f fix metadata file deleted mistakely +fix devicemapper metadata file "base" be deleted mistakely +Signed-off-by: leizhongkai + +Change-Id: Ieb40a8cfd3272abf28bfb046d77ecb61aea02bfa +Signed-off-by: zhangyu235 +--- + components/engine/layer/layer_store.go | 22 +++++++++++++++------- + 1 file changed, 15 insertions(+), 7 deletions(-) + +diff --git a/components/engine/layer/layer_store.go b/components/engine/layer/layer_store.go +index 20c0195b79..cbb1ee4a19 100644 +--- a/components/engine/layer/layer_store.go ++++ b/components/engine/layer/layer_store.go +@@ -599,16 +599,16 @@ func (ls *layerStore) CreateRWLayer(name string, parent ChainID, opts *CreateRWL + defer func() { + if err != nil { + m.Lock() +- if err := ls.driver.Remove(m.mountID); err != nil { +- logrus.Errorf("Error removing mounted layer during create rw layer %s: %s", m.name, err) ++ if deferErr := ls.driver.Remove(m.mountID); deferErr != nil { ++ logrus.Errorf("Error removing mounted layer during create rw layer %s: %s", m.name, deferErr) + } + if m.initID != "" { +- if err := ls.driver.Remove(m.initID); err != nil { +- logrus.Errorf("Error removing init layer during create rw layer %s: %s", m.name, err) ++ if deferErr := ls.driver.Remove(m.initID); deferErr != nil { ++ logrus.Errorf("Error removing init layer during create rw layer %s: %s", m.name, deferErr) + } + } +- if err := ls.store.RemoveMount(m.name); err != nil { +- logrus.Errorf("Error removing mount metadata during create rw layer %s: %s", m.name, err) ++ if deferErr := ls.store.RemoveMount(m.name); deferErr != nil { ++ logrus.Errorf("Error removing mount metadata during create rw layer %s: %s", m.name, deferErr) + } + m.Unlock() + +@@ -743,7 +743,7 @@ func (ls *layerStore) saveMount(mount *mountedLayer) error { + return nil + } + +-func (ls *layerStore) initMount(graphID, parent, mountLabel string, initFunc MountInit, storageOpt map[string]string) (string, error) { ++func (ls *layerStore) initMount(graphID, parent, mountLabel string, initFunc MountInit, storageOpt map[string]string) (id string, err error) { + // Use "-init" to maintain compatibility with graph drivers + // which are expecting this layer with this special name. If all + // graph drivers can be updated to not rely on knowing about this layer +@@ -758,6 +758,14 @@ func (ls *layerStore) initMount(graphID, parent, mountLabel string, initFunc Mou + if err := ls.driver.CreateReadWrite(initID, parent, createOpts); err != nil { + return "", err + } ++ defer func() { ++ if err != nil { ++ if deferErr := ls.driver.Remove(initID); deferErr != nil { ++ logrus.Errorf("Error removing init layer during init mount %s: %s", initID, deferErr) ++ } ++ } ++ }() ++ + p, err := ls.driver.Get(initID, "") + if err != nil { + return "", err +-- +2.17.1 + diff --git a/patch/0055-docker-range-checking-for-memory-and-memory.s.patch b/patch/0055-docker-range-checking-for-memory-and-memory.s.patch new file mode 100644 index 0000000..af6cbff --- /dev/null +++ b/patch/0055-docker-range-checking-for-memory-and-memory.s.patch @@ -0,0 +1,84 @@ +From 04094d4f0ca583bf2a3eccc390515840ad322853 Mon Sep 17 00:00:00 2001 +From: zhangyu235 +Date: Thu, 17 Jan 2019 20:45:45 +0800 +Subject: [PATCH 055/111] docker: range checking for memory and + memory.swap + +reason:range checking for memory and memory.swap, avoid overflow + +Cherry-pick from docker 1.11.2: +- 3dd33a7 range checking for memory and memory.swap + +Change-Id: I1736627a3f847decd36117f307a4919707908b32 +Signed-off-by: stella +Signed-off-by: zhangyu235 +--- + components/cli/vendor/github.com/docker/go-units/size.go | 4 ++++ + components/engine/daemon/daemon_unix.go | 5 ++++- + .../engine/vendor/github.com/docker/go-units/Checklist | 1 + + components/engine/vendor/github.com/docker/go-units/size.go | 4 ++++ + 4 files changed, 13 insertions(+), 1 deletion(-) + create mode 100644 components/engine/vendor/github.com/docker/go-units/Checklist + +diff --git a/components/cli/vendor/github.com/docker/go-units/size.go b/components/cli/vendor/github.com/docker/go-units/size.go +index 85f6ab0715..2b47b662ba 100644 +--- a/components/cli/vendor/github.com/docker/go-units/size.go ++++ b/components/cli/vendor/github.com/docker/go-units/size.go +@@ -104,5 +104,9 @@ func parseSize(sizeStr string, uMap unitMap) (int64, error) { + size *= float64(mul) + } + ++ if int64(size) < 0 { ++ return -1, fmt.Errorf("%s converted to int64 overflowed!", sizeStr) ++ } ++ + return int64(size), nil + } +diff --git a/components/engine/daemon/daemon_unix.go b/components/engine/daemon/daemon_unix.go +index d4a32a0b25..e48dfcd1ef 100644 +--- a/components/engine/daemon/daemon_unix.go ++++ b/components/engine/daemon/daemon_unix.go +@@ -65,7 +65,7 @@ const ( + linuxMinCPUShares = 2 + linuxMaxCPUShares = 262144 + platformSupported = true +- // It's not kernel limit, we want this 4M limit to supply a reasonable functional container ++ // It's not kernel limit, we want this 4MB limit to supply a reasonable functional container + linuxMinMemory = 4194304 + // constants for remapped root settings + defaultIDSpecifier = "default" +@@ -293,6 +293,9 @@ func (daemon *Daemon) adaptContainerSettings(hostConfig *containertypes.HostConf + if hostConfig.Memory > 0 && hostConfig.MemorySwap == 0 { + // By default, MemorySwap is set to twice the size of Memory. + hostConfig.MemorySwap = hostConfig.Memory * 2 ++ if hostConfig.MemorySwap < 0 { ++ return fmt.Errorf("invalid memory swap! The memory swap is double of memory, and should be less than the maximum of int64.") ++ } + } + if hostConfig.ShmSize == 0 { + hostConfig.ShmSize = config.DefaultShmSize +diff --git a/components/engine/vendor/github.com/docker/go-units/Checklist b/components/engine/vendor/github.com/docker/go-units/Checklist +new file mode 100644 +index 0000000000..6b3f1461e8 +--- /dev/null ++++ b/components/engine/vendor/github.com/docker/go-units/Checklist +@@ -0,0 +1 @@ ++add value range checking when converting units +\ No newline at end of file +diff --git a/components/engine/vendor/github.com/docker/go-units/size.go b/components/engine/vendor/github.com/docker/go-units/size.go +index 85f6ab0715..2b47b662ba 100644 +--- a/components/engine/vendor/github.com/docker/go-units/size.go ++++ b/components/engine/vendor/github.com/docker/go-units/size.go +@@ -104,5 +104,9 @@ func parseSize(sizeStr string, uMap unitMap) (int64, error) { + size *= float64(mul) + } + ++ if int64(size) < 0 { ++ return -1, fmt.Errorf("%s converted to int64 overflowed!", sizeStr) ++ } ++ + return int64(size), nil + } +-- +2.17.1 + diff --git a/patch/0056-docker-check-cpuset.cpu-and-cpuset.mem.patch b/patch/0056-docker-check-cpuset.cpu-and-cpuset.mem.patch new file mode 100644 index 0000000..97234c7 --- /dev/null +++ b/patch/0056-docker-check-cpuset.cpu-and-cpuset.mem.patch @@ -0,0 +1,132 @@ +From e894ad0fa1c84d1a01afc47ccc52c3556121bbcd Mon Sep 17 00:00:00 2001 +From: zhangyu235 +Date: Tue, 8 Jan 2019 20:17:21 +0800 +Subject: [PATCH 056/111] docker: check cpuset.cpu and cpuset.mem + +reason:check cpuset.cpu and cpuset.mem + +cherry-pick from docker 1.11.2: +- 1c769f0 check cpuset.cpu and cpuset.mem +Signed-off-by: lujingxiao +----- +- 782f78f check cpuset.cpu and cpuset.mem +Signed-off-by: wangyi45 + +Change-Id: If21cad2023d737d03ba3d4a83e62d11fb2297945 +Signed-off-by: zhangyu235 +--- + components/engine/daemon/daemon_unix.go | 2 + + components/engine/pkg/sysinfo/sysinfo.go | 64 +++++++++++++++++++++++- + 2 files changed, 65 insertions(+), 1 deletion(-) + +diff --git a/components/engine/daemon/daemon_unix.go b/components/engine/daemon/daemon_unix.go +index e48dfcd1ef..b20c66e27b 100644 +--- a/components/engine/daemon/daemon_unix.go ++++ b/components/engine/daemon/daemon_unix.go +@@ -499,6 +499,7 @@ func (daemon *Daemon) verifyContainerResources(hostConfig *containertypes.HostCo + } + cpusAvailable, err := sysInfo.IsCpusetCpusAvailable(resources.CpusetCpus) + if err != nil { ++ logrus.Errorf("Checking cpuset.cpus got %#v", err.Error()) + return warnings, errors.Wrapf(err, "Invalid value %s for cpuset cpus", resources.CpusetCpus) + } + if !cpusAvailable { +@@ -506,6 +507,7 @@ func (daemon *Daemon) verifyContainerResources(hostConfig *containertypes.HostCo + } + memsAvailable, err := sysInfo.IsCpusetMemsAvailable(resources.CpusetMems) + if err != nil { ++ logrus.Errorf("Checking cpuset.mems got %#v", err.Error()) + return warnings, errors.Wrapf(err, "Invalid value %s for cpuset mems", resources.CpusetMems) + } + if !memsAvailable { +diff --git a/components/engine/pkg/sysinfo/sysinfo.go b/components/engine/pkg/sysinfo/sysinfo.go +index 5d9320218c..7ea1be5daf 100644 +--- a/components/engine/pkg/sysinfo/sysinfo.go ++++ b/components/engine/pkg/sysinfo/sysinfo.go +@@ -1,6 +1,12 @@ + package sysinfo // import "github.com/docker/docker/pkg/sysinfo" + +-import "github.com/docker/docker/pkg/parsers" ++import ( ++ "fmt" ++ "strconv" ++ "strings" ++ ++ "github.com/docker/docker/pkg/parsers" ++) + + // SysInfo stores information about which features a kernel supports. + // TODO Windows: Factor out platform specific capabilities. +@@ -123,6 +129,13 @@ func (c cgroupCpusetInfo) IsCpusetMemsAvailable(provided string) (bool, error) { + } + + func isCpusetListAvailable(provided, available string) (bool, error) { ++ if err := checkCPU(provided, available); err != nil { ++ if strings.Contains(err.Error(), "invalid format") { ++ return false, err ++ } ++ return false, nil ++ } ++ + parsedAvailable, err := parsers.ParseUintList(available) + if err != nil { + return false, err +@@ -147,6 +160,55 @@ func isCpusetListAvailable(provided, available string) (bool, error) { + return true, nil + } + ++func checkCPU(provided, available string) error { ++ if provided == "" { ++ return nil ++ } ++ ++ maxAvailable, err := maxCPU(available) ++ if err != nil { ++ return err ++ } ++ ++ maxRequest, err := maxCPU(provided) ++ if err != nil { ++ return err ++ } ++ ++ if maxRequest > maxAvailable { ++ return fmt.Errorf("invalid maxRequest is %d, max available: %d", maxRequest, maxAvailable) ++ } ++ ++ return nil ++} ++ ++func maxCPU(cores string) (int, error) { ++ var max int ++ split := strings.Split(cores, ",") ++ errInvalidFormat := fmt.Errorf("invalid format: %s", cores) ++ for _, r := range split { ++ if !strings.Contains(r, "-") { ++ v, err := strconv.Atoi(r) ++ if err != nil { ++ return max, errInvalidFormat ++ } ++ if v > max { ++ max = v ++ } ++ } else { ++ split := strings.SplitN(r, "-", 2) ++ end, err := strconv.Atoi(split[1]) ++ if err != nil { ++ return max, errInvalidFormat ++ } ++ if end > max { ++ max = end ++ } ++ } ++ } ++ return max, nil ++} ++ + // Returns bit count of 1, used by NumCPU + func popcnt(x uint64) (n byte) { + x -= (x >> 1) & 0x5555555555555555 +-- +2.17.1 + diff --git a/patch/0057-docker-Ignore-ToDisk-error-in-StateChanged.patch b/patch/0057-docker-Ignore-ToDisk-error-in-StateChanged.patch new file mode 100644 index 0000000..9474729 --- /dev/null +++ b/patch/0057-docker-Ignore-ToDisk-error-in-StateChanged.patch @@ -0,0 +1,55 @@ +From 241fc5d726e63e995d3518b734e18efff76284ac Mon Sep 17 00:00:00 2001 +From: zhangyu235 +Date: Fri, 18 Jan 2019 11:07:06 +0800 +Subject: [PATCH 057/111] docker: Ignore ToDisk error in StateChanged + +reason:If container is started normally, but failed to save + state to disk due to error "no space left on device", + then container can not be stopped, because container's + infomation in libcontainerd is cleaned up when error + occurred(so it can not process event 'exit'). + + We can ignore ToDisk error in StateChanged, it only + change status of container. Status is correct if docker + daemon not restart, because right status exists in memory. + If docker daemon restart, it will restore these status + using status of containerd, so status is also correct. + + This fix can break consistency of status in memory and + disk, but considering there is no space in disk, it is + not a big problem in this situation. Status in disk can + recover if disk have space and if ToDisk is written + again(for example, status changed). + + Fix issuse #322 + +Cherry-pick from docker 1.11.2: +- 5eb9015 Ignore ToDisk error in StateChanged + +Change-Id: Ifbdbffac06d1d739b03ea13962fb2d1fde7b5b3e +Signed-off-by: Fengtu Wang +Signed-off-by: zhangyu235 +--- + components/engine/daemon/monitor.go | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/components/engine/daemon/monitor.go b/components/engine/daemon/monitor.go +index 9b4452d7ef..807cdcaa89 100644 +--- a/components/engine/daemon/monitor.go ++++ b/components/engine/daemon/monitor.go +@@ -154,7 +154,11 @@ func (daemon *Daemon) ProcessEvent(id string, e libcontainerd.EventType, ei libc + daemon.initHealthMonitor(c) + + if err := c.CheckpointTo(daemon.containersReplica); err != nil { +- return err ++ // If return err, container can not be stopped, see issue #322 for detail. ++ // Ignore error is safe, because if daemon not restart, status in memory is ++ // correct, and if daemon restart, it will restore status using status in ++ // containerd, so status in memory is also correct. ++ logrus.Debugf("Set status %v to disk failed: %v", e, err) + } + daemon.LogContainerEvent(c, "start") + } +-- +2.17.1 + diff --git a/patch/0058-cleanup-cleanup-incompleted-device-while-dock.patch b/patch/0058-cleanup-cleanup-incompleted-device-while-dock.patch new file mode 100644 index 0000000..81b0037 --- /dev/null +++ b/patch/0058-cleanup-cleanup-incompleted-device-while-dock.patch @@ -0,0 +1,35 @@ +From ee64a0b5fd645d1765b0c50cbaab76c17bab9b28 Mon Sep 17 00:00:00 2001 +From: zhangyu235 +Date: Fri, 18 Jan 2019 14:46:21 +0800 +Subject: [PATCH 058/111] cleanup: cleanup incompleted device while + docker startup + +cleanup incompleted device while docker startup + +Change-Id: I9a9a96c655b61ac132fa7ed101dc9953c1bb613d +Signed-off-by: Wentao Zhang +Signed-off-by: zhangyu235 +--- + components/engine/daemon/graphdriver/devmapper/deviceset.go | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/components/engine/daemon/graphdriver/devmapper/deviceset.go b/components/engine/daemon/graphdriver/devmapper/deviceset.go +index b3e142e2ba..f4dc589c6e 100644 +--- a/components/engine/daemon/graphdriver/devmapper/deviceset.go ++++ b/components/engine/daemon/graphdriver/devmapper/deviceset.go +@@ -1753,6 +1753,12 @@ func (devices *DeviceSet) initDevmapper(doInit bool) (retErr error) { + } + logrus.Debugf("devmapper: remove broken device: %s", name) + } ++ if _, err := os.Stat(filepath.Join("/dev/mapper/", name)); err != nil { ++ if err := devicemapper.RemoveDevice(name); err != nil { ++ logrus.Warnf("devmapper: remove incompelete device(%s): %v", name, err) ++ } ++ logrus.Debugf("devmapper: remove incompelete device: %s", name) ++ } + } + + // Check for the existence of the thin-pool device +-- +2.17.1 + diff --git a/patch/0059-overlay2-avoid-middle-state-while-removing-im.patch b/patch/0059-overlay2-avoid-middle-state-while-removing-im.patch new file mode 100644 index 0000000..65266cc --- /dev/null +++ b/patch/0059-overlay2-avoid-middle-state-while-removing-im.patch @@ -0,0 +1,110 @@ +From ef238d650231f8346078536b6133f147df11283a Mon Sep 17 00:00:00 2001 +From: zhangyu235 +Date: Fri, 18 Jan 2019 19:21:39 +0800 +Subject: [PATCH 059/111] overlay2: avoid middle state while removing + images under overlay2 + +reason:avoid middle state while removing images under overlay2 + + kill -9 daemon while rmi images may cause some inconsistent state + of that image + + if image symlink is removed but the id directory remained. daemon + restart and load again, the images look fine but create container + with it will error out: + `docker: Error response from daemon: error creating overlay + mount to /var/lib/docker/overlay2/xxx-init/merged: + no such file or directory` + + This patch move symlink removal after directory removal, and check + link file state while daemon restore, to clean up stale links. + +Change-Id: I44328bc2261c9ec73bcdcbed3d741e569cd4a834 +Signed-off-by: Deng Guangxing +Signed-off-by: zhangyu235 +--- + .../daemon/graphdriver/overlay2/overlay.go | 40 ++++++++++++++++--- + 1 file changed, 34 insertions(+), 6 deletions(-) + +diff --git a/components/engine/daemon/graphdriver/overlay2/overlay.go b/components/engine/daemon/graphdriver/overlay2/overlay.go +index 773d5232cc..cf8993e9f3 100644 +--- a/components/engine/daemon/graphdriver/overlay2/overlay.go ++++ b/components/engine/daemon/graphdriver/overlay2/overlay.go +@@ -229,6 +229,8 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap + return nil, fmt.Errorf("Storage Option overlay2.size only supported for backingFS XFS or ext4. Found %v", backingFs) + } + ++ d.cleanupLinkDir() ++ + // figure out whether "index=off" option is recognized by the kernel + _, err = os.Stat("/sys/module/overlay/parameters/index") + switch { +@@ -245,6 +247,19 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap + return d, nil + } + ++func (d *Driver) cleanupLinkDir() { ++ filepath.Walk(path.Join(d.home, linkDir), func(path string, f os.FileInfo, err error) error { ++ if _, serr := filepath.EvalSymlinks(path); serr != nil { ++ logrus.Warnf("[overlay2]: remove invalid symlink: %s", path) ++ os.RemoveAll(path) ++ } ++ // always return nil, to walk all the symlink ++ return nil ++ }) ++ ++ return ++} ++ + func parseOptions(options []string) (*overlayOptions, error) { + o := &overlayOptions{} + for _, option := range options { +@@ -575,8 +590,11 @@ func (d *Driver) Remove(id string) error { + d.locker.Lock(id) + defer d.locker.Unlock(id) + dir := d.dir(id) +- lid, err := ioutil.ReadFile(path.Join(dir, "link")) +- if err == nil { ++ lid, lerr := ioutil.ReadFile(path.Join(dir, "link")) ++ if err := system.EnsureRemoveAll(dir); err != nil && !os.IsNotExist(err) { ++ return err ++ } ++ if lerr == nil { + if !verifyID(string(lid), idLength) { + logrus.WithField("storage-driver", "overlay2").Errorf("refusing to remove empty link for layer %v", id) + } else if err := os.RemoveAll(path.Join(d.home, linkDir, string(lid))); err != nil { +@@ -568,9 +586,6 @@ func (d *Driver) Remove(id string) error { + } + } + +- if err := system.EnsureRemoveAll(dir); err != nil && !os.IsNotExist(err) { +- return err +- } + return nil + } + +@@ -710,7 +725,20 @@ func (d *Driver) Put(id string) error { + + // Exists checks to see if the id is already mounted. + func (d *Driver) Exists(id string) bool { +- _, rerr := os.Stat(d.dir(id)) ++ var rerr error ++ defer func() { ++ if rerr != nil { ++ logrus.Warnf("layer(%s) not exist: %s", id, rerr) ++ d.Remove(id) ++ } ++ }() ++ ++ // check if the id directory exist and is valid ++ // check if link file exist and get link string from it ++ // check if symlink file exist ++ // if symlink not exist, create a new one and update link file ++ // any steps failed ,we will return false and remove this id layer ++ _, rerr = os.Stat(d.dir(id)) + if rerr == nil { + lstr, err := ioutil.ReadFile(path.Join(d.dir(id), "link")) + // link is valid +-- +2.17.1 + diff --git a/patch/0060-debug-Add-check-when-execute-docker-cp-export.patch b/patch/0060-debug-Add-check-when-execute-docker-cp-export.patch new file mode 100644 index 0000000..673d47a --- /dev/null +++ b/patch/0060-debug-Add-check-when-execute-docker-cp-export.patch @@ -0,0 +1,90 @@ +From 42d1e785f2343323822db35966412fdcfce87989 Mon Sep 17 00:00:00 2001 +From: zhangyu235 +Date: Fri, 18 Jan 2019 15:55:51 +0800 +Subject: [PATCH 060/111] debug: Add check when execute docker {cp, + export, diff} + +reason:If a container is in Dead or RemovalInProgress state,it should return err for tip. + +Cherry-pick from docker 1.11.2: +- 903a5de Add check when execute docker {cp, export, diff} + +Change-Id: Idf441bf7d194cc61c618c20c0e6ef8b339e81191 +Signed-off-by: yangshukui +Signed-off-by: zhangyu235 +--- + components/engine/daemon/archive.go | 13 +++++++++++++ + components/engine/daemon/changes.go | 5 +++++ + 2 files changed, 18 insertions(+) + +diff --git a/components/engine/daemon/archive.go b/components/engine/daemon/archive.go +index 9c7971b56e..f1b715d9ae 100644 +--- a/components/engine/daemon/archive.go ++++ b/components/engine/daemon/archive.go +@@ -1,6 +1,7 @@ + package daemon // import "github.com/docker/docker/daemon" + + import ( ++ "fmt" + "io" + "os" + "strings" +@@ -77,6 +78,10 @@ func (daemon *Daemon) ContainerStatPath(name string, path string) (stat *types.C + return nil, err + } + ++ if container.RemovalInProgress || container.Dead { ++ return nil, fmt.Errorf("can't stat file from a container which is dead or marked for removal") ++ } ++ + // Make sure an online file-system operation is permitted. + if err := daemon.isOnlineFSOperationPermitted(container); err != nil { + return nil, errdefs.System(err) +@@ -102,6 +107,10 @@ func (daemon *Daemon) ContainerArchivePath(name string, path string) (content io + return nil, nil, err + } + ++ if container.RemovalInProgress || container.Dead { ++ return nil, nil, fmt.Errorf("can't copy file from a container which is dead or marked for removal") ++ } ++ + // Make sure an online file-system operation is permitted. + if err := daemon.isOnlineFSOperationPermitted(container); err != nil { + return nil, nil, errdefs.System(err) +@@ -130,6 +139,10 @@ func (daemon *Daemon) ContainerExtractToDir(name, path string, copyUIDGID, noOve + return err + } + ++ if container.RemovalInProgress || container.Dead { ++ return fmt.Errorf("can't copy to a container which is dead or marked for removal") ++ } ++ + // Make sure an online file-system operation is permitted. + if err := daemon.isOnlineFSOperationPermitted(container); err != nil { + return errdefs.System(err) +diff --git a/components/engine/daemon/changes.go b/components/engine/daemon/changes.go +index 70b3f6b943..55575a96bd 100644 +--- a/components/engine/daemon/changes.go ++++ b/components/engine/daemon/changes.go +@@ -6,6 +6,7 @@ import ( + "time" + + "github.com/docker/docker/pkg/archive" ++ "fmt" + ) + + // ContainerChanges returns a list of container fs changes +@@ -16,6 +17,10 @@ func (daemon *Daemon) ContainerChanges(name string) ([]archive.Change, error) { + return nil, err + } + ++ if container.RemovalInProgress || container.Dead { ++ return nil, fmt.Errorf("can't diff a container which is dead or marked for removal") ++ } ++ + if runtime.GOOS == "windows" && container.IsRunning() { + return nil, errors.New("Windows does not support diff of a running container") + } +-- +2.17.1 + diff --git a/patch/0061-docker-check-seccomp-file-size-max-10M-before.patch b/patch/0061-docker-check-seccomp-file-size-max-10M-before.patch new file mode 100644 index 0000000..432d9fd --- /dev/null +++ b/patch/0061-docker-check-seccomp-file-size-max-10M-before.patch @@ -0,0 +1,54 @@ +From 3b24c397492b921ce00f9786c8f6dd22cf2bb420 Mon Sep 17 00:00:00 2001 +From: zhangyu235 +Date: Fri, 18 Jan 2019 21:31:47 +0800 +Subject: [PATCH 061/111] docker: check seccomp file size(max:10M) + before read into memory + +reason:when seccomp file size is not limited, docker may result in memory OOM + +Cherry-pick from docker 1.11.2: +- 3660784 check seccomp file size(max:10M) before read into memory + +Change-Id: I6289b2e84e5aaf6d876e689c842f9d18acaf6814 +Signed-off-by: xueshaojia +Signed-off-by: zhangyu235 +--- + components/cli/cli/command/container/opts.go | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/components/cli/cli/command/container/opts.go b/components/cli/cli/command/container/opts.go +index 8e07aa77cb..cbbc110f9c 100644 +--- a/components/cli/cli/command/container/opts.go ++++ b/components/cli/cli/command/container/opts.go +@@ -5,6 +5,7 @@ import ( + "encoding/json" + "fmt" + "io/ioutil" ++ "os" + "path" + "regexp" + "strconv" +@@ -27,6 +28,8 @@ var ( + deviceCgroupRuleRegexp = regexp.MustCompile(`^[acb] ([0-9]+|\*):([0-9]+|\*) [rwm]{1,3}$`) + ) + ++const seccompFileMaxSize = 10 * 1024 * 1024 ++ + // containerOptions is a data object with all the options for creating a container + type containerOptions struct { + attach opts.ListOpts +@@ -726,6 +729,11 @@ func parseSecurityOpts(securityOpts []string) ([]string, error) { + } + } + if con[0] == "seccomp" && con[1] != "unconfined" { ++ if fileInfo, err := os.Stat(con[1]); err != nil { ++ return securityOpts, fmt.Errorf("stat seccomp profile (%s) failed: %v", con[1], err) ++ } else if fileInfo.Size() > seccompFileMaxSize { ++ return securityOpts, fmt.Errorf("stat seccomp profile (%s) failed: size exceed limit %dM", con[1], seccompFileMaxSize/1024/1024) ++ } + f, err := ioutil.ReadFile(con[1]) + if err != nil { + return securityOpts, errors.Errorf("opening seccomp profile (%s) failed: %v", con[1], err) +-- +2.17.1 + diff --git a/patch/0062-docker-check-file-size-before-reading-envfile.patch b/patch/0062-docker-check-file-size-before-reading-envfile.patch new file mode 100644 index 0000000..c768f16 --- /dev/null +++ b/patch/0062-docker-check-file-size-before-reading-envfile.patch @@ -0,0 +1,87 @@ +From e065d6675c95e37144284f2ee5af3f7e326f9efe Mon Sep 17 00:00:00 2001 +From: zhangyu235 +Date: Fri, 18 Jan 2019 22:11:29 +0800 +Subject: [PATCH 062/111] docker: check file size before reading + "envfile" and "labelfile", in case OOM + +reason:check file size before reading "envfile" and "labelfile", in case OOM + +Cherry-pick from docker 1.11.2: +- 931660a check file size before reading "envfile" and "labelfile", in case OOM + +Change-Id: I32bc7951565d0e6e720cf7d9f1d53f8709ebc8b3 +Signed-off-by: panwenxiang +Signed-off-by: zhangyu235 +--- + components/cli/cli/command/container/opts.go | 28 +++++++++++++++++--- + 1 file changed, 25 insertions(+), 3 deletions(-) + +diff --git a/components/cli/cli/command/container/opts.go b/components/cli/cli/command/container/opts.go +index cbbc110f9c..a1bf2be79a 100644 +--- a/components/cli/cli/command/container/opts.go ++++ b/components/cli/cli/command/container/opts.go +@@ -28,7 +28,7 @@ var ( + deviceCgroupRuleRegexp = regexp.MustCompile(`^[acb] ([0-9]+|\*):([0-9]+|\*) [rwm]{1,3}$`) + ) + +-const seccompFileMaxSize = 10 * 1024 * 1024 ++const fileMaxSize = 10 * 1024 * 1024 + + // containerOptions is a data object with all the options for creating a container + type containerOptions struct { +@@ -435,12 +435,20 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err + } + + // collect all the environment variables for the container ++ err = checkFileSizeValid(copts.envFile.GetAll()) ++ if err != nil { ++ return nil, err ++ } + envVariables, err := opts.ReadKVEnvStrings(copts.envFile.GetAll(), copts.env.GetAll()) + if err != nil { + return nil, err + } + + // collect all the labels for the container ++ err = checkFileSizeValid(copts.envFile.GetAll()) ++ if err != nil { ++ return nil, err ++ } + labels, err := opts.ReadKVStrings(copts.labelsFile.GetAll(), copts.labels.GetAll()) + if err != nil { + return nil, err +@@ -692,6 +700,20 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err + }, nil + } + ++// check file size ++func checkFileSizeValid(files []string) error { ++ for _, ef := range files { ++ fileInfo, err := os.Stat(ef) ++ if err != nil { ++ return err ++ } ++ if fileInfo.Size() > fileMaxSize { ++ return fmt.Errorf("check (%s) file size is %d, size exceed limit %d ", ef, fileInfo.Size(), fileMaxSize) ++ } ++ } ++ return nil ++} ++ + func parsePortOpts(publishOpts []string) ([]string, error) { + optsList := []string{} + for _, publish := range publishOpts { +@@ -731,8 +753,8 @@ func parseSecurityOpts(securityOpts []string) ([]string, error) { + if con[0] == "seccomp" && con[1] != "unconfined" { + if fileInfo, err := os.Stat(con[1]); err != nil { + return securityOpts, fmt.Errorf("stat seccomp profile (%s) failed: %v", con[1], err) +- } else if fileInfo.Size() > seccompFileMaxSize { +- return securityOpts, fmt.Errorf("stat seccomp profile (%s) failed: size exceed limit %dM", con[1], seccompFileMaxSize/1024/1024) ++ } else if fileInfo.Size() > fileMaxSize { ++ return securityOpts, fmt.Errorf("stat seccomp profile (%s) failed: size exceed limit %dM", con[1], fileMaxSize/1024/1024) + } + f, err := ioutil.ReadFile(con[1]) + if err != nil { +-- +2.17.1 + diff --git a/patch/0063-test-fix-umask-make-file-mode-failed.patch b/patch/0063-test-fix-umask-make-file-mode-failed.patch new file mode 100644 index 0000000..ff7a378 --- /dev/null +++ b/patch/0063-test-fix-umask-make-file-mode-failed.patch @@ -0,0 +1,38 @@ +From a683efcf5e14acdd40574edeaed6b293b0536867 Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Tue, 15 Jan 2019 10:16:25 +0800 +Subject: [PATCH 063/111] test: fix umask make file mode failed + +reason: set umask to 0022 make sure test added file's mode match expect. + +Change-Id: I258d3d999c82041a16851f438f556746e6477ebf +Signed-off-by: jingrui +--- + components/engine/internal/test/fakecontext/context.go | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/components/engine/internal/test/fakecontext/context.go b/components/engine/internal/test/fakecontext/context.go +index 8b11da207e..24f326b864 100644 +--- a/components/engine/internal/test/fakecontext/context.go ++++ b/components/engine/internal/test/fakecontext/context.go +@@ -6,6 +6,7 @@ import ( + "io/ioutil" + "os" + "path/filepath" ++ "syscall" + + "github.com/docker/docker/internal/test" + "github.com/docker/docker/pkg/archive" +@@ -96,6 +97,9 @@ func (f *Fake) Add(file, content string) error { + } + + func (f *Fake) addFile(file string, content []byte) error { ++ mask := syscall.Umask(0022) ++ defer syscall.Umask(mask) ++ + fp := filepath.Join(f.Dir, filepath.FromSlash(file)) + dirpath := filepath.Dir(fp) + if dirpath != "." { +-- +2.17.1 + diff --git a/patch/0066-test-skip-swarm-integration-test.patch b/patch/0066-test-skip-swarm-integration-test.patch new file mode 100644 index 0000000..cece00b --- /dev/null +++ b/patch/0066-test-skip-swarm-integration-test.patch @@ -0,0 +1,2090 @@ +From c1e3ed6a8c993370d3ea9e6559cca31f7aab36f3 Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Sat, 12 Jan 2019 10:26:48 +0800 +Subject: [PATCH 066/111] test: skip swarm integration test + +reason: drop unsed swarm integration test. + +Change-Id: Ibb0745de970dcc1cd83b2ebf6ba090bc28edde67 +Signed-off-by: jingrui +--- + .../integration-cli/docker_cli_swarm_test.go | 2067 ----------------- + 1 file changed, 2067 deletions(-) + delete mode 100644 components/engine/integration-cli/docker_cli_swarm_test.go + +diff --git a/components/engine/integration-cli/docker_cli_swarm_test.go b/components/engine/integration-cli/docker_cli_swarm_test.go +deleted file mode 100644 +index f6fadcf995..0000000000 +--- a/components/engine/integration-cli/docker_cli_swarm_test.go ++++ /dev/null +@@ -1,2067 +0,0 @@ +-// +build !windows +- +-package main +- +-import ( +- "bytes" +- "context" +- "encoding/json" +- "encoding/pem" +- "fmt" +- "io/ioutil" +- "net/http" +- "net/http/httptest" +- "os" +- "path/filepath" +- "strings" +- "time" +- +- "github.com/cloudflare/cfssl/helpers" +- "github.com/docker/docker/api/types" +- "github.com/docker/docker/api/types/swarm" +- "github.com/docker/docker/integration-cli/checker" +- "github.com/docker/docker/integration-cli/cli" +- "github.com/docker/docker/integration-cli/daemon" +- "github.com/docker/libnetwork/driverapi" +- "github.com/docker/libnetwork/ipamapi" +- remoteipam "github.com/docker/libnetwork/ipams/remote/api" +- "github.com/docker/swarmkit/ca/keyutils" +- "github.com/go-check/check" +- "github.com/vishvananda/netlink" +- "gotest.tools/fs" +- "gotest.tools/icmd" +-) +- +-func (s *DockerSwarmSuite) TestSwarmUpdate(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- getSpec := func() swarm.Spec { +- sw := d.GetSwarm(c) +- return sw.Spec +- } +- +- out, err := d.Cmd("swarm", "update", "--cert-expiry", "30h", "--dispatcher-heartbeat", "11s") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- spec := getSpec() +- c.Assert(spec.CAConfig.NodeCertExpiry, checker.Equals, 30*time.Hour) +- c.Assert(spec.Dispatcher.HeartbeatPeriod, checker.Equals, 11*time.Second) +- +- // setting anything under 30m for cert-expiry is not allowed +- out, err = d.Cmd("swarm", "update", "--cert-expiry", "15m") +- c.Assert(err, checker.NotNil) +- c.Assert(out, checker.Contains, "minimum certificate expiry time") +- spec = getSpec() +- c.Assert(spec.CAConfig.NodeCertExpiry, checker.Equals, 30*time.Hour) +- +- // passing an external CA (this is without starting a root rotation) does not fail +- cli.Docker(cli.Args("swarm", "update", "--external-ca", "protocol=cfssl,url=https://something.org", +- "--external-ca", "protocol=cfssl,url=https://somethingelse.org,cacert=fixtures/https/ca.pem"), +- cli.Daemon(d)).Assert(c, icmd.Success) +- +- expected, err := ioutil.ReadFile("fixtures/https/ca.pem") +- c.Assert(err, checker.IsNil) +- +- spec = getSpec() +- c.Assert(spec.CAConfig.ExternalCAs, checker.HasLen, 2) +- c.Assert(spec.CAConfig.ExternalCAs[0].CACert, checker.Equals, "") +- c.Assert(spec.CAConfig.ExternalCAs[1].CACert, checker.Equals, string(expected)) +- +- // passing an invalid external CA fails +- tempFile := fs.NewFile(c, "testfile", fs.WithContent("fakecert")) +- defer tempFile.Remove() +- +- result := cli.Docker(cli.Args("swarm", "update", +- "--external-ca", fmt.Sprintf("protocol=cfssl,url=https://something.org,cacert=%s", tempFile.Path())), +- cli.Daemon(d)) +- result.Assert(c, icmd.Expected{ +- ExitCode: 125, +- Err: "must be in PEM format", +- }) +-} +- +-func (s *DockerSwarmSuite) TestSwarmInit(c *check.C) { +- d := s.AddDaemon(c, false, false) +- +- getSpec := func() swarm.Spec { +- sw := d.GetSwarm(c) +- return sw.Spec +- } +- +- // passing an invalid external CA fails +- tempFile := fs.NewFile(c, "testfile", fs.WithContent("fakecert")) +- defer tempFile.Remove() +- +- result := cli.Docker(cli.Args("swarm", "init", "--cert-expiry", "30h", "--dispatcher-heartbeat", "11s", +- "--external-ca", fmt.Sprintf("protocol=cfssl,url=https://somethingelse.org,cacert=%s", tempFile.Path())), +- cli.Daemon(d)) +- result.Assert(c, icmd.Expected{ +- ExitCode: 125, +- Err: "must be in PEM format", +- }) +- +- cli.Docker(cli.Args("swarm", "init", "--cert-expiry", "30h", "--dispatcher-heartbeat", "11s", +- "--external-ca", "protocol=cfssl,url=https://something.org", +- "--external-ca", "protocol=cfssl,url=https://somethingelse.org,cacert=fixtures/https/ca.pem"), +- cli.Daemon(d)).Assert(c, icmd.Success) +- +- expected, err := ioutil.ReadFile("fixtures/https/ca.pem") +- c.Assert(err, checker.IsNil) +- +- spec := getSpec() +- c.Assert(spec.CAConfig.NodeCertExpiry, checker.Equals, 30*time.Hour) +- c.Assert(spec.Dispatcher.HeartbeatPeriod, checker.Equals, 11*time.Second) +- c.Assert(spec.CAConfig.ExternalCAs, checker.HasLen, 2) +- c.Assert(spec.CAConfig.ExternalCAs[0].CACert, checker.Equals, "") +- c.Assert(spec.CAConfig.ExternalCAs[1].CACert, checker.Equals, string(expected)) +- +- c.Assert(d.SwarmLeave(true), checker.IsNil) +- cli.Docker(cli.Args("swarm", "init"), cli.Daemon(d)).Assert(c, icmd.Success) +- +- spec = getSpec() +- c.Assert(spec.CAConfig.NodeCertExpiry, checker.Equals, 90*24*time.Hour) +- c.Assert(spec.Dispatcher.HeartbeatPeriod, checker.Equals, 5*time.Second) +-} +- +-func (s *DockerSwarmSuite) TestSwarmInitIPv6(c *check.C) { +- testRequires(c, IPv6) +- d1 := s.AddDaemon(c, false, false) +- cli.Docker(cli.Args("swarm", "init", "--listen-add", "::1"), cli.Daemon(d1)).Assert(c, icmd.Success) +- +- d2 := s.AddDaemon(c, false, false) +- cli.Docker(cli.Args("swarm", "join", "::1"), cli.Daemon(d2)).Assert(c, icmd.Success) +- +- out := cli.Docker(cli.Args("info"), cli.Daemon(d2)).Assert(c, icmd.Success).Combined() +- c.Assert(out, checker.Contains, "Swarm: active") +-} +- +-func (s *DockerSwarmSuite) TestSwarmInitUnspecifiedAdvertiseAddr(c *check.C) { +- d := s.AddDaemon(c, false, false) +- out, err := d.Cmd("swarm", "init", "--advertise-addr", "0.0.0.0") +- c.Assert(err, checker.NotNil) +- c.Assert(out, checker.Contains, "advertise address must be a non-zero IP address") +-} +- +-func (s *DockerSwarmSuite) TestSwarmIncompatibleDaemon(c *check.C) { +- // init swarm mode and stop a daemon +- d := s.AddDaemon(c, true, true) +- info := d.SwarmInfo(c) +- c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive) +- d.Stop(c) +- +- // start a daemon with --cluster-store and --cluster-advertise +- err := d.StartWithError("--cluster-store=consul://consuladdr:consulport/some/path", "--cluster-advertise=1.1.1.1:2375") +- c.Assert(err, checker.NotNil) +- content, err := d.ReadLogFile() +- c.Assert(err, checker.IsNil) +- c.Assert(string(content), checker.Contains, "--cluster-store and --cluster-advertise daemon configurations are incompatible with swarm mode") +- +- // start a daemon with --live-restore +- err = d.StartWithError("--live-restore") +- c.Assert(err, checker.NotNil) +- content, err = d.ReadLogFile() +- c.Assert(err, checker.IsNil) +- c.Assert(string(content), checker.Contains, "--live-restore daemon configuration is incompatible with swarm mode") +- // restart for teardown +- d.Start(c) +-} +- +-func (s *DockerSwarmSuite) TestSwarmServiceTemplatingHostname(c *check.C) { +- d := s.AddDaemon(c, true, true) +- hostname, err := d.Cmd("node", "inspect", "--format", "{{.Description.Hostname}}", "self") +- c.Assert(err, checker.IsNil, check.Commentf("%s", hostname)) +- +- out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", "test", "--hostname", "{{.Service.Name}}-{{.Task.Slot}}-{{.Node.Hostname}}", "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- // make sure task has been deployed. +- waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1) +- +- containers := d.ActiveContainers(c) +- out, err = d.Cmd("inspect", "--type", "container", "--format", "{{.Config.Hostname}}", containers[0]) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.Split(out, "\n")[0], checker.Equals, "test-1-"+strings.Split(hostname, "\n")[0], check.Commentf("hostname with templating invalid")) +-} +- +-// Test case for #24270 +-func (s *DockerSwarmSuite) TestSwarmServiceListFilter(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- name1 := "redis-cluster-md5" +- name2 := "redis-cluster" +- name3 := "other-cluster" +- out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", name1, "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") +- +- out, err = d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", name2, "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") +- +- out, err = d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", name3, "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") +- +- filter1 := "name=redis-cluster-md5" +- filter2 := "name=redis-cluster" +- +- // We search checker.Contains with `name+" "` to prevent prefix only. +- out, err = d.Cmd("service", "ls", "--filter", filter1) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Contains, name1+" ") +- c.Assert(out, checker.Not(checker.Contains), name2+" ") +- c.Assert(out, checker.Not(checker.Contains), name3+" ") +- +- out, err = d.Cmd("service", "ls", "--filter", filter2) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Contains, name1+" ") +- c.Assert(out, checker.Contains, name2+" ") +- c.Assert(out, checker.Not(checker.Contains), name3+" ") +- +- out, err = d.Cmd("service", "ls") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Contains, name1+" ") +- c.Assert(out, checker.Contains, name2+" ") +- c.Assert(out, checker.Contains, name3+" ") +-} +- +-func (s *DockerSwarmSuite) TestSwarmNodeListFilter(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- out, err := d.Cmd("node", "inspect", "--format", "{{ .Description.Hostname }}", "self") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") +- name := strings.TrimSpace(out) +- +- filter := "name=" + name[:4] +- +- out, err = d.Cmd("node", "ls", "--filter", filter) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Contains, name) +- +- out, err = d.Cmd("node", "ls", "--filter", "name=none") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Not(checker.Contains), name) +-} +- +-func (s *DockerSwarmSuite) TestSwarmNodeTaskListFilter(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- name := "redis-cluster-md5" +- out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", name, "--replicas=3", "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") +- +- // make sure task has been deployed. +- waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 3) +- +- filter := "name=redis-cluster" +- +- out, err = d.Cmd("node", "ps", "--filter", filter, "self") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Contains, name+".1") +- c.Assert(out, checker.Contains, name+".2") +- c.Assert(out, checker.Contains, name+".3") +- +- out, err = d.Cmd("node", "ps", "--filter", "name=none", "self") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Not(checker.Contains), name+".1") +- c.Assert(out, checker.Not(checker.Contains), name+".2") +- c.Assert(out, checker.Not(checker.Contains), name+".3") +-} +- +-// Test case for #25375 +-func (s *DockerSwarmSuite) TestSwarmPublishAdd(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- name := "top" +- out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", name, "--label", "x=y", "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") +- +- out, err = d.Cmd("service", "update", "--detach", "--publish-add", "80:80", name) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- out, err = d.Cmd("service", "update", "--detach", "--publish-add", "80:80", name) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- _, err = d.Cmd("service", "update", "--detach", "--publish-add", "80:80", "--publish-add", "80:20", name) +- c.Assert(err, checker.NotNil) +- +- out, err = d.Cmd("service", "inspect", "--format", "{{ .Spec.EndpointSpec.Ports }}", name) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Equals, "[{ tcp 80 80 ingress}]") +-} +- +-func (s *DockerSwarmSuite) TestSwarmServiceWithGroup(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- name := "top" +- out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", name, "--user", "root:root", "--group", "wheel", "--group", "audio", "--group", "staff", "--group", "777", "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") +- +- // make sure task has been deployed. +- waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1) +- +- out, err = d.Cmd("ps", "-q") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") +- +- container := strings.TrimSpace(out) +- +- out, err = d.Cmd("exec", container, "id") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Equals, "uid=0(root) gid=0(root) groups=10(wheel),29(audio),50(staff),777") +-} +- +-func (s *DockerSwarmSuite) TestSwarmContainerAutoStart(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- out, err := d.Cmd("network", "create", "--attachable", "-d", "overlay", "foo") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") +- +- out, err = d.Cmd("run", "-id", "--restart=always", "--net=foo", "--name=test", "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") +- +- out, err = d.Cmd("ps", "-q") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") +- +- d.Restart(c) +- +- out, err = d.Cmd("ps", "-q") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") +-} +- +-func (s *DockerSwarmSuite) TestSwarmContainerEndpointOptions(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- out, err := d.Cmd("network", "create", "--attachable", "-d", "overlay", "foo") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") +- +- _, err = d.Cmd("run", "-d", "--net=foo", "--name=first", "--net-alias=first-alias", "busybox:glibc", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- _, err = d.Cmd("run", "-d", "--net=foo", "--name=second", "busybox:glibc", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- _, err = d.Cmd("run", "-d", "--net=foo", "--net-alias=third-alias", "busybox:glibc", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- // ping first container and its alias, also ping third and anonymous container by its alias +- _, err = d.Cmd("exec", "second", "ping", "-c", "1", "first") +- c.Assert(err, check.IsNil, check.Commentf("%s", out)) +- _, err = d.Cmd("exec", "second", "ping", "-c", "1", "first-alias") +- c.Assert(err, check.IsNil, check.Commentf("%s", out)) +- _, err = d.Cmd("exec", "second", "ping", "-c", "1", "third-alias") +- c.Assert(err, check.IsNil, check.Commentf("%s", out)) +-} +- +-func (s *DockerSwarmSuite) TestSwarmContainerAttachByNetworkId(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- out, err := d.Cmd("network", "create", "--attachable", "-d", "overlay", "testnet") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") +- networkID := strings.TrimSpace(out) +- +- out, err = d.Cmd("run", "-d", "--net", networkID, "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- cID := strings.TrimSpace(out) +- d.WaitRun(cID) +- +- out, err = d.Cmd("rm", "-f", cID) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- out, err = d.Cmd("network", "rm", "testnet") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- checkNetwork := func(*check.C) (interface{}, check.CommentInterface) { +- out, err := d.Cmd("network", "ls") +- c.Assert(err, checker.IsNil) +- return out, nil +- } +- +- waitAndAssert(c, 3*time.Second, checkNetwork, checker.Not(checker.Contains), "testnet") +-} +- +-func (s *DockerSwarmSuite) TestOverlayAttachable(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- out, err := d.Cmd("network", "create", "-d", "overlay", "--attachable", "ovnet") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- // validate attachable +- out, err = d.Cmd("network", "inspect", "--format", "{{json .Attachable}}", "ovnet") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Equals, "true") +- +- // validate containers can attache to this overlay network +- out, err = d.Cmd("run", "-d", "--network", "ovnet", "--name", "c1", "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- // redo validation, there was a bug that the value of attachable changes after +- // containers attach to the network +- out, err = d.Cmd("network", "inspect", "--format", "{{json .Attachable}}", "ovnet") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Equals, "true") +-} +- +-func (s *DockerSwarmSuite) TestOverlayAttachableOnSwarmLeave(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- // Create an attachable swarm network +- nwName := "attovl" +- out, err := d.Cmd("network", "create", "-d", "overlay", "--attachable", nwName) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- // Connect a container to the network +- out, err = d.Cmd("run", "-d", "--network", nwName, "--name", "c1", "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- // Leave the swarm +- err = d.SwarmLeave(true) +- c.Assert(err, checker.IsNil) +- +- // Check the container is disconnected +- out, err = d.Cmd("inspect", "c1", "--format", "{{.NetworkSettings.Networks."+nwName+"}}") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Equals, "") +- +- // Check the network is gone +- out, err = d.Cmd("network", "ls", "--format", "{{.Name}}") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Not(checker.Contains), nwName) +-} +- +-func (s *DockerSwarmSuite) TestOverlayAttachableReleaseResourcesOnFailure(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- // Create attachable network +- out, err := d.Cmd("network", "create", "-d", "overlay", "--attachable", "--subnet", "10.10.9.0/24", "ovnet") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- // Attach a container with specific IP +- out, err = d.Cmd("run", "-d", "--network", "ovnet", "--name", "c1", "--ip", "10.10.9.33", "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- // Attempt to attach another container with same IP, must fail +- out, err = d.Cmd("run", "-d", "--network", "ovnet", "--name", "c2", "--ip", "10.10.9.33", "busybox", "top") +- c.Assert(err, checker.NotNil, check.Commentf("%s", out)) +- +- // Remove first container +- out, err = d.Cmd("rm", "-f", "c1") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- // Verify the network can be removed, no phantom network attachment task left over +- out, err = d.Cmd("network", "rm", "ovnet") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +-} +- +-func (s *DockerSwarmSuite) TestSwarmIngressNetwork(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- // Ingress network can be removed +- removeNetwork := func(name string) *icmd.Result { +- return cli.Docker( +- cli.Args("-H", d.Sock(), "network", "rm", name), +- cli.WithStdin(strings.NewReader("Y"))) +- } +- +- result := removeNetwork("ingress") +- result.Assert(c, icmd.Success) +- +- // And recreated +- out, err := d.Cmd("network", "create", "-d", "overlay", "--ingress", "new-ingress") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- // But only one is allowed +- out, err = d.Cmd("network", "create", "-d", "overlay", "--ingress", "another-ingress") +- c.Assert(err, checker.NotNil) +- c.Assert(strings.TrimSpace(out), checker.Contains, "is already present") +- +- // It cannot be removed if it is being used +- out, err = d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", "srv1", "-p", "9000:8000", "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- result = removeNetwork("new-ingress") +- result.Assert(c, icmd.Expected{ +- ExitCode: 1, +- Err: "ingress network cannot be removed because service", +- }) +- +- // But it can be removed once no more services depend on it +- out, err = d.Cmd("service", "update", "--detach", "--publish-rm", "9000:8000", "srv1") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- result = removeNetwork("new-ingress") +- result.Assert(c, icmd.Success) +- +- // A service which needs the ingress network cannot be created if no ingress is present +- out, err = d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", "srv2", "-p", "500:500", "busybox", "top") +- c.Assert(err, checker.NotNil) +- c.Assert(strings.TrimSpace(out), checker.Contains, "no ingress network is present") +- +- // An existing service cannot be updated to use the ingress nw if the nw is not present +- out, err = d.Cmd("service", "update", "--detach", "--publish-add", "9000:8000", "srv1") +- c.Assert(err, checker.NotNil) +- c.Assert(strings.TrimSpace(out), checker.Contains, "no ingress network is present") +- +- // But services which do not need routing mesh can be created regardless +- out, err = d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", "srv3", "--endpoint-mode", "dnsrr", "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +-} +- +-func (s *DockerSwarmSuite) TestSwarmCreateServiceWithNoIngressNetwork(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- // Remove ingress network +- result := cli.Docker( +- cli.Args("-H", d.Sock(), "network", "rm", "ingress"), +- cli.WithStdin(strings.NewReader("Y"))) +- result.Assert(c, icmd.Success) +- +- // Create a overlay network and launch a service on it +- // Make sure nothing panics because ingress network is missing +- out, err := d.Cmd("network", "create", "-d", "overlay", "another-network") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- out, err = d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", "srv4", "--network", "another-network", "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +-} +- +-// Test case for #24108, also the case from: +-// https://github.com/docker/docker/pull/24620#issuecomment-233715656 +-func (s *DockerSwarmSuite) TestSwarmTaskListFilter(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- name := "redis-cluster-md5" +- out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", name, "--replicas=3", "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") +- +- filter := "name=redis-cluster" +- +- checkNumTasks := func(*check.C) (interface{}, check.CommentInterface) { +- out, err := d.Cmd("service", "ps", "--filter", filter, name) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- return len(strings.Split(out, "\n")) - 2, nil // includes header and nl in last line +- } +- +- // wait until all tasks have been created +- waitAndAssert(c, defaultReconciliationTimeout, checkNumTasks, checker.Equals, 3) +- +- out, err = d.Cmd("service", "ps", "--filter", filter, name) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Contains, name+".1") +- c.Assert(out, checker.Contains, name+".2") +- c.Assert(out, checker.Contains, name+".3") +- +- out, err = d.Cmd("service", "ps", "--filter", "name="+name+".1", name) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Contains, name+".1") +- c.Assert(out, checker.Not(checker.Contains), name+".2") +- c.Assert(out, checker.Not(checker.Contains), name+".3") +- +- out, err = d.Cmd("service", "ps", "--filter", "name=none", name) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Not(checker.Contains), name+".1") +- c.Assert(out, checker.Not(checker.Contains), name+".2") +- c.Assert(out, checker.Not(checker.Contains), name+".3") +- +- name = "redis-cluster-sha1" +- out, err = d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", name, "--mode=global", "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") +- +- waitAndAssert(c, defaultReconciliationTimeout, checkNumTasks, checker.Equals, 1) +- +- filter = "name=redis-cluster" +- out, err = d.Cmd("service", "ps", "--filter", filter, name) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Contains, name) +- +- out, err = d.Cmd("service", "ps", "--filter", "name="+name, name) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Contains, name) +- +- out, err = d.Cmd("service", "ps", "--filter", "name=none", name) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Not(checker.Contains), name) +-} +- +-func (s *DockerSwarmSuite) TestPsListContainersFilterIsTask(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- // Create a bare container +- out, err := d.Cmd("run", "-d", "--name=bare-container", "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- bareID := strings.TrimSpace(out)[:12] +- // Create a service +- name := "busybox-top" +- out, err = d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", name, "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") +- +- // make sure task has been deployed. +- waitAndAssert(c, defaultReconciliationTimeout, d.CheckServiceRunningTasks(name), checker.Equals, 1) +- +- // Filter non-tasks +- out, err = d.Cmd("ps", "-a", "-q", "--filter=is-task=false") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- psOut := strings.TrimSpace(out) +- c.Assert(psOut, checker.Equals, bareID, check.Commentf("Expected id %s, got %s for is-task label, output %q", bareID, psOut, out)) +- +- // Filter tasks +- out, err = d.Cmd("ps", "-a", "-q", "--filter=is-task=true") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- lines := strings.Split(strings.Trim(out, "\n "), "\n") +- c.Assert(lines, checker.HasLen, 1) +- c.Assert(lines[0], checker.Not(checker.Equals), bareID, check.Commentf("Expected not %s, but got it for is-task label, output %q", bareID, out)) +-} +- +-const globalNetworkPlugin = "global-network-plugin" +-const globalIPAMPlugin = "global-ipam-plugin" +- +-func setupRemoteGlobalNetworkPlugin(c *check.C, mux *http.ServeMux, url, netDrv, ipamDrv string) { +- +- mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) { +- w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") +- fmt.Fprintf(w, `{"Implements": ["%s", "%s"]}`, driverapi.NetworkPluginEndpointType, ipamapi.PluginEndpointType) +- }) +- +- // Network driver implementation +- mux.HandleFunc(fmt.Sprintf("/%s.GetCapabilities", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) { +- w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") +- fmt.Fprintf(w, `{"Scope":"global"}`) +- }) +- +- mux.HandleFunc(fmt.Sprintf("/%s.AllocateNetwork", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) { +- err := json.NewDecoder(r.Body).Decode(&remoteDriverNetworkRequest) +- if err != nil { +- http.Error(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest) +- return +- } +- w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") +- fmt.Fprintf(w, "null") +- }) +- +- mux.HandleFunc(fmt.Sprintf("/%s.FreeNetwork", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) { +- w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") +- fmt.Fprintf(w, "null") +- }) +- +- mux.HandleFunc(fmt.Sprintf("/%s.CreateNetwork", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) { +- err := json.NewDecoder(r.Body).Decode(&remoteDriverNetworkRequest) +- if err != nil { +- http.Error(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest) +- return +- } +- w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") +- fmt.Fprintf(w, "null") +- }) +- +- mux.HandleFunc(fmt.Sprintf("/%s.DeleteNetwork", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) { +- w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") +- fmt.Fprintf(w, "null") +- }) +- +- mux.HandleFunc(fmt.Sprintf("/%s.CreateEndpoint", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) { +- w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") +- fmt.Fprintf(w, `{"Interface":{"MacAddress":"a0:b1:c2:d3:e4:f5"}}`) +- }) +- +- mux.HandleFunc(fmt.Sprintf("/%s.Join", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) { +- w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") +- +- veth := &netlink.Veth{ +- LinkAttrs: netlink.LinkAttrs{Name: "randomIfName", TxQLen: 0}, PeerName: "cnt0"} +- if err := netlink.LinkAdd(veth); err != nil { +- fmt.Fprintf(w, `{"Error":"failed to add veth pair: `+err.Error()+`"}`) +- } else { +- fmt.Fprintf(w, `{"InterfaceName":{ "SrcName":"cnt0", "DstPrefix":"veth"}}`) +- } +- }) +- +- mux.HandleFunc(fmt.Sprintf("/%s.Leave", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) { +- w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") +- fmt.Fprintf(w, "null") +- }) +- +- mux.HandleFunc(fmt.Sprintf("/%s.DeleteEndpoint", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) { +- w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") +- if link, err := netlink.LinkByName("cnt0"); err == nil { +- netlink.LinkDel(link) +- } +- fmt.Fprintf(w, "null") +- }) +- +- // IPAM Driver implementation +- var ( +- poolRequest remoteipam.RequestPoolRequest +- poolReleaseReq remoteipam.ReleasePoolRequest +- addressRequest remoteipam.RequestAddressRequest +- addressReleaseReq remoteipam.ReleaseAddressRequest +- lAS = "localAS" +- gAS = "globalAS" +- pool = "172.28.0.0/16" +- poolID = lAS + "/" + pool +- gw = "172.28.255.254/16" +- ) +- +- mux.HandleFunc(fmt.Sprintf("/%s.GetDefaultAddressSpaces", ipamapi.PluginEndpointType), func(w http.ResponseWriter, r *http.Request) { +- w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") +- fmt.Fprintf(w, `{"LocalDefaultAddressSpace":"`+lAS+`", "GlobalDefaultAddressSpace": "`+gAS+`"}`) +- }) +- +- mux.HandleFunc(fmt.Sprintf("/%s.RequestPool", ipamapi.PluginEndpointType), func(w http.ResponseWriter, r *http.Request) { +- err := json.NewDecoder(r.Body).Decode(&poolRequest) +- if err != nil { +- http.Error(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest) +- return +- } +- w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") +- if poolRequest.AddressSpace != lAS && poolRequest.AddressSpace != gAS { +- fmt.Fprintf(w, `{"Error":"Unknown address space in pool request: `+poolRequest.AddressSpace+`"}`) +- } else if poolRequest.Pool != "" && poolRequest.Pool != pool { +- fmt.Fprintf(w, `{"Error":"Cannot handle explicit pool requests yet"}`) +- } else { +- fmt.Fprintf(w, `{"PoolID":"`+poolID+`", "Pool":"`+pool+`"}`) +- } +- }) +- +- mux.HandleFunc(fmt.Sprintf("/%s.RequestAddress", ipamapi.PluginEndpointType), func(w http.ResponseWriter, r *http.Request) { +- err := json.NewDecoder(r.Body).Decode(&addressRequest) +- if err != nil { +- http.Error(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest) +- return +- } +- w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") +- // make sure libnetwork is now querying on the expected pool id +- if addressRequest.PoolID != poolID { +- fmt.Fprintf(w, `{"Error":"unknown pool id"}`) +- } else if addressRequest.Address != "" { +- fmt.Fprintf(w, `{"Error":"Cannot handle explicit address requests yet"}`) +- } else { +- fmt.Fprintf(w, `{"Address":"`+gw+`"}`) +- } +- }) +- +- mux.HandleFunc(fmt.Sprintf("/%s.ReleaseAddress", ipamapi.PluginEndpointType), func(w http.ResponseWriter, r *http.Request) { +- err := json.NewDecoder(r.Body).Decode(&addressReleaseReq) +- if err != nil { +- http.Error(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest) +- return +- } +- w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") +- // make sure libnetwork is now asking to release the expected address from the expected poolid +- if addressRequest.PoolID != poolID { +- fmt.Fprintf(w, `{"Error":"unknown pool id"}`) +- } else if addressReleaseReq.Address != gw { +- fmt.Fprintf(w, `{"Error":"unknown address"}`) +- } else { +- fmt.Fprintf(w, "null") +- } +- }) +- +- mux.HandleFunc(fmt.Sprintf("/%s.ReleasePool", ipamapi.PluginEndpointType), func(w http.ResponseWriter, r *http.Request) { +- err := json.NewDecoder(r.Body).Decode(&poolReleaseReq) +- if err != nil { +- http.Error(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest) +- return +- } +- w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") +- // make sure libnetwork is now asking to release the expected poolid +- if addressRequest.PoolID != poolID { +- fmt.Fprintf(w, `{"Error":"unknown pool id"}`) +- } else { +- fmt.Fprintf(w, "null") +- } +- }) +- +- err := os.MkdirAll("/etc/docker/plugins", 0755) +- c.Assert(err, checker.IsNil) +- +- fileName := fmt.Sprintf("/etc/docker/plugins/%s.spec", netDrv) +- err = ioutil.WriteFile(fileName, []byte(url), 0644) +- c.Assert(err, checker.IsNil) +- +- ipamFileName := fmt.Sprintf("/etc/docker/plugins/%s.spec", ipamDrv) +- err = ioutil.WriteFile(ipamFileName, []byte(url), 0644) +- c.Assert(err, checker.IsNil) +-} +- +-func (s *DockerSwarmSuite) TestSwarmNetworkPlugin(c *check.C) { +- mux := http.NewServeMux() +- s.server = httptest.NewServer(mux) +- c.Assert(s.server, check.NotNil) // check that HTTP server has started +- setupRemoteGlobalNetworkPlugin(c, mux, s.server.URL, globalNetworkPlugin, globalIPAMPlugin) +- defer func() { +- s.server.Close() +- err := os.RemoveAll("/etc/docker/plugins") +- c.Assert(err, checker.IsNil) +- }() +- +- d := s.AddDaemon(c, true, true) +- +- out, err := d.Cmd("network", "create", "-d", globalNetworkPlugin, "foo") +- c.Assert(err, checker.NotNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Contains, "not supported in swarm mode") +-} +- +-// Test case for #24712 +-func (s *DockerSwarmSuite) TestSwarmServiceEnvFile(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- path := filepath.Join(d.Folder, "env.txt") +- err := ioutil.WriteFile(path, []byte("VAR1=A\nVAR2=A\n"), 0644) +- c.Assert(err, checker.IsNil) +- +- name := "worker" +- out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--env-file", path, "--env", "VAR1=B", "--env", "VAR1=C", "--env", "VAR2=", "--env", "VAR2", "--name", name, "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") +- +- // The complete env is [VAR1=A VAR2=A VAR1=B VAR1=C VAR2= VAR2] and duplicates will be removed => [VAR1=C VAR2] +- out, err = d.Cmd("inspect", "--format", "{{ .Spec.TaskTemplate.ContainerSpec.Env }}", name) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Contains, "[VAR1=C VAR2]") +-} +- +-func (s *DockerSwarmSuite) TestSwarmServiceTTY(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- name := "top" +- +- ttyCheck := "if [ -t 0 ]; then echo TTY > /status && top; else echo none > /status && top; fi" +- +- // Without --tty +- expectedOutput := "none" +- out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", name, "busybox", "sh", "-c", ttyCheck) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- // Make sure task has been deployed. +- waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1) +- +- // We need to get the container id. +- out, err = d.Cmd("ps", "-q", "--no-trunc") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- id := strings.TrimSpace(out) +- +- out, err = d.Cmd("exec", id, "cat", "/status") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Contains, expectedOutput, check.Commentf("Expected '%s', but got %q", expectedOutput, out)) +- +- // Remove service +- out, err = d.Cmd("service", "rm", name) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- // Make sure container has been destroyed. +- waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 0) +- +- // With --tty +- expectedOutput = "TTY" +- out, err = d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", name, "--tty", "busybox", "sh", "-c", ttyCheck) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- // Make sure task has been deployed. +- waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1) +- +- // We need to get the container id. +- out, err = d.Cmd("ps", "-q", "--no-trunc") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- id = strings.TrimSpace(out) +- +- out, err = d.Cmd("exec", id, "cat", "/status") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Contains, expectedOutput, check.Commentf("Expected '%s', but got %q", expectedOutput, out)) +-} +- +-func (s *DockerSwarmSuite) TestSwarmServiceTTYUpdate(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- // Create a service +- name := "top" +- out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", name, "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- // Make sure task has been deployed. +- waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1) +- +- out, err = d.Cmd("service", "inspect", "--format", "{{ .Spec.TaskTemplate.ContainerSpec.TTY }}", name) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Equals, "false") +- +- out, err = d.Cmd("service", "update", "--detach", "--tty", name) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- out, err = d.Cmd("service", "inspect", "--format", "{{ .Spec.TaskTemplate.ContainerSpec.TTY }}", name) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Equals, "true") +-} +- +-func (s *DockerSwarmSuite) TestSwarmServiceNetworkUpdate(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- result := icmd.RunCmd(d.Command("network", "create", "-d", "overlay", "foo")) +- result.Assert(c, icmd.Success) +- fooNetwork := strings.TrimSpace(string(result.Combined())) +- +- result = icmd.RunCmd(d.Command("network", "create", "-d", "overlay", "bar")) +- result.Assert(c, icmd.Success) +- barNetwork := strings.TrimSpace(string(result.Combined())) +- +- result = icmd.RunCmd(d.Command("network", "create", "-d", "overlay", "baz")) +- result.Assert(c, icmd.Success) +- bazNetwork := strings.TrimSpace(string(result.Combined())) +- +- // Create a service +- name := "top" +- result = icmd.RunCmd(d.Command("service", "create", "--detach", "--no-resolve-image", "--network", "foo", "--network", "bar", "--name", name, "busybox", "top")) +- result.Assert(c, icmd.Success) +- +- // Make sure task has been deployed. +- waitAndAssert(c, defaultReconciliationTimeout, d.CheckRunningTaskNetworks, checker.DeepEquals, +- map[string]int{fooNetwork: 1, barNetwork: 1}) +- +- // Remove a network +- result = icmd.RunCmd(d.Command("service", "update", "--detach", "--network-rm", "foo", name)) +- result.Assert(c, icmd.Success) +- +- waitAndAssert(c, defaultReconciliationTimeout, d.CheckRunningTaskNetworks, checker.DeepEquals, +- map[string]int{barNetwork: 1}) +- +- // Add a network +- result = icmd.RunCmd(d.Command("service", "update", "--detach", "--network-add", "baz", name)) +- result.Assert(c, icmd.Success) +- +- waitAndAssert(c, defaultReconciliationTimeout, d.CheckRunningTaskNetworks, checker.DeepEquals, +- map[string]int{barNetwork: 1, bazNetwork: 1}) +-} +- +-func (s *DockerSwarmSuite) TestDNSConfig(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- // Create a service +- name := "top" +- out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", name, "--dns=1.2.3.4", "--dns-search=example.com", "--dns-option=timeout:3", "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- // Make sure task has been deployed. +- waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1) +- +- // We need to get the container id. +- out, err = d.Cmd("ps", "-a", "-q", "--no-trunc") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- id := strings.TrimSpace(out) +- +- // Compare against expected output. +- expectedOutput1 := "nameserver 1.2.3.4" +- expectedOutput2 := "search example.com" +- expectedOutput3 := "options timeout:3" +- out, err = d.Cmd("exec", id, "cat", "/etc/resolv.conf") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Contains, expectedOutput1, check.Commentf("Expected '%s', but got %q", expectedOutput1, out)) +- c.Assert(out, checker.Contains, expectedOutput2, check.Commentf("Expected '%s', but got %q", expectedOutput2, out)) +- c.Assert(out, checker.Contains, expectedOutput3, check.Commentf("Expected '%s', but got %q", expectedOutput3, out)) +-} +- +-func (s *DockerSwarmSuite) TestDNSConfigUpdate(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- // Create a service +- name := "top" +- out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", name, "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- // Make sure task has been deployed. +- waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1) +- +- out, err = d.Cmd("service", "update", "--detach", "--dns-add=1.2.3.4", "--dns-search-add=example.com", "--dns-option-add=timeout:3", name) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- out, err = d.Cmd("service", "inspect", "--format", "{{ .Spec.TaskTemplate.ContainerSpec.DNSConfig }}", name) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Equals, "{[1.2.3.4] [example.com] [timeout:3]}") +-} +- +-func getNodeStatus(c *check.C, d *daemon.Daemon) swarm.LocalNodeState { +- info := d.SwarmInfo(c) +- return info.LocalNodeState +-} +- +-func checkKeyIsEncrypted(d *daemon.Daemon) func(*check.C) (interface{}, check.CommentInterface) { +- return func(c *check.C) (interface{}, check.CommentInterface) { +- keyBytes, err := ioutil.ReadFile(filepath.Join(d.Folder, "root", "swarm", "certificates", "swarm-node.key")) +- if err != nil { +- return fmt.Errorf("error reading key: %v", err), nil +- } +- +- keyBlock, _ := pem.Decode(keyBytes) +- if keyBlock == nil { +- return fmt.Errorf("invalid PEM-encoded private key"), nil +- } +- +- return keyutils.IsEncryptedPEMBlock(keyBlock), nil +- } +-} +- +-func checkSwarmLockedToUnlocked(c *check.C, d *daemon.Daemon, unlockKey string) { +- // Wait for the PEM file to become unencrypted +- waitAndAssert(c, defaultReconciliationTimeout, checkKeyIsEncrypted(d), checker.Equals, false) +- +- d.Restart(c) +- c.Assert(getNodeStatus(c, d), checker.Equals, swarm.LocalNodeStateActive) +-} +- +-func checkSwarmUnlockedToLocked(c *check.C, d *daemon.Daemon) { +- // Wait for the PEM file to become encrypted +- waitAndAssert(c, defaultReconciliationTimeout, checkKeyIsEncrypted(d), checker.Equals, true) +- +- d.Restart(c) +- c.Assert(getNodeStatus(c, d), checker.Equals, swarm.LocalNodeStateLocked) +-} +- +-func (s *DockerSwarmSuite) TestUnlockEngineAndUnlockedSwarm(c *check.C) { +- d := s.AddDaemon(c, false, false) +- +- // unlocking a normal engine should return an error - it does not even ask for the key +- cmd := d.Command("swarm", "unlock") +- result := icmd.RunCmd(cmd) +- result.Assert(c, icmd.Expected{ +- ExitCode: 1, +- }) +- c.Assert(result.Combined(), checker.Contains, "Error: This node is not part of a swarm") +- c.Assert(result.Combined(), checker.Not(checker.Contains), "Please enter unlock key") +- +- out, err := d.Cmd("swarm", "init") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- // unlocking an unlocked swarm should return an error - it does not even ask for the key +- cmd = d.Command("swarm", "unlock") +- result = icmd.RunCmd(cmd) +- result.Assert(c, icmd.Expected{ +- ExitCode: 1, +- }) +- c.Assert(result.Combined(), checker.Contains, "Error: swarm is not locked") +- c.Assert(result.Combined(), checker.Not(checker.Contains), "Please enter unlock key") +-} +- +-func (s *DockerSwarmSuite) TestSwarmInitLocked(c *check.C) { +- d := s.AddDaemon(c, false, false) +- +- outs, err := d.Cmd("swarm", "init", "--autolock") +- c.Assert(err, checker.IsNil, check.Commentf("%s", outs)) +- +- c.Assert(outs, checker.Contains, "docker swarm unlock") +- +- var unlockKey string +- for _, line := range strings.Split(outs, "\n") { +- if strings.Contains(line, "SWMKEY") { +- unlockKey = strings.TrimSpace(line) +- break +- } +- } +- +- c.Assert(unlockKey, checker.Not(checker.Equals), "") +- +- outs, err = d.Cmd("swarm", "unlock-key", "-q") +- c.Assert(err, checker.IsNil, check.Commentf("%s", outs)) +- c.Assert(outs, checker.Equals, unlockKey+"\n") +- +- c.Assert(getNodeStatus(c, d), checker.Equals, swarm.LocalNodeStateActive) +- +- // It starts off locked +- d.Restart(c) +- c.Assert(getNodeStatus(c, d), checker.Equals, swarm.LocalNodeStateLocked) +- +- cmd := d.Command("swarm", "unlock") +- cmd.Stdin = bytes.NewBufferString("wrong-secret-key") +- icmd.RunCmd(cmd).Assert(c, icmd.Expected{ +- ExitCode: 1, +- Err: "invalid key", +- }) +- +- c.Assert(getNodeStatus(c, d), checker.Equals, swarm.LocalNodeStateLocked) +- +- cmd = d.Command("swarm", "unlock") +- cmd.Stdin = bytes.NewBufferString(unlockKey) +- icmd.RunCmd(cmd).Assert(c, icmd.Success) +- +- c.Assert(getNodeStatus(c, d), checker.Equals, swarm.LocalNodeStateActive) +- +- outs, err = d.Cmd("node", "ls") +- c.Assert(err, checker.IsNil, check.Commentf("%s", outs)) +- c.Assert(outs, checker.Not(checker.Contains), "Swarm is encrypted and needs to be unlocked") +- +- outs, err = d.Cmd("swarm", "update", "--autolock=false") +- c.Assert(err, checker.IsNil, check.Commentf("%s", outs)) +- +- checkSwarmLockedToUnlocked(c, d, unlockKey) +- +- outs, err = d.Cmd("node", "ls") +- c.Assert(err, checker.IsNil, check.Commentf("%s", outs)) +- c.Assert(outs, checker.Not(checker.Contains), "Swarm is encrypted and needs to be unlocked") +-} +- +-func (s *DockerSwarmSuite) TestSwarmLeaveLocked(c *check.C) { +- d := s.AddDaemon(c, false, false) +- +- outs, err := d.Cmd("swarm", "init", "--autolock") +- c.Assert(err, checker.IsNil, check.Commentf("%s", outs)) +- +- // It starts off locked +- d.Restart(c, "--swarm-default-advertise-addr=lo") +- +- info := d.SwarmInfo(c) +- c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateLocked) +- +- outs, _ = d.Cmd("node", "ls") +- c.Assert(outs, checker.Contains, "Swarm is encrypted and needs to be unlocked") +- +- // `docker swarm leave` a locked swarm without --force will return an error +- outs, _ = d.Cmd("swarm", "leave") +- c.Assert(outs, checker.Contains, "Swarm is encrypted and locked.") +- +- // It is OK for user to leave a locked swarm with --force +- outs, err = d.Cmd("swarm", "leave", "--force") +- c.Assert(err, checker.IsNil, check.Commentf("%s", outs)) +- +- info = d.SwarmInfo(c) +- c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateInactive) +- +- outs, err = d.Cmd("swarm", "init") +- c.Assert(err, checker.IsNil, check.Commentf("%s", outs)) +- +- info = d.SwarmInfo(c) +- c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive) +-} +- +-func (s *DockerSwarmSuite) TestSwarmLockUnlockCluster(c *check.C) { +- d1 := s.AddDaemon(c, true, true) +- d2 := s.AddDaemon(c, true, true) +- d3 := s.AddDaemon(c, true, true) +- +- // they start off unlocked +- d2.Restart(c) +- c.Assert(getNodeStatus(c, d2), checker.Equals, swarm.LocalNodeStateActive) +- +- // stop this one so it does not get autolock info +- d2.Stop(c) +- +- // enable autolock +- outs, err := d1.Cmd("swarm", "update", "--autolock") +- c.Assert(err, checker.IsNil, check.Commentf("%s", outs)) +- +- c.Assert(outs, checker.Contains, "docker swarm unlock") +- +- var unlockKey string +- for _, line := range strings.Split(outs, "\n") { +- if strings.Contains(line, "SWMKEY") { +- unlockKey = strings.TrimSpace(line) +- break +- } +- } +- +- c.Assert(unlockKey, checker.Not(checker.Equals), "") +- +- outs, err = d1.Cmd("swarm", "unlock-key", "-q") +- c.Assert(err, checker.IsNil) +- c.Assert(outs, checker.Equals, unlockKey+"\n") +- +- // The ones that got the cluster update should be set to locked +- for _, d := range []*daemon.Daemon{d1, d3} { +- checkSwarmUnlockedToLocked(c, d) +- +- cmd := d.Command("swarm", "unlock") +- cmd.Stdin = bytes.NewBufferString(unlockKey) +- icmd.RunCmd(cmd).Assert(c, icmd.Success) +- c.Assert(getNodeStatus(c, d), checker.Equals, swarm.LocalNodeStateActive) +- } +- +- // d2 never got the cluster update, so it is still set to unlocked +- d2.Start(c) +- c.Assert(getNodeStatus(c, d2), checker.Equals, swarm.LocalNodeStateActive) +- +- // d2 is now set to lock +- checkSwarmUnlockedToLocked(c, d2) +- +- // leave it locked, and set the cluster to no longer autolock +- outs, err = d1.Cmd("swarm", "update", "--autolock=false") +- c.Assert(err, checker.IsNil, check.Commentf("out: %v", outs)) +- +- // the ones that got the update are now set to unlocked +- for _, d := range []*daemon.Daemon{d1, d3} { +- checkSwarmLockedToUnlocked(c, d, unlockKey) +- } +- +- // d2 still locked +- c.Assert(getNodeStatus(c, d2), checker.Equals, swarm.LocalNodeStateLocked) +- +- // unlock it +- cmd := d2.Command("swarm", "unlock") +- cmd.Stdin = bytes.NewBufferString(unlockKey) +- icmd.RunCmd(cmd).Assert(c, icmd.Success) +- c.Assert(getNodeStatus(c, d2), checker.Equals, swarm.LocalNodeStateActive) +- +- // once it's caught up, d2 is set to not be locked +- checkSwarmLockedToUnlocked(c, d2, unlockKey) +- +- // managers who join now are never set to locked in the first place +- d4 := s.AddDaemon(c, true, true) +- d4.Restart(c) +- c.Assert(getNodeStatus(c, d4), checker.Equals, swarm.LocalNodeStateActive) +-} +- +-func (s *DockerSwarmSuite) TestSwarmJoinPromoteLocked(c *check.C) { +- d1 := s.AddDaemon(c, true, true) +- +- // enable autolock +- outs, err := d1.Cmd("swarm", "update", "--autolock") +- c.Assert(err, checker.IsNil, check.Commentf("out: %v", outs)) +- +- c.Assert(outs, checker.Contains, "docker swarm unlock") +- +- var unlockKey string +- for _, line := range strings.Split(outs, "\n") { +- if strings.Contains(line, "SWMKEY") { +- unlockKey = strings.TrimSpace(line) +- break +- } +- } +- +- c.Assert(unlockKey, checker.Not(checker.Equals), "") +- +- outs, err = d1.Cmd("swarm", "unlock-key", "-q") +- c.Assert(err, checker.IsNil) +- c.Assert(outs, checker.Equals, unlockKey+"\n") +- +- // joined workers start off unlocked +- d2 := s.AddDaemon(c, true, false) +- d2.Restart(c) +- c.Assert(getNodeStatus(c, d2), checker.Equals, swarm.LocalNodeStateActive) +- +- // promote worker +- outs, err = d1.Cmd("node", "promote", d2.NodeID()) +- c.Assert(err, checker.IsNil) +- c.Assert(outs, checker.Contains, "promoted to a manager in the swarm") +- +- // join new manager node +- d3 := s.AddDaemon(c, true, true) +- +- // both new nodes are locked +- for _, d := range []*daemon.Daemon{d2, d3} { +- checkSwarmUnlockedToLocked(c, d) +- +- cmd := d.Command("swarm", "unlock") +- cmd.Stdin = bytes.NewBufferString(unlockKey) +- icmd.RunCmd(cmd).Assert(c, icmd.Success) +- c.Assert(getNodeStatus(c, d), checker.Equals, swarm.LocalNodeStateActive) +- } +- +- // demote manager back to worker - workers are not locked +- outs, err = d1.Cmd("node", "demote", d3.NodeID()) +- c.Assert(err, checker.IsNil) +- c.Assert(outs, checker.Contains, "demoted in the swarm") +- +- // Wait for it to actually be demoted, for the key and cert to be replaced. +- // Then restart and assert that the node is not locked. If we don't wait for the cert +- // to be replaced, then the node still has the manager TLS key which is still locked +- // (because we never want a manager TLS key to be on disk unencrypted if the cluster +- // is set to autolock) +- waitAndAssert(c, defaultReconciliationTimeout, d3.CheckControlAvailable, checker.False) +- waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) { +- certBytes, err := ioutil.ReadFile(filepath.Join(d3.Folder, "root", "swarm", "certificates", "swarm-node.crt")) +- if err != nil { +- return "", check.Commentf("error: %v", err) +- } +- certs, err := helpers.ParseCertificatesPEM(certBytes) +- if err == nil && len(certs) > 0 && len(certs[0].Subject.OrganizationalUnit) > 0 { +- return certs[0].Subject.OrganizationalUnit[0], nil +- } +- return "", check.Commentf("could not get organizational unit from certificate") +- }, checker.Equals, "swarm-worker") +- +- // by now, it should *never* be locked on restart +- d3.Restart(c) +- c.Assert(getNodeStatus(c, d3), checker.Equals, swarm.LocalNodeStateActive) +-} +- +-func (s *DockerSwarmSuite) TestSwarmRotateUnlockKey(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- outs, err := d.Cmd("swarm", "update", "--autolock") +- c.Assert(err, checker.IsNil, check.Commentf("out: %v", outs)) +- +- c.Assert(outs, checker.Contains, "docker swarm unlock") +- +- var unlockKey string +- for _, line := range strings.Split(outs, "\n") { +- if strings.Contains(line, "SWMKEY") { +- unlockKey = strings.TrimSpace(line) +- break +- } +- } +- +- c.Assert(unlockKey, checker.Not(checker.Equals), "") +- +- outs, err = d.Cmd("swarm", "unlock-key", "-q") +- c.Assert(err, checker.IsNil) +- c.Assert(outs, checker.Equals, unlockKey+"\n") +- +- // Rotate multiple times +- for i := 0; i != 3; i++ { +- outs, err = d.Cmd("swarm", "unlock-key", "-q", "--rotate") +- c.Assert(err, checker.IsNil, check.Commentf("out: %v", outs)) +- // Strip \n +- newUnlockKey := outs[:len(outs)-1] +- c.Assert(newUnlockKey, checker.Not(checker.Equals), "") +- c.Assert(newUnlockKey, checker.Not(checker.Equals), unlockKey) +- +- d.Restart(c) +- c.Assert(getNodeStatus(c, d), checker.Equals, swarm.LocalNodeStateLocked) +- +- outs, _ = d.Cmd("node", "ls") +- c.Assert(outs, checker.Contains, "Swarm is encrypted and needs to be unlocked") +- +- cmd := d.Command("swarm", "unlock") +- cmd.Stdin = bytes.NewBufferString(unlockKey) +- result := icmd.RunCmd(cmd) +- +- if result.Error == nil { +- // On occasion, the daemon may not have finished +- // rotating the KEK before restarting. The test is +- // intentionally written to explore this behavior. +- // When this happens, unlocking with the old key will +- // succeed. If we wait for the rotation to happen and +- // restart again, the new key should be required this +- // time. +- +- time.Sleep(3 * time.Second) +- +- d.Restart(c) +- +- cmd = d.Command("swarm", "unlock") +- cmd.Stdin = bytes.NewBufferString(unlockKey) +- result = icmd.RunCmd(cmd) +- } +- result.Assert(c, icmd.Expected{ +- ExitCode: 1, +- Err: "invalid key", +- }) +- +- outs, _ = d.Cmd("node", "ls") +- c.Assert(outs, checker.Contains, "Swarm is encrypted and needs to be unlocked") +- +- cmd = d.Command("swarm", "unlock") +- cmd.Stdin = bytes.NewBufferString(newUnlockKey) +- icmd.RunCmd(cmd).Assert(c, icmd.Success) +- +- c.Assert(getNodeStatus(c, d), checker.Equals, swarm.LocalNodeStateActive) +- +- outs, err = d.Cmd("node", "ls") +- c.Assert(err, checker.IsNil) +- c.Assert(outs, checker.Not(checker.Contains), "Swarm is encrypted and needs to be unlocked") +- +- unlockKey = newUnlockKey +- } +-} +- +-// This differs from `TestSwarmRotateUnlockKey` because that one rotates a single node, which is the leader. +-// This one keeps the leader up, and asserts that other manager nodes in the cluster also have their unlock +-// key rotated. +-func (s *DockerSwarmSuite) TestSwarmClusterRotateUnlockKey(c *check.C) { +- d1 := s.AddDaemon(c, true, true) // leader - don't restart this one, we don't want leader election delays +- d2 := s.AddDaemon(c, true, true) +- d3 := s.AddDaemon(c, true, true) +- +- outs, err := d1.Cmd("swarm", "update", "--autolock") +- c.Assert(err, checker.IsNil, check.Commentf("%s", outs)) +- +- c.Assert(outs, checker.Contains, "docker swarm unlock") +- +- var unlockKey string +- for _, line := range strings.Split(outs, "\n") { +- if strings.Contains(line, "SWMKEY") { +- unlockKey = strings.TrimSpace(line) +- break +- } +- } +- +- c.Assert(unlockKey, checker.Not(checker.Equals), "") +- +- outs, err = d1.Cmd("swarm", "unlock-key", "-q") +- c.Assert(err, checker.IsNil, check.Commentf("%s", outs)) +- c.Assert(outs, checker.Equals, unlockKey+"\n") +- +- // Rotate multiple times +- for i := 0; i != 3; i++ { +- outs, err = d1.Cmd("swarm", "unlock-key", "-q", "--rotate") +- c.Assert(err, checker.IsNil, check.Commentf("%s", outs)) +- // Strip \n +- newUnlockKey := outs[:len(outs)-1] +- c.Assert(newUnlockKey, checker.Not(checker.Equals), "") +- c.Assert(newUnlockKey, checker.Not(checker.Equals), unlockKey) +- +- d2.Restart(c) +- d3.Restart(c) +- +- for _, d := range []*daemon.Daemon{d2, d3} { +- c.Assert(getNodeStatus(c, d), checker.Equals, swarm.LocalNodeStateLocked) +- +- outs, _ := d.Cmd("node", "ls") +- c.Assert(outs, checker.Contains, "Swarm is encrypted and needs to be unlocked") +- +- cmd := d.Command("swarm", "unlock") +- cmd.Stdin = bytes.NewBufferString(unlockKey) +- result := icmd.RunCmd(cmd) +- +- if result.Error == nil { +- // On occasion, the daemon may not have finished +- // rotating the KEK before restarting. The test is +- // intentionally written to explore this behavior. +- // When this happens, unlocking with the old key will +- // succeed. If we wait for the rotation to happen and +- // restart again, the new key should be required this +- // time. +- +- time.Sleep(3 * time.Second) +- +- d.Restart(c) +- +- cmd = d.Command("swarm", "unlock") +- cmd.Stdin = bytes.NewBufferString(unlockKey) +- result = icmd.RunCmd(cmd) +- } +- result.Assert(c, icmd.Expected{ +- ExitCode: 1, +- Err: "invalid key", +- }) +- +- outs, _ = d.Cmd("node", "ls") +- c.Assert(outs, checker.Contains, "Swarm is encrypted and needs to be unlocked") +- +- cmd = d.Command("swarm", "unlock") +- cmd.Stdin = bytes.NewBufferString(newUnlockKey) +- icmd.RunCmd(cmd).Assert(c, icmd.Success) +- +- c.Assert(getNodeStatus(c, d), checker.Equals, swarm.LocalNodeStateActive) +- +- outs, err = d.Cmd("node", "ls") +- c.Assert(err, checker.IsNil, check.Commentf("%s", outs)) +- c.Assert(outs, checker.Not(checker.Contains), "Swarm is encrypted and needs to be unlocked") +- } +- +- unlockKey = newUnlockKey +- } +-} +- +-func (s *DockerSwarmSuite) TestSwarmAlternateLockUnlock(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- var unlockKey string +- for i := 0; i < 2; i++ { +- // set to lock +- outs, err := d.Cmd("swarm", "update", "--autolock") +- c.Assert(err, checker.IsNil, check.Commentf("out: %v", outs)) +- c.Assert(outs, checker.Contains, "docker swarm unlock") +- +- for _, line := range strings.Split(outs, "\n") { +- if strings.Contains(line, "SWMKEY") { +- unlockKey = strings.TrimSpace(line) +- break +- } +- } +- +- c.Assert(unlockKey, checker.Not(checker.Equals), "") +- checkSwarmUnlockedToLocked(c, d) +- +- cmd := d.Command("swarm", "unlock") +- cmd.Stdin = bytes.NewBufferString(unlockKey) +- icmd.RunCmd(cmd).Assert(c, icmd.Success) +- +- c.Assert(getNodeStatus(c, d), checker.Equals, swarm.LocalNodeStateActive) +- +- outs, err = d.Cmd("swarm", "update", "--autolock=false") +- c.Assert(err, checker.IsNil, check.Commentf("out: %v", outs)) +- +- checkSwarmLockedToUnlocked(c, d, unlockKey) +- } +-} +- +-func (s *DockerSwarmSuite) TestExtraHosts(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- // Create a service +- name := "top" +- out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", name, "--host=example.com:1.2.3.4", "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- // Make sure task has been deployed. +- waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1) +- +- // We need to get the container id. +- out, err = d.Cmd("ps", "-a", "-q", "--no-trunc") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- id := strings.TrimSpace(out) +- +- // Compare against expected output. +- expectedOutput := "1.2.3.4\texample.com" +- out, err = d.Cmd("exec", id, "cat", "/etc/hosts") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Contains, expectedOutput, check.Commentf("Expected '%s', but got %q", expectedOutput, out)) +-} +- +-func (s *DockerSwarmSuite) TestSwarmManagerAddress(c *check.C) { +- d1 := s.AddDaemon(c, true, true) +- d2 := s.AddDaemon(c, true, false) +- d3 := s.AddDaemon(c, true, false) +- +- // Manager Addresses will always show Node 1's address +- expectedOutput := fmt.Sprintf("Manager Addresses:\n 127.0.0.1:%d\n", d1.SwarmPort) +- +- out, err := d1.Cmd("info") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Contains, expectedOutput) +- +- out, err = d2.Cmd("info") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Contains, expectedOutput) +- +- out, err = d3.Cmd("info") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Contains, expectedOutput) +-} +- +-func (s *DockerSwarmSuite) TestSwarmNetworkIPAMOptions(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- out, err := d.Cmd("network", "create", "-d", "overlay", "--ipam-opt", "foo=bar", "foo") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") +- +- out, err = d.Cmd("network", "inspect", "--format", "{{.IPAM.Options}}", "foo") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Contains, "foo:bar") +- c.Assert(strings.TrimSpace(out), checker.Contains, "com.docker.network.ipam.serial:true") +- +- out, err = d.Cmd("service", "create", "--detach", "--no-resolve-image", "--network=foo", "--name", "top", "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- // make sure task has been deployed. +- waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1) +- +- out, err = d.Cmd("network", "inspect", "--format", "{{.IPAM.Options}}", "foo") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Contains, "foo:bar") +- c.Assert(strings.TrimSpace(out), checker.Contains, "com.docker.network.ipam.serial:true") +-} +- +-// Test case for issue #27866, which did not allow NW name that is the prefix of a swarm NW ID. +-// e.g. if the ingress ID starts with "n1", it was impossible to create a NW named "n1". +-func (s *DockerSwarmSuite) TestSwarmNetworkCreateIssue27866(c *check.C) { +- d := s.AddDaemon(c, true, true) +- out, err := d.Cmd("network", "inspect", "-f", "{{.Id}}", "ingress") +- c.Assert(err, checker.IsNil, check.Commentf("out: %v", out)) +- ingressID := strings.TrimSpace(out) +- c.Assert(ingressID, checker.Not(checker.Equals), "") +- +- // create a network of which name is the prefix of the ID of an overlay network +- // (ingressID in this case) +- newNetName := ingressID[0:2] +- out, err = d.Cmd("network", "create", "--driver", "overlay", newNetName) +- // In #27866, it was failing because of "network with name %s already exists" +- c.Assert(err, checker.IsNil, check.Commentf("out: %v", out)) +- out, err = d.Cmd("network", "rm", newNetName) +- c.Assert(err, checker.IsNil, check.Commentf("out: %v", out)) +-} +- +-// Test case for https://github.com/docker/docker/pull/27938#issuecomment-265768303 +-// This test creates two networks with the same name sequentially, with various drivers. +-// Since the operations in this test are done sequentially, the 2nd call should fail with +-// "network with name FOO already exists". +-// Note that it is to ok have multiple networks with the same name if the operations are done +-// in parallel. (#18864) +-func (s *DockerSwarmSuite) TestSwarmNetworkCreateDup(c *check.C) { +- d := s.AddDaemon(c, true, true) +- drivers := []string{"bridge", "overlay"} +- for i, driver1 := range drivers { +- nwName := fmt.Sprintf("network-test-%d", i) +- for _, driver2 := range drivers { +- c.Logf("Creating a network named %q with %q, then %q", +- nwName, driver1, driver2) +- out, err := d.Cmd("network", "create", "--driver", driver1, nwName) +- c.Assert(err, checker.IsNil, check.Commentf("out: %v", out)) +- out, err = d.Cmd("network", "create", "--driver", driver2, nwName) +- c.Assert(out, checker.Contains, +- fmt.Sprintf("network with name %s already exists", nwName)) +- c.Assert(err, checker.NotNil) +- c.Logf("As expected, the attempt to network %q with %q failed: %s", +- nwName, driver2, out) +- out, err = d.Cmd("network", "rm", nwName) +- c.Assert(err, checker.IsNil, check.Commentf("out: %v", out)) +- } +- } +-} +- +-func (s *DockerSwarmSuite) TestSwarmPublishDuplicatePorts(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- out, err := d.Cmd("service", "create", "--no-resolve-image", "--detach=true", "--publish", "5005:80", "--publish", "5006:80", "--publish", "80", "--publish", "80", "busybox", "top") +- c.Assert(err, check.IsNil, check.Commentf("%s", out)) +- id := strings.TrimSpace(out) +- +- // make sure task has been deployed. +- waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1) +- +- // Total len = 4, with 2 dynamic ports and 2 non-dynamic ports +- // Dynamic ports are likely to be 30000 and 30001 but doesn't matter +- out, err = d.Cmd("service", "inspect", "--format", "{{.Endpoint.Ports}} len={{len .Endpoint.Ports}}", id) +- c.Assert(err, check.IsNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Contains, "len=4") +- c.Assert(out, checker.Contains, "{ tcp 80 5005 ingress}") +- c.Assert(out, checker.Contains, "{ tcp 80 5006 ingress}") +-} +- +-func (s *DockerSwarmSuite) TestSwarmJoinWithDrain(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- out, err := d.Cmd("node", "ls") +- c.Assert(err, checker.IsNil) +- c.Assert(out, checker.Not(checker.Contains), "Drain") +- +- out, err = d.Cmd("swarm", "join-token", "-q", "manager") +- c.Assert(err, checker.IsNil) +- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") +- +- token := strings.TrimSpace(out) +- +- d1 := s.AddDaemon(c, false, false) +- +- out, err = d1.Cmd("swarm", "join", "--availability=drain", "--token", token, d.SwarmListenAddr()) +- c.Assert(err, checker.IsNil) +- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") +- +- out, err = d.Cmd("node", "ls") +- c.Assert(err, checker.IsNil) +- c.Assert(out, checker.Contains, "Drain") +- +- out, err = d1.Cmd("node", "ls") +- c.Assert(err, checker.IsNil) +- c.Assert(out, checker.Contains, "Drain") +-} +- +-func (s *DockerSwarmSuite) TestSwarmInitWithDrain(c *check.C) { +- d := s.AddDaemon(c, false, false) +- +- out, err := d.Cmd("swarm", "init", "--availability", "drain") +- c.Assert(err, checker.IsNil, check.Commentf("out: %v", out)) +- +- out, err = d.Cmd("node", "ls") +- c.Assert(err, checker.IsNil) +- c.Assert(out, checker.Contains, "Drain") +-} +- +-func (s *DockerSwarmSuite) TestSwarmReadonlyRootfs(c *check.C) { +- testRequires(c, DaemonIsLinux, UserNamespaceROMount) +- +- d := s.AddDaemon(c, true, true) +- +- out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", "top", "--read-only", "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- // make sure task has been deployed. +- waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1) +- +- out, err = d.Cmd("service", "inspect", "--format", "{{ .Spec.TaskTemplate.ContainerSpec.ReadOnly }}", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Equals, "true") +- +- containers := d.ActiveContainers(c) +- out, err = d.Cmd("inspect", "--type", "container", "--format", "{{.HostConfig.ReadonlyRootfs}}", containers[0]) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Equals, "true") +-} +- +-func (s *DockerSwarmSuite) TestNetworkInspectWithDuplicateNames(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- name := "foo" +- options := types.NetworkCreate{ +- CheckDuplicate: false, +- Driver: "bridge", +- } +- +- cli, err := d.NewClient() +- c.Assert(err, checker.IsNil) +- defer cli.Close() +- +- n1, err := cli.NetworkCreate(context.Background(), name, options) +- c.Assert(err, checker.IsNil) +- +- // Full ID always works +- out, err := d.Cmd("network", "inspect", "--format", "{{.ID}}", n1.ID) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Equals, n1.ID) +- +- // Name works if it is unique +- out, err = d.Cmd("network", "inspect", "--format", "{{.ID}}", name) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Equals, n1.ID) +- +- n2, err := cli.NetworkCreate(context.Background(), name, options) +- c.Assert(err, checker.IsNil) +- // Full ID always works +- out, err = d.Cmd("network", "inspect", "--format", "{{.ID}}", n1.ID) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Equals, n1.ID) +- +- out, err = d.Cmd("network", "inspect", "--format", "{{.ID}}", n2.ID) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Equals, n2.ID) +- +- // Name with duplicates +- out, err = d.Cmd("network", "inspect", "--format", "{{.ID}}", name) +- c.Assert(err, checker.NotNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Contains, "2 matches found based on name") +- +- out, err = d.Cmd("network", "rm", n2.ID) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- // Dupliates with name but with different driver +- options.Driver = "overlay" +- +- n2, err = cli.NetworkCreate(context.Background(), name, options) +- c.Assert(err, checker.IsNil) +- +- // Full ID always works +- out, err = d.Cmd("network", "inspect", "--format", "{{.ID}}", n1.ID) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Equals, n1.ID) +- +- out, err = d.Cmd("network", "inspect", "--format", "{{.ID}}", n2.ID) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Equals, n2.ID) +- +- // Name with duplicates +- out, err = d.Cmd("network", "inspect", "--format", "{{.ID}}", name) +- c.Assert(err, checker.NotNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Contains, "2 matches found based on name") +-} +- +-func (s *DockerSwarmSuite) TestSwarmStopSignal(c *check.C) { +- testRequires(c, DaemonIsLinux, UserNamespaceROMount) +- +- d := s.AddDaemon(c, true, true) +- +- out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", "top", "--stop-signal=SIGHUP", "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- // make sure task has been deployed. +- waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1) +- +- out, err = d.Cmd("service", "inspect", "--format", "{{ .Spec.TaskTemplate.ContainerSpec.StopSignal }}", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Equals, "SIGHUP") +- +- containers := d.ActiveContainers(c) +- out, err = d.Cmd("inspect", "--type", "container", "--format", "{{.Config.StopSignal}}", containers[0]) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Equals, "SIGHUP") +- +- out, err = d.Cmd("service", "update", "--detach", "--stop-signal=SIGUSR1", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- out, err = d.Cmd("service", "inspect", "--format", "{{ .Spec.TaskTemplate.ContainerSpec.StopSignal }}", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Equals, "SIGUSR1") +-} +- +-func (s *DockerSwarmSuite) TestSwarmServiceLsFilterMode(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", "top1", "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") +- +- out, err = d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", "top2", "--mode=global", "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") +- +- // make sure task has been deployed. +- waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 2) +- +- out, err = d.Cmd("service", "ls") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Contains, "top1") +- c.Assert(out, checker.Contains, "top2") +- c.Assert(out, checker.Not(checker.Contains), "localnet") +- +- out, err = d.Cmd("service", "ls", "--filter", "mode=global") +- c.Assert(out, checker.Not(checker.Contains), "top1") +- c.Assert(out, checker.Contains, "top2") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- out, err = d.Cmd("service", "ls", "--filter", "mode=replicated") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- c.Assert(out, checker.Contains, "top1") +- c.Assert(out, checker.Not(checker.Contains), "top2") +-} +- +-func (s *DockerSwarmSuite) TestSwarmInitUnspecifiedDataPathAddr(c *check.C) { +- d := s.AddDaemon(c, false, false) +- +- out, err := d.Cmd("swarm", "init", "--data-path-addr", "0.0.0.0") +- c.Assert(err, checker.NotNil) +- c.Assert(out, checker.Contains, "data path address must be a non-zero IP") +- +- out, err = d.Cmd("swarm", "init", "--data-path-addr", "0.0.0.0:2000") +- c.Assert(err, checker.NotNil) +- c.Assert(out, checker.Contains, "data path address must be a non-zero IP") +-} +- +-func (s *DockerSwarmSuite) TestSwarmJoinLeave(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- out, err := d.Cmd("swarm", "join-token", "-q", "worker") +- c.Assert(err, checker.IsNil) +- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") +- +- token := strings.TrimSpace(out) +- +- // Verify that back to back join/leave does not cause panics +- d1 := s.AddDaemon(c, false, false) +- for i := 0; i < 10; i++ { +- out, err = d1.Cmd("swarm", "join", "--token", token, d.SwarmListenAddr()) +- c.Assert(err, checker.IsNil) +- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") +- +- _, err = d1.Cmd("swarm", "leave") +- c.Assert(err, checker.IsNil) +- } +-} +- +-const defaultRetryCount = 10 +- +-func waitForEvent(c *check.C, d *daemon.Daemon, since string, filter string, event string, retry int) string { +- if retry < 1 { +- c.Fatalf("retry count %d is invalid. It should be no less than 1", retry) +- return "" +- } +- var out string +- for i := 0; i < retry; i++ { +- until := daemonUnixTime(c) +- var err error +- if len(filter) > 0 { +- out, err = d.Cmd("events", "--since", since, "--until", until, filter) +- } else { +- out, err = d.Cmd("events", "--since", since, "--until", until) +- } +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- if strings.Contains(out, event) { +- return strings.TrimSpace(out) +- } +- // no need to sleep after last retry +- if i < retry-1 { +- time.Sleep(200 * time.Millisecond) +- } +- } +- c.Fatalf("docker events output '%s' doesn't contain event '%s'", out, event) +- return "" +-} +- +-func (s *DockerSwarmSuite) TestSwarmClusterEventsSource(c *check.C) { +- d1 := s.AddDaemon(c, true, true) +- d2 := s.AddDaemon(c, true, true) +- d3 := s.AddDaemon(c, true, false) +- +- // create a network +- out, err := d1.Cmd("network", "create", "--attachable", "-d", "overlay", "foo") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- networkID := strings.TrimSpace(out) +- c.Assert(networkID, checker.Not(checker.Equals), "") +- +- // d1, d2 are managers that can get swarm events +- waitForEvent(c, d1, "0", "-f scope=swarm", "network create "+networkID, defaultRetryCount) +- waitForEvent(c, d2, "0", "-f scope=swarm", "network create "+networkID, defaultRetryCount) +- +- // d3 is a worker, not able to get cluster events +- out = waitForEvent(c, d3, "0", "-f scope=swarm", "", 1) +- c.Assert(out, checker.Not(checker.Contains), "network create ") +-} +- +-func (s *DockerSwarmSuite) TestSwarmClusterEventsScope(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- // create a service +- out, err := d.Cmd("service", "create", "--no-resolve-image", "--name", "test", "--detach=false", "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- serviceID := strings.Split(out, "\n")[0] +- +- // scope swarm filters cluster events +- out = waitForEvent(c, d, "0", "-f scope=swarm", "service create "+serviceID, defaultRetryCount) +- c.Assert(out, checker.Not(checker.Contains), "container create ") +- +- // all events are returned if scope is not specified +- waitForEvent(c, d, "0", "", "service create "+serviceID, 1) +- waitForEvent(c, d, "0", "", "container create ", defaultRetryCount) +- +- // scope local only shows non-cluster events +- out = waitForEvent(c, d, "0", "-f scope=local", "container create ", 1) +- c.Assert(out, checker.Not(checker.Contains), "service create ") +-} +- +-func (s *DockerSwarmSuite) TestSwarmClusterEventsType(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- // create a service +- out, err := d.Cmd("service", "create", "--no-resolve-image", "--name", "test", "--detach=false", "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- serviceID := strings.Split(out, "\n")[0] +- +- // create a network +- out, err = d.Cmd("network", "create", "--attachable", "-d", "overlay", "foo") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- networkID := strings.TrimSpace(out) +- c.Assert(networkID, checker.Not(checker.Equals), "") +- +- // filter by service +- out = waitForEvent(c, d, "0", "-f type=service", "service create "+serviceID, defaultRetryCount) +- c.Assert(out, checker.Not(checker.Contains), "network create") +- +- // filter by network +- out = waitForEvent(c, d, "0", "-f type=network", "network create "+networkID, defaultRetryCount) +- c.Assert(out, checker.Not(checker.Contains), "service create") +-} +- +-func (s *DockerSwarmSuite) TestSwarmClusterEventsService(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- // create a service +- out, err := d.Cmd("service", "create", "--no-resolve-image", "--name", "test", "--detach=false", "busybox", "top") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- serviceID := strings.Split(out, "\n")[0] +- +- // validate service create event +- waitForEvent(c, d, "0", "-f scope=swarm", "service create "+serviceID, defaultRetryCount) +- +- t1 := daemonUnixTime(c) +- out, err = d.Cmd("service", "update", "--force", "--detach=false", "test") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- // wait for service update start +- out = waitForEvent(c, d, t1, "-f scope=swarm", "service update "+serviceID, defaultRetryCount) +- c.Assert(out, checker.Contains, "updatestate.new=updating") +- +- // allow service update complete. This is a service with 1 instance +- time.Sleep(400 * time.Millisecond) +- out = waitForEvent(c, d, t1, "-f scope=swarm", "service update "+serviceID, defaultRetryCount) +- c.Assert(out, checker.Contains, "updatestate.new=completed, updatestate.old=updating") +- +- // scale service +- t2 := daemonUnixTime(c) +- out, err = d.Cmd("service", "scale", "test=3") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- out = waitForEvent(c, d, t2, "-f scope=swarm", "service update "+serviceID, defaultRetryCount) +- c.Assert(out, checker.Contains, "replicas.new=3, replicas.old=1") +- +- // remove service +- t3 := daemonUnixTime(c) +- out, err = d.Cmd("service", "rm", "test") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- waitForEvent(c, d, t3, "-f scope=swarm", "service remove "+serviceID, defaultRetryCount) +-} +- +-func (s *DockerSwarmSuite) TestSwarmClusterEventsNode(c *check.C) { +- d1 := s.AddDaemon(c, true, true) +- s.AddDaemon(c, true, true) +- d3 := s.AddDaemon(c, true, true) +- +- d3ID := d3.NodeID() +- waitForEvent(c, d1, "0", "-f scope=swarm", "node create "+d3ID, defaultRetryCount) +- +- t1 := daemonUnixTime(c) +- out, err := d1.Cmd("node", "update", "--availability=pause", d3ID) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- // filter by type +- out = waitForEvent(c, d1, t1, "-f type=node", "node update "+d3ID, defaultRetryCount) +- c.Assert(out, checker.Contains, "availability.new=pause, availability.old=active") +- +- t2 := daemonUnixTime(c) +- out, err = d1.Cmd("node", "demote", d3ID) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- waitForEvent(c, d1, t2, "-f type=node", "node update "+d3ID, defaultRetryCount) +- +- t3 := daemonUnixTime(c) +- out, err = d1.Cmd("node", "rm", "-f", d3ID) +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- // filter by scope +- waitForEvent(c, d1, t3, "-f scope=swarm", "node remove "+d3ID, defaultRetryCount) +-} +- +-func (s *DockerSwarmSuite) TestSwarmClusterEventsNetwork(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- // create a network +- out, err := d.Cmd("network", "create", "--attachable", "-d", "overlay", "foo") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- networkID := strings.TrimSpace(out) +- +- waitForEvent(c, d, "0", "-f scope=swarm", "network create "+networkID, defaultRetryCount) +- +- // remove network +- t1 := daemonUnixTime(c) +- out, err = d.Cmd("network", "rm", "foo") +- c.Assert(err, checker.IsNil, check.Commentf("%s", out)) +- +- // filtered by network +- waitForEvent(c, d, t1, "-f type=network", "network remove "+networkID, defaultRetryCount) +-} +- +-func (s *DockerSwarmSuite) TestSwarmClusterEventsSecret(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- testName := "test_secret" +- id := d.CreateSecret(c, swarm.SecretSpec{ +- Annotations: swarm.Annotations{ +- Name: testName, +- }, +- Data: []byte("TESTINGDATA"), +- }) +- c.Assert(id, checker.Not(checker.Equals), "", check.Commentf("secrets: %s", id)) +- +- waitForEvent(c, d, "0", "-f scope=swarm", "secret create "+id, defaultRetryCount) +- +- t1 := daemonUnixTime(c) +- d.DeleteSecret(c, id) +- // filtered by secret +- waitForEvent(c, d, t1, "-f type=secret", "secret remove "+id, defaultRetryCount) +-} +- +-func (s *DockerSwarmSuite) TestSwarmClusterEventsConfig(c *check.C) { +- d := s.AddDaemon(c, true, true) +- +- testName := "test_config" +- id := d.CreateConfig(c, swarm.ConfigSpec{ +- Annotations: swarm.Annotations{ +- Name: testName, +- }, +- Data: []byte("TESTINGDATA"), +- }) +- c.Assert(id, checker.Not(checker.Equals), "", check.Commentf("configs: %s", id)) +- +- waitForEvent(c, d, "0", "-f scope=swarm", "config create "+id, defaultRetryCount) +- +- t1 := daemonUnixTime(c) +- d.DeleteConfig(c, id) +- // filtered by config +- waitForEvent(c, d, t1, "-f type=config", "config remove "+id, defaultRetryCount) +-} +-- +2.17.1 + diff --git a/patch/0067-pause-fix-build-missing-dep-packages.patch b/patch/0067-pause-fix-build-missing-dep-packages.patch new file mode 100644 index 0000000..f7aa1ab --- /dev/null +++ b/patch/0067-pause-fix-build-missing-dep-packages.patch @@ -0,0 +1,5759 @@ +From 962e669ab1b1d3545faca5b668ca9d1be0d3b786 Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Mon, 21 Jan 2019 21:25:33 +0800 +Subject: [PATCH 067/111] pause: fix build missing dep packages + +reason: update vendor for libcontainer/cgroup dependency. + +checkout from runc-1.0.0: +eff62015 runc: support specify umask +- coreos/go-systemd/util +- opencontainers/runc/libcontainer/cgroups/fs +- opencontainers/runc/libcontainer/cgroups/systemd +- opencontainers/runc/libcontainer/utils + +files modified support FilesLimit: +- opencontainers/runc/libcontainer/cgroups/stats.go +- opencontainers/runc/libcontainer/configs/cgroup_linux.go + +Change-Id: I794f0bf9be87c6068788f866555c346ca2372c02 +Signed-off-by: jingrui +--- + .../github.com/coreos/go-systemd/Checklist | 2 + + .../github.com/coreos/go-systemd/util/util.go | 33 + + .../runc/libcontainer/Checklist | 4 + + .../runc/libcontainer/cgroups/fs/apply_raw.go | 361 ++++++++++ + .../libcontainer/cgroups/fs/apply_raw_test.go | 272 ++++++++ + .../runc/libcontainer/cgroups/fs/blkio.go | 237 +++++++ + .../libcontainer/cgroups/fs/blkio_test.go | 636 ++++++++++++++++++ + .../runc/libcontainer/cgroups/fs/cpu.go | 125 ++++ + .../runc/libcontainer/cgroups/fs/cpu_test.go | 209 ++++++ + .../runc/libcontainer/cgroups/fs/cpuacct.go | 121 ++++ + .../runc/libcontainer/cgroups/fs/cpuset.go | 183 +++++ + .../libcontainer/cgroups/fs/cpuset_test.go | 65 ++ + .../runc/libcontainer/cgroups/fs/devices.go | 80 +++ + .../libcontainer/cgroups/fs/devices_test.go | 98 +++ + .../runc/libcontainer/cgroups/fs/files.go | 72 ++ + .../runc/libcontainer/cgroups/fs/freezer.go | 61 ++ + .../libcontainer/cgroups/fs/freezer_test.go | 47 ++ + .../libcontainer/cgroups/fs/fs_unsupported.go | 3 + + .../runc/libcontainer/cgroups/fs/hugetlb.go | 71 ++ + .../libcontainer/cgroups/fs/hugetlb_test.go | 154 +++++ + .../runc/libcontainer/cgroups/fs/memory.go | 301 +++++++++ + .../libcontainer/cgroups/fs/memory_test.go | 453 +++++++++++++ + .../runc/libcontainer/cgroups/fs/name.go | 40 ++ + .../runc/libcontainer/cgroups/fs/net_cls.go | 43 ++ + .../libcontainer/cgroups/fs/net_cls_test.go | 39 ++ + .../runc/libcontainer/cgroups/fs/net_prio.go | 41 ++ + .../libcontainer/cgroups/fs/net_prio_test.go | 38 ++ + .../libcontainer/cgroups/fs/perf_event.go | 35 + + .../runc/libcontainer/cgroups/fs/pids.go | 73 ++ + .../runc/libcontainer/cgroups/fs/pids_test.go | 111 +++ + .../cgroups/fs/stats_util_test.go | 117 ++++ + .../runc/libcontainer/cgroups/fs/util_test.go | 67 ++ + .../runc/libcontainer/cgroups/fs/utils.go | 78 +++ + .../libcontainer/cgroups/fs/utils_test.go | 97 +++ + .../libcontainer/cgroups/rootless/rootless.go | 128 ++++ + .../runc/libcontainer/cgroups/stats.go | 8 + + .../cgroups/systemd/apply_nosystemd.go | 55 ++ + .../cgroups/systemd/apply_systemd.go | 556 +++++++++++++++ + .../runc/libcontainer/configs/cgroup_linux.go | 3 + + .../runc/libcontainer/utils/cmsg.go | 95 +++ + .../runc/libcontainer/utils/utils.go | 126 ++++ + .../runc/libcontainer/utils/utils_unix.go | 43 ++ + 42 files changed, 5381 insertions(+) + create mode 100644 components/engine/vendor/github.com/coreos/go-systemd/Checklist + create mode 100644 components/engine/vendor/github.com/coreos/go-systemd/util/util.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/Checklist + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/apply_raw.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/apply_raw_test.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio_test.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu_test.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuacct.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset_test.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices_test.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/files.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer_test.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/fs_unsupported.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb_test.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory_test.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/name.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls_test.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio_test.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/perf_event.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids_test.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/stats_util_test.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/util_test.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/utils.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/utils_test.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/rootless/rootless.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_nosystemd.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_systemd.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/utils/cmsg.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go + create mode 100644 components/engine/vendor/github.com/opencontainers/runc/libcontainer/utils/utils_unix.go + +diff --git a/components/engine/vendor/github.com/coreos/go-systemd/Checklist b/components/engine/vendor/github.com/coreos/go-systemd/Checklist +new file mode 100644 +index 0000000000..c231fc1636 +--- /dev/null ++++ b/components/engine/vendor/github.com/coreos/go-systemd/Checklist +@@ -0,0 +1,2 @@ ++Add these packages from go-systemd v4 for moving Pause handling from runc to dockerd ++- github.com/coreos/go-systemd/util +\ No newline at end of file +diff --git a/components/engine/vendor/github.com/coreos/go-systemd/util/util.go b/components/engine/vendor/github.com/coreos/go-systemd/util/util.go +new file mode 100644 +index 0000000000..33832a1ed4 +--- /dev/null ++++ b/components/engine/vendor/github.com/coreos/go-systemd/util/util.go +@@ -0,0 +1,33 @@ ++// Copyright 2015 CoreOS, 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 ++// ++// http://www.apache.org/licenses/LICENSE-2.0 ++// ++// Unless required by applicable law or agreed to in writing, software ++// distributed under the License is distributed on an "AS IS" BASIS, ++// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++// See the License for the specific language governing permissions and ++// limitations under the License. ++ ++// Package util contains utility functions related to systemd that applications ++// can use to check things like whether systemd is running. ++package util ++ ++import ( ++ "os" ++) ++ ++// IsRunningSystemd checks whether the host was booted with systemd as its init ++// system. This functions similar to systemd's `sd_booted(3)`: internally, it ++// checks whether /run/systemd/system/ exists and is a directory. ++// http://www.freedesktop.org/software/systemd/man/sd_booted.html ++func IsRunningSystemd() bool { ++ fi, err := os.Lstat("/run/systemd/system") ++ if err != nil { ++ return false ++ } ++ return fi.IsDir() ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/Checklist b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/Checklist +new file mode 100644 +index 0000000000..d0900fc618 +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/Checklist +@@ -0,0 +1,4 @@ ++Add these packages for moving Pause handling from runc to dockerd ++- github.com/opencontainers/runc/libcontainer/cgroups/fs ++- github.com/opencontainers/runc/libcontainer/cgroups/systemd ++- github.com/opencontainers/runc/libcontainer/utils +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/apply_raw.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/apply_raw.go +new file mode 100644 +index 0000000000..1bf59a47be +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/apply_raw.go +@@ -0,0 +1,361 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "errors" ++ "fmt" ++ "io" ++ "io/ioutil" ++ "os" ++ "path/filepath" ++ "sync" ++ ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++ "github.com/opencontainers/runc/libcontainer/configs" ++ libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils" ++) ++ ++var ( ++ subsystems = subsystemSet{ ++ &CpusetGroup{}, ++ &DevicesGroup{}, ++ &MemoryGroup{}, ++ &CpuGroup{}, ++ &CpuacctGroup{}, ++ &PidsGroup{}, ++ &FilesGroup{}, ++ &BlkioGroup{}, ++ &HugetlbGroup{}, ++ &NetClsGroup{}, ++ &NetPrioGroup{}, ++ &PerfEventGroup{}, ++ &FreezerGroup{}, ++ &NameGroup{GroupName: "name=systemd", Join: true}, ++ } ++ HugePageSizes, _ = cgroups.GetHugePageSize() ++) ++ ++var errSubsystemDoesNotExist = errors.New("cgroup: subsystem does not exist") ++ ++type subsystemSet []subsystem ++ ++func (s subsystemSet) Get(name string) (subsystem, error) { ++ for _, ss := range s { ++ if ss.Name() == name { ++ return ss, nil ++ } ++ } ++ return nil, errSubsystemDoesNotExist ++} ++ ++type subsystem interface { ++ // Name returns the name of the subsystem. ++ Name() string ++ // Returns the stats, as 'stats', corresponding to the cgroup under 'path'. ++ GetStats(path string, stats *cgroups.Stats) error ++ // Removes the cgroup represented by 'cgroupData'. ++ Remove(*cgroupData) error ++ // Creates and joins the cgroup represented by 'cgroupData'. ++ Apply(*cgroupData) error ++ // Set the cgroup represented by cgroup. ++ Set(path string, cgroup *configs.Cgroup) error ++} ++ ++type Manager struct { ++ mu sync.Mutex ++ Cgroups *configs.Cgroup ++ Paths map[string]string ++} ++ ++// The absolute path to the root of the cgroup hierarchies. ++var cgroupRootLock sync.Mutex ++var cgroupRoot string ++ ++// Gets the cgroupRoot. ++func getCgroupRoot() (string, error) { ++ cgroupRootLock.Lock() ++ defer cgroupRootLock.Unlock() ++ ++ if cgroupRoot != "" { ++ return cgroupRoot, nil ++ } ++ ++ root, err := cgroups.FindCgroupMountpointDir() ++ if err != nil { ++ return "", err ++ } ++ ++ if _, err := os.Stat(root); err != nil { ++ return "", err ++ } ++ ++ cgroupRoot = root ++ return cgroupRoot, nil ++} ++ ++type cgroupData struct { ++ root string ++ innerPath string ++ config *configs.Cgroup ++ pid int ++} ++ ++func (m *Manager) Apply(pid int) (err error) { ++ if m.Cgroups == nil { ++ return nil ++ } ++ m.mu.Lock() ++ defer m.mu.Unlock() ++ ++ var c = m.Cgroups ++ ++ d, err := getCgroupData(m.Cgroups, pid) ++ if err != nil { ++ return err ++ } ++ ++ m.Paths = make(map[string]string) ++ if c.Paths != nil { ++ for name, path := range c.Paths { ++ _, err := d.path(name) ++ if err != nil { ++ if cgroups.IsNotFound(err) { ++ continue ++ } ++ return err ++ } ++ m.Paths[name] = path ++ } ++ return cgroups.EnterPid(m.Paths, pid) ++ } ++ ++ for _, sys := range subsystems { ++ // TODO: Apply should, ideally, be reentrant or be broken up into a separate ++ // create and join phase so that the cgroup hierarchy for a container can be ++ // created then join consists of writing the process pids to cgroup.procs ++ p, err := d.path(sys.Name()) ++ if err != nil { ++ // The non-presence of the devices subsystem is ++ // considered fatal for security reasons. ++ if cgroups.IsNotFound(err) && sys.Name() != "devices" { ++ continue ++ } ++ return err ++ } ++ m.Paths[sys.Name()] = p ++ ++ if err := sys.Apply(d); err != nil { ++ return err ++ } ++ } ++ return nil ++} ++ ++func (m *Manager) Destroy() error { ++ if m.Cgroups == nil || m.Cgroups.Paths != nil { ++ return nil ++ } ++ m.mu.Lock() ++ defer m.mu.Unlock() ++ if err := cgroups.RemovePaths(m.Paths); err != nil { ++ return err ++ } ++ m.Paths = make(map[string]string) ++ return nil ++} ++ ++func (m *Manager) GetPaths() map[string]string { ++ m.mu.Lock() ++ paths := m.Paths ++ m.mu.Unlock() ++ return paths ++} ++ ++func (m *Manager) GetStats() (*cgroups.Stats, error) { ++ m.mu.Lock() ++ defer m.mu.Unlock() ++ stats := cgroups.NewStats() ++ for name, path := range m.Paths { ++ sys, err := subsystems.Get(name) ++ if err == errSubsystemDoesNotExist || !cgroups.PathExists(path) { ++ continue ++ } ++ if err := sys.GetStats(path, stats); err != nil { ++ return nil, err ++ } ++ } ++ return stats, nil ++} ++ ++func (m *Manager) Set(container *configs.Config) error { ++ // If Paths are set, then we are just joining cgroups paths ++ // and there is no need to set any values. ++ if m.Cgroups.Paths != nil { ++ return nil ++ } ++ ++ paths := m.GetPaths() ++ for _, sys := range subsystems { ++ path := paths[sys.Name()] ++ if err := sys.Set(path, container.Cgroups); err != nil { ++ return err ++ } ++ } ++ ++ if m.Paths["cpu"] != "" { ++ if err := CheckCpushares(m.Paths["cpu"], container.Cgroups.Resources.CpuShares); err != nil { ++ return err ++ } ++ } ++ return nil ++} ++ ++// Freeze toggles the container's freezer cgroup depending on the state ++// provided ++func (m *Manager) Freeze(state configs.FreezerState) error { ++ paths := m.GetPaths() ++ dir := paths["freezer"] ++ prevState := m.Cgroups.Resources.Freezer ++ m.Cgroups.Resources.Freezer = state ++ freezer, err := subsystems.Get("freezer") ++ if err != nil { ++ return err ++ } ++ err = freezer.Set(dir, m.Cgroups) ++ if err != nil { ++ m.Cgroups.Resources.Freezer = prevState ++ return err ++ } ++ return nil ++} ++ ++func (m *Manager) GetPids() ([]int, error) { ++ paths := m.GetPaths() ++ return cgroups.GetPids(paths["devices"]) ++} ++ ++func (m *Manager) GetAllPids() ([]int, error) { ++ paths := m.GetPaths() ++ return cgroups.GetAllPids(paths["devices"]) ++} ++ ++func getCgroupData(c *configs.Cgroup, pid int) (*cgroupData, error) { ++ root, err := getCgroupRoot() ++ if err != nil { ++ return nil, err ++ } ++ ++ if (c.Name != "" || c.Parent != "") && c.Path != "" { ++ return nil, fmt.Errorf("cgroup: either Path or Name and Parent should be used") ++ } ++ ++ // XXX: Do not remove this code. Path safety is important! -- cyphar ++ cgPath := libcontainerUtils.CleanPath(c.Path) ++ cgParent := libcontainerUtils.CleanPath(c.Parent) ++ cgName := libcontainerUtils.CleanPath(c.Name) ++ ++ innerPath := cgPath ++ if innerPath == "" { ++ innerPath = filepath.Join(cgParent, cgName) ++ } ++ ++ return &cgroupData{ ++ root: root, ++ innerPath: innerPath, ++ config: c, ++ pid: pid, ++ }, nil ++} ++ ++func (raw *cgroupData) path(subsystem string) (string, error) { ++ mnt, err := cgroups.FindCgroupMountpoint(subsystem) ++ // If we didn't mount the subsystem, there is no point we make the path. ++ if err != nil { ++ return "", err ++ } ++ ++ // If the cgroup name/path is absolute do not look relative to the cgroup of the init process. ++ if filepath.IsAbs(raw.innerPath) { ++ // Sometimes subsystems can be mounted together as 'cpu,cpuacct'. ++ return filepath.Join(raw.root, filepath.Base(mnt), raw.innerPath), nil ++ } ++ ++ // Use GetOwnCgroupPath instead of GetInitCgroupPath, because the creating ++ // process could in container and shared pid namespace with host, and ++ // /proc/1/cgroup could point to whole other world of cgroups. ++ parentPath, err := cgroups.GetOwnCgroupPath(subsystem) ++ if err != nil { ++ return "", err ++ } ++ ++ return filepath.Join(parentPath, raw.innerPath), nil ++} ++ ++func (raw *cgroupData) join(subsystem string) (string, error) { ++ path, err := raw.path(subsystem) ++ if err != nil { ++ return "", err ++ } ++ if err := os.MkdirAll(path, 0755); err != nil { ++ return "", err ++ } ++ if err := cgroups.WriteCgroupProc(path, raw.pid); err != nil { ++ return "", err ++ } ++ return path, nil ++} ++ ++func writeFile(dir, file, data string) error { ++ // Normally dir should not be empty, one case is that cgroup subsystem ++ // is not mounted, we will get empty dir, and we want it fail here. ++ if dir == "" { ++ return fmt.Errorf("no such directory for %s", file) ++ } ++ if err := ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700); err != nil { ++ return fmt.Errorf("failed to write %v to %v: %v", data, file, err) ++ } ++ return nil ++} ++ ++func readFile(dir, file string) (string, error) { ++ data, err := ioutil.ReadFile(filepath.Join(dir, file)) ++ return string(data), err ++} ++ ++func removePath(p string, err error) error { ++ if err != nil { ++ return err ++ } ++ if p != "" { ++ return os.RemoveAll(p) ++ } ++ return nil ++} ++ ++func CheckCpushares(path string, c uint64) error { ++ var cpuShares uint64 ++ ++ if c == 0 { ++ return nil ++ } ++ ++ fd, err := os.Open(filepath.Join(path, "cpu.shares")) ++ if err != nil { ++ return err ++ } ++ defer fd.Close() ++ ++ _, err = fmt.Fscanf(fd, "%d", &cpuShares) ++ if err != nil && err != io.EOF { ++ return err ++ } ++ ++ if c > cpuShares { ++ return fmt.Errorf("The maximum allowed cpu-shares is %d", cpuShares) ++ } else if c < cpuShares { ++ return fmt.Errorf("The minimum allowed cpu-shares is %d", cpuShares) ++ } ++ ++ return nil ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/apply_raw_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/apply_raw_test.go +new file mode 100644 +index 0000000000..ba4e9e543c +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/apply_raw_test.go +@@ -0,0 +1,272 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "path/filepath" ++ "strings" ++ "testing" ++ ++ "github.com/opencontainers/runc/libcontainer/configs" ++) ++ ++func TestInvalidCgroupPath(t *testing.T) { ++ root, err := getCgroupRoot() ++ if err != nil { ++ t.Errorf("couldn't get cgroup root: %v", err) ++ } ++ ++ config := &configs.Cgroup{ ++ Path: "../../../../../../../../../../some/path", ++ } ++ ++ data, err := getCgroupData(config, 0) ++ if err != nil { ++ t.Errorf("couldn't get cgroup data: %v", err) ++ } ++ ++ // Make sure the final innerPath doesn't go outside the cgroup mountpoint. ++ if strings.HasPrefix(data.innerPath, "..") { ++ t.Errorf("SECURITY: cgroup innerPath is outside cgroup mountpoint!") ++ } ++ ++ // Double-check, using an actual cgroup. ++ deviceRoot := filepath.Join(root, "devices") ++ devicePath, err := data.path("devices") ++ if err != nil { ++ t.Errorf("couldn't get cgroup path: %v", err) ++ } ++ if !strings.HasPrefix(devicePath, deviceRoot) { ++ t.Errorf("SECURITY: cgroup path() is outside cgroup mountpoint!") ++ } ++} ++ ++func TestInvalidAbsoluteCgroupPath(t *testing.T) { ++ root, err := getCgroupRoot() ++ if err != nil { ++ t.Errorf("couldn't get cgroup root: %v", err) ++ } ++ ++ config := &configs.Cgroup{ ++ Path: "/../../../../../../../../../../some/path", ++ } ++ ++ data, err := getCgroupData(config, 0) ++ if err != nil { ++ t.Errorf("couldn't get cgroup data: %v", err) ++ } ++ ++ // Make sure the final innerPath doesn't go outside the cgroup mountpoint. ++ if strings.HasPrefix(data.innerPath, "..") { ++ t.Errorf("SECURITY: cgroup innerPath is outside cgroup mountpoint!") ++ } ++ ++ // Double-check, using an actual cgroup. ++ deviceRoot := filepath.Join(root, "devices") ++ devicePath, err := data.path("devices") ++ if err != nil { ++ t.Errorf("couldn't get cgroup path: %v", err) ++ } ++ if !strings.HasPrefix(devicePath, deviceRoot) { ++ t.Errorf("SECURITY: cgroup path() is outside cgroup mountpoint!") ++ } ++} ++ ++// XXX: Remove me after we get rid of configs.Cgroup.Name and configs.Cgroup.Parent. ++func TestInvalidCgroupParent(t *testing.T) { ++ root, err := getCgroupRoot() ++ if err != nil { ++ t.Errorf("couldn't get cgroup root: %v", err) ++ } ++ ++ config := &configs.Cgroup{ ++ Parent: "../../../../../../../../../../some/path", ++ Name: "name", ++ } ++ ++ data, err := getCgroupData(config, 0) ++ if err != nil { ++ t.Errorf("couldn't get cgroup data: %v", err) ++ } ++ ++ // Make sure the final innerPath doesn't go outside the cgroup mountpoint. ++ if strings.HasPrefix(data.innerPath, "..") { ++ t.Errorf("SECURITY: cgroup innerPath is outside cgroup mountpoint!") ++ } ++ ++ // Double-check, using an actual cgroup. ++ deviceRoot := filepath.Join(root, "devices") ++ devicePath, err := data.path("devices") ++ if err != nil { ++ t.Errorf("couldn't get cgroup path: %v", err) ++ } ++ if !strings.HasPrefix(devicePath, deviceRoot) { ++ t.Errorf("SECURITY: cgroup path() is outside cgroup mountpoint!") ++ } ++} ++ ++// XXX: Remove me after we get rid of configs.Cgroup.Name and configs.Cgroup.Parent. ++func TestInvalidAbsoluteCgroupParent(t *testing.T) { ++ root, err := getCgroupRoot() ++ if err != nil { ++ t.Errorf("couldn't get cgroup root: %v", err) ++ } ++ ++ config := &configs.Cgroup{ ++ Parent: "/../../../../../../../../../../some/path", ++ Name: "name", ++ } ++ ++ data, err := getCgroupData(config, 0) ++ if err != nil { ++ t.Errorf("couldn't get cgroup data: %v", err) ++ } ++ ++ // Make sure the final innerPath doesn't go outside the cgroup mountpoint. ++ if strings.HasPrefix(data.innerPath, "..") { ++ t.Errorf("SECURITY: cgroup innerPath is outside cgroup mountpoint!") ++ } ++ ++ // Double-check, using an actual cgroup. ++ deviceRoot := filepath.Join(root, "devices") ++ devicePath, err := data.path("devices") ++ if err != nil { ++ t.Errorf("couldn't get cgroup path: %v", err) ++ } ++ if !strings.HasPrefix(devicePath, deviceRoot) { ++ t.Errorf("SECURITY: cgroup path() is outside cgroup mountpoint!") ++ } ++} ++ ++// XXX: Remove me after we get rid of configs.Cgroup.Name and configs.Cgroup.Parent. ++func TestInvalidCgroupName(t *testing.T) { ++ root, err := getCgroupRoot() ++ if err != nil { ++ t.Errorf("couldn't get cgroup root: %v", err) ++ } ++ ++ config := &configs.Cgroup{ ++ Parent: "parent", ++ Name: "../../../../../../../../../../some/path", ++ } ++ ++ data, err := getCgroupData(config, 0) ++ if err != nil { ++ t.Errorf("couldn't get cgroup data: %v", err) ++ } ++ ++ // Make sure the final innerPath doesn't go outside the cgroup mountpoint. ++ if strings.HasPrefix(data.innerPath, "..") { ++ t.Errorf("SECURITY: cgroup innerPath is outside cgroup mountpoint!") ++ } ++ ++ // Double-check, using an actual cgroup. ++ deviceRoot := filepath.Join(root, "devices") ++ devicePath, err := data.path("devices") ++ if err != nil { ++ t.Errorf("couldn't get cgroup path: %v", err) ++ } ++ if !strings.HasPrefix(devicePath, deviceRoot) { ++ t.Errorf("SECURITY: cgroup path() is outside cgroup mountpoint!") ++ } ++ ++} ++ ++// XXX: Remove me after we get rid of configs.Cgroup.Name and configs.Cgroup.Parent. ++func TestInvalidAbsoluteCgroupName(t *testing.T) { ++ root, err := getCgroupRoot() ++ if err != nil { ++ t.Errorf("couldn't get cgroup root: %v", err) ++ } ++ ++ config := &configs.Cgroup{ ++ Parent: "parent", ++ Name: "/../../../../../../../../../../some/path", ++ } ++ ++ data, err := getCgroupData(config, 0) ++ if err != nil { ++ t.Errorf("couldn't get cgroup data: %v", err) ++ } ++ ++ // Make sure the final innerPath doesn't go outside the cgroup mountpoint. ++ if strings.HasPrefix(data.innerPath, "..") { ++ t.Errorf("SECURITY: cgroup innerPath is outside cgroup mountpoint!") ++ } ++ ++ // Double-check, using an actual cgroup. ++ deviceRoot := filepath.Join(root, "devices") ++ devicePath, err := data.path("devices") ++ if err != nil { ++ t.Errorf("couldn't get cgroup path: %v", err) ++ } ++ if !strings.HasPrefix(devicePath, deviceRoot) { ++ t.Errorf("SECURITY: cgroup path() is outside cgroup mountpoint!") ++ } ++} ++ ++// XXX: Remove me after we get rid of configs.Cgroup.Name and configs.Cgroup.Parent. ++func TestInvalidCgroupNameAndParent(t *testing.T) { ++ root, err := getCgroupRoot() ++ if err != nil { ++ t.Errorf("couldn't get cgroup root: %v", err) ++ } ++ ++ config := &configs.Cgroup{ ++ Parent: "../../../../../../../../../../some/path", ++ Name: "../../../../../../../../../../some/path", ++ } ++ ++ data, err := getCgroupData(config, 0) ++ if err != nil { ++ t.Errorf("couldn't get cgroup data: %v", err) ++ } ++ ++ // Make sure the final innerPath doesn't go outside the cgroup mountpoint. ++ if strings.HasPrefix(data.innerPath, "..") { ++ t.Errorf("SECURITY: cgroup innerPath is outside cgroup mountpoint!") ++ } ++ ++ // Double-check, using an actual cgroup. ++ deviceRoot := filepath.Join(root, "devices") ++ devicePath, err := data.path("devices") ++ if err != nil { ++ t.Errorf("couldn't get cgroup path: %v", err) ++ } ++ if !strings.HasPrefix(devicePath, deviceRoot) { ++ t.Errorf("SECURITY: cgroup path() is outside cgroup mountpoint!") ++ } ++} ++ ++// XXX: Remove me after we get rid of configs.Cgroup.Name and configs.Cgroup.Parent. ++func TestInvalidAbsoluteCgroupNameAndParent(t *testing.T) { ++ root, err := getCgroupRoot() ++ if err != nil { ++ t.Errorf("couldn't get cgroup root: %v", err) ++ } ++ ++ config := &configs.Cgroup{ ++ Parent: "/../../../../../../../../../../some/path", ++ Name: "/../../../../../../../../../../some/path", ++ } ++ ++ data, err := getCgroupData(config, 0) ++ if err != nil { ++ t.Errorf("couldn't get cgroup data: %v", err) ++ } ++ ++ // Make sure the final innerPath doesn't go outside the cgroup mountpoint. ++ if strings.HasPrefix(data.innerPath, "..") { ++ t.Errorf("SECURITY: cgroup innerPath is outside cgroup mountpoint!") ++ } ++ ++ // Double-check, using an actual cgroup. ++ deviceRoot := filepath.Join(root, "devices") ++ devicePath, err := data.path("devices") ++ if err != nil { ++ t.Errorf("couldn't get cgroup path: %v", err) ++ } ++ if !strings.HasPrefix(devicePath, deviceRoot) { ++ t.Errorf("SECURITY: cgroup path() is outside cgroup mountpoint!") ++ } ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio.go +new file mode 100644 +index 0000000000..a142cb991d +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio.go +@@ -0,0 +1,237 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "bufio" ++ "fmt" ++ "os" ++ "path/filepath" ++ "strconv" ++ "strings" ++ ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++ "github.com/opencontainers/runc/libcontainer/configs" ++) ++ ++type BlkioGroup struct { ++} ++ ++func (s *BlkioGroup) Name() string { ++ return "blkio" ++} ++ ++func (s *BlkioGroup) Apply(d *cgroupData) error { ++ _, err := d.join("blkio") ++ if err != nil && !cgroups.IsNotFound(err) { ++ return err ++ } ++ return nil ++} ++ ++func (s *BlkioGroup) Set(path string, cgroup *configs.Cgroup) error { ++ if cgroup.Resources.BlkioWeight != 0 { ++ if err := writeFile(path, "blkio.weight", strconv.FormatUint(uint64(cgroup.Resources.BlkioWeight), 10)); err != nil { ++ return err ++ } ++ } ++ ++ if cgroup.Resources.BlkioLeafWeight != 0 { ++ if err := writeFile(path, "blkio.leaf_weight", strconv.FormatUint(uint64(cgroup.Resources.BlkioLeafWeight), 10)); err != nil { ++ return err ++ } ++ } ++ for _, wd := range cgroup.Resources.BlkioWeightDevice { ++ if err := writeFile(path, "blkio.weight_device", wd.WeightString()); err != nil { ++ return err ++ } ++ if err := writeFile(path, "blkio.leaf_weight_device", wd.LeafWeightString()); err != nil { ++ return err ++ } ++ } ++ for _, td := range cgroup.Resources.BlkioThrottleReadBpsDevice { ++ if err := writeFile(path, "blkio.throttle.read_bps_device", td.String()); err != nil { ++ return err ++ } ++ } ++ for _, td := range cgroup.Resources.BlkioThrottleWriteBpsDevice { ++ if err := writeFile(path, "blkio.throttle.write_bps_device", td.String()); err != nil { ++ return err ++ } ++ } ++ for _, td := range cgroup.Resources.BlkioThrottleReadIOPSDevice { ++ if err := writeFile(path, "blkio.throttle.read_iops_device", td.String()); err != nil { ++ return err ++ } ++ } ++ for _, td := range cgroup.Resources.BlkioThrottleWriteIOPSDevice { ++ if err := writeFile(path, "blkio.throttle.write_iops_device", td.String()); err != nil { ++ return err ++ } ++ } ++ ++ return nil ++} ++ ++func (s *BlkioGroup) Remove(d *cgroupData) error { ++ return removePath(d.path("blkio")) ++} ++ ++/* ++examples: ++ ++ blkio.sectors ++ 8:0 6792 ++ ++ blkio.io_service_bytes ++ 8:0 Read 1282048 ++ 8:0 Write 2195456 ++ 8:0 Sync 2195456 ++ 8:0 Async 1282048 ++ 8:0 Total 3477504 ++ Total 3477504 ++ ++ blkio.io_serviced ++ 8:0 Read 124 ++ 8:0 Write 104 ++ 8:0 Sync 104 ++ 8:0 Async 124 ++ 8:0 Total 228 ++ Total 228 ++ ++ blkio.io_queued ++ 8:0 Read 0 ++ 8:0 Write 0 ++ 8:0 Sync 0 ++ 8:0 Async 0 ++ 8:0 Total 0 ++ Total 0 ++*/ ++ ++func splitBlkioStatLine(r rune) bool { ++ return r == ' ' || r == ':' ++} ++ ++func getBlkioStat(path string) ([]cgroups.BlkioStatEntry, error) { ++ var blkioStats []cgroups.BlkioStatEntry ++ f, err := os.Open(path) ++ if err != nil { ++ if os.IsNotExist(err) { ++ return blkioStats, nil ++ } ++ return nil, err ++ } ++ defer f.Close() ++ ++ sc := bufio.NewScanner(f) ++ for sc.Scan() { ++ // format: dev type amount ++ fields := strings.FieldsFunc(sc.Text(), splitBlkioStatLine) ++ if len(fields) < 3 { ++ if len(fields) == 2 && fields[0] == "Total" { ++ // skip total line ++ continue ++ } else { ++ return nil, fmt.Errorf("Invalid line found while parsing %s: %s", path, sc.Text()) ++ } ++ } ++ ++ v, err := strconv.ParseUint(fields[0], 10, 64) ++ if err != nil { ++ return nil, err ++ } ++ major := v ++ ++ v, err = strconv.ParseUint(fields[1], 10, 64) ++ if err != nil { ++ return nil, err ++ } ++ minor := v ++ ++ op := "" ++ valueField := 2 ++ if len(fields) == 4 { ++ op = fields[2] ++ valueField = 3 ++ } ++ v, err = strconv.ParseUint(fields[valueField], 10, 64) ++ if err != nil { ++ return nil, err ++ } ++ blkioStats = append(blkioStats, cgroups.BlkioStatEntry{Major: major, Minor: minor, Op: op, Value: v}) ++ } ++ ++ return blkioStats, nil ++} ++ ++func (s *BlkioGroup) GetStats(path string, stats *cgroups.Stats) error { ++ // Try to read CFQ stats available on all CFQ enabled kernels first ++ if blkioStats, err := getBlkioStat(filepath.Join(path, "blkio.io_serviced_recursive")); err == nil && blkioStats != nil { ++ return getCFQStats(path, stats) ++ } ++ return getStats(path, stats) // Use generic stats as fallback ++} ++ ++func getCFQStats(path string, stats *cgroups.Stats) error { ++ var blkioStats []cgroups.BlkioStatEntry ++ var err error ++ ++ if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.sectors_recursive")); err != nil { ++ return err ++ } ++ stats.BlkioStats.SectorsRecursive = blkioStats ++ ++ if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_service_bytes_recursive")); err != nil { ++ return err ++ } ++ stats.BlkioStats.IoServiceBytesRecursive = blkioStats ++ ++ if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_serviced_recursive")); err != nil { ++ return err ++ } ++ stats.BlkioStats.IoServicedRecursive = blkioStats ++ ++ if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_queued_recursive")); err != nil { ++ return err ++ } ++ stats.BlkioStats.IoQueuedRecursive = blkioStats ++ ++ if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_service_time_recursive")); err != nil { ++ return err ++ } ++ stats.BlkioStats.IoServiceTimeRecursive = blkioStats ++ ++ if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_wait_time_recursive")); err != nil { ++ return err ++ } ++ stats.BlkioStats.IoWaitTimeRecursive = blkioStats ++ ++ if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_merged_recursive")); err != nil { ++ return err ++ } ++ stats.BlkioStats.IoMergedRecursive = blkioStats ++ ++ if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.time_recursive")); err != nil { ++ return err ++ } ++ stats.BlkioStats.IoTimeRecursive = blkioStats ++ ++ return nil ++} ++ ++func getStats(path string, stats *cgroups.Stats) error { ++ var blkioStats []cgroups.BlkioStatEntry ++ var err error ++ ++ if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.throttle.io_service_bytes")); err != nil { ++ return err ++ } ++ stats.BlkioStats.IoServiceBytesRecursive = blkioStats ++ ++ if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.throttle.io_serviced")); err != nil { ++ return err ++ } ++ stats.BlkioStats.IoServicedRecursive = blkioStats ++ ++ return nil ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio_test.go +new file mode 100644 +index 0000000000..6957392048 +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio_test.go +@@ -0,0 +1,636 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "strconv" ++ "testing" ++ ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++ "github.com/opencontainers/runc/libcontainer/configs" ++) ++ ++const ( ++ sectorsRecursiveContents = `8:0 1024` ++ serviceBytesRecursiveContents = `8:0 Read 100 ++8:0 Write 200 ++8:0 Sync 300 ++8:0 Async 500 ++8:0 Total 500 ++Total 500` ++ servicedRecursiveContents = `8:0 Read 10 ++8:0 Write 40 ++8:0 Sync 20 ++8:0 Async 30 ++8:0 Total 50 ++Total 50` ++ queuedRecursiveContents = `8:0 Read 1 ++8:0 Write 4 ++8:0 Sync 2 ++8:0 Async 3 ++8:0 Total 5 ++Total 5` ++ serviceTimeRecursiveContents = `8:0 Read 173959 ++8:0 Write 0 ++8:0 Sync 0 ++8:0 Async 173959 ++8:0 Total 17395 ++Total 17395` ++ waitTimeRecursiveContents = `8:0 Read 15571 ++8:0 Write 0 ++8:0 Sync 0 ++8:0 Async 15571 ++8:0 Total 15571` ++ mergedRecursiveContents = `8:0 Read 5 ++8:0 Write 10 ++8:0 Sync 0 ++8:0 Async 0 ++8:0 Total 15 ++Total 15` ++ timeRecursiveContents = `8:0 8` ++ throttleServiceBytes = `8:0 Read 11030528 ++8:0 Write 23 ++8:0 Sync 42 ++8:0 Async 11030528 ++8:0 Total 11030528 ++252:0 Read 11030528 ++252:0 Write 23 ++252:0 Sync 42 ++252:0 Async 11030528 ++252:0 Total 11030528 ++Total 22061056` ++ throttleServiced = `8:0 Read 164 ++8:0 Write 23 ++8:0 Sync 42 ++8:0 Async 164 ++8:0 Total 164 ++252:0 Read 164 ++252:0 Write 23 ++252:0 Sync 42 ++252:0 Async 164 ++252:0 Total 164 ++Total 328` ++) ++ ++func appendBlkioStatEntry(blkioStatEntries *[]cgroups.BlkioStatEntry, major, minor, value uint64, op string) { ++ *blkioStatEntries = append(*blkioStatEntries, cgroups.BlkioStatEntry{Major: major, Minor: minor, Value: value, Op: op}) ++} ++ ++func TestBlkioSetWeight(t *testing.T) { ++ helper := NewCgroupTestUtil("blkio", t) ++ defer helper.cleanup() ++ ++ const ( ++ weightBefore = 100 ++ weightAfter = 200 ++ ) ++ ++ helper.writeFileContents(map[string]string{ ++ "blkio.weight": strconv.Itoa(weightBefore), ++ }) ++ ++ helper.CgroupData.config.Resources.BlkioWeight = weightAfter ++ blkio := &BlkioGroup{} ++ if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ value, err := getCgroupParamUint(helper.CgroupPath, "blkio.weight") ++ if err != nil { ++ t.Fatalf("Failed to parse blkio.weight - %s", err) ++ } ++ ++ if value != weightAfter { ++ t.Fatal("Got the wrong value, set blkio.weight failed.") ++ } ++} ++ ++func TestBlkioSetWeightDevice(t *testing.T) { ++ helper := NewCgroupTestUtil("blkio", t) ++ defer helper.cleanup() ++ ++ const ( ++ weightDeviceBefore = "8:0 400" ++ ) ++ ++ wd := configs.NewWeightDevice(8, 0, 500, 0) ++ weightDeviceAfter := wd.WeightString() ++ ++ helper.writeFileContents(map[string]string{ ++ "blkio.weight_device": weightDeviceBefore, ++ }) ++ ++ helper.CgroupData.config.Resources.BlkioWeightDevice = []*configs.WeightDevice{wd} ++ blkio := &BlkioGroup{} ++ if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ value, err := getCgroupParamString(helper.CgroupPath, "blkio.weight_device") ++ if err != nil { ++ t.Fatalf("Failed to parse blkio.weight_device - %s", err) ++ } ++ ++ if value != weightDeviceAfter { ++ t.Fatal("Got the wrong value, set blkio.weight_device failed.") ++ } ++} ++ ++// regression #274 ++func TestBlkioSetMultipleWeightDevice(t *testing.T) { ++ helper := NewCgroupTestUtil("blkio", t) ++ defer helper.cleanup() ++ ++ const ( ++ weightDeviceBefore = "8:0 400" ++ ) ++ ++ wd1 := configs.NewWeightDevice(8, 0, 500, 0) ++ wd2 := configs.NewWeightDevice(8, 16, 500, 0) ++ // we cannot actually set and check both because normal ioutil.WriteFile ++ // when writing to cgroup file will overwrite the whole file content instead ++ // of updating it as the kernel is doing. Just check the second device ++ // is present will suffice for the test to ensure multiple writes are done. ++ weightDeviceAfter := wd2.WeightString() ++ ++ helper.writeFileContents(map[string]string{ ++ "blkio.weight_device": weightDeviceBefore, ++ }) ++ ++ helper.CgroupData.config.Resources.BlkioWeightDevice = []*configs.WeightDevice{wd1, wd2} ++ blkio := &BlkioGroup{} ++ if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ value, err := getCgroupParamString(helper.CgroupPath, "blkio.weight_device") ++ if err != nil { ++ t.Fatalf("Failed to parse blkio.weight_device - %s", err) ++ } ++ ++ if value != weightDeviceAfter { ++ t.Fatal("Got the wrong value, set blkio.weight_device failed.") ++ } ++} ++ ++func TestBlkioStats(t *testing.T) { ++ helper := NewCgroupTestUtil("blkio", t) ++ defer helper.cleanup() ++ helper.writeFileContents(map[string]string{ ++ "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, ++ "blkio.io_serviced_recursive": servicedRecursiveContents, ++ "blkio.io_queued_recursive": queuedRecursiveContents, ++ "blkio.io_service_time_recursive": serviceTimeRecursiveContents, ++ "blkio.io_wait_time_recursive": waitTimeRecursiveContents, ++ "blkio.io_merged_recursive": mergedRecursiveContents, ++ "blkio.time_recursive": timeRecursiveContents, ++ "blkio.sectors_recursive": sectorsRecursiveContents, ++ }) ++ ++ blkio := &BlkioGroup{} ++ actualStats := *cgroups.NewStats() ++ err := blkio.GetStats(helper.CgroupPath, &actualStats) ++ if err != nil { ++ t.Fatal(err) ++ } ++ ++ // Verify expected stats. ++ expectedStats := cgroups.BlkioStats{} ++ appendBlkioStatEntry(&expectedStats.SectorsRecursive, 8, 0, 1024, "") ++ ++ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 100, "Read") ++ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 200, "Write") ++ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 300, "Sync") ++ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 500, "Async") ++ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 500, "Total") ++ ++ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 10, "Read") ++ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 40, "Write") ++ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 20, "Sync") ++ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 30, "Async") ++ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 50, "Total") ++ ++ appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 1, "Read") ++ appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 4, "Write") ++ appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 2, "Sync") ++ appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 3, "Async") ++ appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 5, "Total") ++ ++ appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 173959, "Read") ++ appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 0, "Write") ++ appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 0, "Sync") ++ appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 173959, "Async") ++ appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 17395, "Total") ++ ++ appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 15571, "Read") ++ appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 0, "Write") ++ appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 0, "Sync") ++ appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 15571, "Async") ++ appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 15571, "Total") ++ ++ appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 5, "Read") ++ appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 10, "Write") ++ appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 0, "Sync") ++ appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 0, "Async") ++ appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 15, "Total") ++ ++ appendBlkioStatEntry(&expectedStats.IoTimeRecursive, 8, 0, 8, "") ++ ++ expectBlkioStatsEquals(t, expectedStats, actualStats.BlkioStats) ++} ++ ++func TestBlkioStatsNoSectorsFile(t *testing.T) { ++ helper := NewCgroupTestUtil("blkio", t) ++ defer helper.cleanup() ++ helper.writeFileContents(map[string]string{ ++ "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, ++ "blkio.io_serviced_recursive": servicedRecursiveContents, ++ "blkio.io_queued_recursive": queuedRecursiveContents, ++ "blkio.io_service_time_recursive": serviceTimeRecursiveContents, ++ "blkio.io_wait_time_recursive": waitTimeRecursiveContents, ++ "blkio.io_merged_recursive": mergedRecursiveContents, ++ "blkio.time_recursive": timeRecursiveContents, ++ }) ++ ++ blkio := &BlkioGroup{} ++ actualStats := *cgroups.NewStats() ++ err := blkio.GetStats(helper.CgroupPath, &actualStats) ++ if err != nil { ++ t.Fatalf("Failed unexpectedly: %s", err) ++ } ++} ++ ++func TestBlkioStatsNoServiceBytesFile(t *testing.T) { ++ helper := NewCgroupTestUtil("blkio", t) ++ defer helper.cleanup() ++ helper.writeFileContents(map[string]string{ ++ "blkio.io_serviced_recursive": servicedRecursiveContents, ++ "blkio.io_queued_recursive": queuedRecursiveContents, ++ "blkio.sectors_recursive": sectorsRecursiveContents, ++ "blkio.io_service_time_recursive": serviceTimeRecursiveContents, ++ "blkio.io_wait_time_recursive": waitTimeRecursiveContents, ++ "blkio.io_merged_recursive": mergedRecursiveContents, ++ "blkio.time_recursive": timeRecursiveContents, ++ }) ++ ++ blkio := &BlkioGroup{} ++ actualStats := *cgroups.NewStats() ++ err := blkio.GetStats(helper.CgroupPath, &actualStats) ++ if err != nil { ++ t.Fatalf("Failed unexpectedly: %s", err) ++ } ++} ++ ++func TestBlkioStatsNoServicedFile(t *testing.T) { ++ helper := NewCgroupTestUtil("blkio", t) ++ defer helper.cleanup() ++ helper.writeFileContents(map[string]string{ ++ "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, ++ "blkio.io_queued_recursive": queuedRecursiveContents, ++ "blkio.sectors_recursive": sectorsRecursiveContents, ++ "blkio.io_service_time_recursive": serviceTimeRecursiveContents, ++ "blkio.io_wait_time_recursive": waitTimeRecursiveContents, ++ "blkio.io_merged_recursive": mergedRecursiveContents, ++ "blkio.time_recursive": timeRecursiveContents, ++ }) ++ ++ blkio := &BlkioGroup{} ++ actualStats := *cgroups.NewStats() ++ err := blkio.GetStats(helper.CgroupPath, &actualStats) ++ if err != nil { ++ t.Fatalf("Failed unexpectedly: %s", err) ++ } ++} ++ ++func TestBlkioStatsNoQueuedFile(t *testing.T) { ++ helper := NewCgroupTestUtil("blkio", t) ++ defer helper.cleanup() ++ helper.writeFileContents(map[string]string{ ++ "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, ++ "blkio.io_serviced_recursive": servicedRecursiveContents, ++ "blkio.sectors_recursive": sectorsRecursiveContents, ++ "blkio.io_service_time_recursive": serviceTimeRecursiveContents, ++ "blkio.io_wait_time_recursive": waitTimeRecursiveContents, ++ "blkio.io_merged_recursive": mergedRecursiveContents, ++ "blkio.time_recursive": timeRecursiveContents, ++ }) ++ ++ blkio := &BlkioGroup{} ++ actualStats := *cgroups.NewStats() ++ err := blkio.GetStats(helper.CgroupPath, &actualStats) ++ if err != nil { ++ t.Fatalf("Failed unexpectedly: %s", err) ++ } ++} ++ ++func TestBlkioStatsNoServiceTimeFile(t *testing.T) { ++ if testing.Short() { ++ t.Skip("skipping test in short mode.") ++ } ++ helper := NewCgroupTestUtil("blkio", t) ++ defer helper.cleanup() ++ helper.writeFileContents(map[string]string{ ++ "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, ++ "blkio.io_serviced_recursive": servicedRecursiveContents, ++ "blkio.io_queued_recursive": queuedRecursiveContents, ++ "blkio.io_wait_time_recursive": waitTimeRecursiveContents, ++ "blkio.io_merged_recursive": mergedRecursiveContents, ++ "blkio.time_recursive": timeRecursiveContents, ++ "blkio.sectors_recursive": sectorsRecursiveContents, ++ }) ++ ++ blkio := &BlkioGroup{} ++ actualStats := *cgroups.NewStats() ++ err := blkio.GetStats(helper.CgroupPath, &actualStats) ++ if err != nil { ++ t.Fatalf("Failed unexpectedly: %s", err) ++ } ++} ++ ++func TestBlkioStatsNoWaitTimeFile(t *testing.T) { ++ if testing.Short() { ++ t.Skip("skipping test in short mode.") ++ } ++ helper := NewCgroupTestUtil("blkio", t) ++ defer helper.cleanup() ++ helper.writeFileContents(map[string]string{ ++ "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, ++ "blkio.io_serviced_recursive": servicedRecursiveContents, ++ "blkio.io_queued_recursive": queuedRecursiveContents, ++ "blkio.io_service_time_recursive": serviceTimeRecursiveContents, ++ "blkio.io_merged_recursive": mergedRecursiveContents, ++ "blkio.time_recursive": timeRecursiveContents, ++ "blkio.sectors_recursive": sectorsRecursiveContents, ++ }) ++ ++ blkio := &BlkioGroup{} ++ actualStats := *cgroups.NewStats() ++ err := blkio.GetStats(helper.CgroupPath, &actualStats) ++ if err != nil { ++ t.Fatalf("Failed unexpectedly: %s", err) ++ } ++} ++ ++func TestBlkioStatsNoMergedFile(t *testing.T) { ++ if testing.Short() { ++ t.Skip("skipping test in short mode.") ++ } ++ helper := NewCgroupTestUtil("blkio", t) ++ defer helper.cleanup() ++ helper.writeFileContents(map[string]string{ ++ "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, ++ "blkio.io_serviced_recursive": servicedRecursiveContents, ++ "blkio.io_queued_recursive": queuedRecursiveContents, ++ "blkio.io_service_time_recursive": serviceTimeRecursiveContents, ++ "blkio.io_wait_time_recursive": waitTimeRecursiveContents, ++ "blkio.time_recursive": timeRecursiveContents, ++ "blkio.sectors_recursive": sectorsRecursiveContents, ++ }) ++ ++ blkio := &BlkioGroup{} ++ actualStats := *cgroups.NewStats() ++ err := blkio.GetStats(helper.CgroupPath, &actualStats) ++ if err != nil { ++ t.Fatalf("Failed unexpectedly: %s", err) ++ } ++} ++ ++func TestBlkioStatsNoTimeFile(t *testing.T) { ++ if testing.Short() { ++ t.Skip("skipping test in short mode.") ++ } ++ helper := NewCgroupTestUtil("blkio", t) ++ defer helper.cleanup() ++ helper.writeFileContents(map[string]string{ ++ "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, ++ "blkio.io_serviced_recursive": servicedRecursiveContents, ++ "blkio.io_queued_recursive": queuedRecursiveContents, ++ "blkio.io_service_time_recursive": serviceTimeRecursiveContents, ++ "blkio.io_wait_time_recursive": waitTimeRecursiveContents, ++ "blkio.io_merged_recursive": mergedRecursiveContents, ++ "blkio.sectors_recursive": sectorsRecursiveContents, ++ }) ++ ++ blkio := &BlkioGroup{} ++ actualStats := *cgroups.NewStats() ++ err := blkio.GetStats(helper.CgroupPath, &actualStats) ++ if err != nil { ++ t.Fatalf("Failed unexpectedly: %s", err) ++ } ++} ++ ++func TestBlkioStatsUnexpectedNumberOfFields(t *testing.T) { ++ helper := NewCgroupTestUtil("blkio", t) ++ defer helper.cleanup() ++ helper.writeFileContents(map[string]string{ ++ "blkio.io_service_bytes_recursive": "8:0 Read 100 100", ++ "blkio.io_serviced_recursive": servicedRecursiveContents, ++ "blkio.io_queued_recursive": queuedRecursiveContents, ++ "blkio.sectors_recursive": sectorsRecursiveContents, ++ "blkio.io_service_time_recursive": serviceTimeRecursiveContents, ++ "blkio.io_wait_time_recursive": waitTimeRecursiveContents, ++ "blkio.io_merged_recursive": mergedRecursiveContents, ++ "blkio.time_recursive": timeRecursiveContents, ++ }) ++ ++ blkio := &BlkioGroup{} ++ actualStats := *cgroups.NewStats() ++ err := blkio.GetStats(helper.CgroupPath, &actualStats) ++ if err == nil { ++ t.Fatal("Expected to fail, but did not") ++ } ++} ++ ++func TestBlkioStatsUnexpectedFieldType(t *testing.T) { ++ helper := NewCgroupTestUtil("blkio", t) ++ defer helper.cleanup() ++ helper.writeFileContents(map[string]string{ ++ "blkio.io_service_bytes_recursive": "8:0 Read Write", ++ "blkio.io_serviced_recursive": servicedRecursiveContents, ++ "blkio.io_queued_recursive": queuedRecursiveContents, ++ "blkio.sectors_recursive": sectorsRecursiveContents, ++ "blkio.io_service_time_recursive": serviceTimeRecursiveContents, ++ "blkio.io_wait_time_recursive": waitTimeRecursiveContents, ++ "blkio.io_merged_recursive": mergedRecursiveContents, ++ "blkio.time_recursive": timeRecursiveContents, ++ }) ++ ++ blkio := &BlkioGroup{} ++ actualStats := *cgroups.NewStats() ++ err := blkio.GetStats(helper.CgroupPath, &actualStats) ++ if err == nil { ++ t.Fatal("Expected to fail, but did not") ++ } ++} ++ ++func TestNonCFQBlkioStats(t *testing.T) { ++ helper := NewCgroupTestUtil("blkio", t) ++ defer helper.cleanup() ++ helper.writeFileContents(map[string]string{ ++ "blkio.io_service_bytes_recursive": "", ++ "blkio.io_serviced_recursive": "", ++ "blkio.io_queued_recursive": "", ++ "blkio.sectors_recursive": "", ++ "blkio.io_service_time_recursive": "", ++ "blkio.io_wait_time_recursive": "", ++ "blkio.io_merged_recursive": "", ++ "blkio.time_recursive": "", ++ "blkio.throttle.io_service_bytes": throttleServiceBytes, ++ "blkio.throttle.io_serviced": throttleServiced, ++ }) ++ ++ blkio := &BlkioGroup{} ++ actualStats := *cgroups.NewStats() ++ err := blkio.GetStats(helper.CgroupPath, &actualStats) ++ if err != nil { ++ t.Fatal(err) ++ } ++ ++ // Verify expected stats. ++ expectedStats := cgroups.BlkioStats{} ++ ++ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 11030528, "Read") ++ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 23, "Write") ++ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 42, "Sync") ++ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 11030528, "Async") ++ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 11030528, "Total") ++ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 252, 0, 11030528, "Read") ++ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 252, 0, 23, "Write") ++ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 252, 0, 42, "Sync") ++ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 252, 0, 11030528, "Async") ++ appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 252, 0, 11030528, "Total") ++ ++ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 164, "Read") ++ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 23, "Write") ++ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 42, "Sync") ++ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 164, "Async") ++ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 164, "Total") ++ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 252, 0, 164, "Read") ++ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 252, 0, 23, "Write") ++ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 252, 0, 42, "Sync") ++ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 252, 0, 164, "Async") ++ appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 252, 0, 164, "Total") ++ ++ expectBlkioStatsEquals(t, expectedStats, actualStats.BlkioStats) ++} ++ ++func TestBlkioSetThrottleReadBpsDevice(t *testing.T) { ++ helper := NewCgroupTestUtil("blkio", t) ++ defer helper.cleanup() ++ ++ const ( ++ throttleBefore = `8:0 1024` ++ ) ++ ++ td := configs.NewThrottleDevice(8, 0, 2048) ++ throttleAfter := td.String() ++ ++ helper.writeFileContents(map[string]string{ ++ "blkio.throttle.read_bps_device": throttleBefore, ++ }) ++ ++ helper.CgroupData.config.Resources.BlkioThrottleReadBpsDevice = []*configs.ThrottleDevice{td} ++ blkio := &BlkioGroup{} ++ if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.read_bps_device") ++ if err != nil { ++ t.Fatalf("Failed to parse blkio.throttle.read_bps_device - %s", err) ++ } ++ ++ if value != throttleAfter { ++ t.Fatal("Got the wrong value, set blkio.throttle.read_bps_device failed.") ++ } ++} ++func TestBlkioSetThrottleWriteBpsDevice(t *testing.T) { ++ helper := NewCgroupTestUtil("blkio", t) ++ defer helper.cleanup() ++ ++ const ( ++ throttleBefore = `8:0 1024` ++ ) ++ ++ td := configs.NewThrottleDevice(8, 0, 2048) ++ throttleAfter := td.String() ++ ++ helper.writeFileContents(map[string]string{ ++ "blkio.throttle.write_bps_device": throttleBefore, ++ }) ++ ++ helper.CgroupData.config.Resources.BlkioThrottleWriteBpsDevice = []*configs.ThrottleDevice{td} ++ blkio := &BlkioGroup{} ++ if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.write_bps_device") ++ if err != nil { ++ t.Fatalf("Failed to parse blkio.throttle.write_bps_device - %s", err) ++ } ++ ++ if value != throttleAfter { ++ t.Fatal("Got the wrong value, set blkio.throttle.write_bps_device failed.") ++ } ++} ++func TestBlkioSetThrottleReadIOpsDevice(t *testing.T) { ++ helper := NewCgroupTestUtil("blkio", t) ++ defer helper.cleanup() ++ ++ const ( ++ throttleBefore = `8:0 1024` ++ ) ++ ++ td := configs.NewThrottleDevice(8, 0, 2048) ++ throttleAfter := td.String() ++ ++ helper.writeFileContents(map[string]string{ ++ "blkio.throttle.read_iops_device": throttleBefore, ++ }) ++ ++ helper.CgroupData.config.Resources.BlkioThrottleReadIOPSDevice = []*configs.ThrottleDevice{td} ++ blkio := &BlkioGroup{} ++ if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.read_iops_device") ++ if err != nil { ++ t.Fatalf("Failed to parse blkio.throttle.read_iops_device - %s", err) ++ } ++ ++ if value != throttleAfter { ++ t.Fatal("Got the wrong value, set blkio.throttle.read_iops_device failed.") ++ } ++} ++func TestBlkioSetThrottleWriteIOpsDevice(t *testing.T) { ++ helper := NewCgroupTestUtil("blkio", t) ++ defer helper.cleanup() ++ ++ const ( ++ throttleBefore = `8:0 1024` ++ ) ++ ++ td := configs.NewThrottleDevice(8, 0, 2048) ++ throttleAfter := td.String() ++ ++ helper.writeFileContents(map[string]string{ ++ "blkio.throttle.write_iops_device": throttleBefore, ++ }) ++ ++ helper.CgroupData.config.Resources.BlkioThrottleWriteIOPSDevice = []*configs.ThrottleDevice{td} ++ blkio := &BlkioGroup{} ++ if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.write_iops_device") ++ if err != nil { ++ t.Fatalf("Failed to parse blkio.throttle.write_iops_device - %s", err) ++ } ++ ++ if value != throttleAfter { ++ t.Fatal("Got the wrong value, set blkio.throttle.write_iops_device failed.") ++ } ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu.go +new file mode 100644 +index 0000000000..b712bd0b1e +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu.go +@@ -0,0 +1,125 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "bufio" ++ "os" ++ "path/filepath" ++ "strconv" ++ ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++ "github.com/opencontainers/runc/libcontainer/configs" ++) ++ ++type CpuGroup struct { ++} ++ ++func (s *CpuGroup) Name() string { ++ return "cpu" ++} ++ ++func (s *CpuGroup) Apply(d *cgroupData) error { ++ // We always want to join the cpu group, to allow fair cpu scheduling ++ // on a container basis ++ path, err := d.path("cpu") ++ if err != nil && !cgroups.IsNotFound(err) { ++ return err ++ } ++ return s.ApplyDir(path, d.config, d.pid) ++} ++ ++func (s *CpuGroup) ApplyDir(path string, cgroup *configs.Cgroup, pid int) error { ++ // This might happen if we have no cpu cgroup mounted. ++ // Just do nothing and don't fail. ++ if path == "" { ++ return nil ++ } ++ if err := os.MkdirAll(path, 0755); err != nil { ++ return err ++ } ++ // We should set the real-Time group scheduling settings before moving ++ // in the process because if the process is already in SCHED_RR mode ++ // and no RT bandwidth is set, adding it will fail. ++ if err := s.SetRtSched(path, cgroup); err != nil { ++ return err ++ } ++ // because we are not using d.join we need to place the pid into the procs file ++ // unlike the other subsystems ++ if err := cgroups.WriteCgroupProc(path, pid); err != nil { ++ return err ++ } ++ ++ return nil ++} ++ ++func (s *CpuGroup) SetRtSched(path string, cgroup *configs.Cgroup) error { ++ if cgroup.Resources.CpuRtPeriod != 0 { ++ if err := writeFile(path, "cpu.rt_period_us", strconv.FormatUint(cgroup.Resources.CpuRtPeriod, 10)); err != nil { ++ return err ++ } ++ } ++ if cgroup.Resources.CpuRtRuntime != 0 { ++ if err := writeFile(path, "cpu.rt_runtime_us", strconv.FormatInt(cgroup.Resources.CpuRtRuntime, 10)); err != nil { ++ return err ++ } ++ } ++ return nil ++} ++ ++func (s *CpuGroup) Set(path string, cgroup *configs.Cgroup) error { ++ if cgroup.Resources.CpuShares != 0 { ++ if err := writeFile(path, "cpu.shares", strconv.FormatUint(cgroup.Resources.CpuShares, 10)); err != nil { ++ return err ++ } ++ } ++ if cgroup.Resources.CpuPeriod != 0 { ++ if err := writeFile(path, "cpu.cfs_period_us", strconv.FormatUint(cgroup.Resources.CpuPeriod, 10)); err != nil { ++ return err ++ } ++ } ++ if cgroup.Resources.CpuQuota != 0 { ++ if err := writeFile(path, "cpu.cfs_quota_us", strconv.FormatInt(cgroup.Resources.CpuQuota, 10)); err != nil { ++ return err ++ } ++ } ++ if err := s.SetRtSched(path, cgroup); err != nil { ++ return err ++ } ++ ++ return nil ++} ++ ++func (s *CpuGroup) Remove(d *cgroupData) error { ++ return removePath(d.path("cpu")) ++} ++ ++func (s *CpuGroup) GetStats(path string, stats *cgroups.Stats) error { ++ f, err := os.Open(filepath.Join(path, "cpu.stat")) ++ if err != nil { ++ if os.IsNotExist(err) { ++ return nil ++ } ++ return err ++ } ++ defer f.Close() ++ ++ sc := bufio.NewScanner(f) ++ for sc.Scan() { ++ t, v, err := getCgroupParamKeyValue(sc.Text()) ++ if err != nil { ++ return err ++ } ++ switch t { ++ case "nr_periods": ++ stats.CpuStats.ThrottlingData.Periods = v ++ ++ case "nr_throttled": ++ stats.CpuStats.ThrottlingData.ThrottledPeriods = v ++ ++ case "throttled_time": ++ stats.CpuStats.ThrottlingData.ThrottledTime = v ++ } ++ } ++ return nil ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu_test.go +new file mode 100644 +index 0000000000..6369c91ad6 +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu_test.go +@@ -0,0 +1,209 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "fmt" ++ "strconv" ++ "testing" ++ ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++) ++ ++func TestCpuSetShares(t *testing.T) { ++ helper := NewCgroupTestUtil("cpu", t) ++ defer helper.cleanup() ++ ++ const ( ++ sharesBefore = 1024 ++ sharesAfter = 512 ++ ) ++ ++ helper.writeFileContents(map[string]string{ ++ "cpu.shares": strconv.Itoa(sharesBefore), ++ }) ++ ++ helper.CgroupData.config.Resources.CpuShares = sharesAfter ++ cpu := &CpuGroup{} ++ if err := cpu.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ value, err := getCgroupParamUint(helper.CgroupPath, "cpu.shares") ++ if err != nil { ++ t.Fatalf("Failed to parse cpu.shares - %s", err) ++ } ++ ++ if value != sharesAfter { ++ t.Fatal("Got the wrong value, set cpu.shares failed.") ++ } ++} ++ ++func TestCpuSetBandWidth(t *testing.T) { ++ helper := NewCgroupTestUtil("cpu", t) ++ defer helper.cleanup() ++ ++ const ( ++ quotaBefore = 8000 ++ quotaAfter = 5000 ++ periodBefore = 10000 ++ periodAfter = 7000 ++ rtRuntimeBefore = 8000 ++ rtRuntimeAfter = 5000 ++ rtPeriodBefore = 10000 ++ rtPeriodAfter = 7000 ++ ) ++ ++ helper.writeFileContents(map[string]string{ ++ "cpu.cfs_quota_us": strconv.Itoa(quotaBefore), ++ "cpu.cfs_period_us": strconv.Itoa(periodBefore), ++ "cpu.rt_runtime_us": strconv.Itoa(rtRuntimeBefore), ++ "cpu.rt_period_us": strconv.Itoa(rtPeriodBefore), ++ }) ++ ++ helper.CgroupData.config.Resources.CpuQuota = quotaAfter ++ helper.CgroupData.config.Resources.CpuPeriod = periodAfter ++ helper.CgroupData.config.Resources.CpuRtRuntime = rtRuntimeAfter ++ helper.CgroupData.config.Resources.CpuRtPeriod = rtPeriodAfter ++ cpu := &CpuGroup{} ++ if err := cpu.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ quota, err := getCgroupParamUint(helper.CgroupPath, "cpu.cfs_quota_us") ++ if err != nil { ++ t.Fatalf("Failed to parse cpu.cfs_quota_us - %s", err) ++ } ++ if quota != quotaAfter { ++ t.Fatal("Got the wrong value, set cpu.cfs_quota_us failed.") ++ } ++ ++ period, err := getCgroupParamUint(helper.CgroupPath, "cpu.cfs_period_us") ++ if err != nil { ++ t.Fatalf("Failed to parse cpu.cfs_period_us - %s", err) ++ } ++ if period != periodAfter { ++ t.Fatal("Got the wrong value, set cpu.cfs_period_us failed.") ++ } ++ rtRuntime, err := getCgroupParamUint(helper.CgroupPath, "cpu.rt_runtime_us") ++ if err != nil { ++ t.Fatalf("Failed to parse cpu.rt_runtime_us - %s", err) ++ } ++ if rtRuntime != rtRuntimeAfter { ++ t.Fatal("Got the wrong value, set cpu.rt_runtime_us failed.") ++ } ++ rtPeriod, err := getCgroupParamUint(helper.CgroupPath, "cpu.rt_period_us") ++ if err != nil { ++ t.Fatalf("Failed to parse cpu.rt_period_us - %s", err) ++ } ++ if rtPeriod != rtPeriodAfter { ++ t.Fatal("Got the wrong value, set cpu.rt_period_us failed.") ++ } ++} ++ ++func TestCpuStats(t *testing.T) { ++ helper := NewCgroupTestUtil("cpu", t) ++ defer helper.cleanup() ++ ++ const ( ++ nrPeriods = 2000 ++ nrThrottled = 200 ++ throttledTime = uint64(18446744073709551615) ++ ) ++ ++ cpuStatContent := fmt.Sprintf("nr_periods %d\n nr_throttled %d\n throttled_time %d\n", ++ nrPeriods, nrThrottled, throttledTime) ++ helper.writeFileContents(map[string]string{ ++ "cpu.stat": cpuStatContent, ++ }) ++ ++ cpu := &CpuGroup{} ++ actualStats := *cgroups.NewStats() ++ err := cpu.GetStats(helper.CgroupPath, &actualStats) ++ if err != nil { ++ t.Fatal(err) ++ } ++ ++ expectedStats := cgroups.ThrottlingData{ ++ Periods: nrPeriods, ++ ThrottledPeriods: nrThrottled, ++ ThrottledTime: throttledTime} ++ ++ expectThrottlingDataEquals(t, expectedStats, actualStats.CpuStats.ThrottlingData) ++} ++ ++func TestNoCpuStatFile(t *testing.T) { ++ helper := NewCgroupTestUtil("cpu", t) ++ defer helper.cleanup() ++ ++ cpu := &CpuGroup{} ++ actualStats := *cgroups.NewStats() ++ err := cpu.GetStats(helper.CgroupPath, &actualStats) ++ if err != nil { ++ t.Fatal("Expected not to fail, but did") ++ } ++} ++ ++func TestInvalidCpuStat(t *testing.T) { ++ helper := NewCgroupTestUtil("cpu", t) ++ defer helper.cleanup() ++ cpuStatContent := `nr_periods 2000 ++ nr_throttled 200 ++ throttled_time fortytwo` ++ helper.writeFileContents(map[string]string{ ++ "cpu.stat": cpuStatContent, ++ }) ++ ++ cpu := &CpuGroup{} ++ actualStats := *cgroups.NewStats() ++ err := cpu.GetStats(helper.CgroupPath, &actualStats) ++ if err == nil { ++ t.Fatal("Expected failed stat parsing.") ++ } ++} ++ ++func TestCpuSetRtSchedAtApply(t *testing.T) { ++ helper := NewCgroupTestUtil("cpu", t) ++ defer helper.cleanup() ++ ++ const ( ++ rtRuntimeBefore = 0 ++ rtRuntimeAfter = 5000 ++ rtPeriodBefore = 0 ++ rtPeriodAfter = 7000 ++ ) ++ ++ helper.writeFileContents(map[string]string{ ++ "cpu.rt_runtime_us": strconv.Itoa(rtRuntimeBefore), ++ "cpu.rt_period_us": strconv.Itoa(rtPeriodBefore), ++ }) ++ ++ helper.CgroupData.config.Resources.CpuRtRuntime = rtRuntimeAfter ++ helper.CgroupData.config.Resources.CpuRtPeriod = rtPeriodAfter ++ cpu := &CpuGroup{} ++ if err := cpu.ApplyDir(helper.CgroupPath, helper.CgroupData.config, 1234); err != nil { ++ t.Fatal(err) ++ } ++ ++ rtRuntime, err := getCgroupParamUint(helper.CgroupPath, "cpu.rt_runtime_us") ++ if err != nil { ++ t.Fatalf("Failed to parse cpu.rt_runtime_us - %s", err) ++ } ++ if rtRuntime != rtRuntimeAfter { ++ t.Fatal("Got the wrong value, set cpu.rt_runtime_us failed.") ++ } ++ rtPeriod, err := getCgroupParamUint(helper.CgroupPath, "cpu.rt_period_us") ++ if err != nil { ++ t.Fatalf("Failed to parse cpu.rt_period_us - %s", err) ++ } ++ if rtPeriod != rtPeriodAfter { ++ t.Fatal("Got the wrong value, set cpu.rt_period_us failed.") ++ } ++ pid, err := getCgroupParamUint(helper.CgroupPath, "cgroup.procs") ++ if err != nil { ++ t.Fatalf("Failed to parse cgroup.procs - %s", err) ++ } ++ if pid != 1234 { ++ t.Fatal("Got the wrong value, set cgroup.procs failed.") ++ } ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuacct.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuacct.go +new file mode 100644 +index 0000000000..53afbaddf1 +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuacct.go +@@ -0,0 +1,121 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "fmt" ++ "io/ioutil" ++ "path/filepath" ++ "strconv" ++ "strings" ++ ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++ "github.com/opencontainers/runc/libcontainer/configs" ++ "github.com/opencontainers/runc/libcontainer/system" ++) ++ ++const ( ++ cgroupCpuacctStat = "cpuacct.stat" ++ nanosecondsInSecond = 1000000000 ++) ++ ++var clockTicks = uint64(system.GetClockTicks()) ++ ++type CpuacctGroup struct { ++} ++ ++func (s *CpuacctGroup) Name() string { ++ return "cpuacct" ++} ++ ++func (s *CpuacctGroup) Apply(d *cgroupData) error { ++ // we just want to join this group even though we don't set anything ++ if _, err := d.join("cpuacct"); err != nil && !cgroups.IsNotFound(err) { ++ return err ++ } ++ ++ return nil ++} ++ ++func (s *CpuacctGroup) Set(path string, cgroup *configs.Cgroup) error { ++ return nil ++} ++ ++func (s *CpuacctGroup) Remove(d *cgroupData) error { ++ return removePath(d.path("cpuacct")) ++} ++ ++func (s *CpuacctGroup) GetStats(path string, stats *cgroups.Stats) error { ++ userModeUsage, kernelModeUsage, err := getCpuUsageBreakdown(path) ++ if err != nil { ++ return err ++ } ++ ++ totalUsage, err := getCgroupParamUint(path, "cpuacct.usage") ++ if err != nil { ++ return err ++ } ++ ++ percpuUsage, err := getPercpuUsage(path) ++ if err != nil { ++ return err ++ } ++ ++ stats.CpuStats.CpuUsage.TotalUsage = totalUsage ++ stats.CpuStats.CpuUsage.PercpuUsage = percpuUsage ++ stats.CpuStats.CpuUsage.UsageInUsermode = userModeUsage ++ stats.CpuStats.CpuUsage.UsageInKernelmode = kernelModeUsage ++ return nil ++} ++ ++// Returns user and kernel usage breakdown in nanoseconds. ++func getCpuUsageBreakdown(path string) (uint64, uint64, error) { ++ userModeUsage := uint64(0) ++ kernelModeUsage := uint64(0) ++ const ( ++ userField = "user" ++ systemField = "system" ++ ) ++ ++ // Expected format: ++ // user ++ // system ++ data, err := ioutil.ReadFile(filepath.Join(path, cgroupCpuacctStat)) ++ if err != nil { ++ return 0, 0, err ++ } ++ fields := strings.Fields(string(data)) ++ if len(fields) != 4 { ++ return 0, 0, fmt.Errorf("failure - %s is expected to have 4 fields", filepath.Join(path, cgroupCpuacctStat)) ++ } ++ if fields[0] != userField { ++ return 0, 0, fmt.Errorf("unexpected field %q in %q, expected %q", fields[0], cgroupCpuacctStat, userField) ++ } ++ if fields[2] != systemField { ++ return 0, 0, fmt.Errorf("unexpected field %q in %q, expected %q", fields[2], cgroupCpuacctStat, systemField) ++ } ++ if userModeUsage, err = strconv.ParseUint(fields[1], 10, 64); err != nil { ++ return 0, 0, err ++ } ++ if kernelModeUsage, err = strconv.ParseUint(fields[3], 10, 64); err != nil { ++ return 0, 0, err ++ } ++ ++ return (userModeUsage * nanosecondsInSecond) / clockTicks, (kernelModeUsage * nanosecondsInSecond) / clockTicks, nil ++} ++ ++func getPercpuUsage(path string) ([]uint64, error) { ++ percpuUsage := []uint64{} ++ data, err := ioutil.ReadFile(filepath.Join(path, "cpuacct.usage_percpu")) ++ if err != nil { ++ return percpuUsage, err ++ } ++ for _, value := range strings.Fields(string(data)) { ++ value, err := strconv.ParseUint(value, 10, 64) ++ if err != nil { ++ return percpuUsage, fmt.Errorf("Unable to convert param value to uint64: %s", err) ++ } ++ percpuUsage = append(percpuUsage, value) ++ } ++ return percpuUsage, nil ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset.go +new file mode 100644 +index 0000000000..e61994fc3c +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset.go +@@ -0,0 +1,183 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "bytes" ++ "fmt" ++ "io/ioutil" ++ "os" ++ "path/filepath" ++ ++ "github.com/sirupsen/logrus" ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++ "github.com/opencontainers/runc/libcontainer/configs" ++ libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils" ++) ++ ++type CpusetGroup struct { ++} ++ ++func (s *CpusetGroup) Name() string { ++ return "cpuset" ++} ++ ++func (s *CpusetGroup) Apply(d *cgroupData) error { ++ dir, err := d.path("cpuset") ++ if err != nil && !cgroups.IsNotFound(err) { ++ return err ++ } ++ return s.ApplyDir(dir, d.config, d.pid) ++} ++ ++func (s *CpusetGroup) Set(path string, cgroup *configs.Cgroup) error { ++ if cgroup.Resources.CpusetCpus != "" { ++ if err := writeFile(path, "cpuset.cpus", cgroup.Resources.CpusetCpus); err != nil { ++ return err ++ } ++ } ++ if cgroup.Resources.CpusetMems != "" { ++ if err := writeFile(path, "cpuset.mems", cgroup.Resources.CpusetMems); err != nil { ++ return err ++ } ++ } ++ return nil ++} ++ ++func (s *CpusetGroup) Remove(d *cgroupData) error { ++ return removePath(d.path("cpuset")) ++} ++ ++func (s *CpusetGroup) GetStats(path string, stats *cgroups.Stats) error { ++ return nil ++} ++ ++func (s *CpusetGroup) ApplyDir(dir string, cgroup *configs.Cgroup, pid int) error { ++ // This might happen if we have no cpuset cgroup mounted. ++ // Just do nothing and don't fail. ++ if dir == "" { ++ return nil ++ } ++ root, err := getCgroupRoot() ++ if err != nil { ++ return err ++ } ++ // 'ensureParent' start with parent because we don't want to ++ // explicitly inherit from parent, it could conflict with ++ // 'cpuset.cpu_exclusive'. ++ if err := s.ensureParent(filepath.Dir(dir), root); err != nil { ++ return err ++ } ++ if err := os.MkdirAll(dir, 0755); err != nil { ++ return err ++ } ++ // We didn't inherit cpuset configs from parent, but we have ++ // to ensure cpuset configs are set before moving task into the ++ // cgroup. ++ // The logic is, if user specified cpuset configs, use these ++ // specified configs, otherwise, inherit from parent. This makes ++ // cpuset configs work correctly with 'cpuset.cpu_exclusive', and ++ // keep backward compatbility. ++ if err := s.ensureCpusAndMems(dir, cgroup); err != nil { ++ return err ++ } ++ ++ // because we are not using d.join we need to place the pid into the procs file ++ // unlike the other subsystems ++ if err := cgroups.WriteCgroupProc(dir, pid); err != nil { ++ return err ++ } ++ ++ return nil ++} ++ ++func (s *CpusetGroup) getSubsystemSettings(parent string) (cpus []byte, mems []byte, err error) { ++ defer func() { ++ if err != nil { ++ minfo, err1 := ioutil.ReadFile("/proc/self/mountinfo") ++ if err1 != nil { ++ logrus.Errorf("Failed to read mountinfo when getSubsystemSettings get an error") ++ } ++ ++ dirInfo := "" ++ fs, err2 := ioutil.ReadDir(parent) ++ if err2 != nil { ++ logrus.Errorf("Failed to read mountinfo when getSubsystemSettings get an error") ++ } ++ for _, f := range fs { ++ dirInfo = dirInfo + " " + f.Name() ++ } ++ ++ logrus.Errorf("Read cpuset cgroup failed, print mountinfo and cgroup info here"+ ++ "path: %s, mountinfo: [%s], dirinfo: [%s]", parent, string(minfo), dirInfo) ++ } ++ }() ++ if cpus, err = ioutil.ReadFile(filepath.Join(parent, "cpuset.cpus")); err != nil { ++ return ++ } ++ if mems, err = ioutil.ReadFile(filepath.Join(parent, "cpuset.mems")); err != nil { ++ return ++ } ++ return cpus, mems, nil ++} ++ ++// ensureParent makes sure that the parent directory of current is created ++// and populated with the proper cpus and mems files copied from ++// it's parent. ++func (s *CpusetGroup) ensureParent(current, root string) error { ++ parent := filepath.Dir(current) ++ if libcontainerUtils.CleanPath(parent) == root { ++ return nil ++ } ++ // Avoid infinite recursion. ++ if parent == current { ++ return fmt.Errorf("cpuset: cgroup parent path outside cgroup root") ++ } ++ if err := s.ensureParent(parent, root); err != nil { ++ return err ++ } ++ if err := os.MkdirAll(current, 0755); err != nil { ++ return err ++ } ++ return s.copyIfNeeded(current, parent) ++} ++ ++// copyIfNeeded copies the cpuset.cpus and cpuset.mems from the parent ++// directory to the current directory if the file's contents are 0 ++func (s *CpusetGroup) copyIfNeeded(current, parent string) error { ++ var ( ++ err error ++ currentCpus, currentMems []byte ++ parentCpus, parentMems []byte ++ ) ++ ++ if currentCpus, currentMems, err = s.getSubsystemSettings(current); err != nil { ++ return err ++ } ++ if parentCpus, parentMems, err = s.getSubsystemSettings(parent); err != nil { ++ return err ++ } ++ ++ if s.isEmpty(currentCpus) { ++ if err := writeFile(current, "cpuset.cpus", string(parentCpus)); err != nil { ++ return err ++ } ++ } ++ if s.isEmpty(currentMems) { ++ if err := writeFile(current, "cpuset.mems", string(parentMems)); err != nil { ++ return err ++ } ++ } ++ return nil ++} ++ ++func (s *CpusetGroup) isEmpty(b []byte) bool { ++ return len(bytes.Trim(b, "\n")) == 0 ++} ++ ++func (s *CpusetGroup) ensureCpusAndMems(path string, cgroup *configs.Cgroup) error { ++ if err := s.Set(path, cgroup); err != nil { ++ return err ++ } ++ return s.copyIfNeeded(path, filepath.Dir(path)) ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset_test.go +new file mode 100644 +index 0000000000..0f929151fc +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset_test.go +@@ -0,0 +1,65 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "testing" ++) ++ ++func TestCpusetSetCpus(t *testing.T) { ++ helper := NewCgroupTestUtil("cpuset", t) ++ defer helper.cleanup() ++ ++ const ( ++ cpusBefore = "0" ++ cpusAfter = "1-3" ++ ) ++ ++ helper.writeFileContents(map[string]string{ ++ "cpuset.cpus": cpusBefore, ++ }) ++ ++ helper.CgroupData.config.Resources.CpusetCpus = cpusAfter ++ cpuset := &CpusetGroup{} ++ if err := cpuset.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ value, err := getCgroupParamString(helper.CgroupPath, "cpuset.cpus") ++ if err != nil { ++ t.Fatalf("Failed to parse cpuset.cpus - %s", err) ++ } ++ ++ if value != cpusAfter { ++ t.Fatal("Got the wrong value, set cpuset.cpus failed.") ++ } ++} ++ ++func TestCpusetSetMems(t *testing.T) { ++ helper := NewCgroupTestUtil("cpuset", t) ++ defer helper.cleanup() ++ ++ const ( ++ memsBefore = "0" ++ memsAfter = "1" ++ ) ++ ++ helper.writeFileContents(map[string]string{ ++ "cpuset.mems": memsBefore, ++ }) ++ ++ helper.CgroupData.config.Resources.CpusetMems = memsAfter ++ cpuset := &CpusetGroup{} ++ if err := cpuset.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ value, err := getCgroupParamString(helper.CgroupPath, "cpuset.mems") ++ if err != nil { ++ t.Fatalf("Failed to parse cpuset.mems - %s", err) ++ } ++ ++ if value != memsAfter { ++ t.Fatal("Got the wrong value, set cpuset.mems failed.") ++ } ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices.go +new file mode 100644 +index 0000000000..0ac5b4ed70 +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices.go +@@ -0,0 +1,80 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++ "github.com/opencontainers/runc/libcontainer/configs" ++ "github.com/opencontainers/runc/libcontainer/system" ++) ++ ++type DevicesGroup struct { ++} ++ ++func (s *DevicesGroup) Name() string { ++ return "devices" ++} ++ ++func (s *DevicesGroup) Apply(d *cgroupData) error { ++ _, err := d.join("devices") ++ if err != nil { ++ // We will return error even it's `not found` error, devices ++ // cgroup is hard requirement for container's security. ++ return err ++ } ++ return nil ++} ++ ++func (s *DevicesGroup) Set(path string, cgroup *configs.Cgroup) error { ++ if system.RunningInUserNS() { ++ return nil ++ } ++ ++ devices := cgroup.Resources.Devices ++ if len(devices) > 0 { ++ for _, dev := range devices { ++ file := "devices.deny" ++ if dev.Allow { ++ file = "devices.allow" ++ } ++ if err := writeFile(path, file, dev.CgroupString()); err != nil { ++ return err ++ } ++ } ++ return nil ++ } ++ if cgroup.Resources.AllowAllDevices != nil { ++ if *cgroup.Resources.AllowAllDevices == false { ++ if err := writeFile(path, "devices.deny", "a"); err != nil { ++ return err ++ } ++ ++ for _, dev := range cgroup.Resources.AllowedDevices { ++ if err := writeFile(path, "devices.allow", dev.CgroupString()); err != nil { ++ return err ++ } ++ } ++ return nil ++ } ++ ++ if err := writeFile(path, "devices.allow", "a"); err != nil { ++ return err ++ } ++ } ++ ++ for _, dev := range cgroup.Resources.DeniedDevices { ++ if err := writeFile(path, "devices.deny", dev.CgroupString()); err != nil { ++ return err ++ } ++ } ++ ++ return nil ++} ++ ++func (s *DevicesGroup) Remove(d *cgroupData) error { ++ return removePath(d.path("devices")) ++} ++ ++func (s *DevicesGroup) GetStats(path string, stats *cgroups.Stats) error { ++ return nil ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices_test.go +new file mode 100644 +index 0000000000..fc635b990f +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices_test.go +@@ -0,0 +1,98 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "testing" ++ ++ "github.com/opencontainers/runc/libcontainer/configs" ++) ++ ++var ( ++ allowedDevices = []*configs.Device{ ++ { ++ Path: "/dev/zero", ++ Type: 'c', ++ Major: 1, ++ Minor: 5, ++ Permissions: "rwm", ++ FileMode: 0666, ++ }, ++ } ++ allowedList = "c 1:5 rwm" ++ deniedDevices = []*configs.Device{ ++ { ++ Path: "/dev/null", ++ Type: 'c', ++ Major: 1, ++ Minor: 3, ++ Permissions: "rwm", ++ FileMode: 0666, ++ }, ++ } ++ deniedList = "c 1:3 rwm" ++) ++ ++func TestDevicesSetAllow(t *testing.T) { ++ helper := NewCgroupTestUtil("devices", t) ++ defer helper.cleanup() ++ ++ helper.writeFileContents(map[string]string{ ++ "devices.deny": "a", ++ }) ++ allowAllDevices := false ++ helper.CgroupData.config.Resources.AllowAllDevices = &allowAllDevices ++ helper.CgroupData.config.Resources.AllowedDevices = allowedDevices ++ devices := &DevicesGroup{} ++ if err := devices.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ value, err := getCgroupParamString(helper.CgroupPath, "devices.allow") ++ if err != nil { ++ t.Fatalf("Failed to parse devices.allow - %s", err) ++ } ++ ++ if value != allowedList { ++ t.Fatal("Got the wrong value, set devices.allow failed.") ++ } ++ ++ // When AllowAllDevices is nil, devices.allow file should not be modified. ++ helper.CgroupData.config.Resources.AllowAllDevices = nil ++ if err := devices.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ value, err = getCgroupParamString(helper.CgroupPath, "devices.allow") ++ if err != nil { ++ t.Fatalf("Failed to parse devices.allow - %s", err) ++ } ++ if value != allowedList { ++ t.Fatal("devices policy shouldn't have changed on AllowedAllDevices=nil.") ++ } ++} ++ ++func TestDevicesSetDeny(t *testing.T) { ++ helper := NewCgroupTestUtil("devices", t) ++ defer helper.cleanup() ++ ++ helper.writeFileContents(map[string]string{ ++ "devices.allow": "a", ++ }) ++ ++ allowAllDevices := true ++ helper.CgroupData.config.Resources.AllowAllDevices = &allowAllDevices ++ helper.CgroupData.config.Resources.DeniedDevices = deniedDevices ++ devices := &DevicesGroup{} ++ if err := devices.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ value, err := getCgroupParamString(helper.CgroupPath, "devices.deny") ++ if err != nil { ++ t.Fatalf("Failed to parse devices.deny - %s", err) ++ } ++ ++ if value != deniedList { ++ t.Fatal("Got the wrong value, set devices.deny failed.") ++ } ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/files.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/files.go +new file mode 100644 +index 0000000000..70e95240c8 +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/files.go +@@ -0,0 +1,72 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "fmt" ++ "strconv" ++ ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++ "github.com/opencontainers/runc/libcontainer/configs" ++ "path/filepath" ++) ++ ++type FilesGroup struct { ++} ++ ++func (s *FilesGroup) Name() string { ++ return "files" ++} ++ ++func (s *FilesGroup) Apply(d *cgroupData) error { ++ _, err := d.join("files") ++ if err != nil && !cgroups.IsNotFound(err) { ++ return err ++ } ++ return nil ++} ++ ++func (s *FilesGroup) Set(path string, cgroup *configs.Cgroup) error { ++ if cgroup.Resources.FilesLimit != 0 { ++ // "max" is the fallback value. ++ limit := "max" ++ if cgroup.Resources.FilesLimit > 0 { ++ limit = strconv.FormatInt(cgroup.Resources.FilesLimit, 10) ++ } ++ ++ if err := writeFile(path, "files.limit", limit); err != nil { ++ return err ++ } ++ } ++ ++ return nil ++} ++ ++func (s *FilesGroup) Remove(d *cgroupData) error { ++ return removePath(d.path("files")) ++} ++ ++func (s *FilesGroup) GetStats(path string, stats *cgroups.Stats) error { ++ usage, err := getCgroupParamUint(path, "files.usage") ++ if err != nil { ++ return fmt.Errorf("failed to parse files.usage - %s", err) ++ } ++ ++ maxString, err := getCgroupParamString(path, "files.limit") ++ if err != nil { ++ return fmt.Errorf("failed to parse files.limit - %s", err) ++ } ++ ++ // Default if files.limit == "max" is 0 -- which represents "no limit". ++ var max uint64 ++ if maxString != "max" { ++ max, err = parseUint(maxString, 10, 64) ++ if err != nil { ++ return fmt.Errorf("failed to parse files.limit -- unable to parse %q as a uint from Cgroup file %q", maxString, filepath.Join(path, "file.limits")) ++ } ++ } ++ ++ stats.FilesStats.Usage = usage ++ stats.FilesStats.Limit = max ++ return nil ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer.go +new file mode 100644 +index 0000000000..e70dfe3b95 +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer.go +@@ -0,0 +1,61 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "fmt" ++ "strings" ++ "time" ++ ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++ "github.com/opencontainers/runc/libcontainer/configs" ++) ++ ++type FreezerGroup struct { ++} ++ ++func (s *FreezerGroup) Name() string { ++ return "freezer" ++} ++ ++func (s *FreezerGroup) Apply(d *cgroupData) error { ++ _, err := d.join("freezer") ++ if err != nil && !cgroups.IsNotFound(err) { ++ return err ++ } ++ return nil ++} ++ ++func (s *FreezerGroup) Set(path string, cgroup *configs.Cgroup) error { ++ switch cgroup.Resources.Freezer { ++ case configs.Frozen, configs.Thawed: ++ if err := writeFile(path, "freezer.state", string(cgroup.Resources.Freezer)); err != nil { ++ return err ++ } ++ ++ for { ++ state, err := readFile(path, "freezer.state") ++ if err != nil { ++ return err ++ } ++ if strings.TrimSpace(state) == string(cgroup.Resources.Freezer) { ++ break ++ } ++ time.Sleep(1 * time.Millisecond) ++ } ++ case configs.Undefined: ++ return nil ++ default: ++ return fmt.Errorf("Invalid argument '%s' to freezer.state", string(cgroup.Resources.Freezer)) ++ } ++ ++ return nil ++} ++ ++func (s *FreezerGroup) Remove(d *cgroupData) error { ++ return removePath(d.path("freezer")) ++} ++ ++func (s *FreezerGroup) GetStats(path string, stats *cgroups.Stats) error { ++ return nil ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer_test.go +new file mode 100644 +index 0000000000..77708db9a6 +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer_test.go +@@ -0,0 +1,47 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "testing" ++ ++ "github.com/opencontainers/runc/libcontainer/configs" ++) ++ ++func TestFreezerSetState(t *testing.T) { ++ helper := NewCgroupTestUtil("freezer", t) ++ defer helper.cleanup() ++ ++ helper.writeFileContents(map[string]string{ ++ "freezer.state": string(configs.Frozen), ++ }) ++ ++ helper.CgroupData.config.Resources.Freezer = configs.Thawed ++ freezer := &FreezerGroup{} ++ if err := freezer.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ value, err := getCgroupParamString(helper.CgroupPath, "freezer.state") ++ if err != nil { ++ t.Fatalf("Failed to parse freezer.state - %s", err) ++ } ++ if value != string(configs.Thawed) { ++ t.Fatal("Got the wrong value, set freezer.state failed.") ++ } ++} ++ ++func TestFreezerSetInvalidState(t *testing.T) { ++ helper := NewCgroupTestUtil("freezer", t) ++ defer helper.cleanup() ++ ++ const ( ++ invalidArg configs.FreezerState = "Invalid" ++ ) ++ ++ helper.CgroupData.config.Resources.Freezer = invalidArg ++ freezer := &FreezerGroup{} ++ if err := freezer.Set(helper.CgroupPath, helper.CgroupData.config); err == nil { ++ t.Fatal("Failed to return invalid argument error") ++ } ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/fs_unsupported.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/fs_unsupported.go +new file mode 100644 +index 0000000000..3ef9e03158 +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/fs_unsupported.go +@@ -0,0 +1,3 @@ ++// +build !linux ++ ++package fs +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb.go +new file mode 100644 +index 0000000000..2f9727719d +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb.go +@@ -0,0 +1,71 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "fmt" ++ "strconv" ++ "strings" ++ ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++ "github.com/opencontainers/runc/libcontainer/configs" ++) ++ ++type HugetlbGroup struct { ++} ++ ++func (s *HugetlbGroup) Name() string { ++ return "hugetlb" ++} ++ ++func (s *HugetlbGroup) Apply(d *cgroupData) error { ++ _, err := d.join("hugetlb") ++ if err != nil && !cgroups.IsNotFound(err) { ++ return err ++ } ++ return nil ++} ++ ++func (s *HugetlbGroup) Set(path string, cgroup *configs.Cgroup) error { ++ for _, hugetlb := range cgroup.Resources.HugetlbLimit { ++ if err := writeFile(path, strings.Join([]string{"hugetlb", hugetlb.Pagesize, "limit_in_bytes"}, "."), strconv.FormatUint(hugetlb.Limit, 10)); err != nil { ++ return err ++ } ++ } ++ ++ return nil ++} ++ ++func (s *HugetlbGroup) Remove(d *cgroupData) error { ++ return removePath(d.path("hugetlb")) ++} ++ ++func (s *HugetlbGroup) GetStats(path string, stats *cgroups.Stats) error { ++ hugetlbStats := cgroups.HugetlbStats{} ++ for _, pageSize := range HugePageSizes { ++ usage := strings.Join([]string{"hugetlb", pageSize, "usage_in_bytes"}, ".") ++ value, err := getCgroupParamUint(path, usage) ++ if err != nil { ++ return fmt.Errorf("failed to parse %s - %v", usage, err) ++ } ++ hugetlbStats.Usage = value ++ ++ maxUsage := strings.Join([]string{"hugetlb", pageSize, "max_usage_in_bytes"}, ".") ++ value, err = getCgroupParamUint(path, maxUsage) ++ if err != nil { ++ return fmt.Errorf("failed to parse %s - %v", maxUsage, err) ++ } ++ hugetlbStats.MaxUsage = value ++ ++ failcnt := strings.Join([]string{"hugetlb", pageSize, "failcnt"}, ".") ++ value, err = getCgroupParamUint(path, failcnt) ++ if err != nil { ++ return fmt.Errorf("failed to parse %s - %v", failcnt, err) ++ } ++ hugetlbStats.Failcnt = value ++ ++ stats.HugetlbStats[pageSize] = hugetlbStats ++ } ++ ++ return nil ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb_test.go +new file mode 100644 +index 0000000000..2d41c4eb20 +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb_test.go +@@ -0,0 +1,154 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "fmt" ++ "strconv" ++ "testing" ++ ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++ "github.com/opencontainers/runc/libcontainer/configs" ++) ++ ++const ( ++ hugetlbUsageContents = "128\n" ++ hugetlbMaxUsageContents = "256\n" ++ hugetlbFailcnt = "100\n" ++) ++ ++var ( ++ usage = "hugetlb.%s.usage_in_bytes" ++ limit = "hugetlb.%s.limit_in_bytes" ++ maxUsage = "hugetlb.%s.max_usage_in_bytes" ++ failcnt = "hugetlb.%s.failcnt" ++) ++ ++func TestHugetlbSetHugetlb(t *testing.T) { ++ helper := NewCgroupTestUtil("hugetlb", t) ++ defer helper.cleanup() ++ ++ const ( ++ hugetlbBefore = 256 ++ hugetlbAfter = 512 ++ ) ++ ++ for _, pageSize := range HugePageSizes { ++ helper.writeFileContents(map[string]string{ ++ fmt.Sprintf(limit, pageSize): strconv.Itoa(hugetlbBefore), ++ }) ++ } ++ ++ for _, pageSize := range HugePageSizes { ++ helper.CgroupData.config.Resources.HugetlbLimit = []*configs.HugepageLimit{ ++ { ++ Pagesize: pageSize, ++ Limit: hugetlbAfter, ++ }, ++ } ++ hugetlb := &HugetlbGroup{} ++ if err := hugetlb.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ } ++ ++ for _, pageSize := range HugePageSizes { ++ limit := fmt.Sprintf(limit, pageSize) ++ value, err := getCgroupParamUint(helper.CgroupPath, limit) ++ if err != nil { ++ t.Fatalf("Failed to parse %s - %s", limit, err) ++ } ++ if value != hugetlbAfter { ++ t.Fatalf("Set hugetlb.limit_in_bytes failed. Expected: %v, Got: %v", hugetlbAfter, value) ++ } ++ } ++} ++ ++func TestHugetlbStats(t *testing.T) { ++ helper := NewCgroupTestUtil("hugetlb", t) ++ defer helper.cleanup() ++ for _, pageSize := range HugePageSizes { ++ helper.writeFileContents(map[string]string{ ++ fmt.Sprintf(usage, pageSize): hugetlbUsageContents, ++ fmt.Sprintf(maxUsage, pageSize): hugetlbMaxUsageContents, ++ fmt.Sprintf(failcnt, pageSize): hugetlbFailcnt, ++ }) ++ } ++ ++ hugetlb := &HugetlbGroup{} ++ actualStats := *cgroups.NewStats() ++ err := hugetlb.GetStats(helper.CgroupPath, &actualStats) ++ if err != nil { ++ t.Fatal(err) ++ } ++ expectedStats := cgroups.HugetlbStats{Usage: 128, MaxUsage: 256, Failcnt: 100} ++ for _, pageSize := range HugePageSizes { ++ expectHugetlbStatEquals(t, expectedStats, actualStats.HugetlbStats[pageSize]) ++ } ++} ++ ++func TestHugetlbStatsNoUsageFile(t *testing.T) { ++ helper := NewCgroupTestUtil("hugetlb", t) ++ defer helper.cleanup() ++ helper.writeFileContents(map[string]string{ ++ maxUsage: hugetlbMaxUsageContents, ++ }) ++ ++ hugetlb := &HugetlbGroup{} ++ actualStats := *cgroups.NewStats() ++ err := hugetlb.GetStats(helper.CgroupPath, &actualStats) ++ if err == nil { ++ t.Fatal("Expected failure") ++ } ++} ++ ++func TestHugetlbStatsNoMaxUsageFile(t *testing.T) { ++ helper := NewCgroupTestUtil("hugetlb", t) ++ defer helper.cleanup() ++ for _, pageSize := range HugePageSizes { ++ helper.writeFileContents(map[string]string{ ++ fmt.Sprintf(usage, pageSize): hugetlbUsageContents, ++ }) ++ } ++ ++ hugetlb := &HugetlbGroup{} ++ actualStats := *cgroups.NewStats() ++ err := hugetlb.GetStats(helper.CgroupPath, &actualStats) ++ if err == nil { ++ t.Fatal("Expected failure") ++ } ++} ++ ++func TestHugetlbStatsBadUsageFile(t *testing.T) { ++ helper := NewCgroupTestUtil("hugetlb", t) ++ defer helper.cleanup() ++ for _, pageSize := range HugePageSizes { ++ helper.writeFileContents(map[string]string{ ++ fmt.Sprintf(usage, pageSize): "bad", ++ maxUsage: hugetlbMaxUsageContents, ++ }) ++ } ++ ++ hugetlb := &HugetlbGroup{} ++ actualStats := *cgroups.NewStats() ++ err := hugetlb.GetStats(helper.CgroupPath, &actualStats) ++ if err == nil { ++ t.Fatal("Expected failure") ++ } ++} ++ ++func TestHugetlbStatsBadMaxUsageFile(t *testing.T) { ++ helper := NewCgroupTestUtil("hugetlb", t) ++ defer helper.cleanup() ++ helper.writeFileContents(map[string]string{ ++ usage: hugetlbUsageContents, ++ maxUsage: "bad", ++ }) ++ ++ hugetlb := &HugetlbGroup{} ++ actualStats := *cgroups.NewStats() ++ err := hugetlb.GetStats(helper.CgroupPath, &actualStats) ++ if err == nil { ++ t.Fatal("Expected failure") ++ } ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go +new file mode 100644 +index 0000000000..118cce8f9a +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go +@@ -0,0 +1,301 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "bufio" ++ "fmt" ++ "io/ioutil" ++ "os" ++ "path/filepath" ++ "strconv" ++ "strings" ++ "syscall" ++ ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++ "github.com/opencontainers/runc/libcontainer/configs" ++) ++ ++const ( ++ cgroupKernelMemoryLimit = "memory.kmem.limit_in_bytes" ++ cgroupMemorySwapLimit = "memory.memsw.limit_in_bytes" ++ cgroupMemoryLimit = "memory.limit_in_bytes" ++) ++ ++type MemoryGroup struct { ++} ++ ++func (s *MemoryGroup) Name() string { ++ return "memory" ++} ++ ++func (s *MemoryGroup) Apply(d *cgroupData) (err error) { ++ path, err := d.path("memory") ++ if err != nil && !cgroups.IsNotFound(err) { ++ return err ++ } else if path == "" { ++ return nil ++ } ++ if memoryAssigned(d.config) { ++ if _, err := os.Stat(path); os.IsNotExist(err) { ++ if err := os.MkdirAll(path, 0755); err != nil { ++ return err ++ } ++ } ++ if d.config.KernelMemory != 0 { ++ if err := EnableKernelMemoryAccounting(path); err != nil { ++ return err ++ } ++ } ++ } ++ defer func() { ++ if err != nil { ++ os.RemoveAll(path) ++ } ++ }() ++ ++ // We need to join memory cgroup after set memory limits, because ++ // kmem.limit_in_bytes can only be set when the cgroup is empty. ++ _, err = d.join("memory") ++ if err != nil && !cgroups.IsNotFound(err) { ++ return err ++ } ++ return nil ++} ++ ++func EnableKernelMemoryAccounting(path string) error { ++ // Check if kernel memory is enabled ++ // We have to limit the kernel memory here as it won't be accounted at all ++ // until a limit is set on the cgroup and limit cannot be set once the ++ // cgroup has children, or if there are already tasks in the cgroup. ++ for _, i := range []int64{1, -1} { ++ if err := setKernelMemory(path, i); err != nil { ++ return err ++ } ++ } ++ return nil ++} ++ ++func setKernelMemory(path string, kernelMemoryLimit int64) error { ++ if path == "" { ++ return fmt.Errorf("no such directory for %s", cgroupKernelMemoryLimit) ++ } ++ if !cgroups.PathExists(filepath.Join(path, cgroupKernelMemoryLimit)) { ++ // kernel memory is not enabled on the system so we should do nothing ++ return nil ++ } ++ if err := ioutil.WriteFile(filepath.Join(path, cgroupKernelMemoryLimit), []byte(strconv.FormatInt(kernelMemoryLimit, 10)), 0700); err != nil { ++ // Check if the error number returned by the syscall is "EBUSY" ++ // The EBUSY signal is returned on attempts to write to the ++ // memory.kmem.limit_in_bytes file if the cgroup has children or ++ // once tasks have been attached to the cgroup ++ if pathErr, ok := err.(*os.PathError); ok { ++ if errNo, ok := pathErr.Err.(syscall.Errno); ok { ++ if errNo == syscall.EBUSY { ++ return fmt.Errorf("failed to set %s, because either tasks have already joined this cgroup or it has children", cgroupKernelMemoryLimit) ++ } ++ } ++ } ++ return fmt.Errorf("failed to write %v to %v: %v", kernelMemoryLimit, cgroupKernelMemoryLimit, err) ++ } ++ return nil ++} ++ ++func setMemoryAndSwap(path string, cgroup *configs.Cgroup) error { ++ // If the memory update is set to -1 we should also ++ // set swap to -1, it means unlimited memory. ++ if cgroup.Resources.Memory == -1 { ++ // Only set swap if it's enabled in kernel ++ if cgroups.PathExists(filepath.Join(path, cgroupMemorySwapLimit)) { ++ cgroup.Resources.MemorySwap = -1 ++ } ++ } ++ ++ // When memory and swap memory are both set, we need to handle the cases ++ // for updating container. ++ if cgroup.Resources.Memory != 0 && cgroup.Resources.MemorySwap != 0 { ++ memoryUsage, err := getMemoryData(path, "") ++ if err != nil { ++ return err ++ } ++ ++ // When update memory limit, we should adapt the write sequence ++ // for memory and swap memory, so it won't fail because the new ++ // value and the old value don't fit kernel's validation. ++ if cgroup.Resources.MemorySwap == -1 || memoryUsage.Limit < uint64(cgroup.Resources.MemorySwap) { ++ if err := writeFile(path, cgroupMemorySwapLimit, strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil { ++ return err ++ } ++ if err := writeFile(path, cgroupMemoryLimit, strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil { ++ return err ++ } ++ } else { ++ if err := writeFile(path, cgroupMemoryLimit, strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil { ++ return err ++ } ++ if err := writeFile(path, cgroupMemorySwapLimit, strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil { ++ return err ++ } ++ } ++ } else { ++ if cgroup.Resources.Memory != 0 { ++ if err := writeFile(path, cgroupMemoryLimit, strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil { ++ return err ++ } ++ } ++ if cgroup.Resources.MemorySwap != 0 { ++ if err := writeFile(path, cgroupMemorySwapLimit, strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil { ++ return err ++ } ++ } ++ } ++ ++ return nil ++} ++ ++func (s *MemoryGroup) Set(path string, cgroup *configs.Cgroup) error { ++ if err := setMemoryAndSwap(path, cgroup); err != nil { ++ return err ++ } ++ ++ if cgroup.Resources.KernelMemory != 0 { ++ if err := setKernelMemory(path, cgroup.Resources.KernelMemory); err != nil { ++ return err ++ } ++ } ++ ++ if cgroup.Resources.MemoryReservation != 0 { ++ if err := writeFile(path, "memory.soft_limit_in_bytes", strconv.FormatInt(cgroup.Resources.MemoryReservation, 10)); err != nil { ++ return err ++ } ++ } ++ ++ if cgroup.Resources.KernelMemoryTCP != 0 { ++ if err := writeFile(path, "memory.kmem.tcp.limit_in_bytes", strconv.FormatInt(cgroup.Resources.KernelMemoryTCP, 10)); err != nil { ++ return err ++ } ++ } ++ if cgroup.Resources.OomKillDisable { ++ if err := writeFile(path, "memory.oom_control", "1"); err != nil { ++ return err ++ } ++ } ++ if cgroup.Resources.MemorySwappiness == nil || int64(*cgroup.Resources.MemorySwappiness) == -1 { ++ return nil ++ } else if *cgroup.Resources.MemorySwappiness <= 100 { ++ if err := writeFile(path, "memory.swappiness", strconv.FormatUint(*cgroup.Resources.MemorySwappiness, 10)); err != nil { ++ return err ++ } ++ } else { ++ return fmt.Errorf("invalid value:%d. valid memory swappiness range is 0-100", *cgroup.Resources.MemorySwappiness) ++ } ++ ++ return nil ++} ++ ++func (s *MemoryGroup) Remove(d *cgroupData) error { ++ return removePath(d.path("memory")) ++} ++ ++func (s *MemoryGroup) GetStats(path string, stats *cgroups.Stats) error { ++ // Set stats from memory.stat. ++ statsFile, err := os.Open(filepath.Join(path, "memory.stat")) ++ if err != nil { ++ if os.IsNotExist(err) { ++ return nil ++ } ++ return err ++ } ++ defer statsFile.Close() ++ ++ sc := bufio.NewScanner(statsFile) ++ for sc.Scan() { ++ t, v, err := getCgroupParamKeyValue(sc.Text()) ++ if err != nil { ++ return fmt.Errorf("failed to parse memory.stat (%q) - %v", sc.Text(), err) ++ } ++ stats.MemoryStats.Stats[t] = v ++ } ++ stats.MemoryStats.Cache = stats.MemoryStats.Stats["cache"] ++ ++ memoryUsage, err := getMemoryData(path, "") ++ if err != nil { ++ return err ++ } ++ stats.MemoryStats.Usage = memoryUsage ++ swapUsage, err := getMemoryData(path, "memsw") ++ if err != nil { ++ return err ++ } ++ stats.MemoryStats.SwapUsage = swapUsage ++ kernelUsage, err := getMemoryData(path, "kmem") ++ if err != nil { ++ return err ++ } ++ stats.MemoryStats.KernelUsage = kernelUsage ++ kernelTCPUsage, err := getMemoryData(path, "kmem.tcp") ++ if err != nil { ++ return err ++ } ++ stats.MemoryStats.KernelTCPUsage = kernelTCPUsage ++ ++ return nil ++} ++ ++func memoryAssigned(cgroup *configs.Cgroup) bool { ++ return cgroup.Resources.Memory != 0 || ++ cgroup.Resources.MemoryReservation != 0 || ++ cgroup.Resources.MemorySwap > 0 || ++ cgroup.Resources.KernelMemory > 0 || ++ cgroup.Resources.KernelMemoryTCP > 0 || ++ cgroup.Resources.OomKillDisable || ++ (cgroup.Resources.MemorySwappiness != nil && int64(*cgroup.Resources.MemorySwappiness) != -1) ++} ++ ++func getMemoryData(path, name string) (cgroups.MemoryData, error) { ++ memoryData := cgroups.MemoryData{} ++ ++ moduleName := "memory" ++ if name != "" { ++ moduleName = strings.Join([]string{"memory", name}, ".") ++ } ++ usage := strings.Join([]string{moduleName, "usage_in_bytes"}, ".") ++ maxUsage := strings.Join([]string{moduleName, "max_usage_in_bytes"}, ".") ++ failcnt := strings.Join([]string{moduleName, "failcnt"}, ".") ++ limit := strings.Join([]string{moduleName, "limit_in_bytes"}, ".") ++ ++ value, err := getCgroupParamUint(path, usage) ++ if err != nil { ++ if moduleName != "memory" && os.IsNotExist(err) { ++ return cgroups.MemoryData{}, nil ++ } ++ return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", usage, err) ++ } ++ memoryData.Usage = value ++ value, err = getCgroupParamUint(path, maxUsage) ++ if err != nil { ++ if moduleName != "memory" && os.IsNotExist(err) { ++ return cgroups.MemoryData{}, nil ++ } ++ return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", maxUsage, err) ++ } ++ memoryData.MaxUsage = value ++ value, err = getCgroupParamUint(path, failcnt) ++ if err != nil { ++ if moduleName != "memory" && os.IsNotExist(err) { ++ return cgroups.MemoryData{}, nil ++ } ++ return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", failcnt, err) ++ } ++ memoryData.Failcnt = value ++ value, err = getCgroupParamUint(path, limit) ++ if err != nil { ++ if moduleName != "memory" && os.IsNotExist(err) { ++ return cgroups.MemoryData{}, nil ++ } ++ return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", limit, err) ++ } ++ memoryData.Limit = value ++ ++ return memoryData, nil ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory_test.go +new file mode 100644 +index 0000000000..4b656dc628 +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory_test.go +@@ -0,0 +1,453 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "strconv" ++ "testing" ++ ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++) ++ ++const ( ++ memoryStatContents = `cache 512 ++rss 1024` ++ memoryUsageContents = "2048\n" ++ memoryMaxUsageContents = "4096\n" ++ memoryFailcnt = "100\n" ++ memoryLimitContents = "8192\n" ++) ++ ++func TestMemorySetMemory(t *testing.T) { ++ helper := NewCgroupTestUtil("memory", t) ++ defer helper.cleanup() ++ ++ const ( ++ memoryBefore = 314572800 // 300M ++ memoryAfter = 524288000 // 500M ++ reservationBefore = 209715200 // 200M ++ reservationAfter = 314572800 // 300M ++ ) ++ ++ helper.writeFileContents(map[string]string{ ++ "memory.limit_in_bytes": strconv.Itoa(memoryBefore), ++ "memory.soft_limit_in_bytes": strconv.Itoa(reservationBefore), ++ }) ++ ++ helper.CgroupData.config.Resources.Memory = memoryAfter ++ helper.CgroupData.config.Resources.MemoryReservation = reservationAfter ++ memory := &MemoryGroup{} ++ if err := memory.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ value, err := getCgroupParamUint(helper.CgroupPath, "memory.limit_in_bytes") ++ if err != nil { ++ t.Fatalf("Failed to parse memory.limit_in_bytes - %s", err) ++ } ++ if value != memoryAfter { ++ t.Fatal("Got the wrong value, set memory.limit_in_bytes failed.") ++ } ++ ++ value, err = getCgroupParamUint(helper.CgroupPath, "memory.soft_limit_in_bytes") ++ if err != nil { ++ t.Fatalf("Failed to parse memory.soft_limit_in_bytes - %s", err) ++ } ++ if value != reservationAfter { ++ t.Fatal("Got the wrong value, set memory.soft_limit_in_bytes failed.") ++ } ++} ++ ++func TestMemorySetMemoryswap(t *testing.T) { ++ helper := NewCgroupTestUtil("memory", t) ++ defer helper.cleanup() ++ ++ const ( ++ memoryswapBefore = 314572800 // 300M ++ memoryswapAfter = 524288000 // 500M ++ ) ++ ++ helper.writeFileContents(map[string]string{ ++ "memory.memsw.limit_in_bytes": strconv.Itoa(memoryswapBefore), ++ }) ++ ++ helper.CgroupData.config.Resources.MemorySwap = memoryswapAfter ++ memory := &MemoryGroup{} ++ if err := memory.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ value, err := getCgroupParamUint(helper.CgroupPath, "memory.memsw.limit_in_bytes") ++ if err != nil { ++ t.Fatalf("Failed to parse memory.memsw.limit_in_bytes - %s", err) ++ } ++ if value != memoryswapAfter { ++ t.Fatal("Got the wrong value, set memory.memsw.limit_in_bytes failed.") ++ } ++} ++ ++func TestMemorySetMemoryLargerThanSwap(t *testing.T) { ++ helper := NewCgroupTestUtil("memory", t) ++ defer helper.cleanup() ++ ++ const ( ++ memoryBefore = 314572800 // 300M ++ memoryswapBefore = 524288000 // 500M ++ memoryAfter = 629145600 // 600M ++ memoryswapAfter = 838860800 // 800M ++ ) ++ ++ helper.writeFileContents(map[string]string{ ++ "memory.limit_in_bytes": strconv.Itoa(memoryBefore), ++ "memory.memsw.limit_in_bytes": strconv.Itoa(memoryswapBefore), ++ // Set will call getMemoryData when memory and swap memory are ++ // both set, fake these fields so we don't get error. ++ "memory.usage_in_bytes": "0", ++ "memory.max_usage_in_bytes": "0", ++ "memory.failcnt": "0", ++ }) ++ ++ helper.CgroupData.config.Resources.Memory = memoryAfter ++ helper.CgroupData.config.Resources.MemorySwap = memoryswapAfter ++ memory := &MemoryGroup{} ++ if err := memory.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ value, err := getCgroupParamUint(helper.CgroupPath, "memory.limit_in_bytes") ++ if err != nil { ++ t.Fatalf("Failed to parse memory.limit_in_bytes - %s", err) ++ } ++ if value != memoryAfter { ++ t.Fatal("Got the wrong value, set memory.limit_in_bytes failed.") ++ } ++ value, err = getCgroupParamUint(helper.CgroupPath, "memory.memsw.limit_in_bytes") ++ if err != nil { ++ t.Fatalf("Failed to parse memory.memsw.limit_in_bytes - %s", err) ++ } ++ if value != memoryswapAfter { ++ t.Fatal("Got the wrong value, set memory.memsw.limit_in_bytes failed.") ++ } ++} ++ ++func TestMemorySetSwapSmallerThanMemory(t *testing.T) { ++ helper := NewCgroupTestUtil("memory", t) ++ defer helper.cleanup() ++ ++ const ( ++ memoryBefore = 629145600 // 600M ++ memoryswapBefore = 838860800 // 800M ++ memoryAfter = 314572800 // 300M ++ memoryswapAfter = 524288000 // 500M ++ ) ++ ++ helper.writeFileContents(map[string]string{ ++ "memory.limit_in_bytes": strconv.Itoa(memoryBefore), ++ "memory.memsw.limit_in_bytes": strconv.Itoa(memoryswapBefore), ++ // Set will call getMemoryData when memory and swap memory are ++ // both set, fake these fields so we don't get error. ++ "memory.usage_in_bytes": "0", ++ "memory.max_usage_in_bytes": "0", ++ "memory.failcnt": "0", ++ }) ++ ++ helper.CgroupData.config.Resources.Memory = memoryAfter ++ helper.CgroupData.config.Resources.MemorySwap = memoryswapAfter ++ memory := &MemoryGroup{} ++ if err := memory.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ value, err := getCgroupParamUint(helper.CgroupPath, "memory.limit_in_bytes") ++ if err != nil { ++ t.Fatalf("Failed to parse memory.limit_in_bytes - %s", err) ++ } ++ if value != memoryAfter { ++ t.Fatal("Got the wrong value, set memory.limit_in_bytes failed.") ++ } ++ value, err = getCgroupParamUint(helper.CgroupPath, "memory.memsw.limit_in_bytes") ++ if err != nil { ++ t.Fatalf("Failed to parse memory.memsw.limit_in_bytes - %s", err) ++ } ++ if value != memoryswapAfter { ++ t.Fatal("Got the wrong value, set memory.memsw.limit_in_bytes failed.") ++ } ++} ++ ++func TestMemorySetKernelMemory(t *testing.T) { ++ helper := NewCgroupTestUtil("memory", t) ++ defer helper.cleanup() ++ ++ const ( ++ kernelMemoryBefore = 314572800 // 300M ++ kernelMemoryAfter = 524288000 // 500M ++ ) ++ ++ helper.writeFileContents(map[string]string{ ++ "memory.kmem.limit_in_bytes": strconv.Itoa(kernelMemoryBefore), ++ }) ++ ++ helper.CgroupData.config.Resources.KernelMemory = kernelMemoryAfter ++ memory := &MemoryGroup{} ++ if err := memory.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ value, err := getCgroupParamUint(helper.CgroupPath, "memory.kmem.limit_in_bytes") ++ if err != nil { ++ t.Fatalf("Failed to parse memory.kmem.limit_in_bytes - %s", err) ++ } ++ if value != kernelMemoryAfter { ++ t.Fatal("Got the wrong value, set memory.kmem.limit_in_bytes failed.") ++ } ++} ++ ++func TestMemorySetKernelMemoryTCP(t *testing.T) { ++ helper := NewCgroupTestUtil("memory", t) ++ defer helper.cleanup() ++ ++ const ( ++ kernelMemoryTCPBefore = 314572800 // 300M ++ kernelMemoryTCPAfter = 524288000 // 500M ++ ) ++ ++ helper.writeFileContents(map[string]string{ ++ "memory.kmem.tcp.limit_in_bytes": strconv.Itoa(kernelMemoryTCPBefore), ++ }) ++ ++ helper.CgroupData.config.Resources.KernelMemoryTCP = kernelMemoryTCPAfter ++ memory := &MemoryGroup{} ++ if err := memory.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ value, err := getCgroupParamUint(helper.CgroupPath, "memory.kmem.tcp.limit_in_bytes") ++ if err != nil { ++ t.Fatalf("Failed to parse memory.kmem.tcp.limit_in_bytes - %s", err) ++ } ++ if value != kernelMemoryTCPAfter { ++ t.Fatal("Got the wrong value, set memory.kmem.tcp.limit_in_bytes failed.") ++ } ++} ++ ++func TestMemorySetMemorySwappinessDefault(t *testing.T) { ++ helper := NewCgroupTestUtil("memory", t) ++ defer helper.cleanup() ++ ++ swappinessBefore := 60 // default is 60 ++ swappinessAfter := uint64(0) ++ ++ helper.writeFileContents(map[string]string{ ++ "memory.swappiness": strconv.Itoa(swappinessBefore), ++ }) ++ ++ helper.CgroupData.config.Resources.MemorySwappiness = &swappinessAfter ++ memory := &MemoryGroup{} ++ if err := memory.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ value, err := getCgroupParamUint(helper.CgroupPath, "memory.swappiness") ++ if err != nil { ++ t.Fatalf("Failed to parse memory.swappiness - %s", err) ++ } ++ if value != swappinessAfter { ++ t.Fatalf("Got the wrong value (%d), set memory.swappiness = %d failed.", value, swappinessAfter) ++ } ++} ++ ++func TestMemoryStats(t *testing.T) { ++ helper := NewCgroupTestUtil("memory", t) ++ defer helper.cleanup() ++ helper.writeFileContents(map[string]string{ ++ "memory.stat": memoryStatContents, ++ "memory.usage_in_bytes": memoryUsageContents, ++ "memory.limit_in_bytes": memoryLimitContents, ++ "memory.max_usage_in_bytes": memoryMaxUsageContents, ++ "memory.failcnt": memoryFailcnt, ++ "memory.memsw.usage_in_bytes": memoryUsageContents, ++ "memory.memsw.max_usage_in_bytes": memoryMaxUsageContents, ++ "memory.memsw.failcnt": memoryFailcnt, ++ "memory.memsw.limit_in_bytes": memoryLimitContents, ++ "memory.kmem.usage_in_bytes": memoryUsageContents, ++ "memory.kmem.max_usage_in_bytes": memoryMaxUsageContents, ++ "memory.kmem.failcnt": memoryFailcnt, ++ "memory.kmem.limit_in_bytes": memoryLimitContents, ++ }) ++ ++ memory := &MemoryGroup{} ++ actualStats := *cgroups.NewStats() ++ err := memory.GetStats(helper.CgroupPath, &actualStats) ++ if err != nil { ++ t.Fatal(err) ++ } ++ expectedStats := cgroups.MemoryStats{Cache: 512, Usage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, SwapUsage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, KernelUsage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, Stats: map[string]uint64{"cache": 512, "rss": 1024}} ++ expectMemoryStatEquals(t, expectedStats, actualStats.MemoryStats) ++} ++ ++func TestMemoryStatsNoStatFile(t *testing.T) { ++ helper := NewCgroupTestUtil("memory", t) ++ defer helper.cleanup() ++ helper.writeFileContents(map[string]string{ ++ "memory.usage_in_bytes": memoryUsageContents, ++ "memory.max_usage_in_bytes": memoryMaxUsageContents, ++ "memory.limit_in_bytes": memoryLimitContents, ++ }) ++ ++ memory := &MemoryGroup{} ++ actualStats := *cgroups.NewStats() ++ err := memory.GetStats(helper.CgroupPath, &actualStats) ++ if err != nil { ++ t.Fatal(err) ++ } ++} ++ ++func TestMemoryStatsNoUsageFile(t *testing.T) { ++ helper := NewCgroupTestUtil("memory", t) ++ defer helper.cleanup() ++ helper.writeFileContents(map[string]string{ ++ "memory.stat": memoryStatContents, ++ "memory.max_usage_in_bytes": memoryMaxUsageContents, ++ "memory.limit_in_bytes": memoryLimitContents, ++ }) ++ ++ memory := &MemoryGroup{} ++ actualStats := *cgroups.NewStats() ++ err := memory.GetStats(helper.CgroupPath, &actualStats) ++ if err == nil { ++ t.Fatal("Expected failure") ++ } ++} ++ ++func TestMemoryStatsNoMaxUsageFile(t *testing.T) { ++ helper := NewCgroupTestUtil("memory", t) ++ defer helper.cleanup() ++ helper.writeFileContents(map[string]string{ ++ "memory.stat": memoryStatContents, ++ "memory.usage_in_bytes": memoryUsageContents, ++ "memory.limit_in_bytes": memoryLimitContents, ++ }) ++ ++ memory := &MemoryGroup{} ++ actualStats := *cgroups.NewStats() ++ err := memory.GetStats(helper.CgroupPath, &actualStats) ++ if err == nil { ++ t.Fatal("Expected failure") ++ } ++} ++ ++func TestMemoryStatsNoLimitInBytesFile(t *testing.T) { ++ helper := NewCgroupTestUtil("memory", t) ++ defer helper.cleanup() ++ helper.writeFileContents(map[string]string{ ++ "memory.stat": memoryStatContents, ++ "memory.usage_in_bytes": memoryUsageContents, ++ "memory.max_usage_in_bytes": memoryMaxUsageContents, ++ }) ++ ++ memory := &MemoryGroup{} ++ actualStats := *cgroups.NewStats() ++ err := memory.GetStats(helper.CgroupPath, &actualStats) ++ if err == nil { ++ t.Fatal("Expected failure") ++ } ++} ++ ++func TestMemoryStatsBadStatFile(t *testing.T) { ++ helper := NewCgroupTestUtil("memory", t) ++ defer helper.cleanup() ++ helper.writeFileContents(map[string]string{ ++ "memory.stat": "rss rss", ++ "memory.usage_in_bytes": memoryUsageContents, ++ "memory.max_usage_in_bytes": memoryMaxUsageContents, ++ "memory.limit_in_bytes": memoryLimitContents, ++ }) ++ ++ memory := &MemoryGroup{} ++ actualStats := *cgroups.NewStats() ++ err := memory.GetStats(helper.CgroupPath, &actualStats) ++ if err == nil { ++ t.Fatal("Expected failure") ++ } ++} ++ ++func TestMemoryStatsBadUsageFile(t *testing.T) { ++ helper := NewCgroupTestUtil("memory", t) ++ defer helper.cleanup() ++ helper.writeFileContents(map[string]string{ ++ "memory.stat": memoryStatContents, ++ "memory.usage_in_bytes": "bad", ++ "memory.max_usage_in_bytes": memoryMaxUsageContents, ++ "memory.limit_in_bytes": memoryLimitContents, ++ }) ++ ++ memory := &MemoryGroup{} ++ actualStats := *cgroups.NewStats() ++ err := memory.GetStats(helper.CgroupPath, &actualStats) ++ if err == nil { ++ t.Fatal("Expected failure") ++ } ++} ++ ++func TestMemoryStatsBadMaxUsageFile(t *testing.T) { ++ helper := NewCgroupTestUtil("memory", t) ++ defer helper.cleanup() ++ helper.writeFileContents(map[string]string{ ++ "memory.stat": memoryStatContents, ++ "memory.usage_in_bytes": memoryUsageContents, ++ "memory.max_usage_in_bytes": "bad", ++ "memory.limit_in_bytes": memoryLimitContents, ++ }) ++ ++ memory := &MemoryGroup{} ++ actualStats := *cgroups.NewStats() ++ err := memory.GetStats(helper.CgroupPath, &actualStats) ++ if err == nil { ++ t.Fatal("Expected failure") ++ } ++} ++ ++func TestMemoryStatsBadLimitInBytesFile(t *testing.T) { ++ helper := NewCgroupTestUtil("memory", t) ++ defer helper.cleanup() ++ helper.writeFileContents(map[string]string{ ++ "memory.stat": memoryStatContents, ++ "memory.usage_in_bytes": memoryUsageContents, ++ "memory.max_usage_in_bytes": memoryMaxUsageContents, ++ "memory.limit_in_bytes": "bad", ++ }) ++ ++ memory := &MemoryGroup{} ++ actualStats := *cgroups.NewStats() ++ err := memory.GetStats(helper.CgroupPath, &actualStats) ++ if err == nil { ++ t.Fatal("Expected failure") ++ } ++} ++ ++func TestMemorySetOomControl(t *testing.T) { ++ helper := NewCgroupTestUtil("memory", t) ++ defer helper.cleanup() ++ ++ const ( ++ oomKillDisable = 1 // disable oom killer, default is 0 ++ ) ++ ++ helper.writeFileContents(map[string]string{ ++ "memory.oom_control": strconv.Itoa(oomKillDisable), ++ }) ++ ++ memory := &MemoryGroup{} ++ if err := memory.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ value, err := getCgroupParamUint(helper.CgroupPath, "memory.oom_control") ++ if err != nil { ++ t.Fatalf("Failed to parse memory.oom_control - %s", err) ++ } ++ ++ if value != oomKillDisable { ++ t.Fatalf("Got the wrong value, set memory.oom_control failed.") ++ } ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/name.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/name.go +new file mode 100644 +index 0000000000..d8cf1d87c0 +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/name.go +@@ -0,0 +1,40 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++ "github.com/opencontainers/runc/libcontainer/configs" ++) ++ ++type NameGroup struct { ++ GroupName string ++ Join bool ++} ++ ++func (s *NameGroup) Name() string { ++ return s.GroupName ++} ++ ++func (s *NameGroup) Apply(d *cgroupData) error { ++ if s.Join { ++ // ignore errors if the named cgroup does not exist ++ d.join(s.GroupName) ++ } ++ return nil ++} ++ ++func (s *NameGroup) Set(path string, cgroup *configs.Cgroup) error { ++ return nil ++} ++ ++func (s *NameGroup) Remove(d *cgroupData) error { ++ if s.Join { ++ removePath(d.path(s.GroupName)) ++ } ++ return nil ++} ++ ++func (s *NameGroup) GetStats(path string, stats *cgroups.Stats) error { ++ return nil ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls.go +new file mode 100644 +index 0000000000..8e74b645ea +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls.go +@@ -0,0 +1,43 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "strconv" ++ ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++ "github.com/opencontainers/runc/libcontainer/configs" ++) ++ ++type NetClsGroup struct { ++} ++ ++func (s *NetClsGroup) Name() string { ++ return "net_cls" ++} ++ ++func (s *NetClsGroup) Apply(d *cgroupData) error { ++ _, err := d.join("net_cls") ++ if err != nil && !cgroups.IsNotFound(err) { ++ return err ++ } ++ return nil ++} ++ ++func (s *NetClsGroup) Set(path string, cgroup *configs.Cgroup) error { ++ if cgroup.Resources.NetClsClassid != 0 { ++ if err := writeFile(path, "net_cls.classid", strconv.FormatUint(uint64(cgroup.Resources.NetClsClassid), 10)); err != nil { ++ return err ++ } ++ } ++ ++ return nil ++} ++ ++func (s *NetClsGroup) Remove(d *cgroupData) error { ++ return removePath(d.path("net_cls")) ++} ++ ++func (s *NetClsGroup) GetStats(path string, stats *cgroups.Stats) error { ++ return nil ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls_test.go +new file mode 100644 +index 0000000000..c00e8a8d6f +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls_test.go +@@ -0,0 +1,39 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "strconv" ++ "testing" ++) ++ ++const ( ++ classidBefore = 0x100002 ++ classidAfter = 0x100001 ++) ++ ++func TestNetClsSetClassid(t *testing.T) { ++ helper := NewCgroupTestUtil("net_cls", t) ++ defer helper.cleanup() ++ ++ helper.writeFileContents(map[string]string{ ++ "net_cls.classid": strconv.FormatUint(classidBefore, 10), ++ }) ++ ++ helper.CgroupData.config.Resources.NetClsClassid = classidAfter ++ netcls := &NetClsGroup{} ++ if err := netcls.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ // As we are in mock environment, we can't get correct value of classid from ++ // net_cls.classid. ++ // So. we just judge if we successfully write classid into file ++ value, err := getCgroupParamUint(helper.CgroupPath, "net_cls.classid") ++ if err != nil { ++ t.Fatalf("Failed to parse net_cls.classid - %s", err) ++ } ++ if value != classidAfter { ++ t.Fatal("Got the wrong value, set net_cls.classid failed.") ++ } ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio.go +new file mode 100644 +index 0000000000..d0ab2af894 +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio.go +@@ -0,0 +1,41 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++ "github.com/opencontainers/runc/libcontainer/configs" ++) ++ ++type NetPrioGroup struct { ++} ++ ++func (s *NetPrioGroup) Name() string { ++ return "net_prio" ++} ++ ++func (s *NetPrioGroup) Apply(d *cgroupData) error { ++ _, err := d.join("net_prio") ++ if err != nil && !cgroups.IsNotFound(err) { ++ return err ++ } ++ return nil ++} ++ ++func (s *NetPrioGroup) Set(path string, cgroup *configs.Cgroup) error { ++ for _, prioMap := range cgroup.Resources.NetPrioIfpriomap { ++ if err := writeFile(path, "net_prio.ifpriomap", prioMap.CgroupString()); err != nil { ++ return err ++ } ++ } ++ ++ return nil ++} ++ ++func (s *NetPrioGroup) Remove(d *cgroupData) error { ++ return removePath(d.path("net_prio")) ++} ++ ++func (s *NetPrioGroup) GetStats(path string, stats *cgroups.Stats) error { ++ return nil ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio_test.go +new file mode 100644 +index 0000000000..efbf0639a0 +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio_test.go +@@ -0,0 +1,38 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "strings" ++ "testing" ++ ++ "github.com/opencontainers/runc/libcontainer/configs" ++) ++ ++var ( ++ prioMap = []*configs.IfPrioMap{ ++ { ++ Interface: "test", ++ Priority: 5, ++ }, ++ } ++) ++ ++func TestNetPrioSetIfPrio(t *testing.T) { ++ helper := NewCgroupTestUtil("net_prio", t) ++ defer helper.cleanup() ++ ++ helper.CgroupData.config.Resources.NetPrioIfpriomap = prioMap ++ netPrio := &NetPrioGroup{} ++ if err := netPrio.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ value, err := getCgroupParamString(helper.CgroupPath, "net_prio.ifpriomap") ++ if err != nil { ++ t.Fatalf("Failed to parse net_prio.ifpriomap - %s", err) ++ } ++ if !strings.Contains(value, "test 5") { ++ t.Fatal("Got the wrong value, set net_prio.ifpriomap failed.") ++ } ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/perf_event.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/perf_event.go +new file mode 100644 +index 0000000000..5693676d3a +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/perf_event.go +@@ -0,0 +1,35 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++ "github.com/opencontainers/runc/libcontainer/configs" ++) ++ ++type PerfEventGroup struct { ++} ++ ++func (s *PerfEventGroup) Name() string { ++ return "perf_event" ++} ++ ++func (s *PerfEventGroup) Apply(d *cgroupData) error { ++ // we just want to join this group even though we don't set anything ++ if _, err := d.join("perf_event"); err != nil && !cgroups.IsNotFound(err) { ++ return err ++ } ++ return nil ++} ++ ++func (s *PerfEventGroup) Set(path string, cgroup *configs.Cgroup) error { ++ return nil ++} ++ ++func (s *PerfEventGroup) Remove(d *cgroupData) error { ++ return removePath(d.path("perf_event")) ++} ++ ++func (s *PerfEventGroup) GetStats(path string, stats *cgroups.Stats) error { ++ return nil ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids.go +new file mode 100644 +index 0000000000..f1e3720551 +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids.go +@@ -0,0 +1,73 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "fmt" ++ "path/filepath" ++ "strconv" ++ ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++ "github.com/opencontainers/runc/libcontainer/configs" ++) ++ ++type PidsGroup struct { ++} ++ ++func (s *PidsGroup) Name() string { ++ return "pids" ++} ++ ++func (s *PidsGroup) Apply(d *cgroupData) error { ++ _, err := d.join("pids") ++ if err != nil && !cgroups.IsNotFound(err) { ++ return err ++ } ++ return nil ++} ++ ++func (s *PidsGroup) Set(path string, cgroup *configs.Cgroup) error { ++ if cgroup.Resources.PidsLimit != 0 { ++ // "max" is the fallback value. ++ limit := "max" ++ ++ if cgroup.Resources.PidsLimit > 0 { ++ limit = strconv.FormatInt(cgroup.Resources.PidsLimit, 10) ++ } ++ ++ if err := writeFile(path, "pids.max", limit); err != nil { ++ return err ++ } ++ } ++ ++ return nil ++} ++ ++func (s *PidsGroup) Remove(d *cgroupData) error { ++ return removePath(d.path("pids")) ++} ++ ++func (s *PidsGroup) GetStats(path string, stats *cgroups.Stats) error { ++ current, err := getCgroupParamUint(path, "pids.current") ++ if err != nil { ++ return fmt.Errorf("failed to parse pids.current - %s", err) ++ } ++ ++ maxString, err := getCgroupParamString(path, "pids.max") ++ if err != nil { ++ return fmt.Errorf("failed to parse pids.max - %s", err) ++ } ++ ++ // Default if pids.max == "max" is 0 -- which represents "no limit". ++ var max uint64 ++ if maxString != "max" { ++ max, err = parseUint(maxString, 10, 64) ++ if err != nil { ++ return fmt.Errorf("failed to parse pids.max - unable to parse %q as a uint from Cgroup file %q", maxString, filepath.Join(path, "pids.max")) ++ } ++ } ++ ++ stats.PidsStats.Current = current ++ stats.PidsStats.Limit = max ++ return nil ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids_test.go +new file mode 100644 +index 0000000000..10671247ba +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids_test.go +@@ -0,0 +1,111 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "strconv" ++ "testing" ++ ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++) ++ ++const ( ++ maxUnlimited = -1 ++ maxLimited = 1024 ++) ++ ++func TestPidsSetMax(t *testing.T) { ++ helper := NewCgroupTestUtil("pids", t) ++ defer helper.cleanup() ++ ++ helper.writeFileContents(map[string]string{ ++ "pids.max": "max", ++ }) ++ ++ helper.CgroupData.config.Resources.PidsLimit = maxLimited ++ pids := &PidsGroup{} ++ if err := pids.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ value, err := getCgroupParamUint(helper.CgroupPath, "pids.max") ++ if err != nil { ++ t.Fatalf("Failed to parse pids.max - %s", err) ++ } ++ ++ if value != maxLimited { ++ t.Fatalf("Expected %d, got %d for setting pids.max - limited", maxLimited, value) ++ } ++} ++ ++func TestPidsSetUnlimited(t *testing.T) { ++ helper := NewCgroupTestUtil("pids", t) ++ defer helper.cleanup() ++ ++ helper.writeFileContents(map[string]string{ ++ "pids.max": strconv.Itoa(maxLimited), ++ }) ++ ++ helper.CgroupData.config.Resources.PidsLimit = maxUnlimited ++ pids := &PidsGroup{} ++ if err := pids.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { ++ t.Fatal(err) ++ } ++ ++ value, err := getCgroupParamString(helper.CgroupPath, "pids.max") ++ if err != nil { ++ t.Fatalf("Failed to parse pids.max - %s", err) ++ } ++ ++ if value != "max" { ++ t.Fatalf("Expected %s, got %s for setting pids.max - unlimited", "max", value) ++ } ++} ++ ++func TestPidsStats(t *testing.T) { ++ helper := NewCgroupTestUtil("pids", t) ++ defer helper.cleanup() ++ ++ helper.writeFileContents(map[string]string{ ++ "pids.current": strconv.Itoa(1337), ++ "pids.max": strconv.Itoa(maxLimited), ++ }) ++ ++ pids := &PidsGroup{} ++ stats := *cgroups.NewStats() ++ if err := pids.GetStats(helper.CgroupPath, &stats); err != nil { ++ t.Fatal(err) ++ } ++ ++ if stats.PidsStats.Current != 1337 { ++ t.Fatalf("Expected %d, got %d for pids.current", 1337, stats.PidsStats.Current) ++ } ++ ++ if stats.PidsStats.Limit != maxLimited { ++ t.Fatalf("Expected %d, got %d for pids.max", maxLimited, stats.PidsStats.Limit) ++ } ++} ++ ++func TestPidsStatsUnlimited(t *testing.T) { ++ helper := NewCgroupTestUtil("pids", t) ++ defer helper.cleanup() ++ ++ helper.writeFileContents(map[string]string{ ++ "pids.current": strconv.Itoa(4096), ++ "pids.max": "max", ++ }) ++ ++ pids := &PidsGroup{} ++ stats := *cgroups.NewStats() ++ if err := pids.GetStats(helper.CgroupPath, &stats); err != nil { ++ t.Fatal(err) ++ } ++ ++ if stats.PidsStats.Current != 4096 { ++ t.Fatalf("Expected %d, got %d for pids.current", 4096, stats.PidsStats.Current) ++ } ++ ++ if stats.PidsStats.Limit != 0 { ++ t.Fatalf("Expected %d, got %d for pids.max", 0, stats.PidsStats.Limit) ++ } ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/stats_util_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/stats_util_test.go +new file mode 100644 +index 0000000000..d0ab047418 +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/stats_util_test.go +@@ -0,0 +1,117 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "fmt" ++ "testing" ++ ++ "github.com/sirupsen/logrus" ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++) ++ ++func blkioStatEntryEquals(expected, actual []cgroups.BlkioStatEntry) error { ++ if len(expected) != len(actual) { ++ return fmt.Errorf("blkioStatEntries length do not match") ++ } ++ for i, expValue := range expected { ++ actValue := actual[i] ++ if expValue != actValue { ++ return fmt.Errorf("Expected blkio stat entry %v but found %v", expValue, actValue) ++ } ++ } ++ return nil ++} ++ ++func expectBlkioStatsEquals(t *testing.T, expected, actual cgroups.BlkioStats) { ++ if err := blkioStatEntryEquals(expected.IoServiceBytesRecursive, actual.IoServiceBytesRecursive); err != nil { ++ logrus.Printf("blkio IoServiceBytesRecursive do not match - %s\n", err) ++ t.Fail() ++ } ++ ++ if err := blkioStatEntryEquals(expected.IoServicedRecursive, actual.IoServicedRecursive); err != nil { ++ logrus.Printf("blkio IoServicedRecursive do not match - %s\n", err) ++ t.Fail() ++ } ++ ++ if err := blkioStatEntryEquals(expected.IoQueuedRecursive, actual.IoQueuedRecursive); err != nil { ++ logrus.Printf("blkio IoQueuedRecursive do not match - %s\n", err) ++ t.Fail() ++ } ++ ++ if err := blkioStatEntryEquals(expected.SectorsRecursive, actual.SectorsRecursive); err != nil { ++ logrus.Printf("blkio SectorsRecursive do not match - %s\n", err) ++ t.Fail() ++ } ++ ++ if err := blkioStatEntryEquals(expected.IoServiceTimeRecursive, actual.IoServiceTimeRecursive); err != nil { ++ logrus.Printf("blkio IoServiceTimeRecursive do not match - %s\n", err) ++ t.Fail() ++ } ++ ++ if err := blkioStatEntryEquals(expected.IoWaitTimeRecursive, actual.IoWaitTimeRecursive); err != nil { ++ logrus.Printf("blkio IoWaitTimeRecursive do not match - %s\n", err) ++ t.Fail() ++ } ++ ++ if err := blkioStatEntryEquals(expected.IoMergedRecursive, actual.IoMergedRecursive); err != nil { ++ logrus.Printf("blkio IoMergedRecursive do not match - %v vs %v\n", expected.IoMergedRecursive, actual.IoMergedRecursive) ++ t.Fail() ++ } ++ ++ if err := blkioStatEntryEquals(expected.IoTimeRecursive, actual.IoTimeRecursive); err != nil { ++ logrus.Printf("blkio IoTimeRecursive do not match - %s\n", err) ++ t.Fail() ++ } ++} ++ ++func expectThrottlingDataEquals(t *testing.T, expected, actual cgroups.ThrottlingData) { ++ if expected != actual { ++ logrus.Printf("Expected throttling data %v but found %v\n", expected, actual) ++ t.Fail() ++ } ++} ++ ++func expectHugetlbStatEquals(t *testing.T, expected, actual cgroups.HugetlbStats) { ++ if expected != actual { ++ logrus.Printf("Expected hugetlb stats %v but found %v\n", expected, actual) ++ t.Fail() ++ } ++} ++ ++func expectMemoryStatEquals(t *testing.T, expected, actual cgroups.MemoryStats) { ++ expectMemoryDataEquals(t, expected.Usage, actual.Usage) ++ expectMemoryDataEquals(t, expected.SwapUsage, actual.SwapUsage) ++ expectMemoryDataEquals(t, expected.KernelUsage, actual.KernelUsage) ++ ++ for key, expValue := range expected.Stats { ++ actValue, ok := actual.Stats[key] ++ if !ok { ++ logrus.Printf("Expected memory stat key %s not found\n", key) ++ t.Fail() ++ } ++ if expValue != actValue { ++ logrus.Printf("Expected memory stat value %d but found %d\n", expValue, actValue) ++ t.Fail() ++ } ++ } ++} ++ ++func expectMemoryDataEquals(t *testing.T, expected, actual cgroups.MemoryData) { ++ if expected.Usage != actual.Usage { ++ logrus.Printf("Expected memory usage %d but found %d\n", expected.Usage, actual.Usage) ++ t.Fail() ++ } ++ if expected.MaxUsage != actual.MaxUsage { ++ logrus.Printf("Expected memory max usage %d but found %d\n", expected.MaxUsage, actual.MaxUsage) ++ t.Fail() ++ } ++ if expected.Failcnt != actual.Failcnt { ++ logrus.Printf("Expected memory failcnt %d but found %d\n", expected.Failcnt, actual.Failcnt) ++ t.Fail() ++ } ++ if expected.Limit != actual.Limit { ++ logrus.Printf("Expected memory limit %d but found %d\n", expected.Limit, actual.Limit) ++ t.Fail() ++ } ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/util_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/util_test.go +new file mode 100644 +index 0000000000..7067e799fb +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/util_test.go +@@ -0,0 +1,67 @@ ++// +build linux ++ ++/* ++Utility for testing cgroup operations. ++ ++Creates a mock of the cgroup filesystem for the duration of the test. ++*/ ++package fs ++ ++import ( ++ "io/ioutil" ++ "os" ++ "path/filepath" ++ "testing" ++ ++ "github.com/opencontainers/runc/libcontainer/configs" ++) ++ ++type cgroupTestUtil struct { ++ // cgroup data to use in tests. ++ CgroupData *cgroupData ++ ++ // Path to the mock cgroup directory. ++ CgroupPath string ++ ++ // Temporary directory to store mock cgroup filesystem. ++ tempDir string ++ t *testing.T ++} ++ ++// Creates a new test util for the specified subsystem ++func NewCgroupTestUtil(subsystem string, t *testing.T) *cgroupTestUtil { ++ d := &cgroupData{ ++ config: &configs.Cgroup{}, ++ } ++ d.config.Resources = &configs.Resources{} ++ tempDir, err := ioutil.TempDir("", "cgroup_test") ++ if err != nil { ++ t.Fatal(err) ++ } ++ d.root = tempDir ++ testCgroupPath := filepath.Join(d.root, subsystem) ++ if err != nil { ++ t.Fatal(err) ++ } ++ ++ // Ensure the full mock cgroup path exists. ++ err = os.MkdirAll(testCgroupPath, 0755) ++ if err != nil { ++ t.Fatal(err) ++ } ++ return &cgroupTestUtil{CgroupData: d, CgroupPath: testCgroupPath, tempDir: tempDir, t: t} ++} ++ ++func (c *cgroupTestUtil) cleanup() { ++ os.RemoveAll(c.tempDir) ++} ++ ++// Write the specified contents on the mock of the specified cgroup files. ++func (c *cgroupTestUtil) writeFileContents(fileContents map[string]string) { ++ for file, contents := range fileContents { ++ err := writeFile(c.CgroupPath, file, contents) ++ if err != nil { ++ c.t.Fatal(err) ++ } ++ } ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/utils.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/utils.go +new file mode 100644 +index 0000000000..5ff0a16150 +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/utils.go +@@ -0,0 +1,78 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "errors" ++ "fmt" ++ "io/ioutil" ++ "path/filepath" ++ "strconv" ++ "strings" ++) ++ ++var ( ++ ErrNotValidFormat = errors.New("line is not a valid key value format") ++) ++ ++// Saturates negative values at zero and returns a uint64. ++// Due to kernel bugs, some of the memory cgroup stats can be negative. ++func parseUint(s string, base, bitSize int) (uint64, error) { ++ value, err := strconv.ParseUint(s, base, bitSize) ++ if err != nil { ++ intValue, intErr := strconv.ParseInt(s, base, bitSize) ++ // 1. Handle negative values greater than MinInt64 (and) ++ // 2. Handle negative values lesser than MinInt64 ++ if intErr == nil && intValue < 0 { ++ return 0, nil ++ } else if intErr != nil && intErr.(*strconv.NumError).Err == strconv.ErrRange && intValue < 0 { ++ return 0, nil ++ } ++ ++ return value, err ++ } ++ ++ return value, nil ++} ++ ++// Parses a cgroup param and returns as name, value ++// i.e. "io_service_bytes 1234" will return as io_service_bytes, 1234 ++func getCgroupParamKeyValue(t string) (string, uint64, error) { ++ parts := strings.Fields(t) ++ switch len(parts) { ++ case 2: ++ value, err := parseUint(parts[1], 10, 64) ++ if err != nil { ++ return "", 0, fmt.Errorf("unable to convert param value (%q) to uint64: %v", parts[1], err) ++ } ++ ++ return parts[0], value, nil ++ default: ++ return "", 0, ErrNotValidFormat ++ } ++} ++ ++// Gets a single uint64 value from the specified cgroup file. ++func getCgroupParamUint(cgroupPath, cgroupFile string) (uint64, error) { ++ fileName := filepath.Join(cgroupPath, cgroupFile) ++ contents, err := ioutil.ReadFile(fileName) ++ if err != nil { ++ return 0, err ++ } ++ ++ res, err := parseUint(strings.TrimSpace(string(contents)), 10, 64) ++ if err != nil { ++ return res, fmt.Errorf("unable to parse %q as a uint from Cgroup file %q", string(contents), fileName) ++ } ++ return res, nil ++} ++ ++// Gets a string value from the specified cgroup file ++func getCgroupParamString(cgroupPath, cgroupFile string) (string, error) { ++ contents, err := ioutil.ReadFile(filepath.Join(cgroupPath, cgroupFile)) ++ if err != nil { ++ return "", err ++ } ++ ++ return strings.TrimSpace(string(contents)), nil ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/utils_test.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/utils_test.go +new file mode 100644 +index 0000000000..99cdc18e07 +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/utils_test.go +@@ -0,0 +1,97 @@ ++// +build linux ++ ++package fs ++ ++import ( ++ "io/ioutil" ++ "math" ++ "os" ++ "path/filepath" ++ "strconv" ++ "testing" ++) ++ ++const ( ++ cgroupFile = "cgroup.file" ++ floatValue = 2048.0 ++ floatString = "2048" ++) ++ ++func TestGetCgroupParamsInt(t *testing.T) { ++ // Setup tempdir. ++ tempDir, err := ioutil.TempDir("", "cgroup_utils_test") ++ if err != nil { ++ t.Fatal(err) ++ } ++ defer os.RemoveAll(tempDir) ++ tempFile := filepath.Join(tempDir, cgroupFile) ++ ++ // Success. ++ err = ioutil.WriteFile(tempFile, []byte(floatString), 0755) ++ if err != nil { ++ t.Fatal(err) ++ } ++ value, err := getCgroupParamUint(tempDir, cgroupFile) ++ if err != nil { ++ t.Fatal(err) ++ } else if value != floatValue { ++ t.Fatalf("Expected %d to equal %f", value, floatValue) ++ } ++ ++ // Success with new line. ++ err = ioutil.WriteFile(tempFile, []byte(floatString+"\n"), 0755) ++ if err != nil { ++ t.Fatal(err) ++ } ++ value, err = getCgroupParamUint(tempDir, cgroupFile) ++ if err != nil { ++ t.Fatal(err) ++ } else if value != floatValue { ++ t.Fatalf("Expected %d to equal %f", value, floatValue) ++ } ++ ++ // Success with negative values ++ err = ioutil.WriteFile(tempFile, []byte("-12345"), 0755) ++ if err != nil { ++ t.Fatal(err) ++ } ++ value, err = getCgroupParamUint(tempDir, cgroupFile) ++ if err != nil { ++ t.Fatal(err) ++ } else if value != 0 { ++ t.Fatalf("Expected %d to equal %d", value, 0) ++ } ++ ++ // Success with negative values lesser than min int64 ++ s := strconv.FormatFloat(math.MinInt64, 'f', -1, 64) ++ err = ioutil.WriteFile(tempFile, []byte(s), 0755) ++ if err != nil { ++ t.Fatal(err) ++ } ++ value, err = getCgroupParamUint(tempDir, cgroupFile) ++ if err != nil { ++ t.Fatal(err) ++ } else if value != 0 { ++ t.Fatalf("Expected %d to equal %d", value, 0) ++ } ++ ++ // Not a float. ++ err = ioutil.WriteFile(tempFile, []byte("not-a-float"), 0755) ++ if err != nil { ++ t.Fatal(err) ++ } ++ _, err = getCgroupParamUint(tempDir, cgroupFile) ++ if err == nil { ++ t.Fatal("Expecting error, got none") ++ } ++ ++ // Unknown file. ++ err = os.Remove(tempFile) ++ if err != nil { ++ t.Fatal(err) ++ } ++ _, err = getCgroupParamUint(tempDir, cgroupFile) ++ if err == nil { ++ t.Fatal("Expecting error, got none") ++ } ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/rootless/rootless.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/rootless/rootless.go +new file mode 100644 +index 0000000000..b1efbfd999 +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/rootless/rootless.go +@@ -0,0 +1,128 @@ ++// +build linux ++ ++package rootless ++ ++import ( ++ "fmt" ++ ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++ "github.com/opencontainers/runc/libcontainer/cgroups/fs" ++ "github.com/opencontainers/runc/libcontainer/configs" ++ "github.com/opencontainers/runc/libcontainer/configs/validate" ++) ++ ++// TODO: This is copied from libcontainer/cgroups/fs, which duplicates this code ++// needlessly. We should probably export this list. ++ ++var subsystems = []subsystem{ ++ &fs.CpusetGroup{}, ++ &fs.DevicesGroup{}, ++ &fs.MemoryGroup{}, ++ &fs.CpuGroup{}, ++ &fs.CpuacctGroup{}, ++ &fs.PidsGroup{}, ++ &fs.BlkioGroup{}, ++ &fs.HugetlbGroup{}, ++ &fs.NetClsGroup{}, ++ &fs.NetPrioGroup{}, ++ &fs.PerfEventGroup{}, ++ &fs.FreezerGroup{}, ++ &fs.NameGroup{GroupName: "name=systemd"}, ++} ++ ++type subsystem interface { ++ // Name returns the name of the subsystem. ++ Name() string ++ ++ // Returns the stats, as 'stats', corresponding to the cgroup under 'path'. ++ GetStats(path string, stats *cgroups.Stats) error ++} ++ ++// The noop cgroup manager is used for rootless containers, because we currently ++// cannot manage cgroups if we are in a rootless setup. This manager is chosen ++// by factory if we are in rootless mode. We error out if any cgroup options are ++// set in the config -- this may change in the future with upcoming kernel features ++// like the cgroup namespace. ++ ++type Manager struct { ++ Cgroups *configs.Cgroup ++ Paths map[string]string ++} ++ ++func (m *Manager) Apply(pid int) error { ++ // If there are no cgroup settings, there's nothing to do. ++ if m.Cgroups == nil { ++ return nil ++ } ++ ++ // We can't set paths. ++ // TODO(cyphar): Implement the case where the runner of a rootless container ++ // owns their own cgroup, which would allow us to set up a ++ // cgroup for each path. ++ if m.Cgroups.Paths != nil { ++ return fmt.Errorf("cannot change cgroup path in rootless container") ++ } ++ ++ // We load the paths into the manager. ++ paths := make(map[string]string) ++ for _, sys := range subsystems { ++ name := sys.Name() ++ ++ path, err := cgroups.GetOwnCgroupPath(name) ++ if err != nil { ++ // Ignore paths we couldn't resolve. ++ continue ++ } ++ ++ paths[name] = path ++ } ++ ++ m.Paths = paths ++ return nil ++} ++ ++func (m *Manager) GetPaths() map[string]string { ++ return m.Paths ++} ++ ++func (m *Manager) Set(container *configs.Config) error { ++ // We have to re-do the validation here, since someone might decide to ++ // update a rootless container. ++ return validate.New().Validate(container) ++} ++ ++func (m *Manager) GetPids() ([]int, error) { ++ dir, err := cgroups.GetOwnCgroupPath("devices") ++ if err != nil { ++ return nil, err ++ } ++ return cgroups.GetPids(dir) ++} ++ ++func (m *Manager) GetAllPids() ([]int, error) { ++ dir, err := cgroups.GetOwnCgroupPath("devices") ++ if err != nil { ++ return nil, err ++ } ++ return cgroups.GetAllPids(dir) ++} ++ ++func (m *Manager) GetStats() (*cgroups.Stats, error) { ++ // TODO(cyphar): We can make this work if we figure out a way to allow usage ++ // of cgroups with a rootless container. While this doesn't ++ // actually require write access to a cgroup directory, the ++ // statistics are not useful if they can be affected by ++ // non-container processes. ++ return nil, fmt.Errorf("cannot get cgroup stats in rootless container") ++} ++ ++func (m *Manager) Freeze(state configs.FreezerState) error { ++ // TODO(cyphar): We can make this work if we figure out a way to allow usage ++ // of cgroups with a rootless container. ++ return fmt.Errorf("cannot use freezer cgroup in rootless container") ++} ++ ++func (m *Manager) Destroy() error { ++ // We don't have to do anything here because we didn't do any setup. ++ return nil ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/stats.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/stats.go +index 8eeedc55b0..e11c5c7416 100644 +--- a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/stats.go ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/stats.go +@@ -64,6 +64,13 @@ type PidsStats struct { + Limit uint64 `json:"limit,omitempty"` + } + ++type FilesStats struct { ++ // number of pids in the cgroup ++ Usage uint64 `json:"usage,omitempty"` ++ // active pids hard limit ++ Limit uint64 `json:"limit,omitempty"` ++} ++ + type BlkioStatEntry struct { + Major uint64 `json:"major,omitempty"` + Minor uint64 `json:"minor,omitempty"` +@@ -96,6 +103,7 @@ type Stats struct { + CpuStats CpuStats `json:"cpu_stats,omitempty"` + MemoryStats MemoryStats `json:"memory_stats,omitempty"` + PidsStats PidsStats `json:"pids_stats,omitempty"` ++ FilesStats FilesStats `json:"files_stats,omitempty"` + BlkioStats BlkioStats `json:"blkio_stats,omitempty"` + // the map is in the format "size of hugepage: stats of the hugepage" + HugetlbStats map[string]HugetlbStats `json:"hugetlb_stats,omitempty"` +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_nosystemd.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_nosystemd.go +new file mode 100644 +index 0000000000..7de9ae6050 +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_nosystemd.go +@@ -0,0 +1,55 @@ ++// +build !linux ++ ++package systemd ++ ++import ( ++ "fmt" ++ ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++ "github.com/opencontainers/runc/libcontainer/configs" ++) ++ ++type Manager struct { ++ Cgroups *configs.Cgroup ++ Paths map[string]string ++} ++ ++func UseSystemd() bool { ++ return false ++} ++ ++func (m *Manager) Apply(pid int) error { ++ return fmt.Errorf("Systemd not supported") ++} ++ ++func (m *Manager) GetPids() ([]int, error) { ++ return nil, fmt.Errorf("Systemd not supported") ++} ++ ++func (m *Manager) GetAllPids() ([]int, error) { ++ return nil, fmt.Errorf("Systemd not supported") ++} ++ ++func (m *Manager) Destroy() error { ++ return fmt.Errorf("Systemd not supported") ++} ++ ++func (m *Manager) GetPaths() map[string]string { ++ return nil ++} ++ ++func (m *Manager) GetStats() (*cgroups.Stats, error) { ++ return nil, fmt.Errorf("Systemd not supported") ++} ++ ++func (m *Manager) Set(container *configs.Config) error { ++ return nil, fmt.Errorf("Systemd not supported") ++} ++ ++func (m *Manager) Freeze(state configs.FreezerState) error { ++ return fmt.Errorf("Systemd not supported") ++} ++ ++func Freeze(c *configs.Cgroup, state configs.FreezerState) error { ++ return fmt.Errorf("Systemd not supported") ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_systemd.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_systemd.go +new file mode 100644 +index 0000000000..0411b72390 +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_systemd.go +@@ -0,0 +1,556 @@ ++// +build linux ++ ++package systemd ++ ++import ( ++ "errors" ++ "fmt" ++ "os" ++ "path/filepath" ++ "strings" ++ "sync" ++ "time" ++ ++ systemdDbus "github.com/coreos/go-systemd/dbus" ++ systemdUtil "github.com/coreos/go-systemd/util" ++ "github.com/godbus/dbus" ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++ "github.com/opencontainers/runc/libcontainer/cgroups/fs" ++ "github.com/opencontainers/runc/libcontainer/configs" ++) ++ ++type Manager struct { ++ mu sync.Mutex ++ Cgroups *configs.Cgroup ++ Paths map[string]string ++} ++ ++type subsystem interface { ++ // Name returns the name of the subsystem. ++ Name() string ++ // Returns the stats, as 'stats', corresponding to the cgroup under 'path'. ++ GetStats(path string, stats *cgroups.Stats) error ++ // Set the cgroup represented by cgroup. ++ Set(path string, cgroup *configs.Cgroup) error ++} ++ ++var errSubsystemDoesNotExist = errors.New("cgroup: subsystem does not exist") ++ ++type subsystemSet []subsystem ++ ++func (s subsystemSet) Get(name string) (subsystem, error) { ++ for _, ss := range s { ++ if ss.Name() == name { ++ return ss, nil ++ } ++ } ++ return nil, errSubsystemDoesNotExist ++} ++ ++var subsystems = subsystemSet{ ++ &fs.CpusetGroup{}, ++ &fs.DevicesGroup{}, ++ &fs.MemoryGroup{}, ++ &fs.CpuGroup{}, ++ &fs.CpuacctGroup{}, ++ &fs.PidsGroup{}, ++ &fs.FilesGroup{}, ++ &fs.BlkioGroup{}, ++ &fs.HugetlbGroup{}, ++ &fs.PerfEventGroup{}, ++ &fs.FreezerGroup{}, ++ &fs.NetPrioGroup{}, ++ &fs.NetClsGroup{}, ++ &fs.NameGroup{GroupName: "name=systemd"}, ++} ++ ++const ( ++ testScopeWait = 4 ++ testSliceWait = 4 ++) ++ ++var ( ++ connLock sync.Mutex ++ theConn *systemdDbus.Conn ++ hasStartTransientUnit bool ++ hasStartTransientSliceUnit bool ++ hasTransientDefaultDependencies bool ++ hasDelegate bool ++) ++ ++func newProp(name string, units interface{}) systemdDbus.Property { ++ return systemdDbus.Property{ ++ Name: name, ++ Value: dbus.MakeVariant(units), ++ } ++} ++ ++func UseSystemd() bool { ++ if !systemdUtil.IsRunningSystemd() { ++ return false ++ } ++ ++ connLock.Lock() ++ defer connLock.Unlock() ++ ++ if theConn == nil { ++ var err error ++ theConn, err = systemdDbus.New() ++ if err != nil { ++ return false ++ } ++ ++ // Assume we have StartTransientUnit ++ hasStartTransientUnit = true ++ ++ // But if we get UnknownMethod error we don't ++ if _, err := theConn.StartTransientUnit("test.scope", "invalid", nil, nil); err != nil { ++ if dbusError, ok := err.(dbus.Error); ok { ++ if dbusError.Name == "org.freedesktop.DBus.Error.UnknownMethod" { ++ hasStartTransientUnit = false ++ return hasStartTransientUnit ++ } ++ } ++ } ++ ++ // Ensure the scope name we use doesn't exist. Use the Pid to ++ // avoid collisions between multiple libcontainer users on a ++ // single host. ++ scope := fmt.Sprintf("libcontainer-%d-systemd-test-default-dependencies.scope", os.Getpid()) ++ testScopeExists := true ++ for i := 0; i <= testScopeWait; i++ { ++ if _, err := theConn.StopUnit(scope, "replace", nil); err != nil { ++ if dbusError, ok := err.(dbus.Error); ok { ++ if strings.Contains(dbusError.Name, "org.freedesktop.systemd1.NoSuchUnit") { ++ testScopeExists = false ++ break ++ } ++ } ++ } ++ time.Sleep(time.Millisecond) ++ } ++ ++ // Bail out if we can't kill this scope without testing for DefaultDependencies ++ if testScopeExists { ++ return hasStartTransientUnit ++ } ++ ++ // Assume StartTransientUnit on a scope allows DefaultDependencies ++ hasTransientDefaultDependencies = true ++ ddf := newProp("DefaultDependencies", false) ++ if _, err := theConn.StartTransientUnit(scope, "replace", []systemdDbus.Property{ddf}, nil); err != nil { ++ if dbusError, ok := err.(dbus.Error); ok { ++ if strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.PropertyReadOnly") { ++ hasTransientDefaultDependencies = false ++ } ++ } ++ } ++ ++ // Not critical because of the stop unit logic above. ++ theConn.StopUnit(scope, "replace", nil) ++ ++ // Assume StartTransientUnit on a scope allows Delegate ++ hasDelegate = true ++ dl := newProp("Delegate", true) ++ if _, err := theConn.StartTransientUnit(scope, "replace", []systemdDbus.Property{dl}, nil); err != nil { ++ if dbusError, ok := err.(dbus.Error); ok { ++ if strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.PropertyReadOnly") { ++ hasDelegate = false ++ } ++ } ++ } ++ ++ // Assume we have the ability to start a transient unit as a slice ++ // This was broken until systemd v229, but has been back-ported on RHEL environments >= 219 ++ // For details, see: https://bugzilla.redhat.com/show_bug.cgi?id=1370299 ++ hasStartTransientSliceUnit = true ++ ++ // To ensure simple clean-up, we create a slice off the root with no hierarchy ++ slice := fmt.Sprintf("libcontainer_%d_systemd_test_default.slice", os.Getpid()) ++ if _, err := theConn.StartTransientUnit(slice, "replace", nil, nil); err != nil { ++ if _, ok := err.(dbus.Error); ok { ++ hasStartTransientSliceUnit = false ++ } ++ } ++ ++ for i := 0; i <= testSliceWait; i++ { ++ if _, err := theConn.StopUnit(slice, "replace", nil); err != nil { ++ if dbusError, ok := err.(dbus.Error); ok { ++ if strings.Contains(dbusError.Name, "org.freedesktop.systemd1.NoSuchUnit") { ++ hasStartTransientSliceUnit = false ++ break ++ } ++ } ++ } else { ++ break ++ } ++ time.Sleep(time.Millisecond) ++ } ++ ++ // Not critical because of the stop unit logic above. ++ theConn.StopUnit(scope, "replace", nil) ++ theConn.StopUnit(slice, "replace", nil) ++ } ++ return hasStartTransientUnit ++} ++ ++func (m *Manager) Apply(pid int) error { ++ var ( ++ c = m.Cgroups ++ unitName = getUnitName(c) ++ slice = "system.slice" ++ properties []systemdDbus.Property ++ ) ++ ++ if c.Paths != nil { ++ paths := make(map[string]string) ++ for name, path := range c.Paths { ++ _, err := getSubsystemPath(m.Cgroups, name) ++ if err != nil { ++ // Don't fail if a cgroup hierarchy was not found, just skip this subsystem ++ if cgroups.IsNotFound(err) { ++ continue ++ } ++ return err ++ } ++ paths[name] = path ++ } ++ m.Paths = paths ++ return cgroups.EnterPid(m.Paths, pid) ++ } ++ ++ if c.Parent != "" { ++ slice = c.Parent ++ } ++ ++ properties = append(properties, systemdDbus.PropDescription("libcontainer container "+c.Name)) ++ ++ // if we create a slice, the parent is defined via a Wants= ++ if strings.HasSuffix(unitName, ".slice") { ++ // This was broken until systemd v229, but has been back-ported on RHEL environments >= 219 ++ if !hasStartTransientSliceUnit { ++ return fmt.Errorf("systemd version does not support ability to start a slice as transient unit") ++ } ++ properties = append(properties, systemdDbus.PropWants(slice)) ++ } else { ++ // otherwise, we use Slice= ++ properties = append(properties, systemdDbus.PropSlice(slice)) ++ } ++ ++ // only add pid if its valid, -1 is used w/ general slice creation. ++ if pid != -1 { ++ properties = append(properties, newProp("PIDs", []uint32{uint32(pid)})) ++ } ++ ++ if hasDelegate { ++ // This is only supported on systemd versions 218 and above. ++ properties = append(properties, newProp("Delegate", true)) ++ } ++ ++ // Always enable accounting, this gets us the same behaviour as the fs implementation, ++ // plus the kernel has some problems with joining the memory cgroup at a later time. ++ properties = append(properties, ++ newProp("MemoryAccounting", true), ++ newProp("CPUAccounting", true), ++ newProp("BlockIOAccounting", true)) ++ ++ if hasTransientDefaultDependencies { ++ properties = append(properties, ++ newProp("DefaultDependencies", false)) ++ } ++ ++ if c.Resources.Memory != 0 { ++ properties = append(properties, ++ newProp("MemoryLimit", c.Resources.Memory)) ++ } ++ ++ if c.Resources.CpuShares != 0 { ++ properties = append(properties, ++ newProp("CPUShares", c.Resources.CpuShares)) ++ } ++ ++ // cpu.cfs_quota_us and cpu.cfs_period_us are controlled by systemd. ++ if c.Resources.CpuQuota != 0 && c.Resources.CpuPeriod != 0 { ++ cpuQuotaPerSecUSec := uint64(c.Resources.CpuQuota*1000000) / c.Resources.CpuPeriod ++ properties = append(properties, ++ newProp("CPUQuotaPerSecUSec", cpuQuotaPerSecUSec)) ++ } ++ ++ if c.Resources.BlkioWeight != 0 { ++ properties = append(properties, ++ newProp("BlockIOWeight", uint64(c.Resources.BlkioWeight))) ++ } ++ ++ // We have to set kernel memory here, as we can't change it once ++ // processes have been attached to the cgroup. ++ if c.Resources.KernelMemory != 0 { ++ if err := setKernelMemory(c); err != nil { ++ return err ++ } ++ } ++ ++ if _, err := theConn.StartTransientUnit(unitName, "replace", properties, nil); err != nil && !isUnitExists(err) { ++ return err ++ } ++ ++ if err := joinCgroups(c, pid); err != nil { ++ return err ++ } ++ ++ paths := make(map[string]string) ++ for _, s := range subsystems { ++ subsystemPath, err := getSubsystemPath(m.Cgroups, s.Name()) ++ if err != nil { ++ // Don't fail if a cgroup hierarchy was not found, just skip this subsystem ++ if cgroups.IsNotFound(err) { ++ continue ++ } ++ return err ++ } ++ paths[s.Name()] = subsystemPath ++ } ++ m.Paths = paths ++ return nil ++} ++ ++func (m *Manager) Destroy() error { ++ if m.Cgroups.Paths != nil { ++ return nil ++ } ++ m.mu.Lock() ++ defer m.mu.Unlock() ++ theConn.StopUnit(getUnitName(m.Cgroups), "replace", nil) ++ if err := cgroups.RemovePaths(m.Paths); err != nil { ++ return err ++ } ++ m.Paths = make(map[string]string) ++ return nil ++} ++ ++func (m *Manager) GetPaths() map[string]string { ++ m.mu.Lock() ++ paths := m.Paths ++ m.mu.Unlock() ++ return paths ++} ++ ++func join(c *configs.Cgroup, subsystem string, pid int) (string, error) { ++ path, err := getSubsystemPath(c, subsystem) ++ if err != nil { ++ return "", err ++ } ++ if err := os.MkdirAll(path, 0755); err != nil { ++ return "", err ++ } ++ if err := cgroups.WriteCgroupProc(path, pid); err != nil { ++ return "", err ++ } ++ return path, nil ++} ++ ++func joinCgroups(c *configs.Cgroup, pid int) error { ++ for _, sys := range subsystems { ++ name := sys.Name() ++ switch name { ++ case "name=systemd": ++ // let systemd handle this ++ break ++ case "cpuset": ++ path, err := getSubsystemPath(c, name) ++ if err != nil && !cgroups.IsNotFound(err) { ++ return err ++ } ++ s := &fs.CpusetGroup{} ++ if err := s.ApplyDir(path, c, pid); err != nil { ++ return err ++ } ++ break ++ default: ++ _, err := join(c, name, pid) ++ if err != nil { ++ // Even if it's `not found` error, we'll return err ++ // because devices cgroup is hard requirement for ++ // container security. ++ if name == "devices" { ++ return err ++ } ++ // For other subsystems, omit the `not found` error ++ // because they are optional. ++ if !cgroups.IsNotFound(err) { ++ return err ++ } ++ } ++ } ++ } ++ ++ return nil ++} ++ ++// systemd represents slice hierarchy using `-`, so we need to follow suit when ++// generating the path of slice. Essentially, test-a-b.slice becomes ++// test.slice/test-a.slice/test-a-b.slice. ++func ExpandSlice(slice string) (string, error) { ++ suffix := ".slice" ++ // Name has to end with ".slice", but can't be just ".slice". ++ if len(slice) < len(suffix) || !strings.HasSuffix(slice, suffix) { ++ return "", fmt.Errorf("invalid slice name: %s", slice) ++ } ++ ++ // Path-separators are not allowed. ++ if strings.Contains(slice, "/") { ++ return "", fmt.Errorf("invalid slice name: %s", slice) ++ } ++ ++ var path, prefix string ++ sliceName := strings.TrimSuffix(slice, suffix) ++ // if input was -.slice, we should just return root now ++ if sliceName == "-" { ++ return "/", nil ++ } ++ for _, component := range strings.Split(sliceName, "-") { ++ // test--a.slice isn't permitted, nor is -test.slice. ++ if component == "" { ++ return "", fmt.Errorf("invalid slice name: %s", slice) ++ } ++ ++ // Append the component to the path and to the prefix. ++ path += prefix + component + suffix + "/" ++ prefix += component + "-" ++ } ++ ++ return path, nil ++} ++ ++func getSubsystemPath(c *configs.Cgroup, subsystem string) (string, error) { ++ mountpoint, err := cgroups.FindCgroupMountpoint(subsystem) ++ if err != nil { ++ return "", err ++ } ++ ++ initPath, err := cgroups.GetInitCgroup(subsystem) ++ if err != nil { ++ return "", err ++ } ++ // if pid 1 is systemd 226 or later, it will be in init.scope, not the root ++ initPath = strings.TrimSuffix(filepath.Clean(initPath), "init.scope") ++ ++ slice := "system.slice" ++ if c.Parent != "" { ++ slice = c.Parent ++ } ++ ++ slice, err = ExpandSlice(slice) ++ if err != nil { ++ return "", err ++ } ++ ++ return filepath.Join(mountpoint, initPath, slice, getUnitName(c)), nil ++} ++ ++func (m *Manager) Freeze(state configs.FreezerState) error { ++ path, err := getSubsystemPath(m.Cgroups, "freezer") ++ if err != nil { ++ return err ++ } ++ prevState := m.Cgroups.Resources.Freezer ++ m.Cgroups.Resources.Freezer = state ++ freezer, err := subsystems.Get("freezer") ++ if err != nil { ++ return err ++ } ++ err = freezer.Set(path, m.Cgroups) ++ if err != nil { ++ m.Cgroups.Resources.Freezer = prevState ++ return err ++ } ++ return nil ++} ++ ++func (m *Manager) GetPids() ([]int, error) { ++ path, err := getSubsystemPath(m.Cgroups, "devices") ++ if err != nil { ++ return nil, err ++ } ++ return cgroups.GetPids(path) ++} ++ ++func (m *Manager) GetAllPids() ([]int, error) { ++ path, err := getSubsystemPath(m.Cgroups, "devices") ++ if err != nil { ++ return nil, err ++ } ++ return cgroups.GetAllPids(path) ++} ++ ++func (m *Manager) GetStats() (*cgroups.Stats, error) { ++ m.mu.Lock() ++ defer m.mu.Unlock() ++ stats := cgroups.NewStats() ++ for name, path := range m.Paths { ++ sys, err := subsystems.Get(name) ++ if err == errSubsystemDoesNotExist || !cgroups.PathExists(path) { ++ continue ++ } ++ if err := sys.GetStats(path, stats); err != nil { ++ return nil, err ++ } ++ } ++ ++ return stats, nil ++} ++ ++func (m *Manager) Set(container *configs.Config) error { ++ // If Paths are set, then we are just joining cgroups paths ++ // and there is no need to set any values. ++ if m.Cgroups.Paths != nil { ++ return nil ++ } ++ for _, sys := range subsystems { ++ // Get the subsystem path, but don't error out for not found cgroups. ++ path, err := getSubsystemPath(container.Cgroups, sys.Name()) ++ if err != nil && !cgroups.IsNotFound(err) { ++ return err ++ } ++ ++ if err := sys.Set(path, container.Cgroups); err != nil { ++ return err ++ } ++ } ++ ++ if m.Paths["cpu"] != "" { ++ if err := fs.CheckCpushares(m.Paths["cpu"], container.Cgroups.Resources.CpuShares); err != nil { ++ return err ++ } ++ } ++ return nil ++} ++ ++func getUnitName(c *configs.Cgroup) string { ++ // by default, we create a scope unless the user explicitly asks for a slice. ++ if !strings.HasSuffix(c.Name, ".slice") { ++ return fmt.Sprintf("%s-%s.scope", c.ScopePrefix, c.Name) ++ } ++ return c.Name ++} ++ ++func setKernelMemory(c *configs.Cgroup) error { ++ path, err := getSubsystemPath(c, "memory") ++ if err != nil && !cgroups.IsNotFound(err) { ++ return err ++ } ++ ++ if err := os.MkdirAll(path, 0755); err != nil { ++ return err ++ } ++ return fs.EnableKernelMemoryAccounting(path) ++} ++ ++// isUnitExists returns true if the error is that a systemd unit already exists. ++func isUnitExists(err error) bool { ++ if err != nil { ++ if dbusError, ok := err.(dbus.Error); ok { ++ return strings.Contains(dbusError.Name, "org.freedesktop.systemd1.UnitExists") ++ } ++ } ++ return false ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_linux.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_linux.go +index e15a662f52..c5634f597e 100644 +--- a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_linux.go ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_linux.go +@@ -81,6 +81,9 @@ type Resources struct { + // Process limit; set <= `0' to disable limit. + PidsLimit int64 `json:"pids_limit"` + ++ // Process open files limit. ++ FilesLimit int64 `json:"files_limit"` ++ + // Specifies per cgroup weight, range is from 10 to 1000. + BlkioWeight uint16 `json:"blkio_weight"` + +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/utils/cmsg.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/utils/cmsg.go +new file mode 100644 +index 0000000000..2cbb6491a7 +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/utils/cmsg.go +@@ -0,0 +1,95 @@ ++// +build linux ++ ++package utils ++ ++/* ++ * Copyright 2016, 2017 SUSE LLC ++ * ++ * 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. ++ */ ++ ++import ( ++ "fmt" ++ "os" ++ ++ "golang.org/x/sys/unix" ++) ++ ++// MaxSendfdLen is the maximum length of the name of a file descriptor being ++// sent using SendFd. The name of the file handle returned by RecvFd will never ++// be larger than this value. ++const MaxNameLen = 4096 ++ ++// oobSpace is the size of the oob slice required to store a single FD. Note ++// that unix.UnixRights appears to make the assumption that fd is always int32, ++// so sizeof(fd) = 4. ++var oobSpace = unix.CmsgSpace(4) ++ ++// RecvFd waits for a file descriptor to be sent over the given AF_UNIX ++// socket. The file name of the remote file descriptor will be recreated ++// locally (it is sent as non-auxiliary data in the same payload). ++func RecvFd(socket *os.File) (*os.File, error) { ++ // For some reason, unix.Recvmsg uses the length rather than the capacity ++ // when passing the msg_controllen and other attributes to recvmsg. So we ++ // have to actually set the length. ++ name := make([]byte, MaxNameLen) ++ oob := make([]byte, oobSpace) ++ ++ sockfd := socket.Fd() ++ n, oobn, _, _, err := unix.Recvmsg(int(sockfd), name, oob, 0) ++ if err != nil { ++ return nil, err ++ } ++ ++ if n >= MaxNameLen || oobn != oobSpace { ++ return nil, fmt.Errorf("recvfd: incorrect number of bytes read (n=%d oobn=%d)", n, oobn) ++ } ++ ++ // Truncate. ++ name = name[:n] ++ oob = oob[:oobn] ++ ++ scms, err := unix.ParseSocketControlMessage(oob) ++ if err != nil { ++ return nil, err ++ } ++ if len(scms) != 1 { ++ return nil, fmt.Errorf("recvfd: number of SCMs is not 1: %d", len(scms)) ++ } ++ scm := scms[0] ++ ++ fds, err := unix.ParseUnixRights(&scm) ++ if err != nil { ++ return nil, err ++ } ++ if len(fds) != 1 { ++ return nil, fmt.Errorf("recvfd: number of fds is not 1: %d", len(fds)) ++ } ++ fd := uintptr(fds[0]) ++ ++ return os.NewFile(fd, string(name)), nil ++} ++ ++// SendFd sends a file descriptor over the given AF_UNIX socket. In ++// addition, the file.Name() of the given file will also be sent as ++// non-auxiliary data in the same payload (allowing to send contextual ++// information for a file descriptor). ++func SendFd(socket, file *os.File) error { ++ name := []byte(file.Name()) ++ if len(name) >= MaxNameLen { ++ return fmt.Errorf("sendfd: filename too long: %s", file.Name()) ++ } ++ oob := unix.UnixRights(int(file.Fd())) ++ ++ return unix.Sendmsg(int(socket.Fd()), name, oob, nil, 0) ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go +new file mode 100644 +index 0000000000..2b35b9a7b6 +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go +@@ -0,0 +1,126 @@ ++package utils ++ ++import ( ++ "crypto/rand" ++ "encoding/hex" ++ "encoding/json" ++ "io" ++ "os" ++ "path/filepath" ++ "strings" ++ "syscall" ++ "unsafe" ++) ++ ++const ( ++ exitSignalOffset = 128 ++) ++ ++// GenerateRandomName returns a new name joined with a prefix. This size ++// specified is used to truncate the randomly generated value ++func GenerateRandomName(prefix string, size int) (string, error) { ++ id := make([]byte, 32) ++ if _, err := io.ReadFull(rand.Reader, id); err != nil { ++ return "", err ++ } ++ if size > 64 { ++ size = 64 ++ } ++ return prefix + hex.EncodeToString(id)[:size], nil ++} ++ ++// ResolveRootfs ensures that the current working directory is ++// not a symlink and returns the absolute path to the rootfs ++func ResolveRootfs(uncleanRootfs string) (string, error) { ++ rootfs, err := filepath.Abs(uncleanRootfs) ++ if err != nil { ++ return "", err ++ } ++ return filepath.EvalSymlinks(rootfs) ++} ++ ++// ExitStatus returns the correct exit status for a process based on if it ++// was signaled or exited cleanly ++func ExitStatus(status syscall.WaitStatus) int { ++ if status.Signaled() { ++ return exitSignalOffset + int(status.Signal()) ++ } ++ return status.ExitStatus() ++} ++ ++// WriteJSON writes the provided struct v to w using standard json marshaling ++func WriteJSON(w io.Writer, v interface{}) error { ++ data, err := json.Marshal(v) ++ if err != nil { ++ return err ++ } ++ _, err = w.Write(data) ++ return err ++} ++ ++// CleanPath makes a path safe for use with filepath.Join. This is done by not ++// only cleaning the path, but also (if the path is relative) adding a leading ++// '/' and cleaning it (then removing the leading '/'). This ensures that a ++// path resulting from prepending another path will always resolve to lexically ++// be a subdirectory of the prefixed path. This is all done lexically, so paths ++// that include symlinks won't be safe as a result of using CleanPath. ++func CleanPath(path string) string { ++ // Deal with empty strings nicely. ++ if path == "" { ++ return "" ++ } ++ ++ // Ensure that all paths are cleaned (especially problematic ones like ++ // "/../../../../../" which can cause lots of issues). ++ path = filepath.Clean(path) ++ ++ // If the path isn't absolute, we need to do more processing to fix paths ++ // such as "../../../..//some/path". We also shouldn't convert absolute ++ // paths to relative ones. ++ if !filepath.IsAbs(path) { ++ path = filepath.Clean(string(os.PathSeparator) + path) ++ // This can't fail, as (by definition) all paths are relative to root. ++ path, _ = filepath.Rel(string(os.PathSeparator), path) ++ } ++ ++ // Clean the path again for good measure. ++ return filepath.Clean(path) ++} ++ ++// SearchLabels searches a list of key-value pairs for the provided key and ++// returns the corresponding value. The pairs must be separated with '='. ++func SearchLabels(labels []string, query string) string { ++ for _, l := range labels { ++ parts := strings.SplitN(l, "=", 2) ++ if len(parts) < 2 { ++ continue ++ } ++ if parts[0] == query { ++ return parts[1] ++ } ++ } ++ return "" ++} ++ ++// Annotations returns the bundle path and user defined annotations from the ++// libcontainer state. We need to remove the bundle because that is a label ++// added by libcontainer. ++func Annotations(labels []string) (bundle string, userAnnotations map[string]string) { ++ userAnnotations = make(map[string]string) ++ for _, l := range labels { ++ parts := strings.SplitN(l, "=", 2) ++ if len(parts) < 2 { ++ continue ++ } ++ if parts[0] == "bundle" { ++ bundle = parts[1] ++ } else { ++ userAnnotations[parts[0]] = parts[1] ++ } ++ } ++ return ++} ++ ++func GetIntSize() int { ++ return int(unsafe.Sizeof(1)) ++} +diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/utils/utils_unix.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/utils/utils_unix.go +new file mode 100644 +index 0000000000..7b798cc79d +--- /dev/null ++++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/utils/utils_unix.go +@@ -0,0 +1,43 @@ ++// +build !windows ++ ++package utils ++ ++import ( ++ "io/ioutil" ++ "os" ++ "strconv" ++ "syscall" ++) ++ ++func CloseExecFrom(minFd int) error { ++ fdList, err := ioutil.ReadDir("/proc/self/fd") ++ if err != nil { ++ return err ++ } ++ for _, fi := range fdList { ++ fd, err := strconv.Atoi(fi.Name()) ++ if err != nil { ++ // ignore non-numeric file names ++ continue ++ } ++ ++ if fd < minFd { ++ // ignore descriptors lower than our specified minimum ++ continue ++ } ++ ++ // intentionally ignore errors from syscall.CloseOnExec ++ syscall.CloseOnExec(fd) ++ // the cases where this might fail are basically file descriptors that have already been closed (including and especially the one that was created when ioutil.ReadDir did the "opendir" syscall) ++ } ++ return nil ++} ++ ++// NewSockPair returns a new unix socket pair ++func NewSockPair(name string) (parent *os.File, child *os.File, err error) { ++ fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0) ++ if err != nil { ++ return nil, nil, err ++ } ++ return os.NewFile(uintptr(fds[1]), name+"-p"), os.NewFile(uintptr(fds[0]), name+"-c"), nil ++} +-- +2.17.1 + diff --git a/patch/0068-pause-fix-docker-stop-stuck-on-paused-contain.patch b/patch/0068-pause-fix-docker-stop-stuck-on-paused-contain.patch new file mode 100644 index 0000000..c184840 --- /dev/null +++ b/patch/0068-pause-fix-docker-stop-stuck-on-paused-contain.patch @@ -0,0 +1,54 @@ +From 634d7c920176c726b8d32d8aee50c8073f4892a3 Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Sun, 20 Jan 2019 15:35:57 +0800 +Subject: [PATCH 068/111] pause: fix docker stop stuck on paused + container + +reason: docker-18.09 support docker stop while container is paused. +ref 7aed75f09c * Allow stopping of paused container. + +now docker function is moved to docker, so we still have to check +container paused status. + +Change-Id: I60731c76808abb5a76daf96c2aba656d73a87e96 +Signed-off-by: jingrui +--- + components/engine/daemon/kill.go | 5 +++++ + components/engine/daemon/unpause.go | 5 +++++ + 2 files changed, 10 insertions(+) + +diff --git a/components/engine/daemon/kill.go b/components/engine/daemon/kill.go +index 3e6457e952..5b2e497604 100644 +--- a/components/engine/daemon/kill.go ++++ b/components/engine/daemon/kill.go +@@ -66,6 +66,11 @@ func (daemon *Daemon) killWithSignal(container *containerpkg.Container, sig int) + + daemon.stopHealthchecks(container) + ++ // We could unpause the container for them rather than returning this error ++ if container.Paused { ++ return fmt.Errorf("Container %s is paused. Unpause the container before stopping or killing", container.ID) ++ } ++ + if !container.Running { + return errNotRunning(container.ID) + } +diff --git a/components/engine/daemon/unpause.go b/components/engine/daemon/unpause.go +index 290d2b1d0c..2d3c056566 100644 +--- a/components/engine/daemon/unpause.go ++++ b/components/engine/daemon/unpause.go +@@ -23,6 +23,11 @@ func (daemon *Daemon) containerUnpause(container *container.Container) error { + container.Lock() + defer container.Unlock() + ++ // We cannot unpause the container which is not running ++ if !container.Running { ++ return fmt.Errorf("Container %s is not running", container.ID) ++ } ++ + // We cannot unpause the container which is not paused + if !container.Paused { + return fmt.Errorf("Container %s is not paused", container.ID) +-- +2.17.1 + diff --git a/patch/0069-pause-fix-log-pause-unpause-event-twice.patch b/patch/0069-pause-fix-log-pause-unpause-event-twice.patch new file mode 100644 index 0000000..b7b6285 --- /dev/null +++ b/patch/0069-pause-fix-log-pause-unpause-event-twice.patch @@ -0,0 +1,108 @@ +From a6db97d2574d509b2077236ff48d70c3469a042e Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Sun, 20 Jan 2019 16:39:08 +0800 +Subject: [PATCH 069/111] pause: fix log pause/unpause event twice + +reason: fix pause/unpause log event twice + +Change-Id: If136d713afd37806256500d4760d0a344f7c5d92 +Signed-off-by: jingrui +--- + components/engine/daemon/monitor.go | 36 ++++++++++++++++++----------- + components/engine/daemon/pause.go | 7 ------ + components/engine/daemon/unpause.go | 6 ----- + 3 files changed, 22 insertions(+), 27 deletions(-) + +diff --git a/components/engine/daemon/monitor.go b/components/engine/daemon/monitor.go +index 807cdcaa89..51159eb76d 100644 +--- a/components/engine/daemon/monitor.go ++++ b/components/engine/daemon/monitor.go +@@ -167,28 +167,36 @@ func (daemon *Daemon) ProcessEvent(id string, e libcontainerd.EventType, ei libc + c.Lock() + defer c.Unlock() + +- if !c.Paused { +- c.Paused = true +- daemon.setStateCounter(c) +- daemon.updateHealthMonitor(c) +- if err := c.CheckpointTo(daemon.containersReplica); err != nil { +- return err ++ if daemon.IsNativeRuntime(c.HostConfig.Runtime) { ++ logrus.Infof("Pause is moved to docker, skip.") ++ } else { ++ if !c.Paused { ++ c.Paused = true ++ daemon.setStateCounter(c) ++ daemon.updateHealthMonitor(c) ++ if err := c.CheckpointTo(daemon.containersReplica); err != nil { ++ return err ++ } ++ daemon.LogContainerEvent(c, "pause") + } +- daemon.LogContainerEvent(c, "pause") + } + case libcontainerd.EventResumed: + c.Lock() + defer c.Unlock() + +- if c.Paused { +- c.Paused = false +- daemon.setStateCounter(c) +- daemon.updateHealthMonitor(c) ++ if daemon.IsNativeRuntime(c.HostConfig.Runtime) { ++ logrus.Infof("Pause is moved to docker, skip.") ++ } else { ++ if c.Paused { ++ c.Paused = false ++ daemon.setStateCounter(c) ++ daemon.updateHealthMonitor(c) + +- if err := c.CheckpointTo(daemon.containersReplica); err != nil { +- return err ++ if err := c.CheckpointTo(daemon.containersReplica); err != nil { ++ return err ++ } ++ daemon.LogContainerEvent(c, "unpause") + } +- daemon.LogContainerEvent(c, "unpause") + } + } + return nil +diff --git a/components/engine/daemon/pause.go b/components/engine/daemon/pause.go +index 6f9d8b0f70..494aa326d9 100644 +--- a/components/engine/daemon/pause.go ++++ b/components/engine/daemon/pause.go +@@ -48,13 +48,6 @@ func (daemon *Daemon) containerPause(container *container.Container) error { + if err := freezer.Pause(); err != nil { + return fmt.Errorf("Cannot pause container %s: %v", container.ID, err) + } +- +- container.Paused = true +- daemon.setStateCounter(container) +- if err := container.CheckpointTo(daemon.containersReplica); err != nil { +- return err +- } +- daemon.LogContainerEvent(container, "pause") + } else { + if err := daemon.containerd.Pause(context.Background(), container.ID); err != nil { + return fmt.Errorf("Cannot pause container %s: %s", container.ID, err) +diff --git a/components/engine/daemon/unpause.go b/components/engine/daemon/unpause.go +index 2d3c056566..a75589b888 100644 +--- a/components/engine/daemon/unpause.go ++++ b/components/engine/daemon/unpause.go +@@ -41,12 +41,6 @@ func (daemon *Daemon) containerUnpause(container *container.Container) error { + if err := freezer.Resume(); err != nil { + return fmt.Errorf("Cannot unpause container %s: %s", container.ID, err) + } +- container.Paused = false +- daemon.setStateCounter(container) +- if err := container.CheckpointTo(daemon.containersReplica); err != nil { +- return err +- } +- daemon.LogContainerEvent(container, "unpause") + } else { + if err := daemon.containerd.Resume(context.Background(), container.ID); err != nil { + return fmt.Errorf("Cannot unpause container %s: %s", container.ID, err) +-- +2.17.1 + diff --git a/patch/0070-test-fix-umask-make-syscall-test-failed.patch b/patch/0070-test-fix-umask-make-syscall-test-failed.patch new file mode 100644 index 0000000..dbc260c --- /dev/null +++ b/patch/0070-test-fix-umask-make-syscall-test-failed.patch @@ -0,0 +1,39 @@ +From 1bb39edbf403bf31aaece61510b43d9a6e781f7d Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Mon, 21 Jan 2019 17:57:03 +0800 +Subject: [PATCH 070/111] test: fix umask make syscall-test failed + +reason: set umask 0022 make sure add file's mode to image syscall-test +as exepected, so user has permission to exec. + +Change-Id: Iaad1bc328c81b77aa630bcb9f974aeee3a50ecbf +Signed-off-by: jingrui +--- + .../engine/integration-cli/fixtures_linux_daemon_test.go | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/components/engine/integration-cli/fixtures_linux_daemon_test.go b/components/engine/integration-cli/fixtures_linux_daemon_test.go +index 2387a9ebee..7be3c8b185 100644 +--- a/components/engine/integration-cli/fixtures_linux_daemon_test.go ++++ b/components/engine/integration-cli/fixtures_linux_daemon_test.go +@@ -9,6 +9,7 @@ import ( + "runtime" + "strings" + "sync" ++ "syscall" + + "github.com/docker/docker/integration-cli/checker" + "github.com/docker/docker/internal/test/fixtures/load" +@@ -28,6 +29,9 @@ var ensureSyscallTestOnce sync.Once + + func ensureSyscallTest(c *check.C) { + var doIt bool ++ mask := syscall.Umask(0022) ++ defer syscall.Umask(mask) ++ + ensureSyscallTestOnce.Do(func() { + doIt = true + }) +-- +2.17.1 + diff --git a/patch/0071-devmapper-Increace-udev-wait-timeout-to-185s.patch b/patch/0071-devmapper-Increace-udev-wait-timeout-to-185s.patch new file mode 100644 index 0000000..73b8c61 --- /dev/null +++ b/patch/0071-devmapper-Increace-udev-wait-timeout-to-185s.patch @@ -0,0 +1,44 @@ +From 6a40d11a3bc9be18cb50fb86099ff52a6d459335 Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Mon, 21 Jan 2019 18:57:43 +0800 +Subject: [PATCH 071/111] devmapper: Increace udev wait timeout to + 185s + +reason: cherry-pick commits to docker-18.09 + +cherry-pick e89615bd40c32f95d095c17efaf5258b15543080 from docker-1.11.2 + + The default time out of latest systemd-udevd on RTOS and EulerOS + is 180s, we use 185s. Even if the time out of systemd-udevd is 30s, + set 185s on docker side is also safe, it will just take a longer time + to return when time out really happen, we should make sure the timeout + on docker side is bigger than the timeout on systemd-udevd. + + Signed-off-by: Lei Jitang + +Change-Id: I05d722f22a0c7728ae1bbe23ab67c567d1694b67 +Signed-off-by: jingrui +--- + components/engine/daemon/graphdriver/devmapper/deviceset.go | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/components/engine/daemon/graphdriver/devmapper/deviceset.go b/components/engine/daemon/graphdriver/devmapper/deviceset.go +index f4dc589c6e..f5c0b044c4 100644 +--- a/components/engine/daemon/graphdriver/devmapper/deviceset.go ++++ b/components/engine/daemon/graphdriver/devmapper/deviceset.go +@@ -42,9 +42,9 @@ var ( + defaultUdevSyncOverride = false + maxDeviceID = 0xffffff // 24 bit, pool limit + deviceIDMapSz = (maxDeviceID + 1) / 8 +- // The default timeout is 30s from `man systemd-udevd`, we use 35 +- // just to make sure the timeout really happened in systemd-udevd +- defaultUdevWaitTimeout = 35 ++ // The default timeout for latest systemd on RTOS and EulerOS is 180s ++ // we use 185s to make sure the timeout really happened. ++ defaultUdevWaitTimeout = 185 + driverDeferredRemovalSupport = false + enableDeferredRemoval = false + enableDeferredDeletion = false +-- +2.17.1 + diff --git a/patch/0072-pause-fix-test-can-not-stop-paused-container-.patch b/patch/0072-pause-fix-test-can-not-stop-paused-container-.patch new file mode 100644 index 0000000..98a518a --- /dev/null +++ b/patch/0072-pause-fix-test-can-not-stop-paused-container-.patch @@ -0,0 +1,36 @@ +From 59d4dd7fdb0c33dc4c3be2330606ec7724cb368c Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Fri, 11 Jan 2019 17:16:21 +0800 +Subject: [PATCH 072/111] pause: fix test can not stop paused + container failed + +reason: pause function moved to docker and does not support stop paused +container. return error message when stop a paused container. + +Change-Id: Ia7b15877980088cdb4eaf9f7e3dd86667df0157b +Signed-off-by: jingrui +--- + components/engine/integration/container/pause_test.go | 7 ++++--- + 1 file changed, 4 insertions(+), 3 deletions(-) + +diff --git a/components/engine/integration/container/pause_test.go b/components/engine/integration/container/pause_test.go +index 8dd2d784b7..da274c89ce 100644 +--- a/components/engine/integration/container/pause_test.go ++++ b/components/engine/integration/container/pause_test.go +@@ -79,9 +79,10 @@ func TestPauseStopPausedContainer(t *testing.T) { + assert.NilError(t, err) + + err = client.ContainerStop(ctx, cID, nil) +- assert.NilError(t, err) +- +- poll.WaitOn(t, container.IsStopped(ctx, client, cID), poll.WithDelay(100*time.Millisecond)) ++ // assert.NilError(t, err) ++ // poll.WaitOn(t, container.IsStopped(ctx, client, cID), poll.WithDelay(100*time.Millisecond)) ++ assert.Check(t, is.ErrorContains(err, "is paused")) ++ client.ContainerUnpause(ctx, cID) + } + + func getEventActions(t *testing.T, messages <-chan events.Message, errs <-chan error) []string { +-- +2.17.1 + diff --git a/patch/0073-service-update-docker.service-with-old-revisi.patch b/patch/0073-service-update-docker.service-with-old-revisi.patch new file mode 100644 index 0000000..8c3831f --- /dev/null +++ b/patch/0073-service-update-docker.service-with-old-revisi.patch @@ -0,0 +1,68 @@ +From 67f1bfcb1df53fcfc044927eda37f0f8b45498dc Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Mon, 21 Jan 2019 22:25:42 +0800 +Subject: [PATCH 073/111] service: update docker.service with old + revision + +reason: use docker.service as the same one used in Euleros docker +1.11.2 and 17.06 + +Change-Id: I3deb3f3a24d837c98e535550545fd49ea0822629 +Signed-off-by: lujingxiao +--- + .../contrib/init/systemd/docker.service | 28 ++++++++----------- + 1 file changed, 11 insertions(+), 17 deletions(-) + +diff --git a/components/engine/contrib/init/systemd/docker.service b/components/engine/contrib/init/systemd/docker.service +index 517463172b..26c0fe648b 100644 +--- a/components/engine/contrib/init/systemd/docker.service ++++ b/components/engine/contrib/init/systemd/docker.service +@@ -1,34 +1,28 @@ + [Unit] + Description=Docker Application Container Engine + Documentation=https://docs.docker.com +-After=network-online.target docker.socket firewalld.service ++After=network-online.target firewalld.service + Wants=network-online.target +-Requires=docker.socket + + [Service] + Type=notify +-# the default is not to use systemd for cgroups because the delegate issues still +-# exists and systemd currently does not support the cgroup feature set required +-# for containers run by docker +-ExecStart=/usr/bin/dockerd -H fd:// ++EnvironmentFile=-/etc/sysconfig/docker ++EnvironmentFile=-/etc/sysconfig/docker-storage ++EnvironmentFile=-/etc/sysconfig/docker-network ++Environment=GOTRACEBACK=crash ++ ++ExecStart=/usr/bin/dockerd $OPTIONS \ ++ $DOCKER_STORAGE_OPTIONS \ ++ $DOCKER_NETWORK_OPTIONS \ ++ $INSECURE_REGISTRY + ExecReload=/bin/kill -s HUP $MAINPID + LimitNOFILE=1048576 +-# Having non-zero Limit*s causes performance problems due to accounting overhead +-# in the kernel. We recommend using cgroups to do container-local accounting. +-LimitNPROC=infinity ++LimitNPROC=1048576 + LimitCORE=infinity +-# Uncomment TasksMax if your systemd version supports it. +-# Only systemd 226 and above support this version. +-#TasksMax=infinity +-TimeoutStartSec=0 + # set delegate yes so that systemd does not reset the cgroups of docker containers + Delegate=yes + # kill only the docker process, not all processes in the cgroup + KillMode=process +-# restart the docker process if it exits prematurely +-Restart=on-failure +-StartLimitBurst=3 +-StartLimitInterval=60s + + [Install] + WantedBy=multi-user.target +-- +2.17.1 + diff --git a/patch/0076-version-add-EulerVersion.patch b/patch/0076-version-add-EulerVersion.patch new file mode 100644 index 0000000..253544b --- /dev/null +++ b/patch/0076-version-add-EulerVersion.patch @@ -0,0 +1,199 @@ +From 22f9a89d9ddcb03aa5b00dc9ad4372f776c5f73b Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Mon, 21 Jan 2019 23:05:47 +0800 +Subject: [PATCH 076/111] version: add EulerVersion + +reason: Add EulerVersion and update-version.sh + +Cherry-pick from 17.06 for +- 030513895 Add EulerVersion +- 102ee9ddc docker-17: add update-version tool for obs build + +Change-Id: I95c9b98bd35e243ce1074fa7dd0d477bdf7dcee9 +Signed-off-by: Lei Jitang +Signed-off-by: lujingxiao +--- + components/cli/cli/command/system/version.go | 4 ++++ + components/cli/cli/version.go | 1 + + components/cli/docker.Makefile | 3 ++- + components/cli/scripts/build/.variables | 2 ++ + .../github.com/docker/docker/api/types/types.go | 1 + + components/engine/api/types/types.go | 1 + + components/engine/daemon/info.go | 3 ++- + components/engine/dockerversion/version_lib.go | 1 + + components/engine/hack/make.sh | 1 + + components/engine/hack/make/.go-autogen | 1 + + 13 files changed, 34 insertions(+), 2 deletions(-) + +diff --git a/components/cli/cli/command/system/version.go b/components/cli/cli/command/system/version.go +index 7593b11b81..c13d135ae4 100644 +--- a/components/cli/cli/command/system/version.go ++++ b/components/cli/cli/command/system/version.go +@@ -23,6 +23,7 @@ import ( + var versionTemplate = `{{with .Client -}} + Client:{{if ne .Platform.Name ""}} {{.Platform.Name}}{{end}} + Version: {{.Version}} ++ EulerVersion: {{.EulerVersion}} + API version: {{.APIVersion}}{{if ne .APIVersion .DefaultAPIVersion}} (downgraded from {{.DefaultAPIVersion}}){{end}} + Go version: {{.GoVersion}} + Git commit: {{.GitCommit}} +@@ -38,6 +39,7 @@ Server:{{if ne .Platform.Name ""}} {{.Platform.Name}}{{end}} + {{$component.Name}}: + {{- if eq $component.Name "Engine" }} + Version: {{.Version}} ++ EulerVersion: {{index .Details "EulerVersion"}} + API version: {{index .Details "ApiVersion"}} (minimum version {{index .Details "MinAPIVersion"}}) + Go version: {{index .Details "GoVersion"}} + Git commit: {{index .Details "GitCommit"}} +@@ -69,6 +71,7 @@ type clientVersion struct { + Platform struct{ Name string } `json:",omitempty"` + + Version string ++ EulerVersion string + APIVersion string `json:"ApiVersion"` + DefaultAPIVersion string `json:"DefaultAPIVersion,omitempty"` + GitCommit string +@@ -135,6 +138,7 @@ func runVersion(dockerCli command.Cli, opts *versionOptions) error { + Client: clientVersion{ + Platform: struct{ Name string }{cli.PlatformName}, + Version: cli.Version, ++ EulerVersion: cli.EulerVersion, + APIVersion: dockerCli.Client().ClientVersion(), + DefaultAPIVersion: dockerCli.DefaultVersion(), + GoVersion: runtime.Version(), +diff --git a/components/cli/cli/version.go b/components/cli/cli/version.go +index c4120b9585..eeab90cad2 100644 +--- a/components/cli/cli/version.go ++++ b/components/cli/cli/version.go +@@ -5,6 +5,7 @@ package cli + var ( + PlatformName = "" + Version = "unknown-version" ++ EulerVersion = "unkonwn-version" + GitCommit = "unknown-commit" + BuildTime = "unknown-buildtime" + ) +diff --git a/components/cli/docker.Makefile b/components/cli/docker.Makefile +index 28819997bc..3284d8ce0a 100644 +--- a/components/cli/docker.Makefile ++++ b/components/cli/docker.Makefile +@@ -12,7 +12,8 @@ VALIDATE_IMAGE_NAME = docker-cli-shell-validate$(IMAGE_TAG) + E2E_IMAGE_NAME = docker-cli-e2e$(IMAGE_TAG) + MOUNTS = -v "$(CURDIR)":/go/src/github.com/docker/cli + VERSION = $(shell cat VERSION) +-ENVVARS = -e VERSION=$(VERSION) -e GITCOMMIT -e PLATFORM ++GITCOMMIT = $(shell git rev-parse --short HEAD 2> /dev/null || true) ++ENVVARS = -e VERSION=$(VERSION) -e GITCOMMIT=$(GITCOMMIT) -e PLATFORM + + # build docker image (dockerfiles/Dockerfile.build) + .PHONY: build_docker_image +diff --git a/components/cli/scripts/build/.variables b/components/cli/scripts/build/.variables +index 208f44c316..d50403266e 100755 +--- a/components/cli/scripts/build/.variables ++++ b/components/cli/scripts/build/.variables +@@ -3,6 +3,7 @@ set -eu + + PLATFORM=${PLATFORM:-} + VERSION=${VERSION:-"unknown-version"} ++EULERVERSION=${EULERVERSION:-$(cat VERSION-EULER)} + GITCOMMIT=${GITCOMMIT:-$(git rev-parse --short HEAD 2> /dev/null || true)} + BUILDTIME=${BUILDTIME:-$(date --utc --rfc-3339 ns 2> /dev/null | sed -e 's/ /T/')} + +@@ -17,6 +18,7 @@ export LDFLAGS="\ + -X \"github.com/docker/cli/cli.GitCommit=${GITCOMMIT}\" \ + -X \"github.com/docker/cli/cli.BuildTime=${BUILDTIME}\" \ + -X \"github.com/docker/cli/cli.Version=${VERSION}\" \ ++ -X \"github.com/docker/cli/cli.EulerVersion=${EULERVERSION}\" \ + ${LDFLAGS:-} \ + " + +diff --git a/components/cli/vendor/github.com/docker/docker/api/types/types.go b/components/cli/vendor/github.com/docker/docker/api/types/types.go +index 2fb6c5478b..56f556cad7 100644 +--- a/components/cli/vendor/github.com/docker/docker/api/types/types.go ++++ b/components/cli/vendor/github.com/docker/docker/api/types/types.go +@@ -124,6 +124,7 @@ type Version struct { + // The following fields are deprecated, they relate to the Engine component and are kept for backwards compatibility + + Version string ++ EulerVersion string + APIVersion string `json:"ApiVersion"` + MinAPIVersion string `json:"MinAPIVersion,omitempty"` + GitCommit string +diff --git a/components/engine/api/types/types.go b/components/engine/api/types/types.go +index 820d513cbb..78e97daf98 100644 +--- a/components/engine/api/types/types.go ++++ b/components/engine/api/types/types.go +@@ -125,6 +125,7 @@ type Version struct { + // The following fields are deprecated, they relate to the Engine component and are kept for backwards compatibility + + Version string ++ EulerVersion string + APIVersion string `json:"ApiVersion"` + MinAPIVersion string `json:"MinAPIVersion,omitempty"` + GitCommit string +diff --git a/components/engine/daemon/info.go b/components/engine/daemon/info.go +index 523a396643..4acad11b70 100644 +--- a/components/engine/daemon/info.go ++++ b/components/engine/daemon/info.go +@@ -85,13 +85,13 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) { + // SystemVersion returns version information about the daemon. + func (daemon *Daemon) SystemVersion() types.Version { + kernelVersion := kernelVersion() +- + v := types.Version{ + Components: []types.ComponentVersion{ + { + Name: "Engine", + Version: dockerversion.Version, + Details: map[string]string{ ++ "EulerVersion": dockerversion.EulerVersion, + "GitCommit": dockerversion.GitCommit, + "ApiVersion": api.DefaultVersion, + "MinAPIVersion": api.MinVersion, +@@ -107,6 +107,7 @@ func (daemon *Daemon) SystemVersion() types.Version { + + // Populate deprecated fields for older clients + Version: dockerversion.Version, ++ EulerVersion: dockerversion.EulerVersion, + GitCommit: dockerversion.GitCommit, + APIVersion: api.DefaultVersion, + MinAPIVersion: api.MinVersion, +diff --git a/components/engine/dockerversion/version_lib.go b/components/engine/dockerversion/version_lib.go +index 5d9b3fdd2b..84b1339b5b 100644 +--- a/components/engine/dockerversion/version_lib.go ++++ b/components/engine/dockerversion/version_lib.go +@@ -8,6 +8,7 @@ package dockerversion // import "github.com/docker/docker/dockerversion" + const ( + GitCommit = "library-import" + Version = "library-import" ++ EulerVersion = "library-import" + BuildTime = "library-import" + IAmStatic = "library-import" + InitCommitID = "library-import" +diff --git a/components/engine/hack/make.sh b/components/engine/hack/make.sh +index 2f4ece3cdb..fa87d9110f 100755 +--- a/components/engine/hack/make.sh ++++ b/components/engine/hack/make.sh +@@ -65,6 +65,7 @@ DEFAULT_BUNDLES=( + cross + ) + ++VERSION_EULER=$(< ./VERSION-EULER) + VERSION=${VERSION:-dev} + ! BUILDTIME=$(date -u -d "@${SOURCE_DATE_EPOCH:-$(date +%s)}" --rfc-3339 ns 2> /dev/null | sed -e 's/ /T/') + if [ "$DOCKER_GITCOMMIT" ]; then +diff --git a/components/engine/hack/make/.go-autogen b/components/engine/hack/make/.go-autogen +index ea8a32ff5d..99d45e9cb8 100644 +--- a/components/engine/hack/make/.go-autogen ++++ b/components/engine/hack/make/.go-autogen +@@ -17,6 +17,7 @@ package dockerversion + const ( + GitCommit string = "$GITCOMMIT" + Version string = "$VERSION" ++ EulerVersion string = "$VERSION_EULER" + BuildTime string = "$BUILDTIME" + IAmStatic string = "${IAMSTATIC:-true}" + PlatformName string = "${PLATFORM}" +-- +2.17.1 + diff --git a/patch/0077-image-fix-file-not-exist-for-check-file-size.patch b/patch/0077-image-fix-file-not-exist-for-check-file-size.patch new file mode 100644 index 0000000..eb6c3f1 --- /dev/null +++ b/patch/0077-image-fix-file-not-exist-for-check-file-size.patch @@ -0,0 +1,32 @@ +From be262686615a41b7377e5e19c9aae2a1d71cb4fb Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Tue, 22 Jan 2019 17:20:38 +0800 +Subject: [PATCH 077/111] image: fix file not exist for check file + size + +reason: check file size should support file not exist. + +Change-Id: Iae3b8d4f477d6cfc51ba71a185a598687f8393a2 +Signed-off-by: jingrui +--- + components/engine/image/tarexport/load.go | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/components/engine/image/tarexport/load.go b/components/engine/image/tarexport/load.go +index b9f8f7e3ac..34574129f1 100644 +--- a/components/engine/image/tarexport/load.go ++++ b/components/engine/image/tarexport/load.go +@@ -432,6 +432,10 @@ func checkValidParent(img, parent *image.Image) bool { + func checkJsonFileSize(path string) error { + fileInfo, err := os.Stat(path) + if err != nil { ++ // path can not exist. ++ if os.IsNotExist(err) { ++ return nil ++ } + return err + } + fileSize := fileInfo.Size() +-- +2.17.1 + diff --git a/patch/0078-spec-add-missing-sysconfigs-to-rpm.patch b/patch/0078-spec-add-missing-sysconfigs-to-rpm.patch new file mode 100644 index 0000000..eb4a38c --- /dev/null +++ b/patch/0078-spec-add-missing-sysconfigs-to-rpm.patch @@ -0,0 +1,53 @@ +From bcd276b5b62e1b5ff46eadad71417de076797ec8 Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Tue, 22 Jan 2019 17:26:35 +0800 +Subject: [PATCH 078/111] spec: add missing sysconfigs to rpm + +reason: add missing sysconfigs to rpm, including +- /etc/sysconfig/docker +- /etc/sysconfig/docker-network +- /etc/sysconfig/docker-storage + +Partly cherry-pick from 17.06 780e079a + +Change-Id: I7a31bc6d99bab6c6a7a60988bc66db2d48f6ca4b +Signed-off-by: Lei Jitang +Signed-off-by: lujingxiao +--- + .../contrib/init/sysvinit-redhat/docker-network | 2 ++ + .../contrib/init/sysvinit-redhat/docker-storage | 14 ++++++++++++++ + 5 files changed, 27 insertions(+), 4 deletions(-) + create mode 100644 components/engine/contrib/init/sysvinit-redhat/docker-network + create mode 100644 components/engine/contrib/init/sysvinit-redhat/docker-storage + +diff --git a/components/engine/contrib/init/sysvinit-redhat/docker-network b/components/engine/contrib/init/sysvinit-redhat/docker-network +new file mode 100644 +index 0000000000..048d1582ec +--- /dev/null ++++ b/components/engine/contrib/init/sysvinit-redhat/docker-network +@@ -0,0 +1,2 @@ ++# /etc/sysconfig/docker-network ++DOCKER_NETWORK_OPTIONS= +diff --git a/components/engine/contrib/init/sysvinit-redhat/docker-storage b/components/engine/contrib/init/sysvinit-redhat/docker-storage +new file mode 100644 +index 0000000000..3dc16542ff +--- /dev/null ++++ b/components/engine/contrib/init/sysvinit-redhat/docker-storage +@@ -0,0 +1,14 @@ ++# This file may be automatically generated by an installation program. ++ ++# By default, Docker uses a loopback-mounted sparse file in ++# /var/lib/docker. The loopback makes it slower, and there are some ++# restrictive defaults, such as 100GB max storage. ++ ++# If your installation did not set a custom storage for Docker, you ++# may do it below. ++ ++# Example: Use a custom pair of raw logical volumes (one for metadata, ++# one for data). ++# DOCKER_STORAGE_OPTIONS = --storage-opt dm.metadatadev=/dev/mylogvol/my-docker-metadata --storage-opt dm.datadev=/dev/mylogvol/my-docker-data ++ ++DOCKER_STORAGE_OPTIONS= +-- +2.17.1 + diff --git a/patch/0079-rwlayer-fix-deadlock-for-docker-commit-export.patch b/patch/0079-rwlayer-fix-deadlock-for-docker-commit-export.patch new file mode 100644 index 0000000..7612c00 --- /dev/null +++ b/patch/0079-rwlayer-fix-deadlock-for-docker-commit-export.patch @@ -0,0 +1,39 @@ +From 1564b7d772b69da0ba7f6814af2fc094def9d2fd Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Wed, 23 Jan 2019 11:07:12 +0800 +Subject: [PATCH 079/111] rwlayer: fix deadlock for docker + commit/export + +reason: this commit avoid deadlock while del reference count. + +Change-Id: I2627752ed38c7bb9d789f4604acd43d130bcb926 +Signed-off-by: jingrui +--- + components/engine/layer/layer_store.go | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/components/engine/layer/layer_store.go b/components/engine/layer/layer_store.go +index cbb1ee4a19..553b098dfd 100644 +--- a/components/engine/layer/layer_store.go ++++ b/components/engine/layer/layer_store.go +@@ -680,8 +680,6 @@ func (ls *layerStore) ReleaseRWLayer(l RWLayer) ([]Metadata, error) { + return []Metadata{}, nil + } + +- m.Lock() +- defer m.Unlock() + if err := m.deleteReference(l); err != nil { + return nil, err + } +@@ -690,6 +688,8 @@ func (ls *layerStore) ReleaseRWLayer(l RWLayer) ([]Metadata, error) { + return []Metadata{}, nil + } + ++ m.Lock() ++ defer m.Unlock() + if err := ls.driver.Remove(m.mountID); err != nil { + logrus.Errorf("Error removing mounted layer %s: %s", m.name, err) + m.retakeReference(l) +-- +2.17.1 + diff --git a/patch/0081-runtime-spec-revert-the-modify-of-runtime-spe.patch b/patch/0081-runtime-spec-revert-the-modify-of-runtime-spe.patch new file mode 100644 index 0000000..15822e0 --- /dev/null +++ b/patch/0081-runtime-spec-revert-the-modify-of-runtime-spe.patch @@ -0,0 +1,51 @@ +From c1b7332e8f531e53062c740a16f953cd37661d30 Mon Sep 17 00:00:00 2001 +From: leizhongkai +Date: Thu, 24 Jan 2019 20:24:08 +0800 +Subject: [PATCH 081/111] runtime-spec: revert the modify of runtime + spec + +reason:revert the modify of runtime spec,make the compatibility in runc + +Change-Id: Ia294a169ff15c860c7db3b7a9ab14cecb605cfef +Signed-off-by: leizhongkai +--- + .../runtime-spec/specs-go/config.go | 18 +++++++++--------- + 1 file changed, 9 insertions(+), 9 deletions(-) + +diff --git a/components/engine/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go b/components/engine/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go +index aab7b8a098..46049b3bfa 100644 +--- a/components/engine/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go ++++ b/components/engine/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go +@@ -256,20 +256,20 @@ type LinuxThrottleDevice struct { + + // LinuxBlockIO for Linux cgroup 'blkio' resource management + type LinuxBlockIO 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"` ++ // Specifies per cgroup weight ++ Weight *uint16 `json:"weight,omitempty"` ++ // Specifies tasks' weight in the given cgroup while competing with the cgroup's child cgroups, CFQ scheduler only ++ LeafWeight *uint16 `json:"leafWeight,omitempty"` + // Weight per cgroup per device, can override BlkioWeight +- WeightDevice []LinuxWeightDevice `json:"blkioWeightDevice,omitempty"` ++ WeightDevice []LinuxWeightDevice `json:"weightDevice,omitempty"` + // IO read rate limit per cgroup per device, bytes per second +- ThrottleReadBpsDevice []LinuxThrottleDevice `json:"blkioThrottleReadBpsDevice,omitempty"` ++ ThrottleReadBpsDevice []LinuxThrottleDevice `json:"throttleReadBpsDevice,omitempty"` + // IO write rate limit per cgroup per device, bytes per second +- ThrottleWriteBpsDevice []LinuxThrottleDevice `json:"blkioThrottleWriteBpsDevice,omitempty"` ++ ThrottleWriteBpsDevice []LinuxThrottleDevice `json:"throttleWriteBpsDevice,omitempty"` + // IO read rate limit per cgroup per device, IO per second +- ThrottleReadIOPSDevice []LinuxThrottleDevice `json:"blkioThrottleReadIOPSDevice,omitempty"` ++ ThrottleReadIOPSDevice []LinuxThrottleDevice `json:"throttleReadIOPSDevice,omitempty"` + // IO write rate limit per cgroup per device, IO per second +- ThrottleWriteIOPSDevice []LinuxThrottleDevice `json:"blkioThrottleWriteIOPSDevice,omitempty"` ++ ThrottleWriteIOPSDevice []LinuxThrottleDevice `json:"throttleWriteIOPSDevice,omitempty"` + } + + // LinuxMemory for Linux cgroup 'memory' resource management +-- +2.17.1 + diff --git a/patch/0083-test-fix-start-paused-container.patch b/patch/0083-test-fix-start-paused-container.patch new file mode 100644 index 0000000..2255756 --- /dev/null +++ b/patch/0083-test-fix-start-paused-container.patch @@ -0,0 +1,32 @@ +From 3e2f675c4e55a73e9ac2ae7b6c0dfca8d37dd9d8 Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Thu, 24 Jan 2019 16:20:24 +0800 +Subject: [PATCH 083/111] test: fix start paused container + +reason: please unpause before start. + +Change-Id: I435ab7e5cfe4aaed8c6c4e502ca64b0c65fff6a5 +Signed-off-by: jingrui +--- + components/engine/integration-cli/docker_cli_daemon_test.go | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +diff --git a/components/engine/integration-cli/docker_cli_daemon_test.go b/components/engine/integration-cli/docker_cli_daemon_test.go +index d3cd5f1676..02c42c22e2 100644 +--- a/components/engine/integration-cli/docker_cli_daemon_test.go ++++ b/components/engine/integration-cli/docker_cli_daemon_test.go +@@ -1627,9 +1627,8 @@ func (s *DockerDaemonSuite) TestDaemonRestartWithPausedContainer(c *check.C) { + case <-time.After(5 * time.Second): + c.Fatal("Waiting on start a container timed out") + case err := <-errchan: +- if err != nil { +- c.Fatal(err) +- } ++ c.Assert(err, check.NotNil, check.Commentf("cannot start a paused container")) ++ s.d.Cmd("unpause", "test") + } + } + +-- +2.17.1 + diff --git a/patch/0084-test-skip-pause-test-with-ctr.patch b/patch/0084-test-skip-pause-test-with-ctr.patch new file mode 100644 index 0000000..30e3af1 --- /dev/null +++ b/patch/0084-test-skip-pause-test-with-ctr.patch @@ -0,0 +1,47 @@ +From c6edc1d912211c8b35df6c28033fb577a899573e Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Thu, 24 Jan 2019 16:28:45 +0800 +Subject: [PATCH 084/111] test: skip pause test with ctr + +reason: pause managed by docker only + +Change-Id: I8aae73e4ab1e0bf9ed639495aa112469ac3630d9 +Signed-off-by: jingrui +--- + components/engine/integration-cli/docker_cli_daemon_test.go | 3 ++- + components/engine/integration-cli/requirements_test.go | 5 +++++ + 2 files changed, 7 insertions(+), 1 deletion(-) + +diff --git a/components/engine/integration-cli/docker_cli_daemon_test.go b/components/engine/integration-cli/docker_cli_daemon_test.go +index 02c42c22e2..467edb04b4 100644 +--- a/components/engine/integration-cli/docker_cli_daemon_test.go ++++ b/components/engine/integration-cli/docker_cli_daemon_test.go +@@ -2049,7 +2049,8 @@ func (s *DockerDaemonSuite) TestCleanupMountsAfterDaemonCrash(c *check.C) { + + // TestDaemonRestartWithUnpausedRunningContainer requires live restore of running containers. + func (s *DockerDaemonSuite) TestDaemonRestartWithUnpausedRunningContainer(t *check.C) { +- testRequires(t, DaemonIsLinux) ++ testRequires(t, DaemonIsLinux, SupportCtr) ++ + s.d.StartWithBusybox(t, "--live-restore") + + cid, err := s.d.Cmd("run", "-d", "--name", "test", "busybox", "top") +diff --git a/components/engine/integration-cli/requirements_test.go b/components/engine/integration-cli/requirements_test.go +index 28be59cd2c..4647ce8ccc 100644 +--- a/components/engine/integration-cli/requirements_test.go ++++ b/components/engine/integration-cli/requirements_test.go +@@ -38,6 +38,11 @@ func DaemonIsWindowsAtLeastBuild(buildNumber int) func() bool { + } + } + ++func SupportCtr() bool { ++ // not support ctr now. ++ return false ++} ++ + func DaemonIsLinux() bool { + return testEnv.OSType == "linux" + } +-- +2.17.1 + diff --git a/patch/0085-event-log-detailed-event-with-info-level.patch b/patch/0085-event-log-detailed-event-with-info-level.patch new file mode 100644 index 0000000..bb79570 --- /dev/null +++ b/patch/0085-event-log-detailed-event-with-info-level.patch @@ -0,0 +1,129 @@ +From 2a3375c72a5d535864561a0a5fd46dc1fee17013 Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Fri, 25 Jan 2019 16:56:08 +0800 +Subject: [PATCH 085/111] event: log detailed event with info level + +reason: DFX improve event level and record with detailed info. + +Change-Id: Ibb5b1058b84de39f9f7058a46a15c2dbd27bc746 +Signed-off-by: jingrui +--- + .../engine/libcontainerd/client_daemon.go | 51 ++++++++++++++++++- + 1 file changed, 49 insertions(+), 2 deletions(-) + +diff --git a/components/engine/libcontainerd/client_daemon.go b/components/engine/libcontainerd/client_daemon.go +index cb9cb43a73..491bda281c 100644 +--- a/components/engine/libcontainerd/client_daemon.go ++++ b/components/engine/libcontainerd/client_daemon.go +@@ -791,8 +791,6 @@ func (c *client) processEventStream(ctx context.Context, ns string) { + continue + } + +- c.logger.WithField("topic", ev.Topic).Debug("event") +- + switch t := v.(type) { + case *apievents.TaskCreate: + et = EventCreate +@@ -801,6 +799,11 @@ func (c *client) processEventStream(ctx context.Context, ns string) { + ProcessID: t.ContainerID, + Pid: t.Pid, + } ++ c.logger.WithFields(logrus.Fields{ ++ "topic": ev.Topic, ++ "containerID": t.ContainerID, ++ "Pid": t.Pid, ++ }).Infof("event") + case *apievents.TaskStart: + et = EventStart + ei = EventInfo{ +@@ -808,6 +811,11 @@ func (c *client) processEventStream(ctx context.Context, ns string) { + ProcessID: t.ContainerID, + Pid: t.Pid, + } ++ c.logger.WithFields(logrus.Fields{ ++ "topic": ev.Topic, ++ "containerID": t.ContainerID, ++ "Pid": t.Pid, ++ }).Infof("event") + case *apievents.TaskExit: + et = EventExit + ei = EventInfo{ +@@ -817,6 +825,13 @@ func (c *client) processEventStream(ctx context.Context, ns string) { + ExitCode: t.ExitStatus, + ExitedAt: t.ExitedAt, + } ++ c.logger.WithFields(logrus.Fields{ ++ "topic": ev.Topic, ++ "containerID": t.ContainerID, ++ "Pid": t.Pid, ++ "ExitStatus": t.ExitStatus, ++ "ExitedAt": t.ExitedAt, ++ }).Infof("event") + case *apievents.TaskOOM: + et = EventOOM + ei = EventInfo{ +@@ -824,12 +839,22 @@ func (c *client) processEventStream(ctx context.Context, ns string) { + OOMKilled: true, + } + oomKilled = true ++ c.logger.WithFields(logrus.Fields{ ++ "topic": ev.Topic, ++ "containerID": t.ContainerID, ++ "OOMKilled": true, ++ }).Infof("event") + case *apievents.TaskExecAdded: + et = EventExecAdded + ei = EventInfo{ + ContainerID: t.ContainerID, + ProcessID: t.ExecID, + } ++ c.logger.WithFields(logrus.Fields{ ++ "topic": ev.Topic, ++ "containerID": t.ContainerID, ++ "execID": t.ExecID, ++ }).Infof("event") + case *apievents.TaskExecStarted: + et = EventExecStarted + ei = EventInfo{ +@@ -837,16 +862,38 @@ func (c *client) processEventStream(ctx context.Context, ns string) { + ProcessID: t.ExecID, + Pid: t.Pid, + } ++ c.logger.WithFields(logrus.Fields{ ++ "topic": ev.Topic, ++ "containerID": t.ContainerID, ++ "execID": t.ExecID, ++ "Pid": t.Pid, ++ }).Infof("event") + case *apievents.TaskPaused: + et = EventPaused + ei = EventInfo{ + ContainerID: t.ContainerID, + } ++ c.logger.WithFields(logrus.Fields{ ++ "topic": ev.Topic, ++ "containerID": t.ContainerID, ++ }).Infof("event") + case *apievents.TaskResumed: + et = EventResumed + ei = EventInfo{ + ContainerID: t.ContainerID, + } ++ c.logger.WithFields(logrus.Fields{ ++ "topic": ev.Topic, ++ "containerID": t.ContainerID, ++ }).Infof("event") ++ case *apievents.TaskDelete: ++ c.logger.WithFields(logrus.Fields{ ++ "topic": ev.Topic, ++ "containerID": t.ContainerID, ++ "Pid": t.Pid, ++ "ExitStatus": t.ExitStatus, ++ "ExitedAt": t.ExitedAt, ++ }).Infof("event") + default: + c.logger.WithFields(logrus.Fields{ + "topic": ev.Topic, +-- +2.17.1 + diff --git a/patch/0086-restart-fix-daemon-restart-with-restart-alway.patch b/patch/0086-restart-fix-daemon-restart-with-restart-alway.patch new file mode 100644 index 0000000..688f9b8 --- /dev/null +++ b/patch/0086-restart-fix-daemon-restart-with-restart-alway.patch @@ -0,0 +1,62 @@ +From 99b7f823b7dfe88dc2d4f4073f10cbf3a437bd81 Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Fri, 25 Jan 2019 16:59:26 +0800 +Subject: [PATCH 086/111] restart: fix daemon restart with + restart=always container + +reason: fix daemon gracefully restart while container is start with +restart=always. restart-manager blocked to start container while +restoring, when restoring start container successful, the +restart-manager will start container but error with "id already in use". + +related testcase: +TestDaemonRestartKillContainers +TestDockerNetworkHostModeUngracefulDaemonRestart + +ref: +- https://github.com/moby/moby/issues/38249 + +Change-Id: I2545286a8371cb656ec6574d23cd15de4ba60283 +Signed-off-by: jingrui +--- + components/engine/daemon/daemon_unix.go | 3 +++ + components/engine/daemon/start.go | 9 +++++++++ + 2 files changed, 12 insertions(+) + +diff --git a/components/engine/daemon/daemon_unix.go b/components/engine/daemon/daemon_unix.go +index b20c66e27b..9abc9a329a 100644 +--- a/components/engine/daemon/daemon_unix.go ++++ b/components/engine/daemon/daemon_unix.go +@@ -1291,6 +1291,9 @@ func setupDaemonRootPropagation(cfg *config.Config) error { + return nil + } + ++ if err := os.MkdirAll(filepath.Dir(cleanupFile), 0700); err != nil { ++ return errors.Wrap(err, "error mkdir parent to signal mount cleanup on shutdown") ++ } + if err := ioutil.WriteFile(cleanupFile, nil, 0600); err != nil { + return errors.Wrap(err, "error writing file to signal mount cleanup on shutdown") + } +diff --git a/components/engine/daemon/start.go b/components/engine/daemon/start.go +index c00bd9ceb2..96ae45e11e 100644 +--- a/components/engine/daemon/start.go ++++ b/components/engine/daemon/start.go +@@ -108,6 +108,15 @@ func (daemon *Daemon) containerStart(container *container.Container, checkpoint + return nil + } + ++ // fix restartManager restarted the container is already started by restore. ++ if container.Running { ++ _, err := daemon.containerd.Status(context.Background(), container.ID) ++ if err == nil { ++ logrus.Warnf("skip starting the container is exist and running.") ++ return nil ++ } ++ } ++ + if container.RemovalInProgress || container.Dead { + return errdefs.Conflict(errors.New("container is marked for removal and cannot be started")) + } +-- +2.17.1 + diff --git a/patch/0088-attach-add-timeout-return-for-getExitStatus.patch b/patch/0088-attach-add-timeout-return-for-getExitStatus.patch new file mode 100644 index 0000000..5182124 --- /dev/null +++ b/patch/0088-attach-add-timeout-return-for-getExitStatus.patch @@ -0,0 +1,48 @@ +From 4f99f2ea6fac5d585cee3b7362b109dcf869beda Mon Sep 17 00:00:00 2001 +From: zhangyu235 +Date: Wed, 30 Jan 2019 13:42:34 +0800 +Subject: [PATCH 088/111] attach: add timeout return for getExitStatus + +reason:In cli/command/container/attach.go, func getExitStatus() will +return until container turn to unrunning state. When we excute docker +attach and other commands inside container together, for the state of +container will not change, docker attach command is stuck. + +Change-Id: Ifda6096643c659341fd6d343eb4a8cbf08e5a71c +Signed-off-by: zhangyu235 +--- + components/cli/cli/command/container/attach.go | 4 ++++ + 5 files changed, 10 insertions(+), 6 deletions(-) + +diff --git a/components/cli/cli/command/container/attach.go b/components/cli/cli/command/container/attach.go +index de96a3b7d8..ff1014d70c 100644 +--- a/components/cli/cli/command/container/attach.go ++++ b/components/cli/cli/command/container/attach.go +@@ -5,6 +5,7 @@ import ( + "fmt" + "io" + "net/http/httputil" ++ "time" + + "github.com/docker/cli/cli" + "github.com/docker/cli/cli/command" +@@ -150,6 +151,7 @@ func runAttach(dockerCli command.Cli, opts *attachOptions) error { + } + + func getExitStatus(errC <-chan error, resultC <-chan container.ContainerWaitOKBody) error { ++ timeout := time.NewTimer(time.Second) + select { + case result := <-resultC: + if result.Error != nil { +@@ -160,6 +162,8 @@ func getExitStatus(errC <-chan error, resultC <-chan container.ContainerWaitOKBo + } + case err := <-errC: + return err ++ case <-timeout.C: ++ return fmt.Errorf("Wait container status timeout.") + } + + return nil +-- +2.17.1 + diff --git a/patch/0089-libcontainerd-fix-stuck-when-containerd-in-T-.patch b/patch/0089-libcontainerd-fix-stuck-when-containerd-in-T-.patch new file mode 100644 index 0000000..7f55732 --- /dev/null +++ b/patch/0089-libcontainerd-fix-stuck-when-containerd-in-T-.patch @@ -0,0 +1,31 @@ +From 9a48f9b439a03e7fd5eeec4ff2cebac4cddf669f Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Wed, 30 Jan 2019 21:28:16 +0800 +Subject: [PATCH 089/111] libcontainerd: fix stuck when containerd in + T-state + +reason: when containerd is alive but not responding, shall restart +containerd avoid cmd stuck. + +Change-Id: Iab220c8988b50b39f4fd84be9454a0b097968751 +Signed-off-by: jingrui +--- + components/engine/libcontainerd/supervisor/remote_daemon.go | 2 +- + 5 files changed, 7 insertions(+), 7 deletions(-) + +diff --git a/components/engine/libcontainerd/supervisor/remote_daemon.go b/components/engine/libcontainerd/supervisor/remote_daemon.go +index 095300f753..c5da3a56fe 100644 +--- a/components/engine/libcontainerd/supervisor/remote_daemon.go ++++ b/components/engine/libcontainerd/supervisor/remote_daemon.go +@@ -307,7 +307,7 @@ func (r *remote) monitorDaemon(ctx context.Context) { + r.logger.WithError(err).WithField("binary", binaryName).Debug("daemon is not responding") + + transientFailureCount++ +- if transientFailureCount < maxConnectionRetryCount || system.IsProcessAlive(r.daemonPid) { ++ if transientFailureCount < maxConnectionRetryCount { + delay = time.After(time.Duration(transientFailureCount) * 200 * time.Millisecond) + continue + } +-- +2.17.1 + diff --git a/patch/0090-overlay2-Use-index-off-if-possible.patch b/patch/0090-overlay2-Use-index-off-if-possible.patch new file mode 100644 index 0000000..6f48d99 --- /dev/null +++ b/patch/0090-overlay2-Use-index-off-if-possible.patch @@ -0,0 +1,73 @@ +From b2750c914429b4f981848d0c829ddfc0c8acc37e Mon Sep 17 00:00:00 2001 +From: xiadanni +Date: Fri, 1 Feb 2019 01:03:12 +0800 +Subject: [PATCH 090/111] overlay2: Use index=off if possible + +reason: Docker overlay driver can't work with index=on feature of +the Linux kernel "overlay" filesystem. In case the global +default is set to "yes", Docker will fail with EBUSY when +trying to mount. + +Cherry-pick from https://github.com/moby/moby/pull/37993/commits/8422d85087bfa770b62ef4e1daaca95ee6783d86 + +Change-Id: Iad1addaca9983ba35c466951e04c7034b0a18fab +Signed-off-by: Kir Kolyshkin +Signed-off-by: xiadanni +--- + .../daemon/graphdriver/overlay2/overlay.go | 19 ++++++++++++++++--- + 1 file changed, 16 insertions(+), 3 deletions(-) + +diff --git a/components/engine/daemon/graphdriver/overlay2/overlay.go b/components/engine/daemon/graphdriver/overlay2/overlay.go +index cf8993e9f3..d87f979673 100644 +--- a/components/engine/daemon/graphdriver/overlay2/overlay.go ++++ b/components/engine/daemon/graphdriver/overlay2/overlay.go +@@ -111,6 +111,8 @@ var ( + + useNaiveDiffLock sync.Once + useNaiveDiffOnly bool ++ ++ indexOff string + ) + + func init() { +@@ -228,7 +230,18 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap + return nil, fmt.Errorf("Storage Option overlay2.size only supported for backingFS XFS or ext4. Found %v", backingFs) + } + +- logger.Debugf("backingFs=%s, projectQuotaSupported=%v", backingFs, projectQuotaSupported) ++ // figure out whether "index=off" option is recognized by the kernel ++ _, err = os.Stat("/sys/module/overlay/parameters/index") ++ switch { ++ case err == nil: ++ indexOff = "index=off," ++ case os.IsNotExist(err): ++ // old kernel, no index -- do nothing ++ default: ++ logger.Warnf("Unable to detect whether overlay kernel module supports index parameter: %s", err) ++ } ++ ++ logger.Debugf("backingFs=%s, projectQuotaSupported=%v, indexOff=%q", backingFs, projectQuotaSupported, indexOff) + + return d, nil + } +@@ -632,7 +645,7 @@ func (d *Driver) Get(id, mountLabel string) (_ containerfs.ContainerFS, retErr e + for i, s := range splitLowers { + absLowers[i] = path.Join(d.home, s) + } +- opts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", strings.Join(absLowers, ":"), path.Join(dir, "diff"), path.Join(dir, "work")) ++ opts := indexOff + "lowerdir=" + strings.Join(absLowers, ":") + ",upperdir=" + path.Join(dir, "diff") + ",workdir=" + path.Join(dir, "work") + mountData := label.FormatMountLabel(opts, mountLabel) + mount := unix.Mount + mountTarget := mergedDir +@@ -661,7 +674,7 @@ func (d *Driver) Get(id, mountLabel string) (_ containerfs.ContainerFS, retErr e + // fit within a page and relative links make the mount data much + // smaller at the expense of requiring a fork exec to chroot. + if len(mountData) > pageSize { +- opts = fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", string(lowers), path.Join(id, "diff"), path.Join(id, "work")) ++ opts = indexOff + "lowerdir=" + string(lowers) + ",upperdir=" + path.Join(id, "diff") + ",workdir=" + path.Join(id, "work") + mountData = label.FormatMountLabel(opts, mountLabel) + if len(mountData) > pageSize { + return nil, fmt.Errorf("cannot mount layer, mount label too large %d", len(mountData)) +-- +2.17.1 + diff --git a/patch/0091-overlay2-use-global-logger-instance.patch b/patch/0091-overlay2-use-global-logger-instance.patch new file mode 100644 index 0000000..acdd25f --- /dev/null +++ b/patch/0091-overlay2-use-global-logger-instance.patch @@ -0,0 +1,168 @@ +From 7bce81e40dbc73b7e778d8676687efc3b04c6d39 Mon Sep 17 00:00:00 2001 +From: xiadanni +Date: Fri, 1 Feb 2019 01:45:57 +0800 +Subject: [PATCH 091/111] overlay2: use global logger instance + +reason: This simplifies the code a lot. + +Cherry-pick from https://github.com/moby/moby/pull/37993/commits/a55d32546a8556f9e6cabbc99836b573b9944f0c + +Change-Id: I2c4998d677c8500701e077b2520b96fe49ea4364 +Signed-off-by: Kir Kolyshkin +Signed-off-by: xiadanni +--- + .../daemon/graphdriver/overlay2/check.go | 9 ++++----- + .../daemon/graphdriver/overlay2/overlay.go | 20 +++++++++---------- + .../daemon/graphdriver/overlay2/randomid.go | 3 +-- + 3 files changed, 14 insertions(+), 18 deletions(-) + +diff --git a/components/engine/daemon/graphdriver/overlay2/check.go b/components/engine/daemon/graphdriver/overlay2/check.go +index d6ee42f47f..1703b7a00c 100644 +--- a/components/engine/daemon/graphdriver/overlay2/check.go ++++ b/components/engine/daemon/graphdriver/overlay2/check.go +@@ -12,7 +12,6 @@ import ( + + "github.com/docker/docker/pkg/system" + "github.com/pkg/errors" +- "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" + ) + +@@ -27,7 +26,7 @@ func doesSupportNativeDiff(d string) error { + } + defer func() { + if err := os.RemoveAll(td); err != nil { +- logrus.WithField("storage-driver", "overlay2").Warnf("Failed to remove check directory %v: %v", td, err) ++ logger.Warnf("Failed to remove check directory %v: %v", td, err) + } + }() + +@@ -62,7 +61,7 @@ func doesSupportNativeDiff(d string) error { + } + defer func() { + if err := unix.Unmount(filepath.Join(td, "merged"), 0); err != nil { +- logrus.WithField("storage-driver", "overlay2").Warnf("Failed to unmount check directory %v: %v", filepath.Join(td, "merged"), err) ++ logger.Warnf("Failed to unmount check directory %v: %v", filepath.Join(td, "merged"), err) + } + }() + +@@ -113,7 +112,7 @@ func supportsMultipleLowerDir(d string) error { + } + defer func() { + if err := os.RemoveAll(td); err != nil { +- logrus.WithField("storage-driver", "overlay2").Warnf("Failed to remove check directory %v: %v", td, err) ++ logger.Warnf("Failed to remove check directory %v: %v", td, err) + } + }() + +@@ -128,7 +127,7 @@ func supportsMultipleLowerDir(d string) error { + return errors.Wrap(err, "failed to mount overlay") + } + if err := unix.Unmount(filepath.Join(td, "merged"), 0); err != nil { +- logrus.WithField("storage-driver", "overlay2").Warnf("Failed to unmount check directory %v: %v", filepath.Join(td, "merged"), err) ++ logger.Warnf("Failed to unmount check directory %v: %v", filepath.Join(td, "merged"), err) + } + return nil + } +diff --git a/components/engine/daemon/graphdriver/overlay2/overlay.go b/components/engine/daemon/graphdriver/overlay2/overlay.go +index d87f979673..71474f8f36 100644 +--- a/components/engine/daemon/graphdriver/overlay2/overlay.go ++++ b/components/engine/daemon/graphdriver/overlay2/overlay.go +@@ -106,6 +106,7 @@ type Driver struct { + } + + var ( ++ logger = logrus.WithField("storage-driver", "overlay2") + backingFs = "" + projectQuotaSupported = false + +@@ -157,8 +158,6 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap + backingFs = fsName + } + +- logger := logrus.WithField("storage-driver", "overlay2") +- + switch fsMagic { + case graphdriver.FsMagicAufs, graphdriver.FsMagicEcryptfs, graphdriver.FsMagicNfsFs, graphdriver.FsMagicOverlay, graphdriver.FsMagicZfs: + logger.Errorf("'overlay2' is not supported over %s", backingFs) +@@ -306,14 +305,14 @@ func supportsOverlay() error { + return nil + } + } +- logrus.WithField("storage-driver", "overlay2").Error("'overlay' not found as a supported filesystem on this host. Please ensure kernel is new enough and has overlay support loaded.") ++ logger.Error("'overlay' not found as a supported filesystem on this host. Please ensure kernel is new enough and has overlay support loaded.") + return graphdriver.ErrNotSupported + } + + func useNaiveDiff(home string) bool { + useNaiveDiffLock.Do(func() { + if err := doesSupportNativeDiff(home); err != nil { +- logrus.WithField("storage-driver", "overlay2").Warnf("Not using native diff for overlay2, this may cause degraded performance for building images: %v", err) ++ logger.Warnf("Not using native diff for overlay2, this may cause degraded performance for building images: %v", err) + useNaiveDiffOnly = true + } + }) +@@ -629,11 +628,11 @@ func (d *Driver) Get(id, mountLabel string) (_ containerfs.ContainerFS, retErr e + if retErr != nil { + if c := d.ctr.Decrement(mergedDir); c <= 0 { + if mntErr := unix.Unmount(mergedDir, 0); mntErr != nil { +- logrus.WithField("storage-driver", "overlay2").Errorf("error unmounting %v: %v", mergedDir, mntErr) ++ logger.Errorf("error unmounting %v: %v", mergedDir, mntErr) + } + // Cleanup the created merged directory; see the comment in Put's rmdir + if rmErr := unix.Rmdir(mergedDir); rmErr != nil && !os.IsNotExist(rmErr) { +- logrus.WithField("storage-driver", "overlay2").Debugf("Failed to remove %s: %v: %v", id, rmErr, err) ++ logger.Debugf("Failed to remove %s: %v: %v", id, rmErr, err) + } + } + } +@@ -716,7 +715,6 @@ func (d *Driver) Put(id string) error { + } + + mountpoint := path.Join(dir, "merged") +- logger := logrus.WithField("storage-driver", "overlay2") + if count := d.ctr.Decrement(mountpoint); count > 0 { + return nil + } +@@ -802,7 +800,7 @@ func (d *Driver) ApplyDiff(id string, parent string, diff io.Reader) (size int64 + + applyDir := d.getDiffPath(id) + +- logrus.WithField("storage-driver", "overlay2").Debugf("Applying tar in %s", applyDir) ++ logger.Debugf("Applying tar in %s", applyDir) + // Overlay doesn't need the parent id to apply the diff + if err := untar(diff, applyDir, &archive.TarOptions{ + UIDMaps: d.uidMaps, +@@ -840,7 +838,7 @@ func (d *Driver) Diff(id, parent string) (io.ReadCloser, error) { + } + + diffPath := d.getDiffPath(id) +- logrus.WithField("storage-driver", "overlay2").Debugf("Tar with options on %s", diffPath) ++ logger.Debugf("Tar with options on %s", diffPath) + return archive.TarWithOptions(diffPath, &archive.TarOptions{ + Compression: archive.Uncompressed, + UIDMaps: d.uidMaps, +diff --git a/components/engine/daemon/graphdriver/overlay2/randomid.go b/components/engine/daemon/graphdriver/overlay2/randomid.go +index 933d9fccb6..6ab50df8e6 100644 +--- a/components/engine/daemon/graphdriver/overlay2/randomid.go ++++ b/components/engine/daemon/graphdriver/overlay2/randomid.go +@@ -12,7 +12,6 @@ import ( + "syscall" + "time" + +- "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" + ) + +@@ -48,7 +47,7 @@ func generateID(l int) string { + if retryOnError(err) && retries < maxretries { + count += n + retries++ +- logrus.Errorf("error generating version 4 uuid, retrying: %v", err) ++ logger.Errorf("error generating version 4 uuid, retrying: %v", err) + continue + } + +-- +2.17.1 + diff --git a/patch/0092-Revert-docker-Lock-the-RWLayer-while-committi.patch b/patch/0092-Revert-docker-Lock-the-RWLayer-while-committi.patch new file mode 100644 index 0000000..7713b9c --- /dev/null +++ b/patch/0092-Revert-docker-Lock-the-RWLayer-while-committi.patch @@ -0,0 +1,214 @@ +From 5c472ad67723ba45ee92dce62f9f45292213644e Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Fri, 1 Feb 2019 11:02:31 +0800 +Subject: [PATCH 092/111] Revert "docker: Lock the RWLayer while + committing/exporting" + +reason: This reverts commit a3dbaededfff6d20e16740c47ac69de9a91cccff. + +1. rwlayer is already protected by ls.mountL.Lock, no need add lock to +mountedLayer. +2. rwlayer is protected by reference, no need add lock during commit or +export. + +known issues: +- commit or export will put reference during hold lock, if put the last + reference, dead lock happens on release layer. +- delete(docker rm) will set container.RWLayer = nil, it's dangrous + after export or commit when calling container.RWLayer.RUnlockRWLayer(). + +Change-Id: I72cc72a3398133d693cb813fde1964068544ec03 +Signed-off-by: jingrui +--- + components/engine/daemon/commit.go | 2 -- + components/engine/daemon/export.go | 2 -- + components/engine/layer/empty.go | 13 +------------ + components/engine/layer/layer.go | 7 ------- + components/engine/layer/layer_store.go | 4 ---- + components/engine/layer/mounted_layer.go | 10 ---------- + components/engine/layer/ro_layer.go | 10 ---------- + 11 files changed, 7 insertions(+), 53 deletions(-) + +diff --git a/components/engine/daemon/commit.go b/components/engine/daemon/commit.go +index fc7d2782ef..0f6f440514 100644 +--- a/components/engine/daemon/commit.go ++++ b/components/engine/daemon/commit.go +@@ -155,8 +155,6 @@ func (daemon *Daemon) CreateImageFromContainer(name string, c *backend.CreateIma + return "", err + } + +- container.RWLayer.RLockRWLayer() +- defer container.RWLayer.RUnlockRWLayer() + id, err := daemon.imageService.CommitImage(backend.CommitConfig{ + Author: c.Author, + Comment: c.Comment, +diff --git a/components/engine/daemon/export.go b/components/engine/daemon/export.go +index ebd2d75f40..27bc35967d 100644 +--- a/components/engine/daemon/export.go ++++ b/components/engine/daemon/export.go +@@ -34,8 +34,6 @@ func (daemon *Daemon) ContainerExport(name string, out io.Writer) error { + return errdefs.Conflict(err) + } + +- container.RWLayer.RLockRWLayer() +- defer container.RWLayer.RUnlockRWLayer() + data, err := daemon.containerExport(container) + if err != nil { + return fmt.Errorf("Error exporting container %s: %v", name, err) +diff --git a/components/engine/layer/empty.go b/components/engine/layer/empty.go +index 16a49a7abd..c81c702140 100644 +--- a/components/engine/layer/empty.go ++++ b/components/engine/layer/empty.go +@@ -6,16 +6,13 @@ import ( + "fmt" + "io" + "io/ioutil" +- "sync" + ) + + // DigestSHA256EmptyTar is the canonical sha256 digest of empty tar file - + // (1024 NULL bytes) + const DigestSHA256EmptyTar = DiffID("sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef") + +-type emptyLayer struct { +- sync.RWMutex +-} ++type emptyLayer struct{} + + // EmptyLayer is a layer that corresponds to empty tar. + var EmptyLayer = &emptyLayer{} +@@ -58,14 +55,6 @@ func (el *emptyLayer) Metadata() (map[string]string, error) { + return make(map[string]string), nil + } + +-func (el *emptyLayer) RLockRWLayer() { +- el.RLock() +-} +- +-func (el *emptyLayer) RUnlockRWLayer() { +- el.RUnlock() +-} +- + // IsEmpty returns true if the layer is an EmptyLayer + func IsEmpty(diffID DiffID) bool { + return diffID == DigestSHA256EmptyTar +diff --git a/components/engine/layer/layer.go b/components/engine/layer/layer.go +index e35a13135b..cb13c98d0b 100644 +--- a/components/engine/layer/layer.go ++++ b/components/engine/layer/layer.go +@@ -145,13 +145,6 @@ type RWLayer interface { + + // Metadata returns the low level metadata for the mutable layer + Metadata() (map[string]string, error) +- +- // RLockRWLayer locks the RWLayer to block unmounting/removal +- // of that layer +- RLockRWLayer() +- +- // RUnlockRWLayer unlocks the RWLayer +- RUnlockRWLayer() + } + + // Metadata holds information about a +diff --git a/components/engine/layer/layer_store.go b/components/engine/layer/layer_store.go +index 553b098dfd..b6fc45e655 100644 +--- a/components/engine/layer/layer_store.go ++++ b/components/engine/layer/layer_store.go +@@ -598,7 +598,6 @@ func (ls *layerStore) CreateRWLayer(name string, parent ChainID, opts *CreateRWL + // Release parent chain if error + defer func() { + if err != nil { +- m.Lock() + if deferErr := ls.driver.Remove(m.mountID); deferErr != nil { + logrus.Errorf("Error removing mounted layer during create rw layer %s: %s", m.name, deferErr) + } +@@ -610,7 +609,6 @@ func (ls *layerStore) CreateRWLayer(name string, parent ChainID, opts *CreateRWL + if deferErr := ls.store.RemoveMount(m.name); deferErr != nil { + logrus.Errorf("Error removing mount metadata during create rw layer %s: %s", m.name, deferErr) + } +- m.Unlock() + + ls.layerL.Lock() + ls.releaseLayer(p) +@@ -688,8 +686,6 @@ func (ls *layerStore) ReleaseRWLayer(l RWLayer) ([]Metadata, error) { + return []Metadata{}, nil + } + +- m.Lock() +- defer m.Unlock() + if err := ls.driver.Remove(m.mountID); err != nil { + logrus.Errorf("Error removing mounted layer %s: %s", m.name, err) + m.retakeReference(l) +diff --git a/components/engine/layer/mounted_layer.go b/components/engine/layer/mounted_layer.go +index 66711d6cf7..d6858c662c 100644 +--- a/components/engine/layer/mounted_layer.go ++++ b/components/engine/layer/mounted_layer.go +@@ -2,7 +2,6 @@ package layer // import "github.com/docker/docker/layer" + + import ( + "io" +- "sync" + + "github.com/docker/docker/pkg/archive" + "github.com/docker/docker/pkg/containerfs" +@@ -17,7 +16,6 @@ type mountedLayer struct { + layerStore *layerStore + + references map[RWLayer]*referencedRWLayer +- sync.RWMutex + } + + func (ml *mountedLayer) cacheParent() string { +@@ -60,14 +58,6 @@ func (ml *mountedLayer) Metadata() (map[string]string, error) { + return ml.layerStore.driver.GetMetadata(ml.mountID) + } + +-func (ml *mountedLayer) RLockRWLayer() { +- ml.RLock() +-} +- +-func (ml *mountedLayer) RUnlockRWLayer() { +- ml.RUnlock() +-} +- + func (ml *mountedLayer) getReference() RWLayer { + ref := &referencedRWLayer{ + mountedLayer: ml, +diff --git a/components/engine/layer/ro_layer.go b/components/engine/layer/ro_layer.go +index 59bcf17d18..3555e8b027 100644 +--- a/components/engine/layer/ro_layer.go ++++ b/components/engine/layer/ro_layer.go +@@ -3,7 +3,6 @@ package layer // import "github.com/docker/docker/layer" + import ( + "fmt" + "io" +- "sync" + + "github.com/docker/distribution" + "github.com/opencontainers/go-digest" +@@ -20,7 +19,6 @@ type roLayer struct { + + referenceCount int + references map[Layer]struct{} +- sync.RWMutex + } + + // TarStream for roLayer guarantees that the data that is produced is the exact +@@ -94,14 +92,6 @@ func (rl *roLayer) Metadata() (map[string]string, error) { + return rl.layerStore.driver.GetMetadata(rl.cacheID) + } + +-func (rl *roLayer) RLockRWLayer() { +- rl.RLock() +-} +- +-func (rl *roLayer) RUnlockRWLayer() { +- rl.RUnlock() +-} +- + type referencedCacheLayer struct { + *roLayer + } +-- +2.17.1 + diff --git a/patch/0093-docker-do-not-try-to-connect-containerd-if-co.patch b/patch/0093-docker-do-not-try-to-connect-containerd-if-co.patch new file mode 100644 index 0000000..3517793 --- /dev/null +++ b/patch/0093-docker-do-not-try-to-connect-containerd-if-co.patch @@ -0,0 +1,31 @@ +From 03024503528f2355a666cb37a6b6bc3902bc977d Mon Sep 17 00:00:00 2001 +From: zhangsong34 +Date: Fri, 1 Feb 2019 17:34:44 +0800 +Subject: [PATCH 093/111] docker: do not try to connect containerd if + containerd daemon is down + +reason:do not try to connect containerd if containerd daemon is down, reduce +delay time to restart containerd. + +Change-Id: I7e6ab9e4c154a82b3b2609440cbfc7d3ea14d28d +Signed-off-by: zhangsong34 +--- + components/engine/libcontainerd/supervisor/remote_daemon.go | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/components/engine/libcontainerd/supervisor/remote_daemon.go b/components/engine/libcontainerd/supervisor/remote_daemon.go +index c5da3a56fe..45cc3c3032 100644 +--- a/components/engine/libcontainerd/supervisor/remote_daemon.go ++++ b/components/engine/libcontainerd/supervisor/remote_daemon.go +@@ -289,7 +289,7 @@ func (r *remote) monitorDaemon(ctx context.Context) { + } + } + +- if client != nil { ++ if client != nil && system.IsProcessAlive(r.daemonPid) { + tctx, cancel := context.WithTimeout(ctx, healthCheckTimeout) + _, err := client.IsServing(tctx) + cancel() +-- +2.17.1 + diff --git a/patch/0094-docker-change-health-check-minum-param-to-one.patch b/patch/0094-docker-change-health-check-minum-param-to-one.patch new file mode 100644 index 0000000..a493970 --- /dev/null +++ b/patch/0094-docker-change-health-check-minum-param-to-one.patch @@ -0,0 +1,88 @@ +From 69edbbf9eca76adf67e2b26ee564b53eaef76aff Mon Sep 17 00:00:00 2001 +From: zhangsong34 +Date: Fri, 1 Feb 2019 19:00:14 +0800 +Subject: [PATCH 094/111] docker: change health check minum param to + one second + +reason:change health check minum period to one second, include +--health-interval, --health-timeout and --health-start-period. + +Change-Id: I1ebab75c23edf4f9006142f92894114c5d447f75 +Signed-off-by: zhangsong34 +--- + components/engine/daemon/container.go | 12 ++++++------ + .../engine/integration-cli/docker_api_create_test.go | 7 +++---- + 2 files changed, 9 insertions(+), 10 deletions(-) + +diff --git a/components/engine/daemon/container.go b/components/engine/daemon/container.go +index 06a19bb4c8..6d357421f3 100644 +--- a/components/engine/daemon/container.go ++++ b/components/engine/daemon/container.go +@@ -421,20 +421,20 @@ func (daemon *Daemon) verifyContainerSettings(platform string, hostConfig *conta + + // Validate the healthcheck params of Config + if config.Healthcheck != nil { +- if config.Healthcheck.Interval != 0 && config.Healthcheck.Interval < containertypes.MinimumDuration { +- return nil, errors.Errorf("Interval in Healthcheck cannot be less than %s", containertypes.MinimumDuration) ++ if config.Healthcheck.Interval != 0 && config.Healthcheck.Interval < time.Second { ++ return nil, errors.Errorf("Interval in Healthcheck cannot be less than one second") + } + +- if config.Healthcheck.Timeout != 0 && config.Healthcheck.Timeout < containertypes.MinimumDuration { +- return nil, errors.Errorf("Timeout in Healthcheck cannot be less than %s", containertypes.MinimumDuration) ++ if config.Healthcheck.Timeout != 0 && config.Healthcheck.Timeout < time.Second { ++ return nil, errors.Errorf("Timeout in Healthcheck cannot be less than one second") + } + + if config.Healthcheck.Retries < 0 { + return nil, errors.Errorf("Retries in Healthcheck cannot be negative") + } + +- if config.Healthcheck.StartPeriod != 0 && config.Healthcheck.StartPeriod < containertypes.MinimumDuration { +- return nil, errors.Errorf("StartPeriod in Healthcheck cannot be less than %s", containertypes.MinimumDuration) ++ if config.Healthcheck.StartPeriod != 0 && config.Healthcheck.StartPeriod < time.Second { ++ return nil, errors.Errorf("StartPeriod in Healthcheck cannot be less than one second") + } + } + } +diff --git a/components/engine/integration-cli/docker_api_create_test.go b/components/engine/integration-cli/docker_api_create_test.go +index 8c7fff477e..1bbc7653b3 100644 +--- a/components/engine/integration-cli/docker_api_create_test.go ++++ b/components/engine/integration-cli/docker_api_create_test.go +@@ -5,7 +5,6 @@ import ( + "net/http" + "time" + +- "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/versions" + "github.com/docker/docker/integration-cli/checker" + "github.com/docker/docker/internal/test/request" +@@ -35,7 +34,7 @@ func (s *DockerSuite) TestAPICreateWithInvalidHealthcheckParams(c *check.C) { + buf, err := request.ReadBody(body) + c.Assert(err, checker.IsNil) + +- expected := fmt.Sprintf("Interval in Healthcheck cannot be less than %s", container.MinimumDuration) ++ expected := fmt.Sprintf("Interval in Healthcheck cannot be less than one second") + c.Assert(getErrorMessage(c, buf), checker.Contains, expected) + + // test invalid Interval in Healthcheck: larger than 0s but less than 1ms +@@ -82,7 +81,7 @@ func (s *DockerSuite) TestAPICreateWithInvalidHealthcheckParams(c *check.C) { + buf, err = request.ReadBody(body) + c.Assert(err, checker.IsNil) + +- expected = fmt.Sprintf("Timeout in Healthcheck cannot be less than %s", container.MinimumDuration) ++ expected = fmt.Sprintf("Timeout in Healthcheck cannot be less than one second") + c.Assert(getErrorMessage(c, buf), checker.Contains, expected) + + // test invalid Retries in Healthcheck: less than 0 +@@ -131,6 +130,6 @@ func (s *DockerSuite) TestAPICreateWithInvalidHealthcheckParams(c *check.C) { + buf, err = request.ReadBody(body) + c.Assert(err, checker.IsNil) + +- expected = fmt.Sprintf("StartPeriod in Healthcheck cannot be less than %s", container.MinimumDuration) ++ expected = fmt.Sprintf("StartPeriod in Healthcheck cannot be less than one second") + c.Assert(getErrorMessage(c, buf), checker.Contains, expected) + } +-- +2.17.1 + diff --git a/patch/0095-docker-enable-container-health-check-after-re.patch b/patch/0095-docker-enable-container-health-check-after-re.patch new file mode 100644 index 0000000..068f4ab --- /dev/null +++ b/patch/0095-docker-enable-container-health-check-after-re.patch @@ -0,0 +1,41 @@ +From 7f1228356891f66979c7f7e35957ab5392ea8758 Mon Sep 17 00:00:00 2001 +From: zhangsong34 +Date: Fri, 1 Feb 2019 21:18:34 +0800 +Subject: [PATCH 095/111] docker: enable container health check after + restart docker + +reason:enable container health check after restart docker. + +Change-Id: Ic877fdbea8de5b87d2a101c19dbb8a9e8e49c0bb +Signed-off-by: zhangsong34 +--- + components/engine/daemon/daemon.go | 6 +++++- + 5 files changed, 11 insertions(+), 7 deletions(-) + +diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go +index b207709f7c..f5d22bb18b 100644 +--- a/components/engine/daemon/daemon.go ++++ b/components/engine/daemon/daemon.go +@@ -379,6 +379,10 @@ func (daemon *Daemon) restore() error { + if c.IsRunning() || c.IsPaused() { + c.RestartManager().Cancel() // manually start containers because some need to wait for swarm networking + ++ c.Lock() ++ daemon.initHealthMonitor(c) ++ c.Unlock() ++ + if c.IsPaused() && alive { + s, err := daemon.containerd.Status(context.Background(), c.ID) + if err != nil { +@@ -930,7 +934,7 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S + + for operatingSystem, gd := range d.graphDrivers { + layerStores[operatingSystem], err = layer.NewStoreFromOptions(layer.StoreOptions{ +- Root: config.Root, ++ Root: config.Root, + MetadataStorePathTemplate: filepath.Join(config.Root, "image", "%s", "layerdb"), + GraphDriver: gd, + GraphDriverOptions: config.GraphOptions, +-- +2.17.1 + diff --git a/patch/0096-pause-check-tasks-before-updateCgroups.patch b/patch/0096-pause-check-tasks-before-updateCgroups.patch new file mode 100644 index 0000000..7d6be96 --- /dev/null +++ b/patch/0096-pause-check-tasks-before-updateCgroups.patch @@ -0,0 +1,52 @@ +From 086cf3ab125d1c423d07ef877846fd8c1e01f3ac Mon Sep 17 00:00:00 2001 +From: lujingxiao +Date: Mon, 11 Feb 2019 18:44:50 +0800 +Subject: [PATCH 096/111] pause: check tasks before updateCgroups + +reason: In the Pause of dockerd, it updateCgroups first, then +check tasks file if any tasks in the cgroups. We should check the +tasks file before updateCgroups + +Change-Id: I7f30e314b3db9c50459d995bf071e01a47f359f0 +Signed-off-by: lujingxiao +--- + components/engine/daemon/freezer/freezer.go | 9 +++------ + 1 file changed, 3 insertions(+), 6 deletions(-) + +diff --git a/components/engine/daemon/freezer/freezer.go b/components/engine/daemon/freezer/freezer.go +index cd8b3513d7..a0ef299852 100644 +--- a/components/engine/daemon/freezer/freezer.go ++++ b/components/engine/daemon/freezer/freezer.go +@@ -122,10 +122,6 @@ func (f *freezer) Pause() error { + f.Lock() + defer f.Unlock() + +- if err := f.updateCgroup(string(configs.Frozen)); err != nil { +- return err +- } +- + tasks, err := readFile(f.path, "tasks") + if err != nil { + return fmt.Errorf("failed to check container cgroup task status: %v", err) +@@ -134,7 +130,8 @@ func (f *freezer) Pause() error { + if strings.TrimSpace(tasks) == "" { + return fmt.Errorf("error: no tasks running in freeze cgroup") + } +- return nil ++ ++ return f.updateCgroup(string(configs.Frozen)) + } + + // Resume will set the container to running state by writing freeze cgroup. +@@ -186,7 +183,7 @@ func (f *freezer) updateCgroup(state string) error { + } + newState, err := readFile(f.path, "freezer.state") + if err != nil { +- return err ++ return fmt.Errorf("read freezer.state failed after write: %v", err) + } + if strings.TrimSpace(newState) == state { + return nil +-- +2.17.1 + diff --git a/patch/0097-restart-fix-restart-unless-stopped-not-stop-f.patch b/patch/0097-restart-fix-restart-unless-stopped-not-stop-f.patch new file mode 100644 index 0000000..7194897 --- /dev/null +++ b/patch/0097-restart-fix-restart-unless-stopped-not-stop-f.patch @@ -0,0 +1,45 @@ +From 8bd08475a06b5475fc88b207d578a006ad9a45cd Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Wed, 13 Feb 2019 00:31:32 +0800 +Subject: [PATCH 097/111] restart: fix --restart=unless-stopped not + stop for docker stop + +reason: testCE_secure_container_kata_FUN.054.sh + +Change-Id: I536bb6cf1fe698da9fe330b110f122d4e8af17d6 +Signed-off-by: jingrui +--- + components/engine/daemon/monitor.go | 1 + + components/engine/daemon/start.go | 4 +++- + 2 files changed, 4 insertions(+), 1 deletion(-) + +diff --git a/components/engine/daemon/monitor.go b/components/engine/daemon/monitor.go +index 51159eb76d..7ae85f58a9 100644 +--- a/components/engine/daemon/monitor.go ++++ b/components/engine/daemon/monitor.go +@@ -106,6 +106,7 @@ func (daemon *Daemon) ProcessEvent(id string, e libcontainerd.EventType, ei libc + if err != restartmanager.ErrRestartCanceled { + logrus.Errorf("restartmanger wait error: %+v", err) + } ++ c.CheckpointTo(daemon.containersReplica) + } + }() + } +diff --git a/components/engine/daemon/start.go b/components/engine/daemon/start.go +index 96ae45e11e..8ff636b5a5 100644 +--- a/components/engine/daemon/start.go ++++ b/components/engine/daemon/start.go +@@ -203,7 +203,9 @@ func (daemon *Daemon) containerStart(container *container.Container, checkpoint + } + + container.SetRunning(pid, true) +- container.HasBeenManuallyStopped = false ++ if resetRestartManager { ++ container.HasBeenManuallyStopped = false ++ } + container.HasBeenStartedBefore = true + daemon.setStateCounter(container) + +-- +2.17.1 + diff --git a/patch/0099-integration-cli-fix-TestInspectAPIImageRespon.patch b/patch/0099-integration-cli-fix-TestInspectAPIImageRespon.patch new file mode 100644 index 0000000..5fe9caf --- /dev/null +++ b/patch/0099-integration-cli-fix-TestInspectAPIImageRespon.patch @@ -0,0 +1,30 @@ +From b7d540ff87543e02f4d6271afe66edb7aa88477a Mon Sep 17 00:00:00 2001 +From: zhangyu235 +Date: Fri, 15 Feb 2019 15:26:01 +0800 +Subject: [PATCH 099/111] integration-cli: fix + TestInspectAPIImageResponse + +reason:For we tag busybox:glibc before, the tag number will change +in this testcase. So we cancel the action of checking tag number. + +Change-Id: Ib7bf6274d8ca05cc5cbbe3ddd341676b64bb809e +Signed-off-by: zhangyu235 +--- + components/engine/integration-cli/docker_api_inspect_test.go | 1 - + 5 files changed, 6 insertions(+), 7 deletions(-) + +diff --git a/components/engine/integration-cli/docker_api_inspect_test.go b/components/engine/integration-cli/docker_api_inspect_test.go +index 68055b6c14..82d1b19606 100644 +--- a/components/engine/integration-cli/docker_api_inspect_test.go ++++ b/components/engine/integration-cli/docker_api_inspect_test.go +@@ -114,7 +114,6 @@ func (s *DockerSuite) TestInspectAPIImageResponse(c *check.C) { + imageJSON, _, err := cli.ImageInspectWithRaw(context.Background(), "busybox") + c.Assert(err, checker.IsNil) + +- c.Assert(imageJSON.RepoTags, checker.HasLen, 2) + assert.Check(c, is.Contains(imageJSON.RepoTags, "busybox:latest")) + assert.Check(c, is.Contains(imageJSON.RepoTags, "busybox:mytag")) + } +-- +2.17.1 + diff --git a/patch/0100-proquota-fix-quota-basesize.patch b/patch/0100-proquota-fix-quota-basesize.patch new file mode 100644 index 0000000..0c49067 --- /dev/null +++ b/patch/0100-proquota-fix-quota-basesize.patch @@ -0,0 +1,63 @@ +From 582b84ebefcdd71b963dd431fcf9d1d5f9a20552 Mon Sep 17 00:00:00 2001 +From: zhangyu235 +Date: Mon, 18 Feb 2019 21:23:17 +0800 +Subject: [PATCH 100/111] proquota: fix quota basesize + +reason:fix quota basesize + +Change-Id: I268c600b8c63965daf0086796c48ca4e85263e50 +Signed-off-by: zhangyu235 +--- + components/engine/daemon/graphdriver/overlay2/overlay.go | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/components/engine/daemon/graphdriver/overlay2/overlay.go b/components/engine/daemon/graphdriver/overlay2/overlay.go +index 71474f8f36..1a3c9c9d67 100644 +--- a/components/engine/daemon/graphdriver/overlay2/overlay.go ++++ b/components/engine/daemon/graphdriver/overlay2/overlay.go +@@ -89,6 +89,7 @@ const ( + type overlayOptions struct { + overrideKernelCheck bool + quota quota.Quota ++ quotaBaseSize uint64 + } + + // Driver contains information about the home directory and the list of active +@@ -221,6 +222,7 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap + // Try to enable project quota support over xfs and extfs. + if d.quotaCtl, err = quota.NewControl(home, backingFs); err == nil { + projectQuotaSupported = true ++ d.options.quotaBaseSize = opts.quotaBaseSize + } else if opts.quota.Size > 0 { + return nil, fmt.Errorf("Storage option overlay2.size not supported. Filesystem does not support Project Quota: %v", err) + } +@@ -280,7 +282,7 @@ func parseOptions(options []string) (*overlayOptions, error) { + if err != nil { + return nil, err + } +- o.quota.Size = uint64(size) ++ o.quotaBaseSize = uint64(size) + default: + return nil, fmt.Errorf("overlay2: unknown option %s", key) + } +@@ -422,14 +422,14 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts) (retErr + } + }() + +- if (opts != nil && len(opts.StorageOpt) > 0) || d.options.quota.Size > 0 { ++ if opts != nil && (len(opts.StorageOpt) > 0 || d.options.quotaBaseSize > 0) { + driver := &Driver{} + if err := d.parseStorageOpt(opts.StorageOpt, driver); err != nil { + return err + } + +- if driver.options.quota.Size == 0 && d.options.quota.Size > 0 { +- driver.options.quota.Size = d.options.quota.Size ++ if driver.options.quota.Size == 0 && d.options.quotaBaseSize > 0 { ++ driver.options.quota.Size = d.options.quotaBaseSize + } + + if driver.options.quota.Size > 0 { +-- +2.17.1 + diff --git a/patch/0101-daeamon-add-judge-for-client-in-case-of-panic.patch b/patch/0101-daeamon-add-judge-for-client-in-case-of-panic.patch new file mode 100644 index 0000000..f6987c5 --- /dev/null +++ b/patch/0101-daeamon-add-judge-for-client-in-case-of-panic.patch @@ -0,0 +1,34 @@ +From 4711c062b0fd8409eef7ed6ed0963b638765cc7f Mon Sep 17 00:00:00 2001 +From: zhangyu235 +Date: Mon, 18 Feb 2019 21:57:38 +0800 +Subject: [PATCH 101/111] daeamon: add judge for client in case of + panic + +reason:Add judge for client in monitorDaemon(), in case of +null ptr error return cause panic. + +Change-Id: Ia0dd64ba0341414d2dbe213a53daa94e2c5d1723 +--- + components/engine/libcontainerd/supervisor/remote_daemon.go | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/components/engine/libcontainerd/supervisor/remote_daemon.go b/components/engine/libcontainerd/supervisor/remote_daemon.go +index 45cc3c3032..62ea58c8b4 100644 +--- a/components/engine/libcontainerd/supervisor/remote_daemon.go ++++ b/components/engine/libcontainerd/supervisor/remote_daemon.go +@@ -318,8 +318,10 @@ func (r *remote) monitorDaemon(ctx context.Context) { + r.killDaemon() + } + +- client.Close() +- client = nil ++ if client != nil { ++ client.Close() ++ client = nil ++ } + r.daemonPid = -1 + delay = nil + transientFailureCount = 0 +-- +2.17.1 + diff --git a/patch/0102-docker-18.09-fix-docker-stop-error-if-docker-.patch b/patch/0102-docker-18.09-fix-docker-stop-error-if-docker-.patch new file mode 100644 index 0000000..638d73e --- /dev/null +++ b/patch/0102-docker-18.09-fix-docker-stop-error-if-docker-.patch @@ -0,0 +1,57 @@ +From 40c55ba190f46487686d6131b0005737d8bd06af Mon Sep 17 00:00:00 2001 +From: jiangpengfei9 +Date: Fri, 15 Feb 2019 14:13:47 -0500 +Subject: [PATCH 102/111] docker-18.09: fix docker stop error if docker + has been stopped + +reason: If docker stop send SIGTERM signal to stop container failed, it will wait a short time +and send SIGKILL signal to kill container, but if container has exit while docker stop send +SIGKILL to container, which will cause an error which is "Container %s is not running".So if +docker stop meet this problem, we just let it go and print the warning log. + +Change-Id: Ia832aa93c3a94086849cda70110eb772ac3c0a52 +Signed-off-by: jiangpengfei9 +--- + components/engine/daemon/kill.go | 6 ++++-- + components/engine/daemon/stop.go | 1 + + 6 files changed, 11 insertions(+), 8 deletions(-) + +diff --git a/components/engine/daemon/kill.go b/components/engine/daemon/kill.go +index 5b2e497604..d185065b54 100644 +--- a/components/engine/daemon/kill.go ++++ b/components/engine/daemon/kill.go +@@ -72,7 +72,8 @@ func (daemon *Daemon) killWithSignal(container *containerpkg.Container, sig int) + } + + if !container.Running { +- return errNotRunning(container.ID) ++ logrus.Warnf("killWithSignal skip send kill signal to container %s due to container has been stopped",container.ID) ++ return nil + } + + var unpause bool +@@ -127,7 +128,8 @@ func (daemon *Daemon) killWithSignal(container *containerpkg.Container, sig int) + // Kill forcefully terminates a container. + func (daemon *Daemon) Kill(container *containerpkg.Container) error { + if !container.IsRunning() { +- return errNotRunning(container.ID) ++ logrus.Warnf("Kill skip send kill signal to container %s due to container has been stopped",container.ID) ++ return nil + } + + // 1. Send SIGKILL +diff --git a/components/engine/daemon/stop.go b/components/engine/daemon/stop.go +index c3ac09056a..3c4cd766c9 100644 +--- a/components/engine/daemon/stop.go ++++ b/components/engine/daemon/stop.go +@@ -45,6 +45,7 @@ func (daemon *Daemon) containerStop(container *containerpkg.Container, seconds i + stopSignal := container.StopSignal() + // 1. Send a stop signal + if err := daemon.killPossiblyDeadProcess(container, stopSignal); err != nil { ++ logrus.Infof("docker send %d signal to stop container get error: %v", stopSignal, err) + // While normally we might "return err" here we're not going to + // because if we can't stop the container by this point then + // it's probably because it's already stopped. Meaning, between +-- +2.17.1 + diff --git a/patch/0103-docker-fix-parsing-name-with.patch b/patch/0103-docker-fix-parsing-name-with.patch new file mode 100644 index 0000000..c6ff78c --- /dev/null +++ b/patch/0103-docker-fix-parsing-name-with.patch @@ -0,0 +1,62 @@ +From 405d10b7df5fa329a7070cb842a8d5e4e46861d6 Mon Sep 17 00:00:00 2001 +From: lixiang172 +Date: Mon, 18 Feb 2019 22:13:53 +0800 +Subject: [PATCH 103/111] docker: fix parsing name with / + +reason: fix parsing name with / +Do the error check when using --link option, +if the alias name and container name is the same, return error + +Change-Id: I64c39915d34d79ee8abbba2ebe0e66ad3ad08551 +Signed-off-by: yangshukui +Signed-off-by: lixiang172 +--- + components/engine/daemon/daemon.go | 13 +++++++++++++ + .../engine/integration-cli/docker_cli_links_test.go | 8 ++++++++ + 2 files changed, 21 insertions(+) + +diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go +index f5d22bb18b..e26494ed68 100644 +--- a/components/engine/daemon/daemon.go ++++ b/components/engine/daemon/daemon.go +@@ -657,8 +657,21 @@ func (daemon *Daemon) parents(c *container.Container) map[string]*container.Cont + return daemon.linkIndex.parents(c) + } + ++func validateAlias(alias string, c *container.Container) error { ++ if !validContainerNamePattern.MatchString(alias) { ++ return fmt.Errorf("Invalid alias name (%s), only %s are allowed", alias, validContainerNameChars) ++ } ++ if alias == c.Config.Hostname { ++ return fmt.Errorf("Invalid alias name (%s), alias is the same to current container's hostname", alias) ++ } ++ return nil ++} ++ + func (daemon *Daemon) registerLink(parent, child *container.Container, alias string) error { + fullName := path.Join(parent.Name, alias) ++ if err := validateAlias(alias, parent); err != nil { ++ return err ++ } + if err := daemon.containersReplica.ReserveName(fullName, child.ID); err != nil { + if err == container.ErrNameReserved { + logrus.Warnf("error registering link for %s, to %s, as alias %s, ignoring: %v", parent.ID, child.ID, alias, err) +diff --git a/components/engine/integration-cli/docker_cli_links_test.go b/components/engine/integration-cli/docker_cli_links_test.go +index 17b25d7994..9efa1cfbf6 100644 +--- a/components/engine/integration-cli/docker_cli_links_test.go ++++ b/components/engine/integration-cli/docker_cli_links_test.go +@@ -237,3 +237,11 @@ func (s *DockerSuite) TestLinksMultipleWithSameName(c *check.C) { + dockerCmd(c, "run", "-d", "--name=upstream-b", "busybox", "top") + dockerCmd(c, "run", "--link", "upstream-a:upstream", "--link", "upstream-b:upstream", "busybox", "sh", "-c", "ping -c 1 upstream") + } ++func (s *DockerSuite) TestLinksAliasCheck(c *check.C) { ++ testRequires(c, DaemonIsLinux, NotUserNamespace) ++ dockerCmd(c, "run", "-d", "--name=linkalias", "busybox", "top") ++ out, _, _ := dockerCmdWithError("run", "-d", "--link=linkalias:hello/sep", "busybox", "top") ++ c.Assert(out, checker.Contains, "Invalid alias name") ++ out, _, _ = dockerCmdWithError("run", "-d", "--hostname=linkhostname", "--link=linkalias:linkhostname", "busybox", "top") ++ c.Assert(out, checker.Contains, "alias is the same to current container's hostname") ++} +-- +2.17.1 + diff --git a/patch/0104-docker-stats-increase-the-timeout-of-docker-s.patch b/patch/0104-docker-stats-increase-the-timeout-of-docker-s.patch new file mode 100644 index 0000000..4c715b4 --- /dev/null +++ b/patch/0104-docker-stats-increase-the-timeout-of-docker-s.patch @@ -0,0 +1,29 @@ +From f983b1959dae1d5f0dcae1f7480db5ae69906f74 Mon Sep 17 00:00:00 2001 +From: zhangyu235 +Date: Wed, 20 Feb 2019 11:45:45 +0800 +Subject: [PATCH 104/111] docker stats: increase the timeout of docker + stats command + +reason:Increase the timeout of docker stats command, in case of data lost. + +Change-Id: Ib698f7cfdc06928838b343821a6bdca875327ff7 +--- + components/cli/cli/command/container/stats.go | 2 +- + 5 files changed, 7 insertions(+), 7 deletions(-) + +diff --git a/components/cli/cli/command/container/stats.go b/components/cli/cli/command/container/stats.go +index 1f9e1b8556..8387fc988d 100644 +--- a/components/cli/cli/command/container/stats.go ++++ b/components/cli/cli/command/container/stats.go +@@ -172,7 +172,7 @@ func runStats(dockerCli command.Cli, opts *statsOptions) error { + + // Do a quick pause to detect any error with the provided list of + // container names. +- time.Sleep(1500 * time.Millisecond) ++ time.Sleep(2500 * time.Millisecond) + var errs []string + cStats.mu.Lock() + for _, c := range cStats.cs { +-- +2.17.1 + diff --git a/patch/0105-pause-fix-pause-on-exited-container.patch b/patch/0105-pause-fix-pause-on-exited-container.patch new file mode 100644 index 0000000..cab75bf --- /dev/null +++ b/patch/0105-pause-fix-pause-on-exited-container.patch @@ -0,0 +1,55 @@ +From f66b0742f72a0f15c6b805751c00af2c7b0f3193 Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Wed, 20 Feb 2019 23:42:00 +0800 +Subject: [PATCH 105/111] pause: fix pause on exited container + +reason: fix pause on exited container + +Change-Id: I109a88ab6832c3118f6be48f5924679549607740 +Signed-off-by: jingrui +--- + components/engine/daemon/freezer/freezer.go | 21 ++++++++++++++++++- + 5 files changed, 26 insertions(+), 7 deletions(-) + +diff --git a/components/engine/daemon/freezer/freezer.go b/components/engine/daemon/freezer/freezer.go +index a0ef299852..907c7aac2a 100644 +--- a/components/engine/daemon/freezer/freezer.go ++++ b/components/engine/daemon/freezer/freezer.go +@@ -12,6 +12,7 @@ import ( + + "github.com/opencontainers/runc/libcontainer/configs" + "github.com/opencontainers/runc/libcontainer/utils" ++ "github.com/sirupsen/logrus" + ) + + // Freezer is the interface which could be used to pause/resume container, +@@ -131,7 +132,25 @@ func (f *freezer) Pause() error { + return fmt.Errorf("error: no tasks running in freeze cgroup") + } + +- return f.updateCgroup(string(configs.Frozen)) ++ err = f.updateCgroup(string(configs.Frozen)) ++ if err != nil { ++ return err ++ } ++ ++ tasks, err = readFile(f.path, "tasks") ++ if err != nil { ++ err := f.updateCgroup(string(configs.Thawed)) ++ logrus.Warnf("revert pause due to no tasks file. revert-error=%v", err) ++ return fmt.Errorf("failed to check container cgroup task status: %v", err) ++ } ++ ++ if strings.TrimSpace(tasks) == "" { ++ err := f.updateCgroup(string(configs.Thawed)) ++ logrus.Warnf("revert pause due to no tasks. revert-error=%v", err) ++ return fmt.Errorf("error: no tasks running in freeze cgroup") ++ } ++ ++ return nil + } + + // Resume will set the container to running state by writing freeze cgroup. +-- +2.17.1 + diff --git a/patch/0107-restore-cleanup-container-meta-data-when-task.patch b/patch/0107-restore-cleanup-container-meta-data-when-task.patch new file mode 100644 index 0000000..2510dd9 --- /dev/null +++ b/patch/0107-restore-cleanup-container-meta-data-when-task.patch @@ -0,0 +1,37 @@ +From 2e5c40ed0ddf40db1ad0e6964e4391ff6bc8e9e1 Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Fri, 22 Feb 2019 00:13:07 +0800 +Subject: [PATCH 107/111] restore: cleanup container meta data when + task not exist + +reason: docker_hook:testCE_docker_hook_spec_ABN.081.sh +create container include 2 step: +1. create meta data. +2. create task. +when dockerd restart during creating, it may residue meta while create +task failed. cleanup meta data in restore. + +Change-Id: I80ea1694e3df143c4a5679f680e61d43ddcfe3aa +Signed-off-by: jingrui +--- + components/engine/daemon/daemon.go | 4 ++++ + 5 files changed, 10 insertions(+), 6 deletions(-) + +diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go +index e26494ed68..c96e28d88d 100644 +--- a/components/engine/daemon/daemon.go ++++ b/components/engine/daemon/daemon.go +@@ -365,6 +365,10 @@ func (daemon *Daemon) restore() error { + } + if !alive { + ec, exitedAt, err = daemon.containerd.DeleteTask(context.Background(), c.ID) ++ if err != nil && errdefs.IsNotFound(err) { ++ err := daemon.containerd.Delete(context.Background(), c.ID) ++ logrus.Infof("cleanup containerd meta for %s error=%v", c.ID, err) ++ } + if err != nil && !errdefs.IsNotFound(err) { + logrus.WithError(err).Errorf("Failed to delete container %s from containerd", c.ID) + return +-- +2.17.1 + diff --git a/patch/0108-docker-Fix-can-t-run-image-while-the-image-is.patch b/patch/0108-docker-Fix-can-t-run-image-while-the-image-is.patch new file mode 100644 index 0000000..90aed12 --- /dev/null +++ b/patch/0108-docker-Fix-can-t-run-image-while-the-image-is.patch @@ -0,0 +1,107 @@ +From 2b41c414a53011ce005890ff9310bfa5c71fbfbc Mon Sep 17 00:00:00 2001 +From: zhangsong34 +Date: Fri, 18 Jan 2019 17:39:32 +0800 +Subject: [PATCH 108/111] docker: Fix can't run image while the image + is not in `docker images` + +reason:When the layers of image has been damaged, daemon restart fail to load image, +it cause docker can't run the image with unrecognized image ID error. + +Change-Id: I1cecc0dd602cd5a60006ba5c3e6060bd4071fb8e +Signed-off-by: PengFei Yang +Signed-off-by: zhangsong34 +--- + components/engine/daemon/daemon.go | 28 +++++++++++++++++++++++ + components/engine/plugin/backend_linux.go | 4 ++++ + components/engine/reference/store.go | 14 ++++++++++++ + 3 files changed, 46 insertions(+) + +diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go +index c96e28d88d..7716964304 100644 +--- a/components/engine/daemon/daemon.go ++++ b/components/engine/daemon/daemon.go +@@ -1028,6 +1028,34 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S + return nil, err + } + ++ // delete reference of image not nornamlly loaded to imageStore ++ for _, imageID := range rs.List() { ++ if img, err := imageStore.Get(image.ID(imageID)); err == nil { ++ isExist := false ++ if chainID := img.RootFS.ChainID(); chainID != "" { ++ l, err := layerStores[runtime.GOOS].Get(chainID) ++ if err == nil { ++ layer.ReleaseAndLog(layerStores[runtime.GOOS], l) ++ isExist = true ++ } ++ } else { ++ isExist = true ++ } ++ // If the image not exist locally, delete its reference ++ if !isExist { ++ for _, ref := range rs.References(imageID) { ++ isDelete, err := rs.Delete(ref) ++ if isDelete { ++ logrus.Warnf("Delete reference %s for image id %s from reference store", ref.String(), imageID) ++ } ++ if err != nil { ++ logrus.Warnf("Faild to delete reference %s for image id %s: %v", ref.String(), imageID, err) ++ } ++ } ++ } ++ } ++ } ++ + // No content-addressability migration on Windows as it never supported pre-CA + if runtime.GOOS != "windows" { + migrationStart := time.Now() +diff --git a/components/engine/plugin/backend_linux.go b/components/engine/plugin/backend_linux.go +index 044e14b0cb..e5d3be15ee 100644 +--- a/components/engine/plugin/backend_linux.go ++++ b/components/engine/plugin/backend_linux.go +@@ -490,6 +490,10 @@ func (r *pluginReference) References(id digest.Digest) []reference.Named { + return []reference.Named{r.name} + } + ++func (r *pluginReference) List() []digest.Digest { ++ return []digest.Digest{} ++} ++ + func (r *pluginReference) ReferencesByName(ref reference.Named) []refstore.Association { + return []refstore.Association{ + { +diff --git a/components/engine/reference/store.go b/components/engine/reference/store.go +index e54f772b5e..0df11271a7 100644 +--- a/components/engine/reference/store.go ++++ b/components/engine/reference/store.go +@@ -34,6 +34,7 @@ type Store interface { + AddDigest(ref reference.Canonical, id digest.Digest, force bool) error + Delete(ref reference.Named) (bool, error) + Get(ref reference.Named) (digest.Digest, error) ++ List() []digest.Digest + } + + type store struct { +@@ -274,6 +275,19 @@ func (store *store) References(id digest.Digest) []reference.Named { + return references + } + ++// List retrieves list of image ID, return nil if no image ++func (store *store) List() []digest.Digest { ++ store.mu.RLock() ++ defer store.mu.RUnlock() ++ ++ var ids []digest.Digest ++ for id, _ := range store.referencesByIDCache { ++ ids = append(ids, id) ++ } ++ ++ return ids ++} ++ + // ReferencesByName returns the references for a given repository name. + // If there are no references known for this repository name, + // ReferencesByName returns nil. +-- +2.17.1 + diff --git a/patch/0109-graphdriver-add-Checkparent-method.patch b/patch/0109-graphdriver-add-Checkparent-method.patch new file mode 100644 index 0000000..18d1fde --- /dev/null +++ b/patch/0109-graphdriver-add-Checkparent-method.patch @@ -0,0 +1,199 @@ +From f3867c1d93f375441eda73a0c6cb4669fc75f660 Mon Sep 17 00:00:00 2001 +From: zhangsong34 +Date: Sat, 23 Feb 2019 14:39:34 +0800 +Subject: [PATCH 109/111] graphdriver: add Checkparent method + +reason:Use this method to checkout the relationship between +a layer and this parent. It is very useful if graphdriver +stores methdata in its own dirs. + +Change-Id: I07ebe752eb58bc2a027f8a93568b498ad8603713 +Signed-off-by: Liu Hua +Signed-off-by: Deng Guangxing +Signed-off-by: zhangsong34 +--- + .../engine/daemon/graphdriver/aufs/aufs.go | 5 +++++ + .../engine/daemon/graphdriver/btrfs/btrfs.go | 5 +++++ + .../daemon/graphdriver/devmapper/driver.go | 5 +++++ + components/engine/daemon/graphdriver/driver.go | 2 ++ + .../daemon/graphdriver/overlay/overlay.go | 5 +++++ + .../daemon/graphdriver/overlay2/overlay.go | 18 ++++++++++++++++++ + components/engine/daemon/graphdriver/proxy.go | 5 +++++ + .../engine/daemon/graphdriver/vfs/driver.go | 5 +++++ + .../engine/daemon/graphdriver/zfs/zfs.go | 5 +++++ + components/engine/layer/layer_store.go | 5 +++++ + 10 files changed, 60 insertions(+) + +diff --git a/components/engine/daemon/graphdriver/aufs/aufs.go b/components/engine/daemon/graphdriver/aufs/aufs.go +index 303138d48a..eef8387594 100644 +--- a/components/engine/daemon/graphdriver/aufs/aufs.go ++++ b/components/engine/daemon/graphdriver/aufs/aufs.go +@@ -235,6 +235,11 @@ func (a *Driver) GetAll() []string { + return []string{} + } + ++// CheckParent not implemented ++func (a *Driver) CheckParent(id, parent string) error { ++ return nil ++} ++ + // CreateReadWrite creates a layer that is writable for use as a container + // file system. + func (a *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error { +diff --git a/components/engine/daemon/graphdriver/btrfs/btrfs.go b/components/engine/daemon/graphdriver/btrfs/btrfs.go +index d04ce10be9..7d1f9dc2b1 100644 +--- a/components/engine/daemon/graphdriver/btrfs/btrfs.go ++++ b/components/engine/daemon/graphdriver/btrfs/btrfs.go +@@ -172,6 +172,11 @@ func (a *Driver) GetAll() []string { + return []string{} + } + ++// CheckParent not implemented ++func (d *Driver) CheckParent(id, parent string) error { ++ return nil ++} ++ + // Cleanup unmounts the home directory. + func (d *Driver) Cleanup() error { + err := d.subvolDisableQuota() +diff --git a/components/engine/daemon/graphdriver/devmapper/driver.go b/components/engine/daemon/graphdriver/devmapper/driver.go +index a56b26bc8f..1eade3aba5 100644 +--- a/components/engine/daemon/graphdriver/devmapper/driver.go ++++ b/components/engine/daemon/graphdriver/devmapper/driver.go +@@ -131,6 +131,11 @@ func (a *Driver) GetAll() []string { + return []string{} + } + ++// CheckParent not implemented ++func (d *Driver) CheckParent(id, parent string) error { ++ return nil ++} ++ + // Cleanup unmounts a device. + func (d *Driver) Cleanup() error { + err := d.DeviceSet.Shutdown(d.home) +diff --git a/components/engine/daemon/graphdriver/driver.go b/components/engine/daemon/graphdriver/driver.go +index 672257a9b5..9976571cde 100644 +--- a/components/engine/daemon/graphdriver/driver.go ++++ b/components/engine/daemon/graphdriver/driver.go +@@ -80,6 +80,8 @@ type ProtoDriver interface { + Cleanup() error + // GetAll returns all the mountid exists in this driver + GetAll() []string ++ // CheckParent checks whether the parent is the right lower layer ++ CheckParent(id, parent string) error + } + + // DiffDriver is the interface to use to implement graph diffs +diff --git a/components/engine/daemon/graphdriver/overlay/overlay.go b/components/engine/daemon/graphdriver/overlay/overlay.go +index d59a6dfc83..7dbeec5376 100644 +--- a/components/engine/daemon/graphdriver/overlay/overlay.go ++++ b/components/engine/daemon/graphdriver/overlay/overlay.go +@@ -276,6 +276,11 @@ func (d *Driver) Cleanup() error { + return mount.RecursiveUnmount(d.home) + } + ++// CheckParent not implemented ++func (d *Driver) CheckParent(id, parent string) error { ++ return nil ++} ++ + // CreateReadWrite creates a layer that is writable for use as a container + // file system. + func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error { +diff --git a/components/engine/daemon/graphdriver/overlay2/overlay.go b/components/engine/daemon/graphdriver/overlay2/overlay.go +index 1a3c9c9d67..7fd3fab645 100644 +--- a/components/engine/daemon/graphdriver/overlay2/overlay.go ++++ b/components/engine/daemon/graphdriver/overlay2/overlay.go +@@ -360,6 +360,24 @@ func (d *Driver) GetMetadata(id string) (map[string]string, error) { + return metadata, nil + } + ++// CheckParent checks the relationship between id and parent ++func (d *Driver) CheckParent(id, parent string) error { ++ metadata, err := d.GetMetadata(id) ++ if err != nil { ++ return err ++ } ++ lowerDirs, exist := metadata["LowerDir"] ++ if !exist { ++ return fmt.Errorf("%s does not have lower layers", id) ++ } ++ ++ if !strings.Contains(lowerDirs, parent) { ++ return fmt.Errorf("Lower layer(%s) of %s does not exist", parent, id) ++ } ++ return nil ++ ++} ++ + // Cleanup any state created by overlay which should be cleaned when daemon + // is being shutdown. For now, we just have to unmount the bind mounted + // we had created. +diff --git a/components/engine/daemon/graphdriver/proxy.go b/components/engine/daemon/graphdriver/proxy.go +index 6c132d40a8..990f9b4d4e 100644 +--- a/components/engine/daemon/graphdriver/proxy.go ++++ b/components/engine/daemon/graphdriver/proxy.go +@@ -96,6 +96,11 @@ func (d *graphDriverProxy) GetAll() []string { + return []string{} + } + ++// CheckParent not implemented ++func (a *graphDriverProxy) CheckParent(id, parent string) error { ++ return nil ++} ++ + func (d *graphDriverProxy) CreateReadWrite(id, parent string, opts *CreateOpts) error { + return d.create("GraphDriver.CreateReadWrite", id, parent, opts) + } +diff --git a/components/engine/daemon/graphdriver/vfs/driver.go b/components/engine/daemon/graphdriver/vfs/driver.go +index 8c2947d9ee..6b9e92e16c 100644 +--- a/components/engine/daemon/graphdriver/vfs/driver.go ++++ b/components/engine/daemon/graphdriver/vfs/driver.go +@@ -69,6 +69,11 @@ func (a *Driver) GetAll() []string { + return []string{} + } + ++// CheckParent not implemented ++func (d *Driver) CheckParent(id, parent string) error { ++ return nil ++} ++ + // Cleanup is used to implement graphdriver.ProtoDriver. There is no cleanup required for this driver. + func (d *Driver) Cleanup() error { + return nil +diff --git a/components/engine/daemon/graphdriver/zfs/zfs.go b/components/engine/daemon/graphdriver/zfs/zfs.go +index e1e2d0d823..e5958093db 100644 +--- a/components/engine/daemon/graphdriver/zfs/zfs.go ++++ b/components/engine/daemon/graphdriver/zfs/zfs.go +@@ -232,6 +232,11 @@ func (a *Driver) GetAll() []string { + return []string{} + } + ++// CheckParent not implemented ++func (d *Driver) CheckParent(id, parent string) error { ++ return nil ++} ++ + func (d *Driver) cloneFilesystem(name, parentName string) error { + snapshotName := fmt.Sprintf("%d", time.Now().Nanosecond()) + parentDataset := zfs.Dataset{Name: parentName} +diff --git a/components/engine/layer/layer_store.go b/components/engine/layer/layer_store.go +index b6fc45e655..c514ed802a 100644 +--- a/components/engine/layer/layer_store.go ++++ b/components/engine/layer/layer_store.go +@@ -192,6 +192,11 @@ func (ls *layerStore) loadLayer(layer ChainID) (l *roLayer, err error) { + if err != nil { + return nil, err + } ++ ++ err = ls.driver.CheckParent(cacheID, p.cacheID) ++ if err != nil { ++ return nil, err ++ } + cl.parent = p + } else { + _, err := ls.driver.GetMetadata(cacheID) +-- +2.17.1 + diff --git a/patch/0110-docker-Fix-can-t-pull-image-while-the-image-i.patch b/patch/0110-docker-Fix-can-t-pull-image-while-the-image-i.patch new file mode 100644 index 0000000..e6bd215 --- /dev/null +++ b/patch/0110-docker-Fix-can-t-pull-image-while-the-image-i.patch @@ -0,0 +1,139 @@ +From d3cd69071d70e749d0561a6ebedf089c9d973d76 Mon Sep 17 00:00:00 2001 +From: zhangsong34 +Date: Sat, 23 Feb 2019 14:42:44 +0800 +Subject: [PATCH 110/111] docker: Fix can't pull image while the image + is not in `docker images` + +reason:When the layers of image has been damaged, daemon restart fail to load image,it cause +docker pull can't normally download the image. + +delete image reference from repositories.json if no image configfound + cherry-pick from docker 1.11.2: ca25e85 + +Change-Id: Iab6081b34d76bc0bf49414d041115db39c1a256e +Signed-off-by: PengFei Yang yangpengfei4@huawei.com +Signed-off-by: zhangsong34 +--- + components/engine/daemon/daemon.go | 27 +++++++++++-------- + components/engine/daemon/images/image_pull.go | 2 ++ + components/engine/distribution/config.go | 2 ++ + components/engine/distribution/pull_v2.go | 20 +++++++++++--- + 4 files changed, 36 insertions(+), 15 deletions(-) + +diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go +index 7716964304..84a28df78a 100644 +--- a/components/engine/daemon/daemon.go ++++ b/components/engine/daemon/daemon.go +@@ -1029,9 +1029,10 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S + } + + // delete reference of image not nornamlly loaded to imageStore ++ var isExist bool + for _, imageID := range rs.List() { + if img, err := imageStore.Get(image.ID(imageID)); err == nil { +- isExist := false ++ isExist = false + if chainID := img.RootFS.ChainID(); chainID != "" { + l, err := layerStores[runtime.GOOS].Get(chainID) + if err == nil { +@@ -1041,19 +1042,23 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S + } else { + isExist = true + } +- // If the image not exist locally, delete its reference +- if !isExist { +- for _, ref := range rs.References(imageID) { +- isDelete, err := rs.Delete(ref) +- if isDelete { +- logrus.Warnf("Delete reference %s for image id %s from reference store", ref.String(), imageID) +- } +- if err != nil { +- logrus.Warnf("Faild to delete reference %s for image id %s: %v", ref.String(), imageID, err) +- } ++ } else { ++ logrus.Warnf("Failed to get image configration for image id %s, error: %s", imageID, err) ++ } ++ ++ // If the image not exist locally, delete its reference ++ if !isExist { ++ for _, ref := range rs.References(imageID) { ++ isDelete, err := rs.Delete(ref) ++ if isDelete { ++ logrus.Warnf("Delete reference %s for image id %s from reference store", ref.String(), imageID) ++ } ++ if err != nil { ++ logrus.Warnf("Faild to delete reference %s for image id %s: %v", ref.String(), imageID, err) + } + } + } ++ + } + + // No content-addressability migration on Windows as it never supported pre-CA +diff --git a/components/engine/daemon/images/image_pull.go b/components/engine/daemon/images/image_pull.go +index 3e6b433037..1c57c80c3c 100644 +--- a/components/engine/daemon/images/image_pull.go ++++ b/components/engine/daemon/images/image_pull.go +@@ -3,6 +3,7 @@ package images // import "github.com/docker/docker/daemon/images" + import ( + "context" + "io" ++ "runtime" + "strings" + "time" + +@@ -73,6 +74,7 @@ func (i *ImageService) pullImageWithReference(ctx context.Context, ref reference + RegistryService: i.registryService, + ImageEventLogger: i.LogImageEvent, + MetadataStore: i.distributionMetadataStore, ++ LayerStore: i.layerStores[runtime.GOOS], + ImageStore: distribution.NewImageConfigStoreFromStore(i.imageStore), + ReferenceStore: i.referenceStore, + }, +diff --git a/components/engine/distribution/config.go b/components/engine/distribution/config.go +index 438051c296..211d4f049d 100644 +--- a/components/engine/distribution/config.go ++++ b/components/engine/distribution/config.go +@@ -42,6 +42,8 @@ type Config struct { + // MetadataStore is the storage backend for distribution-specific + // metadata. + MetadataStore metadata.Store ++ // LayerStore manages layers. ++ LayerStore layer.Store + // ImageStore manages images. + ImageStore ImageConfigStore + // ReferenceStore manages tags. This value is optional, when excluded +diff --git a/components/engine/distribution/pull_v2.go b/components/engine/distribution/pull_v2.go +index 8f05cfa0b2..2c90e2f93f 100644 +--- a/components/engine/distribution/pull_v2.go ++++ b/components/engine/distribution/pull_v2.go +@@ -555,10 +555,22 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s + } + + target := mfst.Target() +- if _, err := p.config.ImageStore.Get(target.Digest); err == nil { +- // If the image already exists locally, no need to pull +- // anything. +- return target.Digest, manifestDigest, nil ++ if img, err := p.config.ImageStore.Get(target.Digest); err == nil { ++ rootfs, err := p.config.ImageStore.RootFSFromConfig(img) ++ if err == nil { ++ if chainID := rootfs.ChainID(); chainID != "" { ++ l, err := p.config.LayerStore.Get(chainID) ++ if err == nil { ++ layer.ReleaseAndLog(p.config.LayerStore, l) ++ // If the image already exists locally, no need to pull anything. ++ return target.Digest, manifestDigest, nil ++ } ++ } else { ++ return target.Digest, manifestDigest, nil ++ } ++ } else { ++ return target.Digest, manifestDigest, nil ++ } + } + + var descriptors []xfer.DownloadDescriptor +-- +2.17.1 + diff --git a/patch/0112-docker-overlay2-quota-control-bugfix.patch b/patch/0112-docker-overlay2-quota-control-bugfix.patch new file mode 100644 index 0000000..dde7ef5 --- /dev/null +++ b/patch/0112-docker-overlay2-quota-control-bugfix.patch @@ -0,0 +1,46 @@ +From 3e057e395eae95686693584aff1e5236e30ff437 Mon Sep 17 00:00:00 2001 +From: zhangsong34 +Date: Tue, 5 Mar 2019 22:39:26 +0800 +Subject: [PATCH] docker: overlay2 quota control bugfix + +reason:overlay2 quota control bugfix. + +Change-Id: I0e016fdb7619e8213d92c6eb2d5ba67ab606968e +Signed-off-by: zhangsong34 +--- + .../engine/daemon/graphdriver/overlay2/overlay.go | 18 +++++++++++------- + 1 file changed, 11 insertions(+), 7 deletions(-) + +diff --git a/components/engine/daemon/graphdriver/overlay2/overlay.go b/components/engine/daemon/graphdriver/overlay2/overlay.go +index 7fd3fab..a755c86 100644 +--- a/components/engine/daemon/graphdriver/overlay2/overlay.go ++++ b/components/engine/daemon/graphdriver/overlay2/overlay.go +@@ -452,15 +452,19 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts) (retErr + } + + if driver.options.quota.Size > 0 { +- // Set container disk quota limit +- if err := d.quotaCtl.SetQuota(dir, driver.options.quota); err != nil { +- return err ++ if d.quotaCtl != nil { ++ // Set container disk quota limit ++ if err := d.quotaCtl.SetQuota(dir, driver.options.quota); err != nil { ++ return err ++ } + } + } + } else if d.options.quota.Size > 0 { +- // docker run not specified quota size, but dockerd does, so limits it also +- if err := d.quotaCtl.SetQuota(dir, d.options.quota); err != nil { +- return err ++ if d.quotaCtl != nil { ++ // docker run not specified quota size, but dockerd does, so limits it also ++ if err := d.quotaCtl.SetQuota(dir, d.options.quota); err != nil { ++ return err ++ } + } + } + +-- +1.8.3.1 + diff --git a/patch/0114-docker-mask-internal-proc-files.patch b/patch/0114-docker-mask-internal-proc-files.patch new file mode 100644 index 0000000..9bdbe9b --- /dev/null +++ b/patch/0114-docker-mask-internal-proc-files.patch @@ -0,0 +1,46 @@ +From 9074b27c256b5a65883be587c8637dc3926016eb Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Tue, 9 Apr 2019 16:42:47 +0800 +Subject: [PATCH] docker: mask internal proc files + +Change-Id: I249d3f32bac586c37f97f0861afda0ddbfd7561e +Signed-off-by: jingrui +--- + .../github.com/containerd/containerd/oci/spec.go | 14 +++++++++++++- + 1 file changed, 13 insertions(+), 1 deletion(-) + +diff --git a/components/cli/vendor/github.com/containerd/containerd/oci/spec.go b/components/cli/vendor/github.com/containerd/containerd/oci/spec.go +index 6fb31e454c..bc5cc45c16 100644 +--- a/components/cli/vendor/github.com/containerd/containerd/oci/spec.go ++++ b/components/cli/vendor/github.com/containerd/containerd/oci/spec.go +@@ -208,14 +208,26 @@ func populateDefaultUnixSpec(ctx context.Context, s *Spec, id string) error { + Linux: &specs.Linux{ + MaskedPaths: []string{ + "/proc/acpi", ++ "/proc/config.gz", + "/proc/kcore", + "/proc/keys", + "/proc/latency_stats", + "/proc/timer_list", + "/proc/timer_stats", + "/proc/sched_debug", +- "/sys/firmware", + "/proc/scsi", ++ "/proc/signo", ++ "/proc/sig_catch", ++ "/proc/kbox", ++ "/proc/oom_extend", ++ "/proc/fdthreshold", ++ "/proc/fdstat", ++ "/proc/fdenable", ++ "/proc/files_panic_enable", ++ "/sys/firmware", ++ "/proc/cpuirqstat", ++ "/proc/memstat", ++ "/proc/iomem_ext", + }, + ReadonlyPaths: []string{ + "/proc/asound", +-- +2.17.1 + diff --git a/patch/0115-docker-enable-bep-ldflags.patch b/patch/0115-docker-enable-bep-ldflags.patch new file mode 100644 index 0000000..871565c --- /dev/null +++ b/patch/0115-docker-enable-bep-ldflags.patch @@ -0,0 +1,54 @@ +From 3fc47d106cc8648c6a2cb11ae68789b35dac10e2 Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Thu, 11 Apr 2019 23:43:59 +0800 +Subject: [PATCH] docker: enable bep ldflags + +Change-Id: Ib1a6f2a6a1c45f8eba4811e38469c26c6117128b +Signed-off-by: jingrui +--- + components/cli/scripts/build/dynbinary | 7 ++++++- + components/engine/hack/make/.binary | 4 ++++ + 2 files changed, 10 insertions(+), 1 deletion(-) + mode change 100644 => 100755 components/engine/hack/make/.binary + +diff --git a/components/cli/scripts/build/dynbinary b/components/cli/scripts/build/dynbinary +index 4feb7e71d8..c9f33be17b 100755 +--- a/components/cli/scripts/build/dynbinary ++++ b/components/cli/scripts/build/dynbinary +@@ -9,6 +9,11 @@ source ./scripts/build/.variables + + echo "Building dynamically linked $TARGET" + export CGO_ENABLED=1 +-go build -o "${TARGET}" -tags pkcs11 --ldflags "${LDFLAGS}" -buildmode=pie "${SOURCE}" ++ ++BEP_DIR=/tmp/docker-build-bep ++BEP_FLAGS="-tmpdir=$BEP_DIR" ++mkdir -p $BEP_DIR ++ ++go build -o "${TARGET}" -tags pkcs11 --ldflags " $BEP_FLAGS ${LDFLAGS}" -buildmode=pie "${SOURCE}" + + ln -sf "$(basename "${TARGET}")" build/docker +diff --git a/components/engine/hack/make/.binary b/components/engine/hack/make/.binary +old mode 100644 +new mode 100755 +index 010c2c11da..7a203edf40 +--- a/components/engine/hack/make/.binary ++++ b/components/engine/hack/make/.binary +@@ -60,10 +60,14 @@ case "$(go env GOOS)/$(go env GOARCH)" in + esac + + echo "Building: $DEST/$BINARY_FULLNAME" ++BEP_DIR=/tmp/dockerd-build-bep ++BEP_FLAGS="-tmpdir=$BEP_DIR" ++mkdir -p $BEP_DIR + go build \ + -o "$DEST/$BINARY_FULLNAME" \ + "${BUILDFLAGS[@]}" \ + -ldflags " ++ $BEP_FLAGS + $LDFLAGS + $LDFLAGS_STATIC_DOCKER + $DOCKER_LDFLAGS +-- +2.17.1 + diff --git a/patch/0116-docker-build-with-relro-flags.patch b/patch/0116-docker-build-with-relro-flags.patch new file mode 100644 index 0000000..bff1397 --- /dev/null +++ b/patch/0116-docker-build-with-relro-flags.patch @@ -0,0 +1,40 @@ +From 82d288bb3b3457eaecf78510c9ecd86f91beaf2e Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Tue, 16 Apr 2019 10:34:11 +0800 +Subject: [PATCH] docker: build with relro flags + +Change-Id: I227dd6dfcf4560671abd4a7e9362485591686933 +Signed-off-by: jingrui +--- + components/cli/scripts/build/dynbinary | 2 +- + components/engine/hack/make/.binary | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/components/cli/scripts/build/dynbinary b/components/cli/scripts/build/dynbinary +index c9f33be17b..db91da1a28 100755 +--- a/components/cli/scripts/build/dynbinary ++++ b/components/cli/scripts/build/dynbinary +@@ -14,6 +14,6 @@ BEP_DIR=/tmp/docker-build-bep + BEP_FLAGS="-tmpdir=$BEP_DIR" + mkdir -p $BEP_DIR + +-go build -o "${TARGET}" -tags pkcs11 --ldflags " $BEP_FLAGS ${LDFLAGS}" -buildmode=pie "${SOURCE}" ++go build -o "${TARGET}" -tags pkcs11 --ldflags " -extldflags=-zrelro -extldflags=-znow $BEP_FLAGS ${LDFLAGS}" -buildmode=pie "${SOURCE}" + + ln -sf "$(basename "${TARGET}")" build/docker +diff --git a/components/engine/hack/make/.binary b/components/engine/hack/make/.binary +index 7a203edf40..578e8aea5d 100755 +--- a/components/engine/hack/make/.binary ++++ b/components/engine/hack/make/.binary +@@ -67,7 +67,7 @@ go build \ + -o "$DEST/$BINARY_FULLNAME" \ + "${BUILDFLAGS[@]}" \ + -ldflags " +- $BEP_FLAGS ++ -extldflags=-zrelro -extldflags=-znow $BEP_FLAGS + $LDFLAGS + $LDFLAGS_STATIC_DOCKER + $DOCKER_LDFLAGS +-- +2.17.1 + diff --git a/patch/0117-healthcheck-synchronize-the-healthcheck-statu.patch b/patch/0117-healthcheck-synchronize-the-healthcheck-statu.patch new file mode 100644 index 0000000..2b6390d --- /dev/null +++ b/patch/0117-healthcheck-synchronize-the-healthcheck-statu.patch @@ -0,0 +1,34 @@ +From c91cf6bd930293e4eadcd2a20c704b589ab0b371 Mon Sep 17 00:00:00 2001 +From: zhangyu235 +Date: Thu, 18 Apr 2019 16:04:18 +0800 +Subject: [PATCH] healthcheck: synchronize the healthcheck status of + containers when restart daemon + +Change-Id: I6bcced3c69deb9e1a88fff229f344e4ab28f80b2 +--- + components/engine/daemon/daemon.go | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go +index 84a28df..041e714 100644 +--- a/components/engine/daemon/daemon.go ++++ b/components/engine/daemon/daemon.go +@@ -427,6 +427,15 @@ func (daemon *Daemon) restore() error { + c.Unlock() + } + ++ if getProbe(c) != nil { ++ c.Lock() ++ if err := c.CheckpointTo(daemon.containersReplica); err != nil { ++ logrus.WithError(err).WithField("container", c.ID). ++ Error("Failed to checkpoint container state") ++ } ++ c.Unlock() ++ } ++ + // we call Mount and then Unmount to get BaseFs of the container + if err := daemon.Mount(c); err != nil { + // The mount is unlikely to fail. However, in case mount fails +-- +2.7.4.3 + diff --git a/patch/0118-docker-fix-opened-file-not-close.patch b/patch/0118-docker-fix-opened-file-not-close.patch new file mode 100644 index 0000000..855f456 --- /dev/null +++ b/patch/0118-docker-fix-opened-file-not-close.patch @@ -0,0 +1,39 @@ +From 2fb693565515e5aac7ba69b93320edb39f3c7fbb Mon Sep 17 00:00:00 2001 +From: lixiang172 +Date: Fri, 19 Apr 2019 17:38:43 +0800 +Subject: [PATCH] docker: fix opened file not close + +Change-Id: Ibf832b1971a85c85de8dfc0587f5d8762e70c16e +Signed-off-by: lixiang172 +--- + components/cli/internal/pkg/containerized/proxy.go | 1 + + components/engine/daemon/logger/loggerutils/logfile.go | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/components/cli/internal/pkg/containerized/proxy.go b/components/cli/internal/pkg/containerized/proxy.go +index ed195db..f88be52 100644 +--- a/components/cli/internal/pkg/containerized/proxy.go ++++ b/components/cli/internal/pkg/containerized/proxy.go +@@ -57,6 +57,7 @@ func storeConfig(name string, cfg *proxyConfig) error { + if err != nil { + return err + } ++ defer fd.Close() + err = fd.Truncate(0) + if err != nil { + return err +diff --git a/components/engine/daemon/logger/loggerutils/logfile.go b/components/engine/daemon/logger/loggerutils/logfile.go +index 623f78f..3b30ce2 100644 +--- a/components/engine/daemon/logger/loggerutils/logfile.go ++++ b/components/engine/daemon/logger/loggerutils/logfile.go +@@ -118,6 +118,7 @@ func NewLogFile(logPath string, capacity int64, maxFiles int, compress bool, mar + + size, err := log.Seek(0, os.SEEK_END) + if err != nil { ++ log.Close() + return nil, err + } + +-- +1.8.3.1 + diff --git a/patch/0119-docker-set-makefile-buildid.patch b/patch/0119-docker-set-makefile-buildid.patch new file mode 100644 index 0000000..37ffe54 --- /dev/null +++ b/patch/0119-docker-set-makefile-buildid.patch @@ -0,0 +1,41 @@ +From 66e09ef9a29c4a24ca7771fa120cd35b0769a70d Mon Sep 17 00:00:00 2001 +From: lixiang172 +Date: Tue, 23 Apr 2019 12:28:40 +0800 +Subject: [PATCH] docker: set makefile buildid + +reason: set makefile buildid + +Signed-off-by: lixiang172 +--- + components/cli/scripts/build/dynbinary | 2 +- + components/engine/hack/make/.binary | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/components/cli/scripts/build/dynbinary b/components/cli/scripts/build/dynbinary +index db91da1..2442166 100755 +--- a/components/cli/scripts/build/dynbinary ++++ b/components/cli/scripts/build/dynbinary +@@ -14,6 +14,6 @@ BEP_DIR=/tmp/docker-build-bep + BEP_FLAGS="-tmpdir=$BEP_DIR" + mkdir -p $BEP_DIR + +-go build -o "${TARGET}" -tags pkcs11 --ldflags " -extldflags=-zrelro -extldflags=-znow $BEP_FLAGS ${LDFLAGS}" -buildmode=pie "${SOURCE}" ++go build -o "${TARGET}" -tags pkcs11 --ldflags " -buildid=IdByIsula -extldflags=-zrelro -extldflags=-znow $BEP_FLAGS ${LDFLAGS}" -buildmode=pie "${SOURCE}" + + ln -sf "$(basename "${TARGET}")" build/docker +diff --git a/components/engine/hack/make/.binary b/components/engine/hack/make/.binary +index 578e8ae..f76b6f7 100755 +--- a/components/engine/hack/make/.binary ++++ b/components/engine/hack/make/.binary +@@ -67,7 +67,7 @@ go build \ + -o "$DEST/$BINARY_FULLNAME" \ + "${BUILDFLAGS[@]}" \ + -ldflags " +- -extldflags=-zrelro -extldflags=-znow $BEP_FLAGS ++ -buildid=IdByIsula -extldflags=-zrelro -extldflags=-znow $BEP_FLAGS + $LDFLAGS + $LDFLAGS_STATIC_DOCKER + $DOCKER_LDFLAGS +-- +1.8.3.1 + diff --git a/patch/0120-docker-fix-docker-logs-hangs-when-using-journ.patch b/patch/0120-docker-fix-docker-logs-hangs-when-using-journ.patch new file mode 100644 index 0000000..b3823e7 --- /dev/null +++ b/patch/0120-docker-fix-docker-logs-hangs-when-using-journ.patch @@ -0,0 +1,30 @@ +From 34112ccb2adef535a339d32a517976f7674b22ee Mon Sep 17 00:00:00 2001 +From: xiadanni1 +Date: Fri, 10 May 2019 02:59:48 +0800 +Subject: [PATCH] docker: fix docker logs hangs when using journald + +reason: Fix when using journald as log-driver, docker logs process hangs after container stops. + +Change-Id: I41832b77b0282376c6b2ffc77978c76a617361b8 +Signed-off-by: xiadanni1 +--- + components/engine/daemon/logger/journald/read.go | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/components/engine/daemon/logger/journald/read.go b/components/engine/daemon/logger/journald/read.go +index cadb97f..d4b1c75 100644 +--- a/components/engine/daemon/logger/journald/read.go ++++ b/components/engine/daemon/logger/journald/read.go +@@ -303,6 +303,9 @@ func (s *journald) followJournal(logWatcher *logger.LogWatcher, j *C.sd_journal, + // Notify the other goroutine that its work is done. + C.close(pfd[1]) + cursor = <-newCursor ++ case <-logWatcher.WatchProducerGone(): ++ C.close(pfd[1]) ++ cursor = <-newCursor + } + + return cursor +-- +1.8.3.1 + diff --git a/patch/0121-docker-add-start-timeout-for-container.patch b/patch/0121-docker-add-start-timeout-for-container.patch new file mode 100644 index 0000000..474464d --- /dev/null +++ b/patch/0121-docker-add-start-timeout-for-container.patch @@ -0,0 +1,73 @@ +From f669a33c163fb53e64d5b0582418a38662927c87 Mon Sep 17 00:00:00 2001 +From: lixiang172 +Date: Fri, 10 May 2019 16:52:19 +0800 +Subject: [PATCH] docker: add start-timeout for container + +Change-Id: Ife8660b3fc665535086bcb0ea56454c7f5147140 +Signed-off-by: lixiang172 +--- + components/engine/cmd/dockerd/config_unix.go | 1 + + components/engine/cmd/dockerd/daemon.go | 19 +++++++++++++++++++ + components/engine/daemon/config/config_unix.go | 1 + + 3 files changed, 21 insertions(+) + +diff --git a/components/engine/cmd/dockerd/config_unix.go b/components/engine/cmd/dockerd/config_unix.go +index 2dbd84b..8d38d75 100644 +--- a/components/engine/cmd/dockerd/config_unix.go ++++ b/components/engine/cmd/dockerd/config_unix.go +@@ -46,5 +46,6 @@ func installConfigFlags(conf *config.Config, flags *pflag.FlagSet) { + flags.BoolVar(&conf.NoNewPrivileges, "no-new-privileges", false, "Set no-new-privileges by default for new containers") + flags.StringVar(&conf.IpcMode, "default-ipc-mode", config.DefaultIpcMode, `Default mode for containers ipc ("shareable" | "private")`) + flags.Var(&conf.NetworkConfig.DefaultAddressPools, "default-address-pool", "Default address pools for node specific local networks") ++ flags.StringVar(&conf.StartTimeout, "start-timeout", "2m", "Timeout duration for waiting on a container to start before it is killed") + + } +diff --git a/components/engine/cmd/dockerd/daemon.go b/components/engine/cmd/dockerd/daemon.go +index 8395373..ea00c56 100644 +--- a/components/engine/cmd/dockerd/daemon.go ++++ b/components/engine/cmd/dockerd/daemon.go +@@ -94,6 +94,9 @@ func (cli *DaemonCli) start(opts *daemonOptions) (err error) { + logrus.Warn("Running experimental build") + } + ++ if err := cli.setRuntimeStartTimeout(); err != nil { ++ return err ++ } + logrus.SetFormatter(&logrus.TextFormatter{ + TimestampFormat: jsonmessage.RFC3339NanoFixed, + DisableColors: cli.Config.RawLogs, +@@ -670,3 +673,19 @@ func systemContainerdRunning() bool { + _, err := os.Lstat(containerddefaults.DefaultAddress) + return err == nil + } ++ ++func (cli *DaemonCli) setRuntimeStartTimeout() error { ++ minRuntimeTimeout := 30 * time.Second ++ maxRuntimeTimeout := 10 * time.Minute ++ env := "DOCKER_RUNTIME_START_TIMEOUT" ++ defaultRuntimeTimeout := "2m" ++ timeout := cli.Config.StartTimeout ++ if timeout != "" { ++ timeParse, err := time.ParseDuration(timeout) ++ if err != nil || timeParse < minRuntimeTimeout || timeParse > maxRuntimeTimeout { ++ return fmt.Errorf("start-timeout invalid value: %s, should in range [%s-%s]", timeout, minRuntimeTimeout, maxRuntimeTimeout) ++ } ++ return os.Setenv(env, timeout) ++ } ++ return os.Setenv(env, defaultRuntimeTimeout) ++} +diff --git a/components/engine/daemon/config/config_unix.go b/components/engine/daemon/config/config_unix.go +index 5ed6abd..d094269 100644 +--- a/components/engine/daemon/config/config_unix.go ++++ b/components/engine/daemon/config/config_unix.go +@@ -37,6 +37,7 @@ type Config struct { + ShmSize opts.MemBytes `json:"default-shm-size,omitempty"` + NoNewPrivileges bool `json:"no-new-privileges,omitempty"` + IpcMode string `json:"default-ipc-mode,omitempty"` ++ StartTimeout string `json:"start-timeout,omitempty"` + // ResolvConf is the path to the configuration of the host resolver + ResolvConf string `json:"resolv-conf,omitempty"` + } +-- +1.8.3.1 + diff --git a/patch/0122-cleanup-local-db-on-first-start-after-os-star.patch b/patch/0122-cleanup-local-db-on-first-start-after-os-star.patch new file mode 100644 index 0000000..da5a418 --- /dev/null +++ b/patch/0122-cleanup-local-db-on-first-start-after-os-star.patch @@ -0,0 +1,87 @@ +From f7263dbc15c9730bd5dd562790d17607bbd6caec Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Tue, 14 May 2019 12:13:46 +0800 +Subject: [PATCH] cleanup local-db on first start after os start + +Change-Id: I8ed28bbdd4d7d87211339e8d1d752f435c999789 +Signed-off-by: jingrui +--- + components/engine/daemon/daemon.go | 55 ++++++++++++++++++++++++++++++ + 1 file changed, 55 insertions(+) + +diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go +index 041e7142f1..2d5051412e 100644 +--- a/components/engine/daemon/daemon.go ++++ b/components/engine/daemon/daemon.go +@@ -509,6 +509,8 @@ func (daemon *Daemon) restore() error { + logrus.Errorf("removeRedundantMounts failed %v", err) + } + ++ daemon.cleanupLocalDBs() ++ + containerIDs := make(map[string]struct{}) + for cid, _ := range containers { + containerIDs[cid] = struct{}{} +@@ -609,6 +611,59 @@ func (daemon *Daemon) restore() error { + return nil + } + ++func (daemon *Daemon) cleanupLocalDB(db string) { ++ _, err := os.Stat(db) ++ if err == nil { ++ err = os.Remove(db) ++ logrus.Infof("cleanup DB %s error=%v", db, err) ++ } ++} ++ ++// DB files may corrupted on exception poweroff but can be rebuild at run time, ++// so we can remove DB files on OS starts avoid daemon can not startup. ++func (daemon *Daemon) cleanupLocalDBs() { ++ // check db lock is exist, do nothing if file is existed ++ dbLockPath := filepath.Join(daemon.configStore.ExecRoot, "dblock") ++ _, err := os.Stat(dbLockPath) ++ if err == nil { ++ return ++ } ++ if !os.IsNotExist(err) { ++ logrus.Errorf("stat dblock failed %v", err) ++ return ++ } ++ ioutil.WriteFile(dbLockPath, []byte{}, 0600) ++ ++ removeAllDB := func() { ++ daemon.cleanupLocalDB(filepath.Join(daemon.configStore.Root, "builder/fscache.db")) ++ daemon.cleanupLocalDB(filepath.Join(daemon.configStore.Root, "volumes/metadata.db")) ++ daemon.cleanupLocalDB(filepath.Join(daemon.configStore.Root, "network/files/local-kv.db")) ++ // daemon.cleanupLocalDB(filepath.Join(daemon.configStore.Root, "containerd/daemon/io.containerd.metadata.v1.bolt/meta.db")) ++ daemon.cleanupLocalDB(filepath.Join(daemon.configStore.Root, "accelerator/accel.db")) ++ daemon.cleanupLocalDB(filepath.Join(daemon.configStore.Root, "buildkit/metadata.db")) ++ daemon.cleanupLocalDB(filepath.Join(daemon.configStore.Root, "buildkit/cache.db")) ++ daemon.cleanupLocalDB(filepath.Join(daemon.configStore.Root, "buildkit/snapshots.db")) ++ } ++ ++ if daemon.containers == nil { ++ logrus.Warnf("nil containers, cleanup local DB after OS start ...") ++ removeAllDB() ++ return ++ } ++ ++ ls, err := daemon.Containers(&types.ContainerListOptions{}) ++ if err != nil { ++ logrus.Errorf("list containers failed %v", err) ++ return ++ } ++ ++ if len(ls) == 0 { ++ logrus.Warnf("no running containers, cleanup local DB after OS start ...") ++ removeAllDB() ++ return ++ } ++} ++ + // RestartSwarmContainers restarts any autostart container which has a + // swarm endpoint. + func (daemon *Daemon) RestartSwarmContainers() { +-- +2.17.1 + diff --git a/patch/0123-docker-support-exit-on-unhealthy.patch b/patch/0123-docker-support-exit-on-unhealthy.patch new file mode 100644 index 0000000..9b6ad92 --- /dev/null +++ b/patch/0123-docker-support-exit-on-unhealthy.patch @@ -0,0 +1,90 @@ +From 3a9b96e32c226fefee5e410ee9fcf0376bf89f4d Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Wed, 5 Jun 2019 18:48:12 +0800 +Subject: [PATCH] docker support exit on unhealthy + +Change-Id: Ie4ae17e976ac2a4981fdb6f891987ffe3ea900a6 +Signed-off-by: jingrui +--- + components/cli/cli/command/container/opts.go | 3 +++ + .../github.com/docker/docker/api/types/container/config.go | 3 +++ + components/engine/api/types/container/config.go | 3 +++ + components/engine/daemon/health.go | 6 ++++++ + 4 files changed, 15 insertions(+) + +diff --git a/components/cli/cli/command/container/opts.go b/components/cli/cli/command/container/opts.go +index f009cd0ea1..00da8fc570 100644 +--- a/components/cli/cli/command/container/opts.go ++++ b/components/cli/cli/command/container/opts.go +@@ -121,6 +121,7 @@ type containerOptions struct { + healthTimeout time.Duration + healthStartPeriod time.Duration + healthRetries int ++ healthExitOnUnhealthy bool + runtime string + autoRemove bool + init bool +@@ -239,6 +240,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions { + flags.StringVar(&copts.healthCmd, "health-cmd", "", "Command to run to check health") + flags.DurationVar(&copts.healthInterval, "health-interval", 0, "Time between running the check (ms|s|m|h) (default 0s)") + flags.IntVar(&copts.healthRetries, "health-retries", 0, "Consecutive failures needed to report unhealthy") ++ flags.BoolVar(&copts.healthExitOnUnhealthy, "health-exit-on-unhealthy", false, "Shut down a container if it becomes Unhealthy") + flags.DurationVar(&copts.healthTimeout, "health-timeout", 0, "Maximum time to allow one check to run (ms|s|m|h) (default 0s)") + flags.DurationVar(&copts.healthStartPeriod, "health-start-period", 0, "Start period for the container to initialize before starting health-retries countdown (ms|s|m|h) (default 0s)") + flags.SetAnnotation("health-start-period", "version", []string{"1.29"}) +@@ -530,6 +532,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err + Timeout: copts.healthTimeout, + StartPeriod: copts.healthStartPeriod, + Retries: copts.healthRetries, ++ ExitOnUnhealthy: copts.healthExitOnUnhealthy, + } + } + +diff --git a/components/cli/vendor/github.com/docker/docker/api/types/container/config.go b/components/cli/vendor/github.com/docker/docker/api/types/container/config.go +index c28f0b101e..4856626be4 100644 +--- a/components/cli/vendor/github.com/docker/docker/api/types/container/config.go ++++ b/components/cli/vendor/github.com/docker/docker/api/types/container/config.go +@@ -32,6 +32,9 @@ type HealthConfig struct { + // Retries is the number of consecutive failures needed to consider a container as unhealthy. + // Zero means inherit. + Retries int `json:",omitempty"` ++ ++ // Shut down a container if it becomes Unhealthy ++ ExitOnUnhealthy bool `json:",omitempty"` + } + + // Config contains the configuration data about a container. +diff --git a/components/engine/api/types/container/config.go b/components/engine/api/types/container/config.go +index c28f0b101e..4856626be4 100644 +--- a/components/engine/api/types/container/config.go ++++ b/components/engine/api/types/container/config.go +@@ -32,6 +32,9 @@ type HealthConfig struct { + // Retries is the number of consecutive failures needed to consider a container as unhealthy. + // Zero means inherit. + Retries int `json:",omitempty"` ++ ++ // Shut down a container if it becomes Unhealthy ++ ExitOnUnhealthy bool `json:",omitempty"` + } + + // Config contains the configuration data about a container. +diff --git a/components/engine/daemon/health.go b/components/engine/daemon/health.go +index ae0d7f8921..80bda66cb3 100644 +--- a/components/engine/daemon/health.go ++++ b/components/engine/daemon/health.go +@@ -241,6 +241,12 @@ func monitor(d *Daemon, c *container.Container, stop chan struct{}, probe probe) + // signal and we don't want dying probes to pile up). + <-results + } ++ if c.State.Health.Status() == types.Unhealthy && ++ c.Config.Healthcheck.ExitOnUnhealthy == true { ++ d.Kill(c) ++ logrus.Debugf("Shut down container %s because of unhealthy", c.ID) ++ return ++ } + } + } + } +-- +2.17.1 + diff --git a/patch/0124-docker-Support-compress-when-saving-images.patch b/patch/0124-docker-Support-compress-when-saving-images.patch new file mode 100644 index 0000000..c72713e --- /dev/null +++ b/patch/0124-docker-Support-compress-when-saving-images.patch @@ -0,0 +1,368 @@ +From a671cf961c82749cd9025e3e9cce772650134a67 Mon Sep 17 00:00:00 2001 +From: Liu Hua +Date: Thu, 6 Jun 2019 01:13:08 -0400 +Subject: [PATCH] docker Support compress when saving images + +If add option `-c` when executing command `docker save`, +layer.tar in exported archive will be compressed with +algorithm gzip. Usage like this(we can use command `file` +to check format of the layer.tar): + +``` +$ docker save -c -o redis.tar redis +$ tar xvf redis.tar +09b9c5a923b810cc524ec5abf9dd65f796e37397005906b5d81c919bf63cb123/ +09b9c5a923b810cc524ec5abf9dd65f796e37397005906b5d81c919bf63cb123/VERSION +09b9c5a923b810cc524ec5abf9dd65f796e37397005906b5d81c919bf63cb123/json +09b9c5a923b810cc524ec5abf9dd65f796e37397005906b5d81c919bf63cb123/layer.tar +7613529f37fd6709b0d850a8ebecd13c069a9cb2bbf67babea0769c68172c5c9/ +7613529f37fd6709b0d850a8ebecd13c069a9cb2bbf67babea0769c68172c5c9/VERSION +7613529f37fd6709b0d850a8ebecd13c069a9cb2bbf67babea0769c68172c5c9/json +7613529f37fd6709b0d850a8ebecd13c069a9cb2bbf67babea0769c68172c5c9/layer.tar +846767a44c632a336d7204fb30e9215c84fb1ae1df81a557f78e0fc4d1842130/ +846767a44c632a336d7204fb30e9215c84fb1ae1df81a557f78e0fc4d1842130/VERSION +846767a44c632a336d7204fb30e9215c84fb1ae1df81a557f78e0fc4d1842130/json +846767a44c632a336d7204fb30e9215c84fb1ae1df81a557f78e0fc4d1842130/layer.tar +98ee6000320c9e7de792f9c17ac39233abd1881bb127f8227719740981742d7e/ +98ee6000320c9e7de792f9c17ac39233abd1881bb127f8227719740981742d7e/VERSION +98ee6000320c9e7de792f9c17ac39233abd1881bb127f8227719740981742d7e/json +98ee6000320c9e7de792f9c17ac39233abd1881bb127f8227719740981742d7e/layer.tar +a82ad3e3c9cd9fafcb310870d070fe293448e5a6d5681eafe54c88cb486038fc/ +a82ad3e3c9cd9fafcb310870d070fe293448e5a6d5681eafe54c88cb486038fc/VERSION +a82ad3e3c9cd9fafcb310870d070fe293448e5a6d5681eafe54c88cb486038fc/json +a82ad3e3c9cd9fafcb310870d070fe293448e5a6d5681eafe54c88cb486038fc/layer.tar +b047187f532984ce5e2b53a06c09f2621457939f87472673f35d15391ca58188/ +b047187f532984ce5e2b53a06c09f2621457939f87472673f35d15391ca58188/VERSION +b047187f532984ce5e2b53a06c09f2621457939f87472673f35d15391ca58188/json +b047187f532984ce5e2b53a06c09f2621457939f87472673f35d15391ca58188/layer.tar +d4f259423416b3c82b46e1caf01829ac3a99a211dfc691c9d862464768112e7f.json +manifest.json +repositories +$ file +b047187f532984ce5e2b53a06c09f2621457939f87472673f35d15391ca58188/layer.tar +b047187f532984ce5e2b53a06c09f2621457939f87472673f35d15391ca58188/layer.tar: +gzip compressed data +``` + +Change-Id: I9069b5346e4990e90c2f4724841dc3b4f6f5579f +Signed-off-by: Fengtu Wang +Signed-off-by: Liu Hua +--- + .../cli/cli/command/image/client_test.go | 2 +- + components/cli/cli/command/image/save.go | 8 +++++--- + .../docker/docker/client/image_save.go | 6 +++++- + .../docker/docker/client/interface.go | 2 +- + .../engine/api/server/router/image/backend.go | 2 +- + .../api/server/router/image/image_routes.go | 4 +++- + components/engine/client/image_save.go | 6 +++++- + components/engine/client/image_save_test.go | 4 ++-- + components/engine/client/interface.go | 2 +- + .../engine/daemon/images/image_exporter.go | 4 ++-- + components/engine/distribution/push.go | 2 +- + components/engine/distribution/push_v2.go | 2 +- + components/engine/image/image.go | 2 +- + components/engine/image/tarexport/save.go | 18 +++++++++++++++--- + .../plugin/authz/authz_plugin_test.go | 2 +- + .../engine/internal/test/daemon/daemon.go | 2 +- + 16 files changed, 46 insertions(+), 22 deletions(-) + +diff --git a/components/cli/cli/command/image/client_test.go b/components/cli/cli/command/image/client_test.go +index 50e46f4ec1..aa1bf13611 100644 +--- a/components/cli/cli/command/image/client_test.go ++++ b/components/cli/cli/command/image/client_test.go +@@ -37,7 +37,7 @@ func (cli *fakeClient) ImageTag(_ context.Context, image, ref string) error { + return nil + } + +-func (cli *fakeClient) ImageSave(_ context.Context, images []string) (io.ReadCloser, error) { ++func (cli *fakeClient) ImageSave(_ context.Context, images []string, compress bool) (io.ReadCloser, error) { + if cli.imageSaveFunc != nil { + return cli.imageSaveFunc(images) + } +diff --git a/components/cli/cli/command/image/save.go b/components/cli/cli/command/image/save.go +index ef23ca1bb1..e1b4a21482 100644 +--- a/components/cli/cli/command/image/save.go ++++ b/components/cli/cli/command/image/save.go +@@ -13,8 +13,9 @@ import ( + ) + + type saveOptions struct { +- images []string +- output string ++ images []string ++ output string ++ compress bool + } + + // NewSaveCommand creates a new `docker save` command +@@ -34,6 +35,7 @@ func NewSaveCommand(dockerCli command.Cli) *cobra.Command { + flags := cmd.Flags() + + flags.StringVarP(&opts.output, "output", "o", "", "Write to a file, instead of STDOUT") ++ flags.BoolVarP(&opts.compress, "compress", "c", false, "Compress layers when saving images") + + return cmd + } +@@ -48,7 +50,7 @@ func RunSave(dockerCli command.Cli, opts saveOptions) error { + return errors.Wrap(err, "failed to save image") + } + +- responseBody, err := dockerCli.Client().ImageSave(context.Background(), opts.images) ++ responseBody, err := dockerCli.Client().ImageSave(context.Background(), opts.images, opts.compress) + if err != nil { + return err + } +diff --git a/components/cli/vendor/github.com/docker/docker/client/image_save.go b/components/cli/vendor/github.com/docker/docker/client/image_save.go +index d1314e4b22..2bbea0f8ff 100644 +--- a/components/cli/vendor/github.com/docker/docker/client/image_save.go ++++ b/components/cli/vendor/github.com/docker/docker/client/image_save.go +@@ -8,11 +8,15 @@ import ( + + // ImageSave retrieves one or more images from the docker host as an io.ReadCloser. + // It's up to the caller to store the images and close the stream. +-func (cli *Client) ImageSave(ctx context.Context, imageIDs []string) (io.ReadCloser, error) { ++func (cli *Client) ImageSave(ctx context.Context, imageIDs []string, compress bool) (io.ReadCloser, error) { + query := url.Values{ + "names": imageIDs, + } + ++ if compress { ++ query.Set("compress", "1") ++ } ++ + resp, err := cli.get(ctx, "/images/get", query, nil) + if err != nil { + return nil, err +diff --git a/components/cli/vendor/github.com/docker/docker/client/interface.go b/components/cli/vendor/github.com/docker/docker/client/interface.go +index b2d5d7bb72..e68f9f6264 100644 +--- a/components/cli/vendor/github.com/docker/docker/client/interface.go ++++ b/components/cli/vendor/github.com/docker/docker/client/interface.go +@@ -98,7 +98,7 @@ type ImageAPIClient interface { + ImagePush(ctx context.Context, ref string, options types.ImagePushOptions) (io.ReadCloser, error) + ImageRemove(ctx context.Context, image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) + ImageSearch(ctx context.Context, term string, options types.ImageSearchOptions) ([]registry.SearchResult, error) +- ImageSave(ctx context.Context, images []string) (io.ReadCloser, error) ++ ImageSave(ctx context.Context, images []string, compress bool) (io.ReadCloser, error) + ImageTag(ctx context.Context, image, ref string) error + ImagesPrune(ctx context.Context, pruneFilter filters.Args) (types.ImagesPruneReport, error) + } +diff --git a/components/engine/api/server/router/image/backend.go b/components/engine/api/server/router/image/backend.go +index 5837f9a9bc..84509d301a 100644 +--- a/components/engine/api/server/router/image/backend.go ++++ b/components/engine/api/server/router/image/backend.go +@@ -31,7 +31,7 @@ type imageBackend interface { + type importExportBackend interface { + LoadImage(inTar io.ReadCloser, outStream io.Writer, quiet bool) error + ImportImage(src string, repository, platform string, tag string, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error +- ExportImage(names []string, outStream io.Writer) error ++ ExportImage(names []string, compress bool, outStream io.Writer) error + } + + type registryBackend interface { +diff --git a/components/engine/api/server/router/image/image_routes.go b/components/engine/api/server/router/image/image_routes.go +index 85707c06d2..b7bb340e9a 100644 +--- a/components/engine/api/server/router/image/image_routes.go ++++ b/components/engine/api/server/router/image/image_routes.go +@@ -159,7 +159,9 @@ func (s *imageRouter) getImagesGet(ctx context.Context, w http.ResponseWriter, r + names = r.Form["names"] + } + +- if err := s.backend.ExportImage(names, output); err != nil { ++ compress := httputils.BoolValueOrDefault(r, "compress", false) ++ ++ if err := s.backend.ExportImage(names, compress, output); err != nil { + if !output.Flushed() { + return err + } +diff --git a/components/engine/client/image_save.go b/components/engine/client/image_save.go +index d1314e4b22..2bbea0f8ff 100644 +--- a/components/engine/client/image_save.go ++++ b/components/engine/client/image_save.go +@@ -8,11 +8,15 @@ import ( + + // ImageSave retrieves one or more images from the docker host as an io.ReadCloser. + // It's up to the caller to store the images and close the stream. +-func (cli *Client) ImageSave(ctx context.Context, imageIDs []string) (io.ReadCloser, error) { ++func (cli *Client) ImageSave(ctx context.Context, imageIDs []string, compress bool) (io.ReadCloser, error) { + query := url.Values{ + "names": imageIDs, + } + ++ if compress { ++ query.Set("compress", "1") ++ } ++ + resp, err := cli.get(ctx, "/images/get", query, nil) + if err != nil { + return nil, err +diff --git a/components/engine/client/image_save_test.go b/components/engine/client/image_save_test.go +index a40055e583..7325da0c80 100644 +--- a/components/engine/client/image_save_test.go ++++ b/components/engine/client/image_save_test.go +@@ -15,7 +15,7 @@ func TestImageSaveError(t *testing.T) { + client := &Client{ + client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")), + } +- _, err := client.ImageSave(context.Background(), []string{"nothing"}) ++ _, err := client.ImageSave(context.Background(), []string{"nothing"}, false) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server error, got %v", err) + } +@@ -41,7 +41,7 @@ func TestImageSave(t *testing.T) { + }, nil + }), + } +- saveResponse, err := client.ImageSave(context.Background(), []string{"image_id1", "image_id2"}) ++ saveResponse, err := client.ImageSave(context.Background(), []string{"image_id1", "image_id2"}, false) + if err != nil { + t.Fatal(err) + } +diff --git a/components/engine/client/interface.go b/components/engine/client/interface.go +index d190f8e58d..5a7b9eb5b8 100644 +--- a/components/engine/client/interface.go ++++ b/components/engine/client/interface.go +@@ -98,7 +98,7 @@ type ImageAPIClient interface { + ImagePush(ctx context.Context, ref string, options types.ImagePushOptions) (io.ReadCloser, error) + ImageRemove(ctx context.Context, image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) + ImageSearch(ctx context.Context, term string, options types.ImageSearchOptions) ([]registry.SearchResult, error) +- ImageSave(ctx context.Context, images []string) (io.ReadCloser, error) ++ ImageSave(ctx context.Context, images []string, compress bool) (io.ReadCloser, error) + ImageTag(ctx context.Context, image, ref string) error + ImagesPrune(ctx context.Context, pruneFilter filters.Args) (types.ImagesPruneReport, error) + } +diff --git a/components/engine/daemon/images/image_exporter.go b/components/engine/daemon/images/image_exporter.go +index 58105dcb71..dbe5811f30 100644 +--- a/components/engine/daemon/images/image_exporter.go ++++ b/components/engine/daemon/images/image_exporter.go +@@ -11,9 +11,9 @@ import ( + // stream. All images with the given tag and all versions containing + // the same tag are exported. names is the set of tags to export, and + // outStream is the writer which the images are written to. +-func (i *ImageService) ExportImage(names []string, outStream io.Writer) error { ++func (i *ImageService) ExportImage(names []string, compress bool, outStream io.Writer) error { + imageExporter := tarexport.NewTarExporter(i.imageStore, i.layerStores, i.referenceStore, i) +- return imageExporter.Save(names, outStream) ++ return imageExporter.Save(names, compress, outStream) + } + + // LoadImage uploads a set of images into the repository. This is the +diff --git a/components/engine/distribution/push.go b/components/engine/distribution/push.go +index eb3bc55974..ca49f22926 100644 +--- a/components/engine/distribution/push.go ++++ b/components/engine/distribution/push.go +@@ -158,7 +158,7 @@ func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushCo + // is finished. This allows the caller to make sure the goroutine finishes + // before it releases any resources connected with the reader that was + // passed in. +-func compress(in io.Reader) (io.ReadCloser, chan struct{}) { ++func Compress(in io.Reader) (io.ReadCloser, chan struct{}) { + compressionDone := make(chan struct{}) + + pipeReader, pipeWriter := io.Pipe() +diff --git a/components/engine/distribution/push_v2.go b/components/engine/distribution/push_v2.go +index 9dc3e7a2a6..f5922d65bf 100644 +--- a/components/engine/distribution/push_v2.go ++++ b/components/engine/distribution/push_v2.go +@@ -447,7 +447,7 @@ func (pd *v2PushDescriptor) uploadUsingSession( + + switch m := pd.layer.MediaType(); m { + case schema2.MediaTypeUncompressedLayer: +- compressedReader, compressionDone := compress(reader) ++ compressedReader, compressionDone := Compress(reader) + defer func(closer io.Closer) { + closer.Close() + <-compressionDone +diff --git a/components/engine/image/image.go b/components/engine/image/image.go +index 7e0646f072..bb6046b5ec 100644 +--- a/components/engine/image/image.go ++++ b/components/engine/image/image.go +@@ -212,7 +212,7 @@ func NewHistory(author, comment, createdBy string, isEmptyLayer bool) History { + type Exporter interface { + Load(io.ReadCloser, io.Writer, bool) error + // TODO: Load(net.Context, io.ReadCloser, <- chan StatusMessage) error +- Save([]string, io.Writer) error ++ Save([]string, bool, io.Writer) error + } + + // NewFromJSON creates an Image configuration from json. +diff --git a/components/engine/image/tarexport/save.go b/components/engine/image/tarexport/save.go +index 4e734b3503..0683f1704f 100644 +--- a/components/engine/image/tarexport/save.go ++++ b/components/engine/image/tarexport/save.go +@@ -12,6 +12,7 @@ import ( + "time" + + "github.com/docker/distribution" ++ dd "github.com/docker/docker/distribution" + "github.com/docker/distribution/reference" + "github.com/docker/docker/image" + "github.com/docker/docker/image/v1" +@@ -35,9 +36,10 @@ type saveSession struct { + images map[image.ID]*imageDescriptor + savedLayers map[string]struct{} + diffIDPaths map[layer.DiffID]string // cache every diffID blob to avoid duplicates ++ compress bool + } + +-func (l *tarexporter) Save(names []string, outStream io.Writer) error { ++func (l *tarexporter) Save(names []string, compress bool, outStream io.Writer) error { + images, err := l.parseNames(names) + if err != nil { + return err +@@ -45,7 +47,7 @@ func (l *tarexporter) Save(names []string, outStream io.Writer) error { + + // Release all the image top layer references + defer l.releaseLayerReferences(images) +- return (&saveSession{tarexporter: l, images: images}).save(outStream) ++ return (&saveSession{tarexporter: l, images: images, compress: compress}).save(outStream) + } + + // parseNames will parse the image names to a map which contains image.ID to *imageDescriptor. +@@ -408,7 +410,17 @@ func (s *saveSession) saveLayer(id layer.ChainID, legacyImg image.V1Image, creat + } + defer arch.Close() + +- if _, err := io.Copy(tarFile, arch); err != nil { ++ reader := arch ++ var compressionDone chan struct{} ++ if s.compress { ++ reader, compressionDone = dd.Compress(arch) ++ defer func(closer io.Closer) { ++ closer.Close() ++ <-compressionDone ++ }(reader) ++ } ++ ++ if _, err := io.Copy(tarFile, reader); err != nil { + return distribution.Descriptor{}, err + } + +diff --git a/components/engine/integration/plugin/authz/authz_plugin_test.go b/components/engine/integration/plugin/authz/authz_plugin_test.go +index 105affc1af..c740fc1dbd 100644 +--- a/components/engine/integration/plugin/authz/authz_plugin_test.go ++++ b/components/engine/integration/plugin/authz/authz_plugin_test.go +@@ -435,7 +435,7 @@ func TestAuthzPluginEnsureContainerCopyToFrom(t *testing.T) { + + func imageSave(client client.APIClient, path, image string) error { + ctx := context.Background() +- responseReader, err := client.ImageSave(ctx, []string{image}) ++ responseReader, err := client.ImageSave(ctx, []string{image}, false) + if err != nil { + return err + } +diff --git a/components/engine/internal/test/daemon/daemon.go b/components/engine/internal/test/daemon/daemon.go +index 4f56dff9bb..f16a43de45 100644 +--- a/components/engine/internal/test/daemon/daemon.go ++++ b/components/engine/internal/test/daemon/daemon.go +@@ -557,7 +557,7 @@ func (d *Daemon) LoadBusybox(t assert.TestingT) { + defer clientHost.Close() + + ctx := context.Background() +- reader, err := clientHost.ImageSave(ctx, []string{"busybox:latest"}) ++ reader, err := clientHost.ImageSave(ctx, []string{"busybox:latest"}, false) + assert.NilError(t, err, "failed to download busybox") + defer reader.Close() + +-- +2.17.1 + diff --git a/patch/0125-docker-add-hugetlb-limit-option-to-docker.patch b/patch/0125-docker-add-hugetlb-limit-option-to-docker.patch new file mode 100644 index 0000000..d613061 --- /dev/null +++ b/patch/0125-docker-add-hugetlb-limit-option-to-docker.patch @@ -0,0 +1,642 @@ +From 74bad908a52fd2713c72c9f42f2b053369ec53d2 Mon Sep 17 00:00:00 2001 +From: zhaolongquan1 +Date: Wed, 12 Jun 2019 12:37:08 -0400 +Subject: [PATCH] docker: add hugetlb limit option to docker + +reason:add hugetlb limit option to docker + +Change-Id: I418c65fd050d3740da6997589df45b08355fed80 +Signed-off-by: zhaolongquan1 +--- + components/cli/cli/command/container/opts.go | 5 + + components/cli/cli/command/system/info.go | 1 + + components/cli/contrib/completion/bash/docker | 1 + + components/cli/opts/hugetlb.go | 109 +++++++++++++ + .../docker/api/types/container/host_config.go | 8 + + .../github.com/docker/docker/api/types/types.go | 1 + + .../engine/api/types/container/host_config.go | 8 + + components/engine/api/types/types.go | 1 + + components/engine/daemon/daemon_unix.go | 66 ++++++++ + components/engine/daemon/info.go | 1 + + components/engine/daemon/oci_linux.go | 2 + + components/engine/pkg/sysinfo/sysinfo.go | 6 + + components/engine/pkg/sysinfo/sysinfo_linux.go | 30 ++++ + components/engine/pkg/sysinfo/utils_linux.go | 169 +++++++++++++++++++++ + 14 files changed, 408 insertions(+) + create mode 100644 components/cli/opts/hugetlb.go + create mode 100644 components/engine/pkg/sysinfo/utils_linux.go + +diff --git a/components/cli/cli/command/container/opts.go b/components/cli/cli/command/container/opts.go +index 00da8fc..d729a3c 100644 +--- a/components/cli/cli/command/container/opts.go ++++ b/components/cli/cli/command/container/opts.go +@@ -66,6 +66,7 @@ type containerOptions struct { + storageOpt opts.ListOpts + labelsFile opts.ListOpts + loggingOpts opts.ListOpts ++ hugetlb opts.HugetlbOpt + privileged bool + pidMode string + utsMode string +@@ -166,6 +167,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions { + ulimits: opts.NewUlimitOpt(nil), + volumes: opts.NewListOpts(nil), + volumesFrom: opts.NewListOpts(nil), ++ hugetlb: opts.NewHugetlbOpt(opts.ValidateHugetlb), + } + + // General purpose flags +@@ -295,6 +297,8 @@ func addFlags(flags *pflag.FlagSet) *containerOptions { + + flags.BoolVar(&copts.init, "init", false, "Run an init inside the container that forwards signals and reaps processes") + flags.SetAnnotation("init", "version", []string{"1.25"}) ++ ++ flags.Var(&copts.hugetlb, "hugetlb-limit", "Huge page limit (format: [size:], e.g. --hugetlb-limit 2MB:32MB)") + return copts + } + +@@ -538,6 +542,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err + + resources := container.Resources{ + CgroupParent: copts.cgroupParent, ++ Hugetlbs: copts.hugetlb.GetAll(), + Memory: copts.memory.Value(), + MemoryReservation: copts.memoryReservation.Value(), + MemorySwap: copts.memorySwap.Value(), +diff --git a/components/cli/cli/command/system/info.go b/components/cli/cli/command/system/info.go +index 17ccc14..7399d10 100644 +--- a/components/cli/cli/command/system/info.go ++++ b/components/cli/cli/command/system/info.go +@@ -74,6 +74,7 @@ func prettyPrintInfo(dockerCli command.Cli, info types.Info) error { + } + fprintlnNonEmpty(dockerCli.Out(), "Logging Driver:", info.LoggingDriver) + fprintlnNonEmpty(dockerCli.Out(), "Cgroup Driver:", info.CgroupDriver) ++ fprintlnNonEmpty(dockerCli.Out(), "Hugetlb Pagesize:", info.HugetlbPageSize) + + fmt.Fprintln(dockerCli.Out(), "Plugins:") + fmt.Fprintln(dockerCli.Out(), " Volume:", strings.Join(info.Plugins.Volume, " ")) +diff --git a/components/cli/contrib/completion/bash/docker b/components/cli/contrib/completion/bash/docker +index 64f7fe0..330804d 100644 +--- a/components/cli/contrib/completion/bash/docker ++++ b/components/cli/contrib/completion/bash/docker +@@ -1792,6 +1792,7 @@ _docker_container_run_and_create() { + --files-limit + --group-add + --hook-spec ++ --hugetlb-limit + --health-cmd + --health-interval + --health-retries +diff --git a/components/cli/opts/hugetlb.go b/components/cli/opts/hugetlb.go +new file mode 100644 +index 0000000..48cfeff +--- /dev/null ++++ b/components/cli/opts/hugetlb.go +@@ -0,0 +1,109 @@ ++package opts ++ ++import ( ++ "fmt" ++ "strings" ++ ++ "github.com/docker/docker/api/types/container" ++ "github.com/docker/go-units" ++) ++ ++// ValidatorHugetlbType defines a validator function that returns a validated struct and/or an error. ++type ValidatorHugetlbType func(val string) (container.Hugetlb, error) ++ ++// ValidateHugetlb validates that the specified string has a valid hugetlb format. ++func ValidateHugetlb(htlb string) (container.Hugetlb, error) { ++ var size, limit string ++ var hugetlb container.Hugetlb ++ ++ ss := strings.Split(htlb, ":") ++ if len(ss) == 1 { ++ size = "" ++ limit = ss[0] ++ } else if len(ss) == 2 { ++ if ss[0] == "" { ++ size = "" ++ } else { ++ size = formatHugepageSize(ss[0]) ++ } ++ limit = ss[1] ++ } else { ++ return hugetlb, fmt.Errorf("Invalid arguments for hugetlb-limit, too many colons") ++ } ++ ++ ilimit, err := units.RAMInBytes(limit) ++ if err != nil { ++ return hugetlb, fmt.Errorf("Invalid hugetlb limit:%s", limit) ++ } ++ ulimit := uint64(ilimit) ++ hugetlb = container.Hugetlb{ ++ PageSize: size, ++ Limit: ulimit, ++ } ++ return hugetlb, nil ++} ++ ++// HugetlbOpt defines a map of Hugetlbs ++type HugetlbOpt struct { ++ values []container.Hugetlb ++ validator ValidatorHugetlbType ++} ++ ++// NewHugetlbOpt creates a new HugetlbOpt ++func NewHugetlbOpt(validator ValidatorHugetlbType) HugetlbOpt { ++ values := []container.Hugetlb{} ++ return HugetlbOpt{ ++ values: values, ++ validator: validator, ++ } ++} ++ ++// Set validates a Hugetlb and sets its name as a key in HugetlbOpt ++func (opt *HugetlbOpt) Set(val string) error { ++ var value container.Hugetlb ++ if opt.validator != nil { ++ v, err := opt.validator(val) ++ if err != nil { ++ return err ++ } ++ value = v ++ } ++ (opt.values) = append((opt.values), value) ++ return nil ++} ++ ++// String returns HugetlbOpt values as a string. ++func (opt *HugetlbOpt) String() string { ++ var out []string ++ for _, v := range opt.values { ++ out = append(out, fmt.Sprintf("%v", v)) ++ } ++ ++ return fmt.Sprintf("%v", out) ++} ++ ++// GetList returns a slice of pointers to Hugetlbs. ++func (opt *HugetlbOpt) GetAll() []container.Hugetlb { ++ var hugetlbs []container.Hugetlb ++ for _, v := range opt.values { ++ hugetlbs = append(hugetlbs, v) ++ } ++ ++ return hugetlbs ++} ++ ++// Type returns the option type ++func (opt *HugetlbOpt) Type() string { ++ return "hugetlb" ++} ++ ++func formatHugepageSize(s string) string { ++ // make sure size get all 'b/k/m/g' replaced with "B/K/M/G" ++ s = strings.ToUpper(s) ++ // make sure size hase suffix "B" ++ if !strings.HasSuffix(s, "B") { ++ s = s + "B" ++ } ++ ++ return s ++} +diff --git a/components/cli/vendor/github.com/docker/docker/api/types/container/host_config.go b/components/cli/vendor/github.com/docker/docker/api/types/container/host_config.go +index 701cae5..6989b2b 100644 +--- a/components/cli/vendor/github.com/docker/docker/api/types/container/host_config.go ++++ b/components/cli/vendor/github.com/docker/docker/api/types/container/host_config.go +@@ -342,6 +342,14 @@ type Resources struct { + CPUPercent int64 `json:"CpuPercent"` // CPU percent + IOMaximumIOps uint64 // Maximum IOps for the container system drive + IOMaximumBandwidth uint64 // Maximum IO in bytes per second for the container system drive ++ ++ // Hugetlb setting ++ Hugetlbs []Hugetlb ++} ++ ++type Hugetlb struct { ++ PageSize string ++ Limit uint64 + } + + // UpdateConfig holds the mutable attributes of a Container. +diff --git a/components/cli/vendor/github.com/docker/docker/api/types/types.go b/components/cli/vendor/github.com/docker/docker/api/types/types.go +index 56f556c..cfcfd4a 100644 +--- a/components/cli/vendor/github.com/docker/docker/api/types/types.go ++++ b/components/cli/vendor/github.com/docker/docker/api/types/types.go +@@ -173,6 +173,7 @@ type Info struct { + SystemTime string + LoggingDriver string + CgroupDriver string ++ HugetlbPageSize string + NEventsListener int + KernelVersion string + OperatingSystem string +diff --git a/components/engine/api/types/container/host_config.go b/components/engine/api/types/container/host_config.go +index 701cae5..6989b2b 100644 +--- a/components/engine/api/types/container/host_config.go ++++ b/components/engine/api/types/container/host_config.go +@@ -342,6 +342,14 @@ type Resources struct { + CPUPercent int64 `json:"CpuPercent"` // CPU percent + IOMaximumIOps uint64 // Maximum IOps for the container system drive + IOMaximumBandwidth uint64 // Maximum IO in bytes per second for the container system drive ++ ++ // Hugetlb setting ++ Hugetlbs []Hugetlb ++} ++ ++type Hugetlb struct { ++ PageSize string ++ Limit uint64 + } + + // UpdateConfig holds the mutable attributes of a Container. +diff --git a/components/engine/api/types/types.go b/components/engine/api/types/types.go +index 78e97da..55955f2 100644 +--- a/components/engine/api/types/types.go ++++ b/components/engine/api/types/types.go +@@ -174,6 +174,7 @@ type Info struct { + SystemTime string + LoggingDriver string + CgroupDriver string ++ HugetlbPageSize string + NEventsListener int + KernelVersion string + OperatingSystem string +diff --git a/components/engine/daemon/daemon_unix.go b/components/engine/daemon/daemon_unix.go +index 9abc9a3..5a59b32 100644 +--- a/components/engine/daemon/daemon_unix.go ++++ b/components/engine/daemon/daemon_unix.go +@@ -187,6 +187,21 @@ func getBlkioWeightDevices(config containertypes.Resources) ([]specs.LinuxWeight + return blkioWeightDevices, nil + } + ++func getHugetlbResources(config containertypes.Resources) []specs.LinuxHugepageLimit { ++ var hpLimits []specs.LinuxHugepageLimit ++ ++ for _, hpl := range config.Hugetlbs { ++ size := hpl.PageSize ++ limit := uint64(hpl.Limit) ++ hpLimits = append(hpLimits, specs.LinuxHugepageLimit{ ++ Pagesize: size, ++ Limit: limit, ++ }) ++ } ++ ++ return hpLimits ++} ++ + func (daemon *Daemon) parseSecurityOpt(container *container.Container, hostConfig *containertypes.HostConfig) error { + container.NoNewPrivileges = daemon.configStore.NoNewPrivileges + return parseSecurityOpt(container, hostConfig) +@@ -553,9 +568,51 @@ func (daemon *Daemon) verifyContainerResources(hostConfig *containertypes.HostCo + resources.BlkioDeviceWriteIOps = []*pblkiodev.ThrottleDevice{} + } + ++ // hugetlb size checks ++ if len(resources.Hugetlbs) > 0 && !sysInfo.HugetlbLimit { ++ warnings = append(warnings, "Your kernel does not support hugetlb limit.") ++ logrus.Warnf("Your kernel does not support hugetlb limit. --hugetlb-limit discarded.") ++ resources.Hugetlbs = []containertypes.Hugetlb{} ++ } ++ newHugetlbs, warning, err := validateHugetlbs(resources.Hugetlbs) ++ warnings = append(warnings, warning...) ++ if err != nil { ++ return warnings, err ++ } ++ resources.Hugetlbs = newHugetlbs ++ + return warnings, nil + } + ++func validateHugetlbs(hgtlbs []containertypes.Hugetlb) ([]containertypes.Hugetlb, []string, error) { ++ warnings := []string{} ++ htbMap := make(map[string]uint64) ++ ++ for _, hpl := range hgtlbs { ++ size, warning, err := sysinfo.ValidateHugetlb(hpl.PageSize, hpl.Limit) ++ warnings = append(warnings, warning...) ++ if err != nil { ++ return nil, warnings, err ++ } ++ ++ if l, ok := htbMap[size]; ok { ++ warnings = append(warnings, fmt.Sprintf("hugetlb-limit setting of %s is repeated, former setting %d will be replaced with %d", size, l, hpl.Limit)) ++ } ++ htbMap[size] = hpl.Limit ++ } ++ ++ newHgtlbs := []containertypes.Hugetlb{} ++ for k, v := range htbMap { ++ hugetlb := containertypes.Hugetlb{ ++ PageSize: k, ++ Limit: v, ++ } ++ newHgtlbs = append(newHgtlbs, hugetlb) ++ } ++ ++ return newHgtlbs, warnings, nil ++} ++ + func (daemon *Daemon) getCgroupDriver() string { + cgroupDriver := cgroupFsDriver + +@@ -565,6 +622,15 @@ func (daemon *Daemon) getCgroupDriver() string { + return cgroupDriver + } + ++func (daemon *Daemon) getHugetlbPageSize() string { ++ size, err := sysinfo.GetHugepageSize() ++ if err != nil { ++ logrus.Errorf("Failed to get default hugetlb pagesize: %v", err) ++ return "" ++ } ++ return size ++} ++ + // getCD gets the raw value of the native.cgroupdriver option, if set. + func getCD(config *config.Config) string { + for _, option := range config.ExecOptions { +diff --git a/components/engine/daemon/info.go b/components/engine/daemon/info.go +index 4acad11..2ecff72 100644 +--- a/components/engine/daemon/info.go ++++ b/components/engine/daemon/info.go +@@ -47,6 +47,7 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) { + SystemTime: time.Now().Format(time.RFC3339Nano), + LoggingDriver: daemon.defaultLogConfig.Type, + CgroupDriver: daemon.getCgroupDriver(), ++ HugetlbPageSize: daemon.getHugetlbPageSize(), + NEventsListener: daemon.EventsService.SubscribersCount(), + KernelVersion: kernelVersion(), + OperatingSystem: operatingSystem(), +diff --git a/components/engine/daemon/oci_linux.go b/components/engine/daemon/oci_linux.go +index f5270bd..6d3bc16 100644 +--- a/components/engine/daemon/oci_linux.go ++++ b/components/engine/daemon/oci_linux.go +@@ -49,6 +49,7 @@ func setResources(s *specs.Spec, r containertypes.Resources) error { + return err + } + ++ hpRes := getHugetlbResources(r) + memoryRes := getMemoryResources(r) + cpuRes, err := getCPUResources(r) + if err != nil { +@@ -73,6 +74,7 @@ func setResources(s *specs.Spec, r containertypes.Resources) error { + Files: &specs.Files{ + Limit: &r.FilesLimit, + }, ++ HugepageLimits: hpRes, + } + + if s.Linux.Resources != nil && len(s.Linux.Resources.Devices) > 0 { +diff --git a/components/engine/pkg/sysinfo/sysinfo.go b/components/engine/pkg/sysinfo/sysinfo.go +index 7ea1be5..139ad61 100644 +--- a/components/engine/pkg/sysinfo/sysinfo.go ++++ b/components/engine/pkg/sysinfo/sysinfo.go +@@ -17,6 +17,7 @@ type SysInfo struct { + Seccomp bool + + cgroupMemInfo ++ cgroupHugetlbInfo + cgroupCPUInfo + cgroupBlkioInfo + cgroupCpusetInfo +@@ -56,6 +57,11 @@ type cgroupMemInfo struct { + KernelMemory bool + } + ++type cgroupHugetlbInfo struct { ++ // Whether hugetlb limit is supported or not ++ HugetlbLimit bool ++} ++ + type cgroupCPUInfo struct { + // Whether CPU shares is supported or not + CPUShares bool +diff --git a/components/engine/pkg/sysinfo/sysinfo_linux.go b/components/engine/pkg/sysinfo/sysinfo_linux.go +index c0bf280..b4473ee 100644 +--- a/components/engine/pkg/sysinfo/sysinfo_linux.go ++++ b/components/engine/pkg/sysinfo/sysinfo_linux.go +@@ -36,6 +36,7 @@ func New(quiet bool) *SysInfo { + logrus.Warnf("Failed to parse cgroup information: %v", err) + } else { + sysInfo.cgroupMemInfo = checkCgroupMem(cgMounts, quiet) ++ sysInfo.cgroupHugetlbInfo = checkCgroupHugetlb(cgMounts, quiet) + sysInfo.cgroupCPUInfo = checkCgroupCPU(cgMounts, quiet) + sysInfo.cgroupBlkioInfo = checkCgroupBlkioInfo(cgMounts, quiet) + sysInfo.cgroupCpusetInfo = checkCgroupCpusetInfo(cgMounts, quiet) +@@ -66,6 +67,35 @@ func New(quiet bool) *SysInfo { + return sysInfo + } + ++// checkCgroupHugetlb reads the hugetlb information from the hugetlb cgroup mount point. ++func checkCgroupHugetlb(cgMounts map[string]string, quiet bool) cgroupHugetlbInfo { ++ var ( ++ dSize string ++ err error ++ c cgroupHugetlbInfo ++ ) ++ mountPoint, ok := cgMounts["hugetlb"] ++ if !ok { ++ if !quiet { ++ logrus.Warnf("Your kernel does not support cgroup hugetlb limit") ++ } ++ return c ++ } ++ dSize, err = GetDefaultHugepageSize() ++ if err != nil { ++ logrus.Warnf("Your kernel does not support cgroup hugetlb limit") ++ return c ++ } ++ ++ hugetlbLimit := cgroupEnabled(mountPoint, fmt.Sprintf("hugetlb.%s.limit_in_bytes", dSize)) ++ if !quiet && !hugetlbLimit { ++ logrus.Warn("Your kernel does not support hugetlb limit.") ++ } ++ ++ c.HugetlbLimit = hugetlbLimit ++ return c ++} ++ + // checkCgroupMem reads the memory information from the memory cgroup mount point. + func checkCgroupMem(cgMounts map[string]string, quiet bool) cgroupMemInfo { + mountPoint, ok := cgMounts["memory"] +diff --git a/components/engine/pkg/sysinfo/utils_linux.go b/components/engine/pkg/sysinfo/utils_linux.go +new file mode 100644 +index 0000000..905d0b7 +--- /dev/null ++++ b/components/engine/pkg/sysinfo/utils_linux.go +@@ -0,0 +1,169 @@ ++// +build linux ++ ++package sysinfo ++ ++import ( ++ "bufio" ++ "fmt" ++ "os" ++ "strings" ++ ++ "github.com/docker/go-units" ++ "github.com/opencontainers/runc/libcontainer/cgroups" ++) ++ ++// GetHugepageSize returns system supported hugepage sizes ++func GetHugepageSize() (string, error) { ++ hps, err := getHugepageSizes() ++ if err != nil { ++ return "", err ++ } ++ ++ dhp, err := GetDefaultHugepageSize() ++ if err != nil { ++ return "", err ++ } ++ ++ hpsString := strings.Join(hps, ", ") ++ if len(hps) > 1 { ++ hpsString += fmt.Sprintf(" (default is %s)", dhp) ++ } ++ return hpsString, nil ++} ++ ++// ValidateHugetlb check whether hugetlb pagesize and limit legal ++func ValidateHugetlb(pageSize string, limit uint64) (string, []string, error) { ++ var err error ++ warnings := []string{} ++ if pageSize != "" { ++ sizeInt, _ := units.RAMInBytes(pageSize) ++ pageSize = humanSize(sizeInt) ++ if err := isHugepageSizeValid(pageSize); err != nil { ++ return "", warnings, err ++ } ++ } else { ++ pageSize, err = GetDefaultHugepageSize() ++ if err != nil { ++ return "", warnings, fmt.Errorf("Failed to get system hugepage size") ++ } ++ } ++ ++ warn, err := isHugeLimitValid(pageSize, limit) ++ warnings = append(warnings, warn...) ++ if err != nil { ++ return "", warnings, err ++ } ++ ++ return pageSize, warnings, nil ++} ++ ++// isHugeLimitValid check whether input hugetlb limit legal ++// it will check whether the limit size is times of size ++func isHugeLimitValid(size string, limit uint64) ([]string, error) { ++ warnings := []string{} ++ sizeInt, err := units.RAMInBytes(size) ++ if err != nil || sizeInt < 0 { ++ return warnings, fmt.Errorf("Invalid hugepage size:%s -- %s", size, err) ++ } ++ sizeUint := uint64(sizeInt) ++ ++ if limit%sizeUint != 0 { ++ warnings = append(warnings, "HugeTlb limit should be times of hugepage size. "+ ++ "cgroup will down round to the nearest multiple") ++ } ++ ++ return warnings, nil ++} ++ ++// isHugepageSizeValid check whether input size legal ++// it will compare size with all system supported hugepage size ++func isHugepageSizeValid(size string) error { ++ hps, err := getHugepageSizes() ++ if err != nil { ++ return err ++ } ++ ++ for _, hp := range hps { ++ if size == hp { ++ return nil ++ } ++ } ++ return fmt.Errorf("Invalid hugepage size:%s, shoud be one of %v", size, hps) ++} ++ ++func humanSize(i int64) string { ++ // hugetlb may not surpass GB ++ uf := []string{"B", "KB", "MB", "GB"} ++ ui := 0 ++ for { ++ if i < 1024 || ui >= 3 { ++ break ++ } ++ i = int64(i / 1024) ++ ui = ui + 1 ++ } ++ ++ return fmt.Sprintf("%d%s", i, uf[ui]) ++} ++ ++func getHugepageSizes() ([]string, error) { ++ var hps []string ++ ++ hgtlbMp, err := cgroups.FindCgroupMountpoint("hugetlb") ++ if err != nil { ++ return nil, fmt.Errorf("Hugetlb cgroup not supported") ++ } ++ ++ f, err := os.Open(hgtlbMp) ++ if err != nil { ++ return nil, fmt.Errorf("Failed to open hugetlb cgroup directory") ++ } ++ defer f.Close() ++ // -1 here means to read all the fileInfo from the directory, could be any negative number ++ fi, err := f.Readdir(-1) ++ if err != nil { ++ return nil, fmt.Errorf("Failed to read hugetlb cgroup directory") ++ } ++ ++ for _, finfo := range fi { ++ if strings.Contains(finfo.Name(), "limit_in_bytes") { ++ sres := strings.SplitN(finfo.Name(), ".", 3) ++ if len(sres) != 3 { ++ continue ++ } ++ hps = append(hps, sres[1]) ++ } ++ } ++ ++ if len(hps) == 0 { ++ return nil, fmt.Errorf("Hugetlb pagesize not found in cgroup") ++ } ++ ++ return hps, nil ++} ++ ++// GetDefaultHugepageSize returns system default hugepage size ++func GetDefaultHugepageSize() (string, error) { ++ f, err := os.Open("/proc/meminfo") ++ if err != nil { ++ return "", fmt.Errorf("Failed to get hugepage size, cannot open /proc/meminfo") ++ } ++ defer f.Close() ++ ++ s := bufio.NewScanner(f) ++ for s.Scan() { ++ if strings.Contains(s.Text(), "Hugepagesize") { ++ sres := strings.SplitN(s.Text(), ":", 2) ++ if len(sres) != 2 { ++ return "", fmt.Errorf("Failed to get hugepage size, weird /proc/meminfo format") ++ } ++ ++ // return strings.TrimSpace(sres[1]), nil ++ size := strings.Replace(sres[1], " ", "", -1) ++ // transform 2048k to 2M ++ sizeInt, _ := units.RAMInBytes(size) ++ return humanSize(sizeInt), nil ++ } ++ } ++ return "", fmt.Errorf("Failed to get hugepage size") ++} +-- +1.8.3.1 + diff --git a/patch/0126-docker-pass-root-to-chroot-to-for-chroot-ta.patch b/patch/0126-docker-pass-root-to-chroot-to-for-chroot-ta.patch new file mode 100644 index 0000000..9520bad --- /dev/null +++ b/patch/0126-docker-pass-root-to-chroot-to-for-chroot-ta.patch @@ -0,0 +1,551 @@ +From 4a5b04a22ee93b76787c5cf5070b49f6337dd1fe Mon Sep 17 00:00:00 2001 +From: wujibin +Date: Tue, 11 Jun 2019 16:17:02 +0800 +Subject: [PATCH] docker: pass root to chroot to for chroot tar/untar + +This is useful for preventing CVE-2018-15664 where a malicious container process can take +advantage of a race on symlink resolution/sanitization +Vulnerability CVE-2018-15664 link: +https://tools.cisco.com/security/center/viewAlert.x?alertId=60251 +Pass root to chroot to for chroot Untar: +https://github.com/docker/docker-ce/commit/23dc194c079c60a00d079efc73dbb590bc220a6b +Add chroot for tar packing operations: +https://github.com/docker/docker-ce/commit/7432dfa7495303bf7d0246ca02de57d2995f1f78 + +Signed-off-by: wujibin +--- + components/engine/daemon/archive.go | 15 +- + components/engine/daemon/export.go | 2 +- + .../engine/pkg/chrootarchive/archive.go | 32 +++- + .../engine/pkg/chrootarchive/archive_unix.go | 130 ++++++++++++- + .../pkg/chrootarchive/archive_unix_test.go | 171 ++++++++++++++++++ + .../pkg/chrootarchive/archive_windows.go | 9 +- + .../engine/pkg/chrootarchive/init_unix.go | 1 + + 7 files changed, 342 insertions(+), 18 deletions(-) + create mode 100644 components/engine/pkg/chrootarchive/archive_unix_test.go + +diff --git a/components/engine/daemon/archive.go b/components/engine/daemon/archive.go +index f1b715d9ae..d5d0412034 100644 +--- a/components/engine/daemon/archive.go ++++ b/components/engine/daemon/archive.go +@@ -32,18 +32,19 @@ type archiver interface { + } + + // helper functions to extract or archive +-func extractArchive(i interface{}, src io.Reader, dst string, opts *archive.TarOptions) error { ++func extractArchive(i interface{}, src io.Reader, dst string, opts *archive.TarOptions, root string) error { + if ea, ok := i.(extractor); ok { + return ea.ExtractArchive(src, dst, opts) + } +- return chrootarchive.Untar(src, dst, opts) ++ ++ return chrootarchive.UntarWithRoot(src, dst, opts, root) + } + +-func archivePath(i interface{}, src string, opts *archive.TarOptions) (io.ReadCloser, error) { ++func archivePath(i interface{}, src string, opts *archive.TarOptions, root string) (io.ReadCloser, error) { + if ap, ok := i.(archiver); ok { + return ap.ArchivePath(src, opts) + } +- return archive.TarWithOptions(src, opts) ++ return chrootarchive.Tar(src, opts, root) + } + + // ContainerCopy performs a deprecated operation of archiving the resource at +@@ -251,7 +252,7 @@ func (daemon *Daemon) containerArchivePath(container *container.Container, path + sourceDir, sourceBase := driver.Dir(resolvedPath), driver.Base(resolvedPath) + opts := archive.TarResourceRebaseOpts(sourceBase, driver.Base(absPath)) + +- data, err := archivePath(driver, sourceDir, opts) ++ data, err := archivePath(driver, sourceDir, opts, container.BaseFS.Path()) + if err != nil { + return nil, nil, err + } +@@ -380,7 +381,7 @@ func (daemon *Daemon) containerExtractToDir(container *container.Container, path + } + } + +- if err := extractArchive(driver, content, resolvedPath, options); err != nil { ++ if err := extractArchive(driver, content, resolvedPath, options, container.BaseFS.Path()); err != nil { + return err + } + +@@ -445,7 +446,7 @@ func (daemon *Daemon) containerCopy(container *container.Container, resource str + archive, err := archivePath(driver, basePath, &archive.TarOptions{ + Compression: archive.Uncompressed, + IncludeFiles: filter, +- }) ++ }, container.BaseFS.Path()) + if err != nil { + return nil, err + } +diff --git a/components/engine/daemon/export.go b/components/engine/daemon/export.go +index 27bc35967d..01593f4e8a 100644 +--- a/components/engine/daemon/export.go ++++ b/components/engine/daemon/export.go +@@ -70,7 +70,7 @@ func (daemon *Daemon) containerExport(container *container.Container) (arch io.R + Compression: archive.Uncompressed, + UIDMaps: daemon.idMapping.UIDs(), + GIDMaps: daemon.idMapping.GIDs(), +- }) ++ }, basefs.Path()) + if err != nil { + rwlayer.Unmount() + return nil, err +diff --git a/components/engine/pkg/chrootarchive/archive.go b/components/engine/pkg/chrootarchive/archive.go +index 2d9d662830..02fb2de6b2 100644 +--- a/components/engine/pkg/chrootarchive/archive.go ++++ b/components/engine/pkg/chrootarchive/archive.go +@@ -27,18 +27,34 @@ func NewArchiver(idMapping *idtools.IdentityMapping) *archive.Archiver { + // The archive may be compressed with one of the following algorithms: + // identity (uncompressed), gzip, bzip2, xz. + func Untar(tarArchive io.Reader, dest string, options *archive.TarOptions) error { +- return untarHandler(tarArchive, dest, options, true) ++ return untarHandler(tarArchive, dest, options, true, dest) ++} ++ ++// UntarWithRoot is the same as `Untar`, but allows you to pass in a root directory ++// The root directory is the directory that will be chrooted to. ++// `dest` must be a path within `root`, if it is not an error will be returned. ++// ++// `root` should set to a directory which is not controlled by any potentially ++// malicious process. ++// ++// This should be used to prevent a potential attacker from manipulating `dest` ++// such that it would provide access to files outside of `dest` through things ++// like symlinks. Normally `ResolveSymlinksInScope` would handle this, however ++// sanitizing symlinks in this manner is inherrently racey: ++// ref: CVE-2018-15664 ++func UntarWithRoot(tarArchive io.Reader, dest string, options *archive.TarOptions, root string) error { ++ return untarHandler(tarArchive, dest, options, true, root) + } + + // UntarUncompressed reads a stream of bytes from `archive`, parses it as a tar archive, + // and unpacks it into the directory at `dest`. + // The archive must be an uncompressed stream. + func UntarUncompressed(tarArchive io.Reader, dest string, options *archive.TarOptions) error { +- return untarHandler(tarArchive, dest, options, false) ++ return untarHandler(tarArchive, dest, options, false, dest) + } + + // Handler for teasing out the automatic decompression +-func untarHandler(tarArchive io.Reader, dest string, options *archive.TarOptions, decompress bool) error { ++func untarHandler(tarArchive io.Reader, dest string, options *archive.TarOptions, decompress bool, root string) error { + if tarArchive == nil { + return fmt.Errorf("Empty archive") + } +@@ -69,5 +85,13 @@ func untarHandler(tarArchive io.Reader, dest string, options *archive.TarOptions + r = decompressedArchive + } + +- return invokeUnpack(r, dest, options) ++ return invokeUnpack(r, dest, options, root) ++} ++ ++// Tar tars the requested path while chrooted to the specified root. ++func Tar(srcPath string, options *archive.TarOptions, root string) (io.ReadCloser, error) { ++ if options == nil { ++ options = &archive.TarOptions{} ++ } ++ return invokePack(srcPath, options, root) + } +diff --git a/components/engine/pkg/chrootarchive/archive_unix.go b/components/engine/pkg/chrootarchive/archive_unix.go +index 5df8afd662..27e7e99e97 100644 +--- a/components/engine/pkg/chrootarchive/archive_unix.go ++++ b/components/engine/pkg/chrootarchive/archive_unix.go +@@ -10,10 +10,13 @@ import ( + "io" + "io/ioutil" + "os" ++ "path/filepath" + "runtime" ++ "strings" + + "github.com/docker/docker/pkg/archive" + "github.com/docker/docker/pkg/reexec" ++ "github.com/pkg/errors" + ) + + // untar is the entry-point for docker-untar on re-exec. This is not used on +@@ -23,18 +26,28 @@ func untar() { + runtime.LockOSThread() + flag.Parse() + +- var options *archive.TarOptions ++ var options archive.TarOptions + + //read the options from the pipe "ExtraFiles" + if err := json.NewDecoder(os.NewFile(3, "options")).Decode(&options); err != nil { + fatal(err) + } + +- if err := chroot(flag.Arg(0)); err != nil { ++ dst := flag.Arg(0) ++ var root string ++ if len(flag.Args()) > 1 { ++ root = flag.Arg(1) ++ } ++ ++ if root == "" { ++ root = dst ++ } ++ ++ if err := chroot(root); err != nil { + fatal(err) + } + +- if err := archive.Unpack(os.Stdin, "/", options); err != nil { ++ if err := archive.Unpack(os.Stdin, dst, &options); err != nil { + fatal(err) + } + // fully consume stdin in case it is zero padded +@@ -45,7 +58,10 @@ func untar() { + os.Exit(0) + } + +-func invokeUnpack(decompressedArchive io.Reader, dest string, options *archive.TarOptions) error { ++func invokeUnpack(decompressedArchive io.Reader, dest string, options *archive.TarOptions, root string) error { ++ if root == "" { ++ return errors.New("must specify a root to chroot to") ++ } + + // We can't pass a potentially large exclude list directly via cmd line + // because we easily overrun the kernel's max argument/environment size +@@ -57,7 +73,21 @@ func invokeUnpack(decompressedArchive io.Reader, dest string, options *archive.T + return fmt.Errorf("Untar pipe failure: %v", err) + } + +- cmd := reexec.Command("docker-untar", dest) ++ if root != "" { ++ relDest, err := filepath.Rel(root, dest) ++ if err != nil { ++ return err ++ } ++ if relDest == "." { ++ relDest = "/" ++ } ++ if relDest[0] != '/' { ++ relDest = "/" + relDest ++ } ++ dest = relDest ++ } ++ ++ cmd := reexec.Command("docker-untar", dest, root) + cmd.Stdin = decompressedArchive + + cmd.ExtraFiles = append(cmd.ExtraFiles, r) +@@ -69,6 +99,7 @@ func invokeUnpack(decompressedArchive io.Reader, dest string, options *archive.T + w.Close() + return fmt.Errorf("Untar error on re-exec cmd: %v", err) + } ++ + //write the options to the pipe for the untar exec to read + if err := json.NewEncoder(w).Encode(options); err != nil { + w.Close() +@@ -86,3 +117,92 @@ func invokeUnpack(decompressedArchive io.Reader, dest string, options *archive.T + } + return nil + } ++ ++func tar() { ++ runtime.LockOSThread() ++ flag.Parse() ++ ++ src := flag.Arg(0) ++ var root string ++ if len(flag.Args()) > 1 { ++ root = flag.Arg(1) ++ } ++ ++ if root == "" { ++ root = src ++ } ++ ++ if err := realChroot(root); err != nil { ++ fatal(err) ++ } ++ ++ var options archive.TarOptions ++ if err := json.NewDecoder(os.Stdin).Decode(&options); err != nil { ++ fatal(err) ++ } ++ ++ rdr, err := archive.TarWithOptions(src, &options) ++ if err != nil { ++ fatal(err) ++ } ++ defer rdr.Close() ++ ++ if _, err := io.Copy(os.Stdout, rdr); err != nil { ++ fatal(err) ++ } ++ ++ os.Exit(0) ++} ++ ++func invokePack(srcPath string, options *archive.TarOptions, root string) (io.ReadCloser, error) { ++ if root == "" { ++ return nil, errors.New("root path must not be empty") ++ } ++ ++ relSrc, err := filepath.Rel(root, srcPath) ++ if err != nil { ++ return nil, err ++ } ++ if relSrc == "." { ++ relSrc = "/" ++ } ++ if relSrc[0] != '/' { ++ relSrc = "/" + relSrc ++ } ++ ++ // make sure we didn't trim a trailing slash with the call to `Rel` ++ if strings.HasSuffix(srcPath, "/") && !strings.HasSuffix(relSrc, "/") { ++ relSrc += "/" ++ } ++ ++ cmd := reexec.Command("docker-tar", relSrc, root) ++ ++ errBuff := bytes.NewBuffer(nil) ++ cmd.Stderr = errBuff ++ ++ tarR, tarW := io.Pipe() ++ cmd.Stdout = tarW ++ ++ stdin, err := cmd.StdinPipe() ++ if err != nil { ++ return nil, errors.Wrap(err, "error getting options pipe for tar process") ++ } ++ ++ if err := cmd.Start(); err != nil { ++ return nil, errors.Wrap(err, "tar error on re-exec cmd") ++ } ++ ++ go func() { ++ err := cmd.Wait() ++ err = errors.Wrapf(err, "error processing tar file: %s", errBuff) ++ tarW.CloseWithError(err) ++ }() ++ ++ if err := json.NewEncoder(stdin).Encode(options); err != nil { ++ stdin.Close() ++ return nil, errors.Wrap(err, "tar json encode to pipe failed") ++ } ++ stdin.Close() ++ ++ return tarR, nil ++} +diff --git a/components/engine/pkg/chrootarchive/archive_unix_test.go b/components/engine/pkg/chrootarchive/archive_unix_test.go +new file mode 100644 +index 0000000000..f39a88ad38 +--- /dev/null ++++ b/components/engine/pkg/chrootarchive/archive_unix_test.go +@@ -0,0 +1,171 @@ ++// +build !windows ++ ++package chrootarchive ++ ++import ( ++ gotar "archive/tar" ++ "bytes" ++ "io" ++ "io/ioutil" ++ "os" ++ "path" ++ "path/filepath" ++ "strings" ++ "testing" ++ ++ "github.com/docker/docker/pkg/archive" ++ "golang.org/x/sys/unix" ++ "gotest.tools/assert" ++) ++ ++// Test for CVE-2018-15664 ++// Assures that in the case where an "attacker" controlled path is a symlink to ++// some path outside of a container's rootfs that we do not copy data to a ++// container path that will actually overwrite data on the host ++func TestUntarWithMaliciousSymlinks(t *testing.T) { ++ dir, err := ioutil.TempDir("", t.Name()) ++ assert.NilError(t, err) ++ defer os.RemoveAll(dir) ++ ++ root := filepath.Join(dir, "root") ++ ++ err = os.MkdirAll(root, 0755) ++ assert.NilError(t, err) ++ ++ // Add a file into a directory above root ++ // Ensure that we can't access this file while tarring. ++ err = ioutil.WriteFile(filepath.Join(dir, "host-file"), []byte("I am a host file"), 0644) ++ assert.NilError(t, err) ++ ++ // Create some data which which will be copied into the "container" root into ++ // the symlinked path. ++ // Before this change, the copy would overwrite the "host" content. ++ // With this change it should not. ++ data := filepath.Join(dir, "data") ++ err = os.MkdirAll(data, 0755) ++ assert.NilError(t, err) ++ err = ioutil.WriteFile(filepath.Join(data, "local-file"), []byte("pwn3d"), 0644) ++ assert.NilError(t, err) ++ ++ safe := filepath.Join(root, "safe") ++ err = unix.Symlink(dir, safe) ++ assert.NilError(t, err) ++ ++ rdr, err := archive.TarWithOptions(data, &archive.TarOptions{IncludeFiles: []string{"local-file"}, RebaseNames: map[string]string{"local-file": "host-file"}}) ++ assert.NilError(t, err) ++ ++ // Use tee to test both the good case and the bad case w/o recreating the archive ++ bufRdr := bytes.NewBuffer(nil) ++ tee := io.TeeReader(rdr, bufRdr) ++ ++ err = UntarWithRoot(tee, safe, nil, root) ++ assert.Assert(t, err != nil) ++ assert.ErrorContains(t, err, "open /safe/host-file: no such file or directory") ++ ++ // Make sure the "host" file is still in tact ++ // Before the fix the host file would be overwritten ++ hostData, err := ioutil.ReadFile(filepath.Join(dir, "host-file")) ++ assert.NilError(t, err) ++ assert.Equal(t, string(hostData), "I am a host file") ++ ++ // Now test by chrooting to an attacker controlled path ++ // This should succeed as is and overwrite a "host" file ++ // Note that this would be a mis-use of this function. ++ err = UntarWithRoot(bufRdr, safe, nil, safe) ++ assert.NilError(t, err) ++ ++ hostData, err = ioutil.ReadFile(filepath.Join(dir, "host-file")) ++ assert.NilError(t, err) ++ assert.Equal(t, string(hostData), "pwn3d") ++} ++ ++// Test for CVE-2018-15664 ++// Assures that in the case where an "attacker" controlled path is a symlink to ++// some path outside of a container's rootfs that we do not unwittingly leak ++// host data into the archive. ++func TestTarWithMaliciousSymlinks(t *testing.T) { ++ dir, err := ioutil.TempDir("", t.Name()) ++ assert.NilError(t, err) ++ // defer os.RemoveAll(dir) ++ t.Log(dir) ++ ++ root := filepath.Join(dir, "root") ++ ++ err = os.MkdirAll(root, 0755) ++ assert.NilError(t, err) ++ ++ hostFileData := []byte("I am a host file") ++ ++ // Add a file into a directory above root ++ // Ensure that we can't access this file while tarring. ++ err = ioutil.WriteFile(filepath.Join(dir, "host-file"), hostFileData, 0644) ++ assert.NilError(t, err) ++ ++ safe := filepath.Join(root, "safe") ++ err = unix.Symlink(dir, safe) ++ assert.NilError(t, err) ++ ++ data := filepath.Join(dir, "data") ++ err = os.MkdirAll(data, 0755) ++ assert.NilError(t, err) ++ ++ type testCase struct { ++ p string ++ includes []string ++ } ++ ++ cases := []testCase{ ++ {p: safe, includes: []string{"host-file"}}, ++ {p: safe + "/", includes: []string{"host-file"}}, ++ {p: safe, includes: nil}, ++ {p: safe + "/", includes: nil}, ++ {p: root, includes: []string{"safe/host-file"}}, ++ {p: root, includes: []string{"/safe/host-file"}}, ++ {p: root, includes: nil}, ++ } ++ ++ maxBytes := len(hostFileData) ++ ++ for _, tc := range cases { ++ t.Run(path.Join(tc.p+"_"+strings.Join(tc.includes, "_")), func(t *testing.T) { ++ // Here if we use archive.TarWithOptions directly or change the "root" parameter ++ // to be the same as "safe", data from the host will be leaked into the archive ++ var opts *archive.TarOptions ++ if tc.includes != nil { ++ opts = &archive.TarOptions{ ++ IncludeFiles: tc.includes, ++ } ++ } ++ rdr, err := Tar(tc.p, opts, root) ++ assert.NilError(t, err) ++ defer rdr.Close() ++ ++ tr := gotar.NewReader(rdr) ++ assert.Assert(t, !isDataInTar(t, tr, hostFileData, int64(maxBytes)), "host data leaked to archive") ++ }) ++ } ++} ++ ++func isDataInTar(t *testing.T, tr *gotar.Reader, compare []byte, maxBytes int64) bool { ++ for { ++ h, err := tr.Next() ++ if err == io.EOF { ++ break ++ } ++ assert.NilError(t, err) ++ ++ if h.Size == 0 { ++ continue ++ } ++ assert.Assert(t, h.Size <= maxBytes, "%s: file size exceeds max expected size %d: %d", h.Name, maxBytes, h.Size) ++ ++ data := make([]byte, int(h.Size)) ++ _, err = io.ReadFull(tr, data) ++ assert.NilError(t, err) ++ if bytes.Contains(data, compare) { ++ return true ++ } ++ } ++ ++ return false ++} +diff --git a/components/engine/pkg/chrootarchive/archive_windows.go b/components/engine/pkg/chrootarchive/archive_windows.go +index f2973132a3..de87113e95 100644 +--- a/components/engine/pkg/chrootarchive/archive_windows.go ++++ b/components/engine/pkg/chrootarchive/archive_windows.go +@@ -14,9 +14,16 @@ func chroot(path string) error { + + func invokeUnpack(decompressedArchive io.ReadCloser, + dest string, +- options *archive.TarOptions) error { ++ options *archive.TarOptions, root string) error { + // Windows is different to Linux here because Windows does not support + // chroot. Hence there is no point sandboxing a chrooted process to + // do the unpack. We call inline instead within the daemon process. + return archive.Unpack(decompressedArchive, longpath.AddPrefix(dest), options) + } ++ ++func invokePack(srcPath string, options *archive.TarOptions, root string) (io.ReadCloser, error) { ++ // Windows is different to Linux here because Windows does not support ++ // chroot. Hence there is no point sandboxing a chrooted process to ++ // do the pack. We call inline instead within the daemon process. ++ return archive.TarWithOptions(srcPath, options) ++} +diff --git a/components/engine/pkg/chrootarchive/init_unix.go b/components/engine/pkg/chrootarchive/init_unix.go +index a15e4bb83c..c24fea7d9c 100644 +--- a/components/engine/pkg/chrootarchive/init_unix.go ++++ b/components/engine/pkg/chrootarchive/init_unix.go +@@ -14,6 +14,7 @@ import ( + func init() { + reexec.Register("docker-applyLayer", applyLayer) + reexec.Register("docker-untar", untar) ++ reexec.Register("docker-tar", tar) + } + + func fatal(err error) { +-- +2.19.0 + diff --git a/patch/0127-docker-support-docker-cli-using-syslog.patch b/patch/0127-docker-support-docker-cli-using-syslog.patch new file mode 100644 index 0000000..5028a2e --- /dev/null +++ b/patch/0127-docker-support-docker-cli-using-syslog.patch @@ -0,0 +1,227 @@ +From 9b4ec11138037abf3def2fcb1c3f415a915f044e Mon Sep 17 00:00:00 2001 +From: zhaolongquan1 +Date: Thu, 13 Jun 2019 20:49:01 -0400 +Subject: [PATCH] docker: support docker cli using syslog + +reason:add caller information to the docker rm/stop/restart/kill command and print to the log + +Change-Id: I05a109c6a7fe105be6ed680cd5a5700eac99c8bb +Signed-off-by: zhaolongquan1 +--- + .../github.com/docker/docker/pkg/ppid/ppid.go | 41 ++++++++++++++ + .../vendor/github.com/sirupsen/logrus/checklist | 1 + + .../sirupsen/logrus/hooks/syslog/README.md | 39 ++++++++++++++ + .../sirupsen/logrus/hooks/syslog/syslog.go | 62 ++++++++++++++++++++++ + .../sirupsen/logrus/hooks/syslog/syslog_test.go | 27 ++++++++++ + 5 files changed, 170 insertions(+) + create mode 100644 components/cli/vendor/github.com/docker/docker/pkg/ppid/ppid.go + create mode 100644 components/cli/vendor/github.com/sirupsen/logrus/checklist + create mode 100644 components/cli/vendor/github.com/sirupsen/logrus/hooks/syslog/README.md + create mode 100644 components/cli/vendor/github.com/sirupsen/logrus/hooks/syslog/syslog.go + create mode 100644 components/cli/vendor/github.com/sirupsen/logrus/hooks/syslog/syslog_test.go + +diff --git a/components/cli/vendor/github.com/docker/docker/pkg/ppid/ppid.go b/components/cli/vendor/github.com/docker/docker/pkg/ppid/ppid.go +new file mode 100644 +index 0000000..7d634de +--- /dev/null ++++ b/components/cli/vendor/github.com/docker/docker/pkg/ppid/ppid.go +@@ -0,0 +1,41 @@ ++package ppid ++ ++import ( ++ "bytes" ++ "fmt" ++ "io/ioutil" ++ "os" ++ "strings" ++ "log/syslog" ++ ++ "github.com/sirupsen/logrus" ++ lSyslog "github.com/sirupsen/logrus/hooks/syslog" ++) ++ ++func AddSyslogHook() { ++ logrus.SetOutput(ioutil.Discard) ++ hook, serr := lSyslog.NewSyslogHook("", "", syslog.LOG_DEBUG|syslog.LOG_USER, "docker-client") ++ if serr != nil { ++ hook, serr = lSyslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_DEBUG|syslog.LOG_USER, "docker-client") ++ } ++ if serr == nil { ++ logrus.SetFormatter(&logrus.TextFormatter{ ++ DisableColors: true, ++ FullTimestamp: true, ++ }) ++ logrus.AddHook(hook) ++ } ++} ++ ++func Log(command string, args []string) { ++ ppid := os.Getppid() ++ cmdlinePath := fmt.Sprintf("/proc/%v/cmdline", ppid) ++ content, err := ioutil.ReadFile(cmdlinePath) ++ if err != nil { ++ logrus.Infof("read cmdline %s failed: %v", cmdlinePath, err) ++ } else { ++ s := bytes.Replace(content, []byte{0}, []byte(" "), -1) ++ cmd := fmt.Sprintf("docker %s %v", command, strings.Join(args, " ")) ++ logrus.Infof("received command [%v] from parent [%v] cmdline [%v]", cmd, ppid, string(s)) ++ } ++} +diff --git a/components/cli/vendor/github.com/sirupsen/logrus/checklist b/components/cli/vendor/github.com/sirupsen/logrus/checklist +new file mode 100644 +index 0000000..1d505b2 +--- /dev/null ++++ b/components/cli/vendor/github.com/sirupsen/logrus/checklist +@@ -0,0 +1 @@ ++Add log forwarding mechanism in docker-client and print the ppid for docker kill/restart/stop/rm command. +\ No newline at end of file +diff --git a/components/cli/vendor/github.com/sirupsen/logrus/hooks/syslog/README.md b/components/cli/vendor/github.com/sirupsen/logrus/hooks/syslog/README.md +new file mode 100644 +index 0000000..069ce12 +--- /dev/null ++++ b/components/cli/vendor/github.com/sirupsen/logrus/hooks/syslog/README.md +@@ -0,0 +1,39 @@ ++# Syslog Hooks for Logrus :walrus: ++ ++## Usage ++ ++```go ++import ( ++ "log/syslog" ++ "github.com/sirupsen/logrus" ++ logrus_syslog "github.com/sirupsen/logrus/hooks/syslog" ++) ++ ++func main() { ++ log := logrus.New() ++ hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") ++ ++ if err == nil { ++ log.Hooks.Add(hook) ++ } ++} ++``` ++ ++If you want to connect to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). Just assign empty string to the first two parameters of `NewSyslogHook`. It should look like the following. ++ ++```go ++import ( ++ "log/syslog" ++ "github.com/sirupsen/logrus" ++ logrus_syslog "github.com/sirupsen/logrus/hooks/syslog" ++) ++ ++func main() { ++ log := logrus.New() ++ hook, err := logrus_syslog.NewSyslogHook("", "", syslog.LOG_INFO, "") ++ ++ if err == nil { ++ log.Hooks.Add(hook) ++ } ++} ++``` +\ No newline at end of file +diff --git a/components/cli/vendor/github.com/sirupsen/logrus/hooks/syslog/syslog.go b/components/cli/vendor/github.com/sirupsen/logrus/hooks/syslog/syslog.go +new file mode 100644 +index 0000000..e76786e +--- /dev/null ++++ b/components/cli/vendor/github.com/sirupsen/logrus/hooks/syslog/syslog.go +@@ -0,0 +1,62 @@ ++// +build !windows,!nacl,!plan9 ++ ++package logrus_syslog ++ ++import ( ++ "fmt" ++ "log/syslog" ++ "os" ++ ++ "github.com/sirupsen/logrus" ++) ++ ++// SyslogHook to send logs via syslog. ++type SyslogHook struct { ++ Writer *syslog.Writer ++ SyslogNetwork string ++ SyslogRaddr string ++} ++ ++// Creates a hook to be added to an instance of logger. This is called with ++// `hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_DEBUG, "")` ++// `if err == nil { log.Hooks.Add(hook) }` ++func NewSyslogHook(network, raddr string, priority syslog.Priority, tag string) (*SyslogHook, error) { ++ w, err := syslog.Dial(network, raddr, priority, tag) ++ return &SyslogHook{w, network, raddr}, err ++} ++ ++func (hook *SyslogHook) Fire(entry *logrus.Entry) error { ++ line, err := entry.String() ++ if err != nil { ++ fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err) ++ return err ++ } ++ ++ switch entry.Level { ++ case logrus.PanicLevel: ++ return hook.Writer.Crit(line) ++ case logrus.FatalLevel: ++ return hook.Writer.Crit(line) ++ case logrus.ErrorLevel: ++ return hook.Writer.Err(line) ++ case logrus.WarnLevel: ++ return hook.Writer.Warning(line) ++ case logrus.InfoLevel: ++ return hook.Writer.Info(line) ++ case logrus.DebugLevel: ++ return hook.Writer.Debug(line) ++ default: ++ return nil ++ } ++} ++ ++func (hook *SyslogHook) Levels() []logrus.Level { ++ return []logrus.Level{ ++ logrus.PanicLevel, ++ logrus.FatalLevel, ++ logrus.ErrorLevel, ++ logrus.WarnLevel, ++ logrus.InfoLevel, ++ logrus.DebugLevel, ++ } ++} +diff --git a/components/cli/vendor/github.com/sirupsen/logrus/hooks/syslog/syslog_test.go b/components/cli/vendor/github.com/sirupsen/logrus/hooks/syslog/syslog_test.go +new file mode 100644 +index 0000000..89bd1ec +--- /dev/null ++++ b/components/cli/vendor/github.com/sirupsen/logrus/hooks/syslog/syslog_test.go +@@ -0,0 +1,27 @@ ++package logrus_syslog ++ ++import ( ++ "log/syslog" ++ "testing" ++ ++ "github.com/sirupsen/logrus" ++) ++ ++func TestLocalhostAddAndPrint(t *testing.T) { ++ log := logrus.New() ++ hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") ++ ++ if err != nil { ++ t.Errorf("Unable to connect to local syslog.") ++ } ++ ++ log.Hooks.Add(hook) ++ ++ for _, level := range hook.Levels() { ++ if len(log.Hooks[level]) != 1 { ++ t.Errorf("SyslogHook was not added. The length of log.Hooks[%v]: %v", level, len(log.Hooks[level])) ++ } ++ } ++ ++ log.Info("Congratulations!") ++} +-- +1.8.3.1 + diff --git a/patch/0128-docker-add-log-forwarding-mechanism-and-print.patch b/patch/0128-docker-add-log-forwarding-mechanism-and-print.patch new file mode 100644 index 0000000..0cafa17 --- /dev/null +++ b/patch/0128-docker-add-log-forwarding-mechanism-and-print.patch @@ -0,0 +1,103 @@ +From ea43c4a77854f15a7782d3acda03b55145796a34 Mon Sep 17 00:00:00 2001 +From: zhaolongquan1 +Date: Thu, 13 Jun 2019 20:56:05 -0400 +Subject: [PATCH] docker: add log forwarding mechanism and print ppid + +reason:add caller information to the docker rm/stop/restart/kill command and print to the log + +Change-Id: I9de1b9ca2ed8b8802362a7cfeb881dbbafbdfe41 +Signed-off-by: zhaolongquan1 +--- + components/cli/cli/command/container/kill.go | 3 +++ + components/cli/cli/command/container/restart.go | 3 +++ + components/cli/cli/command/container/rm.go | 3 +++ + components/cli/cli/command/container/stop.go | 3 +++ + 4 files changed, 12 insertions(+) + +diff --git a/components/cli/cli/command/container/kill.go b/components/cli/cli/command/container/kill.go +index feedbc0..28e7415 100644 +--- a/components/cli/cli/command/container/kill.go ++++ b/components/cli/cli/command/container/kill.go +@@ -3,6 +3,7 @@ package container + import ( + "context" + "fmt" ++ "github.com/docker/docker/pkg/ppid" + "strings" + + "github.com/docker/cli/cli" +@@ -27,6 +28,8 @@ func NewKillCommand(dockerCli command.Cli) *cobra.Command { + Args: cli.RequiresMinArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + opts.containers = args ++ ppid.AddSyslogHook() ++ ppid.Log(cmd.Name(), args) + return runKill(dockerCli, &opts) + }, + } +diff --git a/components/cli/cli/command/container/restart.go b/components/cli/cli/command/container/restart.go +index 6e02ee4..1951f0f 100644 +--- a/components/cli/cli/command/container/restart.go ++++ b/components/cli/cli/command/container/restart.go +@@ -3,6 +3,7 @@ package container + import ( + "context" + "fmt" ++ "github.com/docker/docker/pkg/ppid" + "strings" + "time" + +@@ -30,6 +31,8 @@ func NewRestartCommand(dockerCli command.Cli) *cobra.Command { + RunE: func(cmd *cobra.Command, args []string) error { + opts.containers = args + opts.nSecondsChanged = cmd.Flags().Changed("time") ++ ppid.AddSyslogHook() ++ ppid.Log(cmd.Name(), args) + return runRestart(dockerCli, &opts) + }, + } +diff --git a/components/cli/cli/command/container/rm.go b/components/cli/cli/command/container/rm.go +index 2dcd4b6..29ba403 100644 +--- a/components/cli/cli/command/container/rm.go ++++ b/components/cli/cli/command/container/rm.go +@@ -3,6 +3,7 @@ package container + import ( + "context" + "fmt" ++ "github.com/docker/docker/pkg/ppid" + "strings" + + "github.com/docker/cli/cli" +@@ -30,6 +31,8 @@ func NewRmCommand(dockerCli command.Cli) *cobra.Command { + Args: cli.RequiresMinArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + opts.containers = args ++ ppid.AddSyslogHook() ++ ppid.Log(cmd.Name(), args) + return runRm(dockerCli, &opts) + }, + } +diff --git a/components/cli/cli/command/container/stop.go b/components/cli/cli/command/container/stop.go +index e299175..e6ab748 100644 +--- a/components/cli/cli/command/container/stop.go ++++ b/components/cli/cli/command/container/stop.go +@@ -3,6 +3,7 @@ package container + import ( + "context" + "fmt" ++ "github.com/docker/docker/pkg/ppid" + "strings" + "time" + +@@ -30,6 +31,8 @@ func NewStopCommand(dockerCli command.Cli) *cobra.Command { + RunE: func(cmd *cobra.Command, args []string) error { + opts.containers = args + opts.timeChanged = cmd.Flags().Changed("time") ++ ppid.AddSyslogHook() ++ ppid.Log(cmd.Name(), args) + return runStop(dockerCli, &opts) + }, + } +-- +1.8.3.1 + diff --git a/patch/0129-docker-check-if-image-exists-in-memory-when-p.patch b/patch/0129-docker-check-if-image-exists-in-memory-when-p.patch new file mode 100644 index 0000000..9b899ec --- /dev/null +++ b/patch/0129-docker-check-if-image-exists-in-memory-when-p.patch @@ -0,0 +1,142 @@ +From d66866bfd936c50b472c78268eb74ad6999b3490 Mon Sep 17 00:00:00 2001 +From: Fengtu Wang +Date: Sat, 26 Aug 2017 18:55:55 +0800 +Subject: [PATCH] docker: check if image exists in memory when pulling + image + +We only check if imageID exists in disk currently, +but it may not be loaded into memory if it's layer +is broken by some reason. In this case we should not +skip downloading layers and image. + +Change-Id: I9ac2f1aaf20ce618ca9a88613417b757d8d4927e +Signed-off-by: wangfengtu +--- + components/engine/distribution/config.go | 9 +++++++++ + components/engine/distribution/pull_v2.go | 2 +- + components/engine/image/store.go | 13 +++++++++++++ + components/engine/plugin/backend_linux.go | 8 ++++++++ + components/engine/plugin/blobstore.go | 3 +++ + 5 files changed, 34 insertions(+), 1 deletion(-) + +diff --git a/components/engine/distribution/config.go b/components/engine/distribution/config.go +index 211d4f0..00a7c26 100644 +--- a/components/engine/distribution/config.go ++++ b/components/engine/distribution/config.go +@@ -88,6 +88,7 @@ type ImagePushConfig struct { + type ImageConfigStore interface { + Put([]byte) (digest.Digest, error) + Get(digest.Digest) ([]byte, error) ++ GetAndCheck(digest.Digest) ([]byte, error) + RootFSFromConfig([]byte) (*image.RootFS, error) + PlatformFromConfig([]byte) (*specs.Platform, error) + } +@@ -143,6 +144,14 @@ func (s *imageConfigStore) Get(d digest.Digest) ([]byte, error) { + return img.RawJSON(), nil + } + ++func (s *imageConfigStore) GetAndCheck(d digest.Digest) ([]byte, error) { ++ img, err := s.Store.GetAndCheck(image.IDFromDigest(d)) ++ if err != nil { ++ return nil, err ++ } ++ return img.RawJSON(), nil ++} ++ + func (s *imageConfigStore) RootFSFromConfig(c []byte) (*image.RootFS, error) { + var unmarshalledConfig image.Image + if err := json.Unmarshal(c, &unmarshalledConfig); err != nil { +diff --git a/components/engine/distribution/pull_v2.go b/components/engine/distribution/pull_v2.go +index 2c90e2f..9d2a303 100644 +--- a/components/engine/distribution/pull_v2.go ++++ b/components/engine/distribution/pull_v2.go +@@ -555,7 +555,7 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s + } + + target := mfst.Target() +- if img, err := p.config.ImageStore.Get(target.Digest); err == nil { ++ if img, err := p.config.ImageStore.GetAndCheck(target.Digest); err == nil { + rootfs, err := p.config.ImageStore.RootFSFromConfig(img) + if err == nil { + if chainID := rootfs.ChainID(); chainID != "" { +diff --git a/components/engine/image/store.go b/components/engine/image/store.go +index b078a26..db75f06 100644 +--- a/components/engine/image/store.go ++++ b/components/engine/image/store.go +@@ -3,6 +3,7 @@ package image // import "github.com/docker/docker/image" + import ( + "encoding/json" + "fmt" ++ "os" + "sync" + "time" + +@@ -18,6 +19,7 @@ import ( + type Store interface { + Create(config []byte) (ID, error) + Get(id ID) (*Image, error) ++ GetAndCheck(id ID) (*Image, error) + Delete(id ID) ([]layer.Metadata, error) + Search(partialID string) (ID, error) + SetParent(id ID, parent ID) error +@@ -223,6 +225,17 @@ func (is *store) Get(id ID) (*Image, error) { + return img, nil + } + ++func (is *store) GetAndCheck(id ID) (*Image, error) { ++ is.Lock() ++ if is.images[id] == nil { ++ is.Unlock() ++ return nil, os.ErrNotExist ++ } ++ is.Unlock() ++ ++ return is.Get(id) ++} ++ + func (is *store) Delete(id ID) ([]layer.Metadata, error) { + is.Lock() + defer is.Unlock() +diff --git a/components/engine/plugin/backend_linux.go b/components/engine/plugin/backend_linux.go +index e5d3be1..b3116b5 100644 +--- a/components/engine/plugin/backend_linux.go ++++ b/components/engine/plugin/backend_linux.go +@@ -147,6 +147,10 @@ func (s *tempConfigStore) Get(d digest.Digest) ([]byte, error) { + return s.config, nil + } + ++func (s *tempConfigStore) GetAndCheck(d digest.Digest) ([]byte, error) { ++ return s.Get(d) ++} ++ + func (s *tempConfigStore) RootFSFromConfig(c []byte) (*image.RootFS, error) { + return configToRootFS(c) + } +@@ -544,6 +548,10 @@ func (s *pluginConfigStore) Get(d digest.Digest) ([]byte, error) { + return ioutil.ReadAll(rwc) + } + ++func (s *pluginConfigStore) GetAndCheck(d digest.Digest) ([]byte, error) { ++ return s.Get(d) ++} ++ + func (s *pluginConfigStore) RootFSFromConfig(c []byte) (*image.RootFS, error) { + return configToRootFS(c) + } +diff --git a/components/engine/plugin/blobstore.go b/components/engine/plugin/blobstore.go +index a24e7bd..ac6b967 100644 +--- a/components/engine/plugin/blobstore.go ++++ b/components/engine/plugin/blobstore.go +@@ -181,6 +181,9 @@ func (dm *downloadManager) Put(dt []byte) (digest.Digest, error) { + func (dm *downloadManager) Get(d digest.Digest) ([]byte, error) { + return nil, fmt.Errorf("digest not found") + } ++func (dm *downloadManager) GetAndCheck(d digest.Digest) ([]byte, error) { ++ return dm.Get(d) ++} + func (dm *downloadManager) RootFSFromConfig(c []byte) (*image.RootFS, error) { + return configToRootFS(c) + } +-- +2.7.4 + diff --git a/patch/0130-docker-mask-internal-proc-add-livepatch.patch b/patch/0130-docker-mask-internal-proc-add-livepatch.patch new file mode 100644 index 0000000..11b7fa2 --- /dev/null +++ b/patch/0130-docker-mask-internal-proc-add-livepatch.patch @@ -0,0 +1,28 @@ +From 6eda894b741eb1a0fe10c5834c0bbb81222d3131 Mon Sep 17 00:00:00 2001 +From: wujibin +Date: Fri, 14 Jun 2019 12:51:01 +0800 +Subject: [PATCH] docker: mask internal proc add livepatch + +reason: mask /proc/livepatch + +Change-Id: I229ee5aaccbd067aecb7decce9a7f701531b037a +Signed-off-by: wujibin +--- + .../cli/vendor/github.com/containerd/containerd/oci/spec.go | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/components/cli/vendor/github.com/containerd/containerd/oci/spec.go b/components/cli/vendor/github.com/containerd/containerd/oci/spec.go +index bc5cc45c16..e96c77aad7 100644 +--- a/components/cli/vendor/github.com/containerd/containerd/oci/spec.go ++++ b/components/cli/vendor/github.com/containerd/containerd/oci/spec.go +@@ -228,6 +228,7 @@ func populateDefaultUnixSpec(ctx context.Context, s *Spec, id string) error { + "/proc/cpuirqstat", + "/proc/memstat", + "/proc/iomem_ext", ++ "/proc/livepatch", + }, + ReadonlyPaths: []string{ + "/proc/asound", +-- +2.19.0 + diff --git a/patch/0132-docker-fix-docker-pull-406-error-on-some-regi.patch b/patch/0132-docker-fix-docker-pull-406-error-on-some-regi.patch new file mode 100644 index 0000000..32e3b3c --- /dev/null +++ b/patch/0132-docker-fix-docker-pull-406-error-on-some-regi.patch @@ -0,0 +1,34 @@ +From 9ef1eaf3c58ec9474ae717c9dd118af347cbd33f Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Wed, 19 Jun 2019 23:53:48 +0800 +Subject: [PATCH] docker: fix docker pull 406 error on some registry + +Change-Id: I7047c2cd4f59167fa692c333bda89d224bf84147 +Signed-off-by: jingrui +--- + .../engine/vendor/github.com/docker/distribution/manifests.go | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/components/engine/vendor/github.com/docker/distribution/manifests.go b/components/engine/vendor/github.com/docker/distribution/manifests.go +index 1816baea1d..fb07c92eb5 100644 +--- a/components/engine/vendor/github.com/docker/distribution/manifests.go ++++ b/components/engine/vendor/github.com/docker/distribution/manifests.go +@@ -4,6 +4,7 @@ import ( + "context" + "fmt" + "mime" ++ "sort" + + "github.com/opencontainers/go-digest" + ) +@@ -81,6 +82,7 @@ func ManifestMediaTypes() (mediaTypes []string) { + mediaTypes = append(mediaTypes, t) + } + } ++ sort.Strings(mediaTypes) + return + } + +-- +2.17.1 + diff --git a/patch/0133-docker-Fixed-docker-ps-and-docker-inspect-sta.patch b/patch/0133-docker-Fixed-docker-ps-and-docker-inspect-sta.patch new file mode 100644 index 0000000..27b27b2 --- /dev/null +++ b/patch/0133-docker-Fixed-docker-ps-and-docker-inspect-sta.patch @@ -0,0 +1,90 @@ +From 4e614995fea775779078f1903fc45bc89e2afe4e Mon Sep 17 00:00:00 2001 +Date: Wed, 26 Jun 2019 12:00:39 +0800 +Subject: [PATCH] docker: Fixed "docker ps" and "docker inspect" + status inconsistency. + +reason: fixed "docker ps" and "docker inspect" status inconsistency. +The SaveFlag is marked when toDisk is failed. Using this flag to +retrycheckpoint. + +Change-Id: I9ea160233043964e48ea0ff2f384f64bac921312 +Signed-off-by: panwenxiang +--- + components/engine/container/container.go | 21 +++++++++++++++++++++ + components/engine/daemon/stop.go | 3 +++ + 2 files changed, 24 insertions(+) + +diff --git a/components/engine/container/container.go b/components/engine/container/container.go +index 02adc20..c194220 100644 +--- a/components/engine/container/container.go ++++ b/components/engine/container/container.go +@@ -11,6 +11,7 @@ import ( + "runtime" + "strings" + "sync" ++ "sync/atomic" + "syscall" + "time" + +@@ -57,6 +58,12 @@ type ExitStatus struct { + ExitedAt time.Time + } + ++const ( ++ SaveSuccess = iota ++ SaveFailed ++ SavePending ++) ++ + // Container holds the structure defining a container object. + type Container struct { + StreamConfig *stream.Config +@@ -109,6 +116,7 @@ type Container struct { + // Fields here are specific to Windows + NetworkSharedContainerID string `json:"-"` + SharedEndpointList []string `json:"-"` ++ SaveFlag int32 + } + + // NewBaseContainer creates a new container with its +@@ -194,10 +202,23 @@ func (container *Container) toDisk() (*Container, error) { + func (container *Container) CheckpointTo(store ViewDB) error { + deepCopy, err := container.toDisk() + if err != nil { ++ atomic.StoreInt32(&container.SaveFlag, SaveFailed) + return err + } + return store.Save(deepCopy) + } ++func (container *Container) RetryCheckPoint(store ViewDB) error { ++ if atomic.CompareAndSwapInt32(&container.SaveFlag, SaveFailed, SavePending) { ++ container.Lock() ++ defer container.Unlock() ++ err := container.CheckpointTo(store) ++ if err != nil { ++ return err ++ } ++ atomic.StoreInt32(&container.SaveFlag, SaveSuccess) ++ } ++ return nil ++} + + // readHostConfig reads the host configuration from disk for the container. + func (container *Container) readHostConfig() error { +diff --git a/components/engine/daemon/stop.go b/components/engine/daemon/stop.go +index 3c4cd76..40bc36d 100644 +--- a/components/engine/daemon/stop.go ++++ b/components/engine/daemon/stop.go +@@ -23,6 +23,9 @@ func (daemon *Daemon) ContainerStop(name string, timeout *int) error { + if err != nil { + return err + } ++ defer func() { ++ go container.RetryCheckPoint(daemon.containersReplica) ++ }() + if !container.IsRunning() { + return containerNotModifiedError{running: false} + } +-- +2.1.3 + diff --git a/patch/0135-docker-delete-containerd-db-first-reboot.patch b/patch/0135-docker-delete-containerd-db-first-reboot.patch new file mode 100644 index 0000000..9ccc655 --- /dev/null +++ b/patch/0135-docker-delete-containerd-db-first-reboot.patch @@ -0,0 +1,60 @@ +From cb7ce3076d0ae553bd2a2aa64a9be145c144d1ed Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Thu, 27 Jun 2019 17:07:21 +0800 +Subject: [PATCH] docker: delete containerd db first reboot + +Change-Id: I90b5e2217df47cef71a410e43188476be751cebe +Signed-off-by: jingrui +--- + components/engine/cmd/dockerd/daemon.go | 1 + + components/engine/daemon/daemon.go | 15 ++++++++++++++- + 2 files changed, 15 insertions(+), 1 deletion(-) + +diff --git a/components/engine/cmd/dockerd/daemon.go b/components/engine/cmd/dockerd/daemon.go +index ea00c56fde..9c2b6602b3 100644 +--- a/components/engine/cmd/dockerd/daemon.go ++++ b/components/engine/cmd/dockerd/daemon.go +@@ -185,6 +185,7 @@ func (cli *DaemonCli) start(opts *daemonOptions) (err error) { + return fmt.Errorf("Error starting daemon: %v", err) + } + ++ d.CleanupContainerdDBs() + d.StoreHosts(hosts) + + // validate after NewDaemon has restored enabled plugins. Dont change order. +diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go +index 72467eb62a..c36cc6395f 100644 +--- a/components/engine/daemon/daemon.go ++++ b/components/engine/daemon/daemon.go +@@ -619,6 +619,20 @@ func (daemon *Daemon) cleanupLocalDB(db string) { + } + } + ++func (daemon *Daemon) CleanupContainerdDBs() { ++ // check db lock is exist, do nothing if file is existed ++ dbLockPath := filepath.Join(daemon.configStore.ExecRoot, "dblock") ++ _, err := os.Stat(dbLockPath) ++ if err == nil { ++ return ++ } ++ if !os.IsNotExist(err) { ++ logrus.Errorf("stat dblock failed %v", err) ++ return ++ } ++ daemon.cleanupLocalDB(filepath.Join(daemon.configStore.Root, "containerd/daemon/io.containerd.metadata.v1.bolt/meta.db")) ++} ++ + // DB files may corrupted on exception poweroff but can be rebuild at run time, + // so we can remove DB files on OS starts avoid daemon can not startup. + func (daemon *Daemon) cleanupLocalDBs() { +@@ -638,7 +652,6 @@ func (daemon *Daemon) cleanupLocalDBs() { + daemon.cleanupLocalDB(filepath.Join(daemon.configStore.Root, "builder/fscache.db")) + daemon.cleanupLocalDB(filepath.Join(daemon.configStore.Root, "volumes/metadata.db")) + daemon.cleanupLocalDB(filepath.Join(daemon.configStore.Root, "network/files/local-kv.db")) +- // daemon.cleanupLocalDB(filepath.Join(daemon.configStore.Root, "containerd/daemon/io.containerd.metadata.v1.bolt/meta.db")) + daemon.cleanupLocalDB(filepath.Join(daemon.configStore.Root, "accelerator/accel.db")) + daemon.cleanupLocalDB(filepath.Join(daemon.configStore.Root, "buildkit/metadata.db")) + daemon.cleanupLocalDB(filepath.Join(daemon.configStore.Root, "buildkit/cache.db")) +-- +2.17.1 + diff --git a/patch/0136-docker-continue-to-check-healthy-instead-of-return.patch b/patch/0136-docker-continue-to-check-healthy-instead-of-return.patch new file mode 100644 index 0000000..58dc9c5 --- /dev/null +++ b/patch/0136-docker-continue-to-check-healthy-instead-of-return.patch @@ -0,0 +1,26 @@ +From f183e106c38722ba8b02d0ad6da9d8368073a020 Mon Sep 17 00:00:00 2001 +From: yangfeiyu2 +Date: Mon, 1 Jul 2019 01:14:50 -0400 +Subject: [PATCH] docker: continue to check healthy instead of return when setup ExitOnUnhealthy. + + +Signed-off-by: yangfeiyu2 +--- + components/engine/daemon/health.go | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/components/engine/daemon/health.go b/components/engine/daemon/health.go +index 80bda66..5f26ee5 100644 +--- a/components/engine/daemon/health.go ++++ b/components/engine/daemon/health.go +@@ -245,7 +245,6 @@ func monitor(d *Daemon, c *container.Container, stop chan struct{}, probe probe) + c.Config.Healthcheck.ExitOnUnhealthy == true { + d.Kill(c) + logrus.Debugf("Shut down container %s because of unhealthy", c.ID) +- return + } + } + } +-- +1.8.3.1 + diff --git a/patch/0137-docker-fix-reboot-dirty-data-in-containerd.patch b/patch/0137-docker-fix-reboot-dirty-data-in-containerd.patch new file mode 100644 index 0000000..016fcba --- /dev/null +++ b/patch/0137-docker-fix-reboot-dirty-data-in-containerd.patch @@ -0,0 +1,109 @@ +From 45c3d6c89fa895f147e74a4388ab604c6c5ad804 Mon Sep 17 00:00:00 2001 +From: jingrui +Date: Mon, 1 Jul 2019 21:17:07 +0800 +Subject: [PATCH] docker: fix reboot dirty data in containerd + +Signed-off-by: jingrui +--- + components/engine/cmd/dockerd/daemon.go | 2 +- + components/engine/daemon/daemon.go | 30 ++++++++++++------------- + 2 files changed, 16 insertions(+), 16 deletions(-) + +diff --git a/components/engine/cmd/dockerd/daemon.go b/components/engine/cmd/dockerd/daemon.go +index 9c2b6602b3..78cd41ac59 100644 +--- a/components/engine/cmd/dockerd/daemon.go ++++ b/components/engine/cmd/dockerd/daemon.go +@@ -151,6 +151,7 @@ func (cli *DaemonCli) start(opts *daemonOptions) (err error) { + return fmt.Errorf("Failed to generate containerd options: %v", err) + } + ++ daemon.CleanupContainerdDBs(cli.Config.ExecRoot, cli.Config.Root) + r, err := supervisor.Start(ctx, filepath.Join(cli.Config.Root, "containerd"), filepath.Join(cli.Config.ExecRoot, "containerd"), opts...) + if err != nil { + cancel() +@@ -185,7 +186,6 @@ func (cli *DaemonCli) start(opts *daemonOptions) (err error) { + return fmt.Errorf("Error starting daemon: %v", err) + } + +- d.CleanupContainerdDBs() + d.StoreHosts(hosts) + + // validate after NewDaemon has restored enabled plugins. Dont change order. +diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go +index c36cc6395f..6c5eafd7c5 100644 +--- a/components/engine/daemon/daemon.go ++++ b/components/engine/daemon/daemon.go +@@ -509,7 +509,7 @@ func (daemon *Daemon) restore() error { + logrus.Errorf("removeRedundantMounts failed %v", err) + } + +- daemon.cleanupLocalDBs() ++ daemon.cleanupLocalDBs(daemon.configStore.ExecRoot, daemon.configStore.Root) + + containerIDs := make(map[string]struct{}) + for cid, _ := range containers { +@@ -611,7 +611,7 @@ func (daemon *Daemon) restore() error { + return nil + } + +-func (daemon *Daemon) cleanupLocalDB(db string) { ++func cleanupLocalDB(db string) { + _, err := os.Stat(db) + if err == nil { + err = os.Remove(db) +@@ -619,25 +619,25 @@ func (daemon *Daemon) cleanupLocalDB(db string) { + } + } + +-func (daemon *Daemon) CleanupContainerdDBs() { ++func CleanupContainerdDBs(run, root string) { + // check db lock is exist, do nothing if file is existed +- dbLockPath := filepath.Join(daemon.configStore.ExecRoot, "dblock") ++ dbLockPath := filepath.Join(run, "dblock") + _, err := os.Stat(dbLockPath) + if err == nil { + return + } + if !os.IsNotExist(err) { +- logrus.Errorf("stat dblock failed %v", err) ++ logrus.Errorf("stat DB dblock failed %v", err) + return + } +- daemon.cleanupLocalDB(filepath.Join(daemon.configStore.Root, "containerd/daemon/io.containerd.metadata.v1.bolt/meta.db")) ++ cleanupLocalDB(filepath.Join(root, "containerd/daemon/io.containerd.metadata.v1.bolt/meta.db")) + } + + // DB files may corrupted on exception poweroff but can be rebuild at run time, + // so we can remove DB files on OS starts avoid daemon can not startup. +-func (daemon *Daemon) cleanupLocalDBs() { ++func (daemon *Daemon) cleanupLocalDBs(run, root string) { + // check db lock is exist, do nothing if file is existed +- dbLockPath := filepath.Join(daemon.configStore.ExecRoot, "dblock") ++ dbLockPath := filepath.Join(run, "dblock") + _, err := os.Stat(dbLockPath) + if err == nil { + return +@@ -649,13 +649,13 @@ func (daemon *Daemon) cleanupLocalDBs() { + ioutil.WriteFile(dbLockPath, []byte{}, 0600) + + removeAllDB := func() { +- daemon.cleanupLocalDB(filepath.Join(daemon.configStore.Root, "builder/fscache.db")) +- daemon.cleanupLocalDB(filepath.Join(daemon.configStore.Root, "volumes/metadata.db")) +- daemon.cleanupLocalDB(filepath.Join(daemon.configStore.Root, "network/files/local-kv.db")) +- daemon.cleanupLocalDB(filepath.Join(daemon.configStore.Root, "accelerator/accel.db")) +- daemon.cleanupLocalDB(filepath.Join(daemon.configStore.Root, "buildkit/metadata.db")) +- daemon.cleanupLocalDB(filepath.Join(daemon.configStore.Root, "buildkit/cache.db")) +- daemon.cleanupLocalDB(filepath.Join(daemon.configStore.Root, "buildkit/snapshots.db")) ++ cleanupLocalDB(filepath.Join(root, "builder/fscache.db")) ++ cleanupLocalDB(filepath.Join(root, "volumes/metadata.db")) ++ cleanupLocalDB(filepath.Join(root, "network/files/local-kv.db")) ++ cleanupLocalDB(filepath.Join(root, "accelerator/accel.db")) ++ cleanupLocalDB(filepath.Join(root, "buildkit/metadata.db")) ++ cleanupLocalDB(filepath.Join(root, "buildkit/cache.db")) ++ cleanupLocalDB(filepath.Join(root, "buildkit/snapshots.db")) + } + + if daemon.containers == nil { +-- +2.17.1 + diff --git a/patch/0138-docker-add-loaded-time-to-images-inspect.patch b/patch/0138-docker-add-loaded-time-to-images-inspect.patch new file mode 100644 index 0000000..b7c58e0 --- /dev/null +++ b/patch/0138-docker-add-loaded-time-to-images-inspect.patch @@ -0,0 +1,149 @@ +From 90345160060de8422eeb8d82b5525b59e31d6a68 Mon Sep 17 00:00:00 2001 +From: yangfeiyu +Date: Mon, 8 Jul 2019 14:45:23 +0800 +Subject: [PATCH] docker: add loaded time to images inspect + +Signed-off-by: yangfeiyu2 + +--- + components/engine/api/types/types.go | 1 + + components/engine/daemon/images/image.go | 13 +++++++++++++ + components/engine/daemon/images/image_inspect.go | 1 + + components/engine/image/fs.go | 9 +++++++++ + components/engine/image/store.go | 5 +++++ + .../engine/integration-cli/docker_cli_save_load_test.go | 8 ++++++-- + 6 files changed, 35 insertions(+), 2 deletions(-) + +diff --git a/components/engine/api/types/types.go b/components/engine/api/types/types.go +index 55955f2..81586e5 100644 +--- a/components/engine/api/types/types.go ++++ b/components/engine/api/types/types.go +@@ -33,6 +33,7 @@ type ImageInspect struct { + Parent string + Comment string + Created string ++ Loaded string + Container string + ContainerConfig *container.Config + DockerVersion string +diff --git a/components/engine/daemon/images/image.go b/components/engine/daemon/images/image.go +index 79cc07c..1536076 100644 +--- a/components/engine/daemon/images/image.go ++++ b/components/engine/daemon/images/image.go +@@ -2,6 +2,8 @@ package images // import "github.com/docker/docker/daemon/images" + + import ( + "fmt" ++ "os" ++ "time" + + "github.com/docker/distribution/reference" + "github.com/docker/docker/errdefs" +@@ -24,6 +26,17 @@ func (e ErrImageDoesNotExist) Error() string { + // NotFound implements the NotFound interface + func (e ErrImageDoesNotExist) NotFound() {} + ++func (i *ImageService) GetImageLoadTime(id image.ID) string { ++ contentFile := i.imageStore.GetContentFile(id) ++ ++ fi, err := os.Stat(contentFile) ++ if err != nil { ++ return "" ++ } ++ ++ return fi.ModTime().Format(time.RFC3339Nano) ++} ++ + // GetImage returns an image corresponding to the image referred to by refOrID. + func (i *ImageService) GetImage(refOrID string) (*image.Image, error) { + ref, err := reference.ParseAnyReference(refOrID) +diff --git a/components/engine/daemon/images/image_inspect.go b/components/engine/daemon/images/image_inspect.go +index 16c4c9b..406cc8d 100644 +--- a/components/engine/daemon/images/image_inspect.go ++++ b/components/engine/daemon/images/image_inspect.go +@@ -70,6 +70,7 @@ func (i *ImageService) LookupImage(name string) (*types.ImageInspect, error) { + Parent: img.Parent.String(), + Comment: comment, + Created: img.Created.Format(time.RFC3339Nano), ++ Loaded: i.GetImageLoadTime(img.ID()), + Container: img.Container, + ContainerConfig: &img.ContainerConfig, + DockerVersion: img.DockerVersion, +diff --git a/components/engine/image/fs.go b/components/engine/image/fs.go +index 7080c8c..06999ea 100644 +--- a/components/engine/image/fs.go ++++ b/components/engine/image/fs.go +@@ -19,6 +19,7 @@ type DigestWalkFunc func(id digest.Digest) error + // StoreBackend provides interface for image.Store persistence + type StoreBackend interface { + Walk(f DigestWalkFunc) error ++ GetContentFile(id digest.Digest) string + Get(id digest.Digest) ([]byte, error) + Set(data []byte) (digest.Digest, error) + Delete(id digest.Digest) error +@@ -86,6 +87,14 @@ func (s *fs) Walk(f DigestWalkFunc) error { + return nil + } + ++// GetContentFile returns the content file path of specified image ++func (s *fs) GetContentFile(id digest.Digest) string { ++ s.RLock() ++ defer s.RUnlock() ++ ++ return s.contentFile(id) ++} ++ + // Get returns the content stored under a given digest. + func (s *fs) Get(dgst digest.Digest) ([]byte, error) { + s.RLock() +diff --git a/components/engine/image/store.go b/components/engine/image/store.go +index db75f06..b31cd4a 100644 +--- a/components/engine/image/store.go ++++ b/components/engine/image/store.go +@@ -19,6 +19,7 @@ import ( + type Store interface { + Create(config []byte) (ID, error) + Get(id ID) (*Image, error) ++ GetContentFile(id ID) string + GetAndCheck(id ID) (*Image, error) + Delete(id ID) ([]layer.Metadata, error) + Search(partialID string) (ID, error) +@@ -225,6 +226,10 @@ func (is *store) Get(id ID) (*Image, error) { + return img, nil + } + ++func (is *store) GetContentFile(id ID) string { ++ return is.fs.GetContentFile(id.Digest()) ++} ++ + func (is *store) GetAndCheck(id ID) (*Image, error) { + is.Lock() + if is.images[id] == nil { +diff --git a/components/engine/integration-cli/docker_cli_save_load_test.go b/components/engine/integration-cli/docker_cli_save_load_test.go +index 688eac6..5fbbfa0 100644 +--- a/components/engine/integration-cli/docker_cli_save_load_test.go ++++ b/components/engine/integration-cli/docker_cli_save_load_test.go +@@ -164,14 +164,18 @@ func (s *DockerSuite) TestSaveAndLoadRepoFlags(c *check.C) { + deleteImages(repoName) + dockerCmd(c, "commit", name, repoName) + +- before, _ := dockerCmd(c, "inspect", repoName) ++ before, _ := RunCommandPipelineWithOutput( ++ exec.Command(dockerBinary, "inspect", repoName), ++ exec.Command("grep", "-v", "Loaded")) + + out, err := RunCommandPipelineWithOutput( + exec.Command(dockerBinary, "save", repoName), + exec.Command(dockerBinary, "load")) + c.Assert(err, checker.IsNil, check.Commentf("failed to save and load repo: %s, %v", out, err)) + +- after, _ := dockerCmd(c, "inspect", repoName) ++ after, _ := RunCommandPipelineWithOutput( ++ exec.Command(dockerBinary, "inspect", repoName), ++ exec.Command("grep", "-v", "Loaded")) + c.Assert(before, checker.Equals, after, check.Commentf("inspect is not the same after a save / load")) + } + +-- +2.6.4.windows.1 + diff --git a/patch/0139-docker-printf-execid-when-task-exit.patch b/patch/0139-docker-printf-execid-when-task-exit.patch new file mode 100644 index 0000000..a027a80 --- /dev/null +++ b/patch/0139-docker-printf-execid-when-task-exit.patch @@ -0,0 +1,31 @@ +From da2bf67c793393f196effcf0f946e7d108448f4b Mon Sep 17 00:00:00 2001 +From: wujibin +Date: Mon, 22 Jul 2019 11:28:55 +0800 +Subject: [PATCH] docker: printf execid when task exit + +reason: distinguish execid and initid when task exit + +Change-Id: Iff89ab6463135a392560684c156e4dc9bf0150d5 +Signed-off-by: wujibin +--- + components/engine/libcontainerd/client_daemon.go | 1 + + 1 file changed, 1 insertion(+) + mode change 100644 => 100755 components/engine/libcontainerd/client_daemon.go + +diff --git a/components/engine/libcontainerd/client_daemon.go b/components/engine/libcontainerd/client_daemon.go +old mode 100644 +new mode 100755 +index 491bda281c..858d6429f1 +--- a/components/engine/libcontainerd/client_daemon.go ++++ b/components/engine/libcontainerd/client_daemon.go +@@ -828,6 +828,7 @@ func (c *client) processEventStream(ctx context.Context, ns string) { + c.logger.WithFields(logrus.Fields{ + "topic": ev.Topic, + "containerID": t.ContainerID, ++ "ProcessID": t.ID, + "Pid": t.Pid, + "ExitStatus": t.ExitStatus, + "ExitedAt": t.ExitedAt, +-- +2.19.0 + diff --git a/patch/0140-docker-add-timestamp-when-setup-iptables.patch b/patch/0140-docker-add-timestamp-when-setup-iptables.patch new file mode 100644 index 0000000..cfd4db6 --- /dev/null +++ b/patch/0140-docker-add-timestamp-when-setup-iptables.patch @@ -0,0 +1,33 @@ +From 7065246077ffc29990c5d58f3b8452169e3cb38a Mon Sep 17 00:00:00 2001 +From: wujibin +Date: Mon, 22 Jul 2019 16:25:25 +0800 +Subject: [PATCH] docker: add timestamp when setup iptables + +reason: show setup iptables begin and end timestamp for debug + +Change-Id: I1d3996f80aa187772bf7f1ae4ea9b3c9314e0b4e +Signed-off-by: wujibin +--- + .../docker/libnetwork/drivers/bridge/setup_ip_tables.go | 3 +++ + 1 file changed, 3 insertions(+) + mode change 100644 => 100755 components/engine/vendor/github.com/docker/libnetwork/drivers/bridge/setup_ip_tables.go + +diff --git a/components/engine/vendor/github.com/docker/libnetwork/drivers/bridge/setup_ip_tables.go b/components/engine/vendor/github.com/docker/libnetwork/drivers/bridge/setup_ip_tables.go +old mode 100644 +new mode 100755 +index 5865a18f18..d134857074 +--- a/components/engine/vendor/github.com/docker/libnetwork/drivers/bridge/setup_ip_tables.go ++++ b/components/engine/vendor/github.com/docker/libnetwork/drivers/bridge/setup_ip_tables.go +@@ -101,6 +101,9 @@ func (n *bridgeNetwork) setupIPTables(config *networkConfiguration, i *bridgeInt + driverConfig := d.config + d.Unlock() + ++ logrus.Info("Setup IP tables begin") ++ defer logrus.Info("Setup IP tables end") ++ + // Sanity check. + if driverConfig.EnableIPTables == false { + return errors.New("Cannot program chains, EnableIPTable is disabled") +-- +2.19.0 + diff --git a/patch/0141-docker-remove-logo-info.patch b/patch/0141-docker-remove-logo-info.patch new file mode 100644 index 0000000..aad52bf --- /dev/null +++ b/patch/0141-docker-remove-logo-info.patch @@ -0,0 +1,43 @@ +From 817a6a5d7f3bf91a4560deac23a656fa53c16e69 Mon Sep 17 00:00:00 2001 +From: lixiang172 +Date: Thu, 15 Aug 2019 20:24:58 +0800 +Subject: [PATCH] docker: remove logo info + +reason: remove logo info + +Change-Id: I1c0317a027ea277e03cdfafd3d8b85a4efbf3d0a +Signed-off-by: lixiang172 +--- + components/cli/scripts/build/.variables | 2 +- + components/engine/hack/make.sh | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/components/cli/scripts/build/.variables b/components/cli/scripts/build/.variables +index d504032..86d0e3a 100755 +--- a/components/cli/scripts/build/.variables ++++ b/components/cli/scripts/build/.variables +@@ -3,7 +3,7 @@ set -eu + + PLATFORM=${PLATFORM:-} + VERSION=${VERSION:-"unknown-version"} +-EULERVERSION=${EULERVERSION:-$(cat VERSION-EULER)} ++EULERVERSION=${EULERVERSION:-$(cat VERSION-openeuler)} + GITCOMMIT=${GITCOMMIT:-$(git rev-parse --short HEAD 2> /dev/null || true)} + BUILDTIME=${BUILDTIME:-$(date --utc --rfc-3339 ns 2> /dev/null | sed -e 's/ /T/')} + +diff --git a/components/engine/hack/make.sh b/components/engine/hack/make.sh +index fa87d91..686e688 100755 +--- a/components/engine/hack/make.sh ++++ b/components/engine/hack/make.sh +@@ -65,7 +65,7 @@ DEFAULT_BUNDLES=( + cross + ) + +-VERSION_EULER=$(< ./VERSION-EULER) ++VERSION_EULER=$(< ./VERSION-openeuler) + VERSION=${VERSION:-dev} + ! BUILDTIME=$(date -u -d "@${SOURCE_DATE_EPOCH:-$(date +%s)}" --rfc-3339 ns 2> /dev/null | sed -e 's/ /T/') + if [ "$DOCKER_GITCOMMIT" ]; then +-- +1.8.3.1 + diff --git a/patch/0142-docker-add-copyright.patch b/patch/0142-docker-add-copyright.patch new file mode 100644 index 0000000..23e6c3b --- /dev/null +++ b/patch/0142-docker-add-copyright.patch @@ -0,0 +1,79 @@ +From 24c82a1f5e930d34f28843478e03e877ede0ab11 Mon Sep 17 00:00:00 2001 +From: xiadanni +Date: Fri, 30 Aug 2019 02:19:15 +0800 +Subject: [PATCH] docker: add copyright + +reason: add copyright + +Change-Id: I26a1a140e2223646154c0980bc9ec4f0b212f764 +Signed-off-by: xiadanni +--- + components/cli/opts/hugetlb.go | 7 +++++++ + components/engine/pkg/sysinfo/utils_linux.go | 7 ++++++- + components/engine/pkg/trylock/mutex.go | 7 +++++++ + components/engine/utils/utils.go | 7 +++++++ + 4 files changed, 27 insertions(+), 1 deletion(-) + +diff --git a/components/cli/opts/hugetlb.go b/components/cli/opts/hugetlb.go +index 48cfeff..c90a00b 100644 +--- a/components/cli/opts/hugetlb.go ++++ b/components/cli/opts/hugetlb.go +@@ -1,3 +1,10 @@ ++/* ++Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. ++Description: hugetlb related common functions ++Author: zhaolongquan ++Create: 2019-06-12 ++*/ ++ + package opts + + import ( +diff --git a/components/engine/pkg/sysinfo/utils_linux.go b/components/engine/pkg/sysinfo/utils_linux.go +index 905d0b7..e380f7a 100644 +--- a/components/engine/pkg/sysinfo/utils_linux.go ++++ b/components/engine/pkg/sysinfo/utils_linux.go +@@ -1,4 +1,9 @@ +-// +build linux ++/* ++Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. ++Description: hugetlb related common functions ++Author: zhaolongquan ++Create: 2019-06-12 ++*/ + + package sysinfo + +diff --git a/components/engine/pkg/trylock/mutex.go b/components/engine/pkg/trylock/mutex.go +index 39fb888..63fba74 100644 +--- a/components/engine/pkg/trylock/mutex.go ++++ b/components/engine/pkg/trylock/mutex.go +@@ -1,3 +1,10 @@ ++/* ++Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. ++Description: mutex related common functions ++Author: lujingxiao ++Create: 2019-01-19 ++*/ ++ + package trylock + + import ( +diff --git a/components/engine/utils/utils.go b/components/engine/utils/utils.go +index 75fd409..5363a76 100644 +--- a/components/engine/utils/utils.go ++++ b/components/engine/utils/utils.go +@@ -1,3 +1,10 @@ ++/* ++Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. ++Description: common functions ++Author: lujingxiao ++Create: 2019-01-19 ++*/ ++ + package utils + + /* +-- +1.8.3.1 + diff --git a/patch/0143-docker-add-license.patch b/patch/0143-docker-add-license.patch new file mode 100644 index 0000000..ec876cc --- /dev/null +++ b/patch/0143-docker-add-license.patch @@ -0,0 +1,71 @@ +From c6c599d5101ceac3c1ec38aa1b615f6009f4ca19 Mon Sep 17 00:00:00 2001 +From: xiadanni1 +Date: Sat, 7 Sep 2019 00:54:32 +0800 +Subject: [PATCH] docker: add license + +reason: add license + +Change-Id: I82f16e0664010fe4789c5e7ff0daa3460042a341 +Signed-off-by: xiadanni1 +--- + components/cli/opts/hugetlb.go | 4 +++- + components/engine/pkg/sysinfo/utils_linux.go | 4 +++- + components/engine/pkg/trylock/mutex.go | 4 +++- + components/engine/utils/utils.go | 4 +++- + 4 files changed, 12 insertions(+), 4 deletions(-) + +diff --git a/components/cli/opts/hugetlb.go b/components/cli/opts/hugetlb.go +index c90a00b..68b771a 100644 +--- a/components/cli/opts/hugetlb.go ++++ b/components/cli/opts/hugetlb.go +@@ -1,5 +1,7 @@ + /* +-Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. ++Copyright (c) Huawei Technologies Co., Ltd. 2019. All rights reserved. ++Use of this source code is governed by Apache-2.0 ++license that can be found in the LICENSE file. + Description: hugetlb related common functions + Author: zhaolongquan + Create: 2019-06-12 +diff --git a/components/engine/pkg/sysinfo/utils_linux.go b/components/engine/pkg/sysinfo/utils_linux.go +index e380f7a..fc8f023 100644 +--- a/components/engine/pkg/sysinfo/utils_linux.go ++++ b/components/engine/pkg/sysinfo/utils_linux.go +@@ -1,5 +1,7 @@ + /* +-Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. ++Copyright (c) Huawei Technologies Co., Ltd. 2019. All rights reserved. ++Use of this source code is governed by Apache-2.0 ++license that can be found in the LICENSE file. + Description: hugetlb related common functions + Author: zhaolongquan + Create: 2019-06-12 +diff --git a/components/engine/pkg/trylock/mutex.go b/components/engine/pkg/trylock/mutex.go +index 63fba74..5069df8 100644 +--- a/components/engine/pkg/trylock/mutex.go ++++ b/components/engine/pkg/trylock/mutex.go +@@ -1,5 +1,7 @@ + /* +-Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. ++Copyright (c) Huawei Technologies Co., Ltd. 2019. All rights reserved. ++Use of this source code is governed by Apache-2.0 ++license that can be found in the LICENSE file. + Description: mutex related common functions + Author: lujingxiao + Create: 2019-01-19 +diff --git a/components/engine/utils/utils.go b/components/engine/utils/utils.go +index 5363a76..53893fc 100644 +--- a/components/engine/utils/utils.go ++++ b/components/engine/utils/utils.go +@@ -1,5 +1,7 @@ + /* +-Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. ++Copyright (c) Huawei Technologies Co., Ltd. 2019. All rights reserved. ++Use of this source code is governed by Apache-2.0 ++license that can be found in the LICENSE file. + Description: common functions + Author: lujingxiao + Create: 2019-01-19 +-- +1.8.3.1 + diff --git a/patch/0144-docker-hide-some-path-in-container.patch b/patch/0144-docker-hide-some-path-in-container.patch new file mode 100644 index 0000000..33b4e13 --- /dev/null +++ b/patch/0144-docker-hide-some-path-in-container.patch @@ -0,0 +1,62 @@ +From 80aa68c35d6199d80a136f480ac8a7fa9ef9146d Mon Sep 17 00:00:00 2001 +From: lixiang +Date: Tue, 17 Sep 2019 22:58:07 +0800 +Subject: [PATCH] docker: mask some path in contianer + +reason: for security reason, we masked the following path to prevent +hackers obtaining the access to the important path on host os. +/proc/cpuirqstat +/proc/memstat +/proc/iomem_ext +/proc/livepatch +/proc/net_namespace + +Change-Id: I394f75e8a277983ad94018e8d51bbddda0f357d7 +Signed-off-by: lixiang +--- + components/engine/oci/defaults.go | 23 ++++++++++++++--------- + 1 file changed, 14 insertions(+), 9 deletions(-) + +diff --git a/components/engine/oci/defaults.go b/components/engine/oci/defaults.go +index 74d3fdb..cd4985f 100644 +--- a/components/engine/oci/defaults.go ++++ b/components/engine/oci/defaults.go +@@ -117,21 +117,26 @@ func DefaultLinuxSpec() specs.Spec { + MaskedPaths: []string{ + "/proc/acpi", + "/proc/config.gz", ++ "/proc/cpuirqstat", ++ "/proc/fdenable", ++ "/proc/fdstat", ++ "/proc/fdthreshold", ++ "/proc/files_panic_enable", ++ "/proc/iomem_ext", ++ "/proc/kbox", + "/proc/kcore", + "/proc/keys", + "/proc/latency_stats", +- "/proc/timer_list", +- "/proc/timer_stats", ++ "/proc/livepatch", ++ "/proc/memstat", ++ "/proc/net_namespace", ++ "/proc/oom_extend", + "/proc/sched_debug", + "/proc/scsi", +- "/proc/files_panic_enable", +- "/proc/fdthreshold", +- "/proc/fdstat", +- "/proc/fdenable", +- "/proc/signo", + "/proc/sig_catch", +- "/proc/kbox", +- "/proc/oom_extend", ++ "/proc/signo", ++ "/proc/timer_list", ++ "/proc/timer_stats", + "/sys/firmware", + }, + ReadonlyPaths: []string{ +-- +1.8.3.1 + diff --git a/series.conf b/series.conf new file mode 100644 index 0000000..8bb8dcb --- /dev/null +++ b/series.conf @@ -0,0 +1,132 @@ +patch/0001-pause-move-pause-function-to-docker.patch +patch/0002-pause-docker-pause-set-timeout-30s.patch +patch/0004-prjquota-support-set-filesystem-quot.patch +patch/0010-annotation-add-annotation-into-cli-flag.patch +patch/0024-runtime-spec-Compatibility-modifications-fo.patch +patch/0026-prjquota-use-dockerd-quota-size-when-docker.patch +patch/0067-pause-fix-build-missing-dep-packages.patch +patch/0090-overlay2-Use-index-off-if-possible.patch +patch/0091-overlay2-use-global-logger-instance.patch +patch/0126-docker-pass-root-to-chroot-to-for-chroot-ta.patch +patch/0127-docker-support-docker-cli-using-syslog.patch + + +patch/0003-pause-fix-integration-testing-faile-about-doc.patch +patch/0005-prjquota-fix-few-overlay2-quota-problems.patch +patch/0006-prjquota-overlay2-quota-control-backward-comp.patch +patch/0007-filelimit-Add-file-fds-limit.patch +patch/0008-filelimit-Ignore-host-kernel-whether-suport-f.patch +patch/0009-healthycheck-add-healthycheck-in-_ping-add-se.patch +patch/0011-hookspec-Allow-adding-custom-hooks.patch +patch/0012-hookspec-Security-enhancement-for-hooks.patch +patch/0013-hookspec-Add-bash-completion-for-hook-spec.patch +patch/0014-hookspec-Add-default-hooks-for-all-containers.patch +patch/0015-hookspec-add-limit-of-hook-spec-file.patch +patch/0016-hookspec-fix-hooks-nil-pointer-dereference.patch +patch/0017-hookspec-canonicalize-hook-path-before-valida.patch +patch/0018-dfx-trylock-add-trylock-and-trylocktimeout-fo.patch +patch/0019-dfx-print-container-name-and-id-when-create-d.patch +patch/0020-cleanup-remove-redundant-files-in-graphdriver.patch +patch/0021-umask-support-specify-umask.patch +patch/0022-umask-fix-nil-pointer-on-c.Annotations-in-set.patch +patch/0023-prjquota-fix-syscall-bugs-in-projectquota.patch +patch/0025-resource-limit-enable-unlimited-usage-of-memo.patch +patch/0027-oci-add-files_panic_enable-to-masked-path.patch +patch/0028-oci-add-fdenable-fdstat-and-fdthreshold-to-ma.patch +patch/0029-oci-add-oom_extend-to-proc-masked-path.patch +patch/0030-restart-fix-docker-stats-blocked-while-docker.patch +patch/0031-restart-Incase-deadlock-when-kill-the-docker-.patch +patch/0032-devmapper-Add-udev-event-time-out-to-fix-dock.patch +patch/0033-devmapper-Fix-devicemapper-issue-power-off-th.patch +patch/0035-restart-reject-to-restart-container-when-remo.patch +patch/0036-restart-do-not-reset-container-restartCount-o.patch +patch/0037-config-Add-liver-restore-to-OPTIION-to-enable.patch +patch/0038-devmapper-devicemapper-ignore-error-when-remo.patch +patch/0039-restart-Remove-redundant-Mounts-when-start-do.patch +patch/0040-devmapper-devicemapper-add-API-GetDeviceList.patch +patch/0041-devmapper-devmapper-remove-broken-device-when.patch +patch/0042-oci-Cannot-join-own-pid-ipc-namespace.patch +patch/0043-docker-Make-sure-the-pid-exist-in-pidfile-is-.patch +patch/0044-plugin-Fix-plugin-security-bug-caused-by-unch.patch +patch/0045-overlay-safely-remove-overlay-layer-directory.patch +patch/0046-debug-add-more-error-info-when-dial-docker.so.patch +patch/0047-docker-fix-panic-slice-bounds-out-of-range.patch +patch/0048-docker-check-metadata-when-load-layer.patch +patch/0049-cleanup-cleanup-useless-netns-file-in-var-run.patch +patch/0050-volume-Make-v.opts-to-nil-if-opts.json-is-nul.patch +patch/0051-docker-fix-panic-when-load-maliciously-image.patch +patch/0052-docker-Lock-the-RWLayer-while-committing-expo.patch +patch/0053-docker-remove-init-layer-when-no-space-to-cre.patch +patch/0054-docker-remove-init-layer-if-fails-in-initMoun.patch +patch/0055-docker-range-checking-for-memory-and-memory.s.patch +patch/0056-docker-check-cpuset.cpu-and-cpuset.mem.patch +patch/0057-docker-Ignore-ToDisk-error-in-StateChanged.patch +patch/0058-cleanup-cleanup-incompleted-device-while-dock.patch +patch/0059-overlay2-avoid-middle-state-while-removing-im.patch +patch/0060-debug-Add-check-when-execute-docker-cp-export.patch +patch/0061-docker-check-seccomp-file-size-max-10M-before.patch +patch/0062-docker-check-file-size-before-reading-envfile.patch +patch/0063-test-fix-umask-make-file-mode-failed.patch +patch/0066-test-skip-swarm-integration-test.patch +patch/0068-pause-fix-docker-stop-stuck-on-paused-contain.patch +patch/0069-pause-fix-log-pause-unpause-event-twice.patch +patch/0070-test-fix-umask-make-syscall-test-failed.patch +patch/0071-devmapper-Increace-udev-wait-timeout-to-185s.patch +patch/0072-pause-fix-test-can-not-stop-paused-container-.patch +patch/0073-service-update-docker.service-with-old-revisi.patch +patch/0076-version-add-EulerVersion.patch +patch/0077-image-fix-file-not-exist-for-check-file-size.patch +patch/0078-spec-add-missing-sysconfigs-to-rpm.patch +patch/0079-rwlayer-fix-deadlock-for-docker-commit-export.patch +patch/0081-runtime-spec-revert-the-modify-of-runtime-spe.patch +patch/0083-test-fix-start-paused-container.patch +patch/0084-test-skip-pause-test-with-ctr.patch +patch/0085-event-log-detailed-event-with-info-level.patch +patch/0086-restart-fix-daemon-restart-with-restart-alway.patch +patch/0088-attach-add-timeout-return-for-getExitStatus.patch +patch/0089-libcontainerd-fix-stuck-when-containerd-in-T-.patch +patch/0092-Revert-docker-Lock-the-RWLayer-while-committi.patch +patch/0093-docker-do-not-try-to-connect-containerd-if-co.patch +patch/0094-docker-change-health-check-minum-param-to-one.patch +patch/0095-docker-enable-container-health-check-after-re.patch +patch/0096-pause-check-tasks-before-updateCgroups.patch +patch/0097-restart-fix-restart-unless-stopped-not-stop-f.patch +patch/0099-integration-cli-fix-TestInspectAPIImageRespon.patch +patch/0100-proquota-fix-quota-basesize.patch +patch/0101-daeamon-add-judge-for-client-in-case-of-panic.patch +patch/0102-docker-18.09-fix-docker-stop-error-if-docker-.patch +patch/0103-docker-fix-parsing-name-with.patch +patch/0104-docker-stats-increase-the-timeout-of-docker-s.patch +patch/0105-pause-fix-pause-on-exited-container.patch +patch/0107-restore-cleanup-container-meta-data-when-task.patch +patch/0108-docker-Fix-can-t-run-image-while-the-image-is.patch +patch/0109-graphdriver-add-Checkparent-method.patch +patch/0110-docker-Fix-can-t-pull-image-while-the-image-i.patch +patch/0112-docker-overlay2-quota-control-bugfix.patch +patch/0114-docker-mask-internal-proc-files.patch +patch/0115-docker-enable-bep-ldflags.patch +patch/0116-docker-build-with-relro-flags.patch +patch/0117-healthcheck-synchronize-the-healthcheck-statu.patch +patch/0118-docker-fix-opened-file-not-close.patch +patch/0119-docker-set-makefile-buildid.patch +patch/0120-docker-fix-docker-logs-hangs-when-using-journ.patch +patch/0121-docker-add-start-timeout-for-container.patch +patch/0122-cleanup-local-db-on-first-start-after-os-star.patch +patch/0123-docker-support-exit-on-unhealthy.patch +patch/0124-docker-Support-compress-when-saving-images.patch +patch/0125-docker-add-hugetlb-limit-option-to-docker.patch +patch/0128-docker-add-log-forwarding-mechanism-and-print.patch +patch/0129-docker-check-if-image-exists-in-memory-when-p.patch +patch/0130-docker-mask-internal-proc-add-livepatch.patch +patch/0132-docker-fix-docker-pull-406-error-on-some-regi.patch +patch/0133-docker-Fixed-docker-ps-and-docker-inspect-sta.patch +patch/0135-docker-delete-containerd-db-first-reboot.patch +patch/0136-docker-continue-to-check-healthy-instead-of-return.patch +patch/0137-docker-fix-reboot-dirty-data-in-containerd.patch +patch/0138-docker-add-loaded-time-to-images-inspect.patch +patch/0139-docker-printf-execid-when-task-exit.patch +patch/0140-docker-add-timestamp-when-setup-iptables.patch +patch/0141-docker-remove-logo-info.patch +patch/0142-docker-add-copyright.patch +patch/0143-docker-add-license.patch +patch/0144-docker-hide-some-path-in-container.patch