diff options
-rw-r--r-- | libpod/networking_linux.go | 11 | ||||
-rw-r--r-- | pkg/domain/infra/abi/play.go | 36 | ||||
-rw-r--r-- | test/e2e/common_test.go | 9 | ||||
-rw-r--r-- | test/e2e/config.go | 1 | ||||
-rw-r--r-- | test/e2e/network_test.go | 37 | ||||
-rw-r--r-- | test/e2e/play_kube_test.go | 131 | ||||
-rw-r--r-- | test/e2e/toolbox_test.go | 368 |
7 files changed, 589 insertions, 4 deletions
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index d16bdc973..f87c311ce 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -828,6 +828,17 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e // We can't do more if the network is down. if c.state.NetNS == nil { + // We still want to make dummy configurations for each CNI net + // the container joined. + if len(c.config.Networks) > 0 { + settings.Networks = make(map[string]*define.InspectAdditionalNetwork, len(c.config.Networks)) + for _, net := range c.config.Networks { + cniNet := new(define.InspectAdditionalNetwork) + cniNet.NetworkID = net + settings.Networks[net] = cniNet + } + } + return settings, nil } diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index 2de98d8f5..a7c66bae6 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -28,6 +28,7 @@ import ( "github.com/sirupsen/logrus" v1apps "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" ) const ( @@ -35,6 +36,8 @@ const ( kubeDirectoryPermission = 0755 // https://kubernetes.io/docs/concepts/storage/volumes/#hostpath kubeFilePermission = 0644 + // Kubernetes sets CPUPeriod to 100000us (100ms): https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/ + defaultCPUPeriod = 100000 ) func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options entities.PlayKubeOptions) (*entities.PlayKubeReport, error) { @@ -506,6 +509,27 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container // but apply to the containers with the prefixed name securityConfig.SeccompProfilePath = seccompPaths.findForContainer(containerYAML.Name) + var err error + milliCPU, err := quantityToInt64(containerYAML.Resources.Limits.Cpu()) + if err != nil { + return nil, errors.Wrap(err, "Failed to set CPU quota") + } + if milliCPU > 0 { + containerConfig.Resources.CPUPeriod = defaultCPUPeriod + // CPU quota is a fraction of the period: milliCPU / 1000.0 * period + // Or, without floating point math: + containerConfig.Resources.CPUQuota = milliCPU * defaultCPUPeriod / 1000 + } + + containerConfig.Resources.Memory, err = quantityToInt64(containerYAML.Resources.Limits.Memory()) + if err != nil { + return nil, errors.Wrap(err, "Failed to set memory limit") + } + containerConfig.Resources.MemoryReservation, err = quantityToInt64(containerYAML.Resources.Requests.Memory()) + if err != nil { + return nil, errors.Wrap(err, "Failed to set memory reservation") + } + containerConfig.Command = []string{} if imageData != nil && imageData.Config != nil { containerConfig.Command = imageData.Config.Entrypoint @@ -748,3 +772,15 @@ func verifySeccompPath(path string, profileRoot string) (string, error) { return "", errors.Errorf("invalid seccomp path: %s", path) } } + +func quantityToInt64(quantity *resource.Quantity) (int64, error) { + if i, ok := quantity.AsInt64(); ok { + return i, nil + } + + if i, ok := quantity.AsDec().Unscaled(); ok { + return i, nil + } + + return 0, errors.Errorf("Quantity cannot be represented as int64: %v", quantity) +} diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go index ec910109b..e36c86690 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/common_test.go @@ -612,6 +612,15 @@ func SkipIfRootlessCgroupsV1(reason string) { } } +func SkipIfUnprevilegedCPULimits() { + info := GetHostDistributionInfo() + if isRootless() && + info.Distribution == "fedora" && + (info.Version == "31" || info.Version == "32") { + ginkgo.Skip("Rootless Fedora doesn't have permission to set CPU limits before version 33") + } +} + func SkipIfRootless(reason string) { checkReason(reason) if os.Geteuid() != 0 { diff --git a/test/e2e/config.go b/test/e2e/config.go index 49a47c7da..54e39f9d2 100644 --- a/test/e2e/config.go +++ b/test/e2e/config.go @@ -14,6 +14,7 @@ var ( BB = "docker.io/library/busybox:latest" healthcheck = "docker.io/libpod/alpine_healthcheck:latest" ImageCacheDir = "/tmp/podman/imagecachedir" + fedoraToolbox = "registry.fedoraproject.org/f32/fedora-toolbox:latest" // This image has seccomp profiles that blocks all syscalls. // The intention behind blocking all syscalls is to prevent diff --git a/test/e2e/network_test.go b/test/e2e/network_test.go index cbfd72da6..9bd16c008 100644 --- a/test/e2e/network_test.go +++ b/test/e2e/network_test.go @@ -211,6 +211,43 @@ var _ = Describe("Podman network", func() { Expect(rmAll.ExitCode()).To(BeZero()) }) + It("podman inspect container two CNI networks (container not running)", func() { + netName1 := "testNetThreeCNI1" + network1 := podmanTest.Podman([]string{"network", "create", netName1}) + network1.WaitWithDefaultTimeout() + Expect(network1.ExitCode()).To(BeZero()) + defer podmanTest.removeCNINetwork(netName1) + + netName2 := "testNetThreeCNI2" + network2 := podmanTest.Podman([]string{"network", "create", netName2}) + network2.WaitWithDefaultTimeout() + Expect(network2.ExitCode()).To(BeZero()) + defer podmanTest.removeCNINetwork(netName2) + + ctrName := "testCtr" + container := podmanTest.Podman([]string{"create", "--network", fmt.Sprintf("%s,%s", netName1, netName2), "--name", ctrName, ALPINE, "top"}) + container.WaitWithDefaultTimeout() + Expect(container.ExitCode()).To(BeZero()) + + inspect := podmanTest.Podman([]string{"inspect", ctrName}) + inspect.WaitWithDefaultTimeout() + Expect(inspect.ExitCode()).To(BeZero()) + conData := inspect.InspectContainerToJSON() + Expect(len(conData)).To(Equal(1)) + Expect(len(conData[0].NetworkSettings.Networks)).To(Equal(2)) + net1, ok := conData[0].NetworkSettings.Networks[netName1] + Expect(ok).To(BeTrue()) + Expect(net1.NetworkID).To(Equal(netName1)) + net2, ok := conData[0].NetworkSettings.Networks[netName2] + Expect(ok).To(BeTrue()) + Expect(net2.NetworkID).To(Equal(netName2)) + + // Necessary to ensure the CNI network is removed cleanly + rmAll := podmanTest.Podman([]string{"rm", "-f", ctrName}) + rmAll.WaitWithDefaultTimeout() + Expect(rmAll.ExitCode()).To(BeZero()) + }) + It("podman inspect container two CNI networks", func() { netName1 := "testNetTwoCNI1" network1 := podmanTest.Podman([]string{"network", "create", "--subnet", "10.50.51.0/25", netName1}) diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go index b6a390950..3906fa49d 100644 --- a/test/e2e/play_kube_test.go +++ b/test/e2e/play_kube_test.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "path/filepath" + "strconv" "strings" "text/template" @@ -111,7 +112,19 @@ spec: image: {{ .Image }} name: {{ .Name }} imagePullPolicy: {{ .PullPolicy }} - resources: {} + {{- if or .CpuRequest .CpuLimit .MemoryRequest .MemoryLimit }} + resources: + {{- if or .CpuRequest .MemoryRequest }} + requests: + {{if .CpuRequest }}cpu: {{ .CpuRequest }}{{ end }} + {{if .MemoryRequest }}memory: {{ .MemoryRequest }}{{ end }} + {{- end }} + {{- if or .CpuLimit .MemoryLimit }} + limits: + {{if .CpuLimit }}cpu: {{ .CpuLimit }}{{ end }} + {{if .MemoryLimit }}memory: {{ .MemoryLimit }}{{ end }} + {{- end }} + {{- end }} {{ if .SecurityContext }} securityContext: allowPrivilegeEscalation: true @@ -223,7 +236,19 @@ spec: image: {{ .Image }} name: {{ .Name }} imagePullPolicy: {{ .PullPolicy }} - resources: {} + {{- if or .CpuRequest .CpuLimit .MemoryRequest .MemoryLimit }} + resources: + {{- if or .CpuRequest .MemoryRequest }} + requests: + {{if .CpuRequest }}cpu: {{ .CpuRequest }}{{ end }} + {{if .MemoryRequest }}memory: {{ .MemoryRequest }}{{ end }} + {{- end }} + {{- if or .CpuLimit .MemoryLimit }} + limits: + {{if .CpuLimit }}cpu: {{ .CpuLimit }}{{ end }} + {{if .MemoryLimit }}memory: {{ .MemoryLimit }}{{ end }} + {{- end }} + {{- end }} {{ if .SecurityContext }} securityContext: allowPrivilegeEscalation: true @@ -261,6 +286,8 @@ var ( defaultDeploymentName = "testDeployment" defaultConfigMapName = "testConfigMap" seccompPwdEPERM = []byte(`{"defaultAction":"SCMP_ACT_ALLOW","syscalls":[{"name":"getcwd","action":"SCMP_ACT_ERRNO"}]}`) + // CPU Period in ms + defaultCPUPeriod = 100 ) func writeYaml(content string, fileName string) error { @@ -503,6 +530,10 @@ type Ctr struct { Image string Cmd []string Arg []string + CpuRequest string + CpuLimit string + MemoryRequest string + MemoryLimit string SecurityContext bool Caps bool CapAdd []string @@ -521,7 +552,25 @@ type Ctr struct { // getCtr takes a list of ctrOptions and returns a Ctr with sane defaults // and the configured options func getCtr(options ...ctrOption) *Ctr { - c := Ctr{defaultCtrName, defaultCtrImage, defaultCtrCmd, defaultCtrArg, true, false, nil, nil, "", "", "", false, "", "", false, []Env{}, []EnvFrom{}} + c := Ctr{ + Name: defaultCtrName, + Image: defaultCtrImage, + Cmd: defaultCtrCmd, + Arg: defaultCtrArg, + SecurityContext: true, + Caps: false, + CapAdd: nil, + CapDrop: nil, + PullPolicy: "", + HostIP: "", + Port: "", + VolumeMount: false, + VolumeMountPath: "", + VolumeName: "", + VolumeReadOnly: false, + Env: []Env{}, + EnvFrom: []EnvFrom{}, + } for _, option := range options { option(&c) } @@ -548,6 +597,30 @@ func withImage(img string) ctrOption { } } +func withCpuRequest(request string) ctrOption { + return func(c *Ctr) { + c.CpuRequest = request + } +} + +func withCpuLimit(limit string) ctrOption { + return func(c *Ctr) { + c.CpuLimit = limit + } +} + +func withMemoryRequest(request string) ctrOption { + return func(c *Ctr) { + c.MemoryRequest = request + } +} + +func withMemoryLimit(limit string) ctrOption { + return func(c *Ctr) { + c.MemoryLimit = limit + } +} + func withSecurityContext(sc bool) ctrOption { return func(c *Ctr) { c.SecurityContext = sc @@ -648,7 +721,12 @@ type EnvFrom struct { From string } -var _ = Describe("Podman generate kube", func() { +func milliCPUToQuota(milliCPU string) int { + milli, _ := strconv.Atoi(strings.Trim(milliCPU, "m")) + return milli * defaultCPUPeriod +} + +var _ = Describe("Podman play kube", func() { var ( tempdir string err error @@ -1324,4 +1402,49 @@ spec: Expect(inspect.OutputToString()).To(ContainSubstring(correctLabels)) } }) + + It("podman play kube allows setting resource limits", func() { + SkipIfContainerized("Resource limits require a running systemd") + SkipIfRootlessCgroupsV1("Limits require root or cgroups v2") + SkipIfUnprevilegedCPULimits() + podmanTest.CgroupManager = "systemd" + + var ( + numReplicas int32 = 3 + expectedCpuRequest string = "100m" + expectedCpuLimit string = "200m" + expectedMemoryRequest string = "10000000" + expectedMemoryLimit string = "20000000" + ) + + expectedCpuQuota := milliCPUToQuota(expectedCpuLimit) + + deployment := getDeployment( + withReplicas(numReplicas), + withPod(getPod(withCtr(getCtr( + withCpuRequest(expectedCpuRequest), + withCpuLimit(expectedCpuLimit), + withMemoryRequest(expectedMemoryRequest), + withMemoryLimit(expectedMemoryLimit), + ))))) + err := generateKubeYaml("deployment", deployment, kubeYaml) + Expect(err).To(BeNil()) + + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) + kube.WaitWithDefaultTimeout() + Expect(kube.ExitCode()).To(Equal(0)) + + for _, pod := range getPodNamesInDeployment(deployment) { + inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(&pod), "--format", ` +CpuPeriod: {{ .HostConfig.CpuPeriod }} +CpuQuota: {{ .HostConfig.CpuQuota }} +Memory: {{ .HostConfig.Memory }} +MemoryReservation: {{ .HostConfig.MemoryReservation }}`}) + inspect.WaitWithDefaultTimeout() + Expect(inspect.ExitCode()).To(Equal(0)) + Expect(inspect.OutputToString()).To(ContainSubstring(fmt.Sprintf("%s: %d", "CpuQuota", expectedCpuQuota))) + Expect(inspect.OutputToString()).To(ContainSubstring("MemoryReservation: " + expectedMemoryRequest)) + Expect(inspect.OutputToString()).To(ContainSubstring("Memory: " + expectedMemoryLimit)) + } + }) }) diff --git a/test/e2e/toolbox_test.go b/test/e2e/toolbox_test.go new file mode 100644 index 000000000..6122cee19 --- /dev/null +++ b/test/e2e/toolbox_test.go @@ -0,0 +1,368 @@ +package integration + +/* + toolbox_test.go is under the care of the Toolbox Team. + + The tests are trying to stress parts of Podman that Toolbox[0] needs for + its functionality. + + [0] https://github.com/containers/toolbox + + Info about test cases: + - some tests rely on a certain configuration of a container that is done by + executing several commands in the entry-point of a container. To make + sure the initialization had enough time to be executed, + WaitContainerReady() after the container is started. + + - in several places there's an invocation of 'podman logs' It is there mainly + to ease debugging when a test goes wrong (during the initialization of a + container) but sometimes it is also used in the test case itself. + + Maintainers (Toolbox Team): + - Ondřej Míchal <harrymichal@fedoraproject.org> + - Debarshi Ray <rishi@fedoraproject.org> + + Also available on Freenode IRC on #silverblue or #podman +*/ + +import ( + "fmt" + "os" + "os/exec" + "os/user" + "strconv" + "strings" + "syscall" + + . "github.com/containers/podman/v2/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Toolbox-specific testing", func() { + var ( + tempdir string + err error + podmanTest *PodmanTestIntegration + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() + podmanTest.SeedImages() + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + processTestResult(f) + }) + + It("podman run --dns=none - allows self-management of /etc/resolv.conf", func() { + var session *PodmanSessionIntegration + + session = podmanTest.Podman([]string{"run", "--dns", "none", ALPINE, "sh", "-c", + "rm -f /etc/resolv.conf; touch -d '1970-01-01 00:02:03' /etc/resolv.conf; stat -c %s:%Y /etc/resolv.conf"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring("0:123")) + }) + + It("podman run --no-hosts - allows self-management of /etc/hosts", func() { + var session *PodmanSessionIntegration + + session = podmanTest.Podman([]string{"run", "--no-hosts", ALPINE, "sh", "-c", + "rm -f /etc/hosts; touch -d '1970-01-01 00:02:03' /etc/hosts; stat -c %s:%Y /etc/hosts"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring("0:123")) + }) + + It("podman create --ulimit host + podman exec - correctly mirrors hosts ulimits", func() { + if podmanTest.RemoteTest { + Skip("Ulimit check does not work with a remote client") + } + var session *PodmanSessionIntegration + var containerHardLimit int + var rlimit syscall.Rlimit + var err error + + err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit) + Expect(err).To(BeNil()) + fmt.Printf("Expected value: %d", rlimit.Max) + + session = podmanTest.Podman([]string{"create", "--name", "test", "--ulimit", "host", ALPINE, + "sleep", "1000"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"start", "test"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"exec", "test", "sh", "-c", + "ulimit -H -n"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + containerHardLimit, err = strconv.Atoi(strings.Trim(session.OutputToString(), "\n")) + Expect(err).To(BeNil()) + Expect(containerHardLimit).To(BeNumerically(">=", rlimit.Max)) + }) + + It("podman create --ipc=host --pid=host + podman exec - correct shared memory limit size", func() { + // Comparison of the size of /dev/shm on the host being equal to the one in + // a container + if podmanTest.RemoteTest { + Skip("Shm size check does not work with a remote client") + } + var session *PodmanSessionIntegration + var cmd *exec.Cmd + var hostShmSize, containerShmSize int + var err error + + // Because Alpine uses busybox, most commands don't offer advanced options + // like "--output" in df. Therefore the value of the field 'Size' (or + // ('1K-blocks') needs to be extracted manually. + cmd = exec.Command("df", "/dev/shm") + res, err := cmd.Output() + Expect(err).To(BeNil()) + lines := strings.SplitN(string(res), "\n", 2) + fields := strings.Fields(lines[len(lines)-1]) + hostShmSize, err = strconv.Atoi(fields[1]) + Expect(err).To(BeNil()) + + session = podmanTest.Podman([]string{"create", "--name", "test", "--ipc=host", "--pid=host", ALPINE, + "sleep", "1000"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"start", "test"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"exec", "test", + "df", "/dev/shm"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + lines = session.OutputToStringArray() + fields = strings.Fields(lines[len(lines)-1]) + containerShmSize, err = strconv.Atoi(fields[1]) + Expect(err).To(BeNil()) + + // In some cases it may happen that the size of /dev/shm is not exactly + // equal. Therefore it's fine if there's a slight tolerance between the + // compared values. + Expect(hostShmSize).To(BeNumerically("~", containerShmSize, 100)) + }) + + It("podman create --userns=keep-id --user root:root - entrypoint - entrypoint is executed as root", func() { + var session *PodmanSessionIntegration + + session = podmanTest.Podman([]string{"run", "--userns=keep-id", "--user", "root:root", ALPINE, + "id"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring("uid=0(root) gid=0(root)")) + }) + + It("podman create --userns=keep-id + podman exec - correct names of user and group", func() { + var session *PodmanSessionIntegration + var err error + + currentUser, err := user.Current() + Expect(err).To(BeNil()) + + currentGroup, err := user.LookupGroupId(currentUser.Gid) + Expect(err).To(BeNil()) + + session = podmanTest.Podman([]string{"create", "--name", "test", "--userns=keep-id", ALPINE, + "sleep", "1000"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(err).To(BeNil()) + + session = podmanTest.Podman([]string{"start", "test"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + expectedOutput := fmt.Sprintf("uid=%s(%s) gid=%s(%s)", + currentUser.Uid, currentUser.Username, + currentGroup.Gid, currentGroup.Name) + + session = podmanTest.Podman([]string{"exec", "test", + "id"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring(expectedOutput)) + }) + + It("podman create --userns=keep-id - entrypoint - adding user with useradd and then removing their password", func() { + var session *PodmanSessionIntegration + + var username string = "testuser" + var homeDir string = "/home/testuser" + var shell string = "/bin/sh" + var uid string = "1001" + var gid string = "1001" + + useradd := fmt.Sprintf("useradd --home-dir %s --shell %s --uid %s %s", + homeDir, shell, uid, username) + passwd := fmt.Sprintf("passwd --delete %s", username) + + session = podmanTest.Podman([]string{"create", "--name", "test", "--userns=keep-id", "--user", "root:root", fedoraToolbox, "sh", "-c", + fmt.Sprintf("%s; %s; echo READY; sleep 1000", useradd, passwd)}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"start", "test"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + Expect(WaitContainerReady(podmanTest, "test", "READY", 2, 1)).To(BeTrue()) + + expectedOutput := fmt.Sprintf("%s:x:%s:%s::%s:%s", + username, uid, gid, homeDir, shell) + + session = podmanTest.Podman([]string{"exec", "test", "cat", "/etc/passwd"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring(expectedOutput)) + + expectedOutput = "passwd: Note: deleting a password also unlocks the password." + + session = podmanTest.Podman([]string{"logs", "test"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring(expectedOutput)) + }) + + It("podman create --userns=keep-id + podman exec - adding group with groupadd", func() { + var session *PodmanSessionIntegration + + var groupName string = "testgroup" + var gid string = "1001" + + groupadd := fmt.Sprintf("groupadd --gid %s %s", gid, groupName) + + session = podmanTest.Podman([]string{"create", "--name", "test", "--userns=keep-id", "--user", "root:root", fedoraToolbox, "sh", "-c", + fmt.Sprintf("%s; echo READY; sleep 1000", groupadd)}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"start", "test"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + Expect(WaitContainerReady(podmanTest, "test", "READY", 2, 1)).To(BeTrue()) + + session = podmanTest.Podman([]string{"exec", "test", "cat", "/etc/group"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring(groupName)) + + session = podmanTest.Podman([]string{"logs", "test"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring("READY")) + }) + + It("podman create --userns=keep-id - entrypoint - modifying existing user with usermod - add to new group, change home/shell/uid", func() { + var session *PodmanSessionIntegration + var badHomeDir string = "/home/badtestuser" + var badShell string = "/bin/sh" + var badUID string = "1001" + var username string = "testuser" + var homeDir string = "/home/testuser" + var shell string = "/bin/bash" + var uid string = "2000" + var groupName string = "testgroup" + var gid string = "2000" + + // The use of bad* in the name of variables does not imply the invocation + // of useradd should fail The user is supposed to be created successfuly + // but later his information (uid, home, shell,..) is changed via usermod. + useradd := fmt.Sprintf("useradd --home-dir %s --shell %s --uid %s %s", + badHomeDir, badShell, badUID, username) + groupadd := fmt.Sprintf("groupadd --gid %s %s", + gid, groupName) + usermod := fmt.Sprintf("usermod --append --groups wheel --home %s --shell %s --uid %s --gid %s %s", + homeDir, shell, uid, gid, username) + + session = podmanTest.Podman([]string{"create", "--name", "test", "--userns=keep-id", "--user", "root:root", fedoraToolbox, "sh", "-c", + fmt.Sprintf("%s; %s; %s; echo READY; sleep 1000", useradd, groupadd, usermod)}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"start", "test"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + Expect(WaitContainerReady(podmanTest, "test", "READY", 2, 1)).To(BeTrue()) + + expectedUser := fmt.Sprintf("%s:x:%s:%s::%s:%s", + username, uid, gid, homeDir, shell) + + session = podmanTest.Podman([]string{"exec", "test", "cat", "/etc/passwd"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring(expectedUser)) + + session = podmanTest.Podman([]string{"logs", "test"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring("READY")) + }) + + It("podman run --privileged --userns=keep-id --user root:root - entrypoint - (bind)mounting", func() { + var session *PodmanSessionIntegration + + session = podmanTest.Podman([]string{"run", "--privileged", "--userns=keep-id", "--user", "root:root", ALPINE, + "mount", "-t", "tmpfs", "tmpfs", "/tmp"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"run", "--privileged", "--userns=keep-id", "--user", "root:root", ALPINE, + "mount", "--rbind", "/tmp", "/var/tmp"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + + It("podman create + start - with all needed switches for create - sleep as entry-point", func() { + var session *PodmanSessionIntegration + + // These should be most of the switches that Toolbox uses to create a "toolbox" container + // https://github.com/containers/toolbox/blob/master/src/cmd/create.go + session = podmanTest.Podman([]string{"create", + "--dns", "none", + "--hostname", "toolbox", + "--ipc", "host", + "--label", "com.github.containers.toolbox=true", + "--name", "test", + "--network", "host", + "--no-hosts", + "--pid", "host", + "--privileged", + "--security-opt", "label=disable", + "--ulimit", "host", + "--userns=keep-id", + "--user", "root:root", + fedoraToolbox, "sh", "-c", "echo READY; sleep 1000"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"start", "test"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + Expect(WaitContainerReady(podmanTest, "test", "READY", 2, 1)).To(BeTrue()) + + session = podmanTest.Podman([]string{"logs", "test"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring("READY")) + }) +}) |