diff options
-rw-r--r-- | .cirrus.yml | 14 | ||||
-rw-r--r-- | docs/podman-create.1.md | 2 | ||||
-rw-r--r-- | docs/podman-run.1.md | 2 | ||||
-rw-r--r-- | libpod/container_internal_linux.go | 1 | ||||
-rw-r--r-- | pkg/cgroups/blkio.go | 2 | ||||
-rw-r--r-- | pkg/cgroups/cgroups.go | 41 | ||||
-rw-r--r-- | pkg/cgroups/cpu.go | 2 | ||||
-rw-r--r-- | pkg/cgroups/cpuset.go | 3 | ||||
-rw-r--r-- | pkg/cgroups/memory.go | 3 | ||||
-rw-r--r-- | pkg/cgroups/pids.go | 3 | ||||
-rw-r--r-- | pkg/spec/spec.go | 20 | ||||
-rw-r--r-- | pkg/spec/spec_linux.go | 42 | ||||
-rw-r--r-- | pkg/spec/spec_unsupported.go | 7 | ||||
-rw-r--r-- | test/README.md | 23 | ||||
-rw-r--r-- | test/e2e/pod_rm_test.go | 17 | ||||
-rw-r--r-- | test/e2e/run_test.go | 21 |
16 files changed, 181 insertions, 22 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index dac41dc5f..da28bb597 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -215,7 +215,8 @@ build_each_commit_task: on_failure: failed_master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh' -build_without_cgo: + +build_without_cgo_task: depends_on: - "gating" @@ -475,17 +476,18 @@ verify_test_built_images_task: always: <<: *standardlogs - -# Post message to IRC if everything passed +# Post message to IRC if everything passed PR testing success_task: only_if: $CIRRUS_BRANCH != 'master' - depends_on: # ignores any dependent task conditions + # ignores any dependent task conditions, include everything except 'release' + depends_on: &alltasks - "gating" - "vendor" - "varlink_api" - "build_each_commit" + - "build_without_cgo" - "meta" - "testing" - "special_testing_rootless" @@ -493,7 +495,6 @@ success_task: - "special_testing_cross" - "test_build_cache_images" - "verify_test_built_images" - - "build_without_cgo" env: CIRRUS_WORKING_DIR: "/usr/src/libpod" @@ -514,8 +515,7 @@ release_task: # allow_failures: $CI == "true" # skip_notifications: $CI == "true" - depends_on: - - "success" + depends_on: *alltasks gce_instance: image_name: "${IMAGE_BUILDER_CACHE_IMAGE_NAME}" diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md index 9cf3e038d..a34111a03 100644 --- a/docs/podman-create.1.md +++ b/docs/podman-create.1.md @@ -723,6 +723,8 @@ The following example maps uids 0-2000 in the container to the uids 30000-31999 Ulimit options +You can pass `host` to copy the current configuration from the host. + **--user**, **-u**=*user* Sets the username or UID used and optionally the groupname or GID for the specified command. diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index 4889e5755..86cc2125c 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -759,6 +759,8 @@ The example maps uids 0-2000 in the container to the uids 30000-31999 on the hos Ulimit options +You can pass `host` to copy the current configuration from the host. + **--user**, **-u**=*user* Sets the username or UID used and optionally the groupname or GID for the specified command. diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 686a595de..aa477611f 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -636,6 +636,7 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO } } + c.state.FinishedTime = time.Now() return c.save() } diff --git a/pkg/cgroups/blkio.go b/pkg/cgroups/blkio.go index 9c2a811d9..bacd4eb93 100644 --- a/pkg/cgroups/blkio.go +++ b/pkg/cgroups/blkio.go @@ -37,7 +37,7 @@ func (c *blkioHandler) Create(ctr *CgroupControl) (bool, error) { // Destroy the cgroup func (c *blkioHandler) Destroy(ctr *CgroupControl) error { - return os.Remove(ctr.getCgroupv1Path(Blkio)) + return rmDirRecursively(ctr.getCgroupv1Path(Blkio)) } // Stat fills a metrics structure with usage stats for the controller diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go index 1dad45d7f..081db772f 100644 --- a/pkg/cgroups/cgroups.go +++ b/pkg/cgroups/cgroups.go @@ -328,6 +328,13 @@ func Load(path string) (*CgroupControl, error) { systemd: false, } if !cgroup2 { + controllers, err := getAvailableControllers(handlers, false) + if err != nil { + return nil, err + } + control.additionalControllers = controllers + } + if !cgroup2 { for name := range handlers { p := control.getCgroupv1Path(name) if _, err := os.Stat(p); err != nil { @@ -355,11 +362,40 @@ func (c *CgroupControl) Delete() error { return c.DeleteByPath(c.path) } +// rmDirRecursively delete recursively a cgroup directory. +// It differs from os.RemoveAll as it doesn't attempt to unlink files. +// On cgroupfs we are allowed only to rmdir empty directories. +func rmDirRecursively(path string) error { + if err := os.Remove(path); err == nil || os.IsNotExist(err) { + return nil + } + entries, err := ioutil.ReadDir(path) + if err != nil { + return errors.Wrapf(err, "read %s", path) + } + for _, i := range entries { + if i.IsDir() { + if err := rmDirRecursively(filepath.Join(path, i.Name())); err != nil { + return err + } + } + } + if os.Remove(path); err != nil { + if !os.IsNotExist(err) { + return errors.Wrapf(err, "remove %s", path) + } + } + return nil +} + // DeleteByPath deletes the specified cgroup path func (c *CgroupControl) DeleteByPath(path string) error { if c.systemd { return systemdDestroy(path) } + if c.cgroup2 { + return rmDirRecursively(filepath.Join(cgroupRoot, c.path)) + } var lastError error for _, h := range handlers { if err := h.Destroy(c); err != nil { @@ -368,8 +404,11 @@ func (c *CgroupControl) DeleteByPath(path string) error { } for _, ctr := range c.additionalControllers { + if ctr.symlink { + continue + } p := c.getCgroupv1Path(ctr.name) - if err := os.Remove(p); err != nil { + if err := rmDirRecursively(p); err != nil { lastError = errors.Wrapf(err, "remove %s", p) } } diff --git a/pkg/cgroups/cpu.go b/pkg/cgroups/cpu.go index 1c8610cc4..03677f1ef 100644 --- a/pkg/cgroups/cpu.go +++ b/pkg/cgroups/cpu.go @@ -68,7 +68,7 @@ func (c *cpuHandler) Create(ctr *CgroupControl) (bool, error) { // Destroy the cgroup func (c *cpuHandler) Destroy(ctr *CgroupControl) error { - return os.Remove(ctr.getCgroupv1Path(CPU)) + return rmDirRecursively(ctr.getCgroupv1Path(CPU)) } // Stat fills a metrics structure with usage stats for the controller diff --git a/pkg/cgroups/cpuset.go b/pkg/cgroups/cpuset.go index 25d2f7f76..46d0484f2 100644 --- a/pkg/cgroups/cpuset.go +++ b/pkg/cgroups/cpuset.go @@ -3,7 +3,6 @@ package cgroups import ( "fmt" "io/ioutil" - "os" "path/filepath" "strings" @@ -77,7 +76,7 @@ func (c *cpusetHandler) Create(ctr *CgroupControl) (bool, error) { // Destroy the cgroup func (c *cpusetHandler) Destroy(ctr *CgroupControl) error { - return os.Remove(ctr.getCgroupv1Path(CPUset)) + return rmDirRecursively(ctr.getCgroupv1Path(CPUset)) } // Stat fills a metrics structure with usage stats for the controller diff --git a/pkg/cgroups/memory.go b/pkg/cgroups/memory.go index 80e88d17c..b3991f7e3 100644 --- a/pkg/cgroups/memory.go +++ b/pkg/cgroups/memory.go @@ -2,7 +2,6 @@ package cgroups import ( "fmt" - "os" "path/filepath" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -33,7 +32,7 @@ func (c *memHandler) Create(ctr *CgroupControl) (bool, error) { // Destroy the cgroup func (c *memHandler) Destroy(ctr *CgroupControl) error { - return os.Remove(ctr.getCgroupv1Path(Memory)) + return rmDirRecursively(ctr.getCgroupv1Path(Memory)) } // Stat fills a metrics structure with usage stats for the controller diff --git a/pkg/cgroups/pids.go b/pkg/cgroups/pids.go index ffbde100d..65b9b5b34 100644 --- a/pkg/cgroups/pids.go +++ b/pkg/cgroups/pids.go @@ -3,7 +3,6 @@ package cgroups import ( "fmt" "io/ioutil" - "os" "path/filepath" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -40,7 +39,7 @@ func (c *pidHandler) Create(ctr *CgroupControl) (bool, error) { // Destroy the cgroup func (c *pidHandler) Destroy(ctr *CgroupControl) error { - return os.Remove(ctr.getCgroupv1Path(Pids)) + return rmDirRecursively(ctr.getCgroupv1Path(Pids)) } // Stat fills a metrics structure with usage stats for the controller diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index 5cc021bf5..d44beb3e4 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -20,6 +20,12 @@ import ( const cpuPeriod = 100000 +type systemUlimit struct { + name string + max uint64 + cur uint64 +} + func getAvailableGids() (int64, error) { idMap, err := user.ParseIDMapFile("/proc/self/gid_map") if err != nil { @@ -557,6 +563,20 @@ func addRlimits(config *CreateConfig, g *generate.Generator) error { ) for _, u := range config.Resources.Ulimit { + if u == "host" { + if len(config.Resources.Ulimit) != 1 { + return errors.New("ulimit can use host only once") + } + hostLimits, err := getHostRlimits() + if err != nil { + return err + } + for _, i := range hostLimits { + g.AddProcessRlimits(i.name, i.max, i.cur) + } + break + } + ul, err := units.ParseUlimit(u) if err != nil { return errors.Wrapf(err, "ulimit option %q requires name=SOFT:HARD, failed to be parsed", u) diff --git a/pkg/spec/spec_linux.go b/pkg/spec/spec_linux.go new file mode 100644 index 000000000..fcdfc5c4e --- /dev/null +++ b/pkg/spec/spec_linux.go @@ -0,0 +1,42 @@ +//+build linux + +package createconfig + +import ( + "syscall" + + "github.com/pkg/errors" +) + +type systemRlimit struct { + name string + value int +} + +var systemLimits = []systemRlimit{ + {"RLIMIT_AS", syscall.RLIMIT_AS}, + {"RLIMIT_CORE", syscall.RLIMIT_CORE}, + {"RLIMIT_CPU", syscall.RLIMIT_CPU}, + {"RLIMIT_DATA", syscall.RLIMIT_DATA}, + {"RLIMIT_FSIZE", syscall.RLIMIT_FSIZE}, + {"RLIMIT_NOFILE", syscall.RLIMIT_NOFILE}, + {"RLIMIT_STACK", syscall.RLIMIT_STACK}, +} + +func getHostRlimits() ([]systemUlimit, error) { + ret := []systemUlimit{} + for _, i := range systemLimits { + var l syscall.Rlimit + if err := syscall.Getrlimit(i.value, &l); err != nil { + return nil, errors.Wrapf(err, "cannot read limits for %s", i.name) + } + s := systemUlimit{ + name: i.name, + max: l.Max, + cur: l.Cur, + } + ret = append(ret, s) + } + return ret, nil + +} diff --git a/pkg/spec/spec_unsupported.go b/pkg/spec/spec_unsupported.go new file mode 100644 index 000000000..0f6a9acdc --- /dev/null +++ b/pkg/spec/spec_unsupported.go @@ -0,0 +1,7 @@ +//+build !linux + +package createconfig + +func getHostRlimits() ([]systemUlimit, error) { + return nil, nil +} diff --git a/test/README.md b/test/README.md index 4e61a0774..9bea679dc 100644 --- a/test/README.md +++ b/test/README.md @@ -110,19 +110,30 @@ make shell This will run a container and give you a shell and you can follow the instructions above. -# System test +# System tests System tests are used for testing the *podman* CLI in the context of a complete system. It requires that *podman*, all dependencies, and configurations are in place. The intention of system testing is to match as closely as possible with real-world user/developer use-cases and environments. The orchestration of the environments and tests is left to external tooling. -* `PodmanTestSystem`: System test *struct* as a composite of `PodmanTest`. It will not add any -options to the command by default. When you run system test, you can set GLOBALOPTIONS, -PODMAN_SUBCMD_OPTIONS or PODMAN_BINARY in ENV to run the test suite for different test matrices. +System tests use Bash Automated Testing System (`bats`) as a testing framework. +Install it via your package manager or get latest stable version +[directly from the repository](https://github.com/bats-core/bats-core), e.g.: -## Run system test -You can run the test with following command: +``` +mkdir -p ~/tools/bats +git clone --single-branch --branch v1.1.0 https://github.com/bats-core/bats-core.git ~/tools/bats +``` + +Make sure that `bats` binary (`bin/bats` in the repository) is in your `PATH`, if not - add it: + +``` +PATH=$PATH:~/tools/bats/bin +``` + +## Running system tests +When `bats` is installed and is in your `PATH`, you can run the test suite with following command: ``` make localsystem diff --git a/test/e2e/pod_rm_test.go b/test/e2e/pod_rm_test.go index 0d3f47f30..f0689f152 100644 --- a/test/e2e/pod_rm_test.go +++ b/test/e2e/pod_rm_test.go @@ -3,6 +3,8 @@ package integration import ( "fmt" "os" + "path/filepath" + "strings" . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" @@ -40,6 +42,21 @@ var _ = Describe("Podman pod rm", func() { result := podmanTest.Podman([]string{"pod", "rm", podid}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) + + // Also check that we don't leak cgroups + err := filepath.Walk("/sys/fs/cgroup", func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() { + Expect(err).To(BeNil()) + } + if strings.Contains(info.Name(), podid) { + return fmt.Errorf("leaking cgroup path %s", path) + } + return nil + }) + Expect(err).To(BeNil()) }) It("podman pod rm latest pod", func() { diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 3fc628589..f95c5298d 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -8,7 +8,9 @@ import ( "net" "os" "path/filepath" + "strconv" "strings" + "syscall" "time" . "github.com/containers/libpod/test/utils" @@ -250,6 +252,25 @@ var _ = Describe("Podman run", func() { Expect(session.OutputToString()).To(ContainSubstring("100")) }) + It("podman run limits host test", func() { + SkipIfRemote() + + var l syscall.Rlimit + + err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &l) + Expect(err).To(BeNil()) + + session := podmanTest.Podman([]string{"run", "--rm", "--ulimit", "host", fedoraMinimal, "ulimit", "-Hn"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + ulimitCtrStr := strings.TrimSpace(session.OutputToString()) + ulimitCtr, err := strconv.ParseUint(ulimitCtrStr, 10, 0) + Expect(err).To(BeNil()) + + Expect(ulimitCtr).Should(BeNumerically(">=", l.Max)) + }) + It("podman run with cidfile", func() { session := podmanTest.Podman([]string{"run", "--cidfile", tempdir + "cidfile", ALPINE, "ls"}) session.WaitWithDefaultTimeout() |