diff options
Diffstat (limited to 'test')
31 files changed, 860 insertions, 306 deletions
diff --git a/test/apiv2/10-images.at b/test/apiv2/10-images.at index d3fde9f9d..b7bcaf81d 100644 --- a/test/apiv2/10-images.at +++ b/test/apiv2/10-images.at @@ -218,6 +218,9 @@ if ! grep -q '400 Bad Request' "${TMPD}/headers.txt"; then BUILD_TEST_ERROR="1" fi +t POST libpod/images/prune 200 +t POST libpod/images/prune 200 length=0 [] + cleanBuildTest if [[ "${BUILD_TEST_ERROR}" ]]; then exit 1 diff --git a/test/apiv2/40-pods.at b/test/apiv2/40-pods.at index 985b26411..f45e85f61 100644 --- a/test/apiv2/40-pods.at +++ b/test/apiv2/40-pods.at @@ -110,11 +110,11 @@ t GET libpod/pods/fakename/top 404 \ .cause="no such pod" t GET libpod/pods/foo/top 200 \ - .Processes[0][-1]="/pause " \ + .Processes[0][-1]="/pause" \ .Titles[-1]="COMMAND" t GET libpod/pods/foo/top?ps_args=args,pid 200 \ - .Processes[0][0]="/pause " \ + .Processes[0][0]="/pause" \ .Processes[0][1]="1" \ .Titles[0]="COMMAND" \ .Titles[1]="PID" \ diff --git a/test/apiv2/python/rest_api/test_v2_0_0_container.py b/test/apiv2/python/rest_api/test_v2_0_0_container.py index 853e9da88..101044bbb 100644 --- a/test/apiv2/python/rest_api/test_v2_0_0_container.py +++ b/test/apiv2/python/rest_api/test_v2_0_0_container.py @@ -1,8 +1,11 @@ +import multiprocessing +import queue import random +import threading import unittest -import json import requests +import time from dateutil.parser import parse from .fixtures import APITestCase @@ -16,7 +19,10 @@ class ContainerTestCase(APITestCase): self.assertEqual(len(obj), 1) def test_list_filters(self): - r = requests.get(self.podman_url + "/v1.40/containers/json?filters%3D%7B%22status%22%3A%5B%22running%22%5D%7D") + r = requests.get( + self.podman_url + + "/v1.40/containers/json?filters%3D%7B%22status%22%3A%5B%22running%22%5D%7D" + ) self.assertEqual(r.status_code, 200, r.text) payload = r.json() containerAmnt = len(payload) @@ -33,18 +39,18 @@ class ContainerTestCase(APITestCase): self.assertId(r.content) _ = parse(r.json()["Created"]) - r = requests.post( self.podman_url + "/v1.40/containers/create?name=topcontainer", - json={"Cmd": ["top"], - "Image": "alpine:latest", - "Healthcheck": { - "Test": ["CMD", "pidof", "top"], - "Interval": 5000000000, - "Timeout": 2000000000, - "Retries": 3, - "StartPeriod": 5000000000 - } + json={ + "Cmd": ["top"], + "Image": "alpine:latest", + "Healthcheck": { + "Test": ["CMD", "pidof", "top"], + "Interval": 5000000000, + "Timeout": 2000000000, + "Retries": 3, + "StartPeriod": 5000000000, + }, }, ) self.assertEqual(r.status_code, 201, r.text) @@ -67,7 +73,7 @@ class ContainerTestCase(APITestCase): self.assertEqual(r.status_code, 200, r.text) self.assertId(r.content) out = r.json() - hc = out["Config"]["Healthcheck"]["Test"] + hc = out["Config"]["Healthcheck"]["Test"] self.assertListEqual(["CMD", "pidof", "top"], hc) r = requests.post(self.podman_url + f"/v1.40/containers/{container_id}/start") @@ -84,7 +90,9 @@ class ContainerTestCase(APITestCase): self.assertIn(r.status_code, (200, 409), r.text) if r.status_code == 200: self.assertId(r.content) - r = requests.get(self.uri(self.resolve_container("/containers/{}/stats?stream=false&one-shot=true"))) + r = requests.get( + self.uri(self.resolve_container("/containers/{}/stats?stream=false&one-shot=true")) + ) self.assertIn(r.status_code, (200, 409), r.text) if r.status_code == 200: self.assertId(r.content) @@ -136,9 +144,15 @@ class ContainerTestCase(APITestCase): payload = r.json() container_id = payload["Id"] self.assertIsNotNone(container_id) - r = requests.get(self.podman_url + f"/v1.40/containers/{payload['Id']}/logs?follow=false&stdout=true&until=0") + r = requests.get( + self.podman_url + + f"/v1.40/containers/{payload['Id']}/logs?follow=false&stdout=true&until=0" + ) self.assertEqual(r.status_code, 200, r.text) - r = requests.get(self.podman_url + f"/v1.40/containers/{payload['Id']}/logs?follow=false&stdout=true&until=1") + r = requests.get( + self.podman_url + + f"/v1.40/containers/{payload['Id']}/logs?follow=false&stdout=true&until=1" + ) self.assertEqual(r.status_code, 200, r.text) def test_commit(self): @@ -257,6 +271,63 @@ class ContainerTestCase(APITestCase): r = requests.delete(self.podman_url + f"/v1.40/containers/{container_id}") self.assertEqual(r.status_code, 204, r.text) + def test_top_no_stream(self): + uri = self.uri(self.resolve_container("/containers/{}/top")) + q = queue.Queue() + + def _impl(fifo): + fifo.put(requests.get(uri, params={"stream": False}, timeout=2)) + + top = threading.Thread(target=_impl, args=(q,)) + top.start() + time.sleep(2) + self.assertFalse(top.is_alive(), f"GET {uri} failed to return in 2s") + + qr = q.get(False) + self.assertEqual(qr.status_code, 200, qr.text) + + qr.close() + top.join() + + def test_top_stream(self): + uri = self.uri(self.resolve_container("/containers/{}/top")) + q = queue.Queue() + + stop_thread = False + + def _impl(fifo, stop): + try: + with requests.get(uri, params={"stream": True, "delay": 1}, stream=True) as r: + r.raise_for_status() + fifo.put(r) + for buf in r.iter_lines(chunk_size=None): + if stop(): + break + fifo.put(buf) + except Exception: + pass + + top = threading.Thread(target=_impl, args=(q, (lambda: stop_thread))) + top.start() + time.sleep(4) + self.assertTrue(top.is_alive(), f"GET {uri} exited too soon") + stop_thread = True + + for _ in range(10): + try: + qr = q.get_nowait() + if qr is not None: + self.assertEqual(qr.status_code, 200) + qr.close() + break + except queue.Empty: + pass + finally: + time.sleep(1) + else: + self.fail("Server failed to respond in 10s") + top.join() + if __name__ == "__main__": unittest.main() diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go index a8efe1ca9..be6b782b5 100644 --- a/test/e2e/checkpoint_test.go +++ b/test/e2e/checkpoint_test.go @@ -5,9 +5,11 @@ import ( "net" "os" "os/exec" + "path/filepath" "strings" "time" + "github.com/checkpoint-restore/go-criu/v5/stats" "github.com/containers/podman/v3/pkg/checkpoint/crutils" "github.com/containers/podman/v3/pkg/criu" . "github.com/containers/podman/v3/test/utils" @@ -1191,4 +1193,55 @@ var _ = Describe("Podman checkpoint", func() { // Remove exported checkpoint os.Remove(fileName) }) + + It("podman checkpoint container with export and statistics", func() { + localRunString := getRunString([]string{ + "--rm", + ALPINE, + "top", + }) + session := podmanTest.Podman(localRunString) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + cid := session.OutputToString() + fileName := "/tmp/checkpoint-" + cid + ".tar.gz" + + result := podmanTest.Podman([]string{ + "container", + "checkpoint", + "-l", "-e", + fileName, + }) + result.WaitWithDefaultTimeout() + + // As the container has been started with '--rm' it will be completely + // cleaned up after checkpointing. + Expect(result).Should(Exit(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + Expect(podmanTest.NumberOfContainers()).To(Equal(0)) + + // Extract checkpoint archive + destinationDirectory, err := CreateTempDirInTempDir() + Expect(err).ShouldNot(HaveOccurred()) + + tarsession := SystemExec( + "tar", + []string{ + "xf", + fileName, + "-C", + destinationDirectory, + }, + ) + Expect(tarsession).Should(Exit(0)) + + _, err = os.Stat(filepath.Join(destinationDirectory, stats.StatsDump)) + Expect(err).ShouldNot(HaveOccurred()) + + Expect(os.RemoveAll(destinationDirectory)).To(BeNil()) + + // Remove exported checkpoint + os.Remove(fileName) + }) }) diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go index 7228682f3..200faae2d 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/common_test.go @@ -208,9 +208,7 @@ var _ = SynchronizedAfterSuite(func() {}, // PodmanTestCreate creates a PodmanTestIntegration instance for the tests func PodmanTestCreateUtil(tempDir string, remote bool) *PodmanTestIntegration { - var ( - podmanRemoteBinary string - ) + var podmanRemoteBinary string host := GetHostDistributionInfo() cwd, _ := os.Getwd() @@ -220,12 +218,11 @@ func PodmanTestCreateUtil(tempDir string, remote bool) *PodmanTestIntegration { podmanBinary = os.Getenv("PODMAN_BINARY") } - if remote { - podmanRemoteBinary = filepath.Join(cwd, "../../bin/podman-remote") - if os.Getenv("PODMAN_REMOTE_BINARY") != "" { - podmanRemoteBinary = os.Getenv("PODMAN_REMOTE_BINARY") - } + podmanRemoteBinary = filepath.Join(cwd, "../../bin/podman-remote") + if os.Getenv("PODMAN_REMOTE_BINARY") != "" { + podmanRemoteBinary = os.Getenv("PODMAN_REMOTE_BINARY") } + conmonBinary := filepath.Join("/usr/libexec/podman/conmon") altConmonBinary := "/usr/bin/conmon" if _, err := os.Stat(conmonBinary); os.IsNotExist(err) { @@ -271,12 +268,13 @@ func PodmanTestCreateUtil(tempDir string, remote bool) *PodmanTestIntegration { p := &PodmanTestIntegration{ PodmanTest: PodmanTest{ - PodmanBinary: podmanBinary, - ArtifactPath: ARTIFACT_DIR, - TempDir: tempDir, - RemoteTest: remote, - ImageCacheFS: storageFs, - ImageCacheDir: ImageCacheDir, + PodmanBinary: podmanBinary, + RemotePodmanBinary: podmanRemoteBinary, + ArtifactPath: ARTIFACT_DIR, + TempDir: tempDir, + RemoteTest: remote, + ImageCacheFS: storageFs, + ImageCacheDir: ImageCacheDir, }, ConmonBinary: conmonBinary, CrioRoot: filepath.Join(tempDir, "crio"), @@ -289,8 +287,8 @@ func PodmanTestCreateUtil(tempDir string, remote bool) *PodmanTestIntegration { CgroupManager: cgroupManager, Host: host, } + if remote { - p.PodmanTest.RemotePodmanBinary = podmanRemoteBinary uuid := stringid.GenerateNonCryptoID() if !rootless.IsRootless() { p.RemoteSocket = fmt.Sprintf("unix:/run/podman/podman-%s.sock", uuid) @@ -632,6 +630,19 @@ func SkipIfNotRootless(reason string) { } } +func SkipIfSystemdNotRunning(reason string) { + checkReason(reason) + + cmd := exec.Command("systemctl", "list-units") + err := cmd.Run() + if err != nil { + if _, ok := err.(*exec.Error); ok { + ginkgo.Skip("[notSystemd]: not running " + reason) + } + Expect(err).ToNot(HaveOccurred()) + } +} + func SkipIfNotSystemd(manager, reason string) { checkReason(reason) if manager != "systemd" { @@ -683,9 +694,44 @@ func SkipIfContainerized(reason string) { } } +func SkipIfRemote(reason string) { + checkReason(reason) + if !IsRemote() { + return + } + ginkgo.Skip("[remote]: " + reason) +} + +// SkipIfInContainer skips a test if the test is run inside a container +func SkipIfInContainer(reason string) { + checkReason(reason) + if os.Getenv("TEST_ENVIRON") == "container" { + Skip("[container]: " + reason) + } +} + +// SkipIfNotActive skips a test if the given systemd unit is not active +func SkipIfNotActive(unit string, reason string) { + checkReason(reason) + + var buffer bytes.Buffer + cmd := exec.Command("systemctl", "is-active", unit) + cmd.Stdout = &buffer + err := cmd.Start() + Expect(err).ToNot(HaveOccurred()) + + err = cmd.Wait() + Expect(err).ToNot(HaveOccurred()) + + Expect(err).ToNot(HaveOccurred()) + if strings.TrimSpace(buffer.String()) != "active" { + Skip(fmt.Sprintf("[systemd]: unit %s is not active: %s", unit, reason)) + } +} + // PodmanAsUser is the exec call to podman on the filesystem with the specified uid/gid and environment func (p *PodmanTestIntegration) PodmanAsUser(args []string, uid, gid uint32, cwd string, env []string) *PodmanSessionIntegration { - podmanSession := p.PodmanAsUserBase(args, uid, gid, cwd, env, false, false, nil) + podmanSession := p.PodmanAsUserBase(args, uid, gid, cwd, env, false, false, nil, nil) return &PodmanSessionIntegration{podmanSession} } diff --git a/test/e2e/config/containers-netns.conf b/test/e2e/config/containers-netns.conf new file mode 100644 index 000000000..3f796f25d --- /dev/null +++ b/test/e2e/config/containers-netns.conf @@ -0,0 +1,3 @@ +[containers] + +netns = "host" diff --git a/test/e2e/healthcheck_run_test.go b/test/e2e/healthcheck_run_test.go index b2666c789..c9a6f926f 100644 --- a/test/e2e/healthcheck_run_test.go +++ b/test/e2e/healthcheck_run_test.go @@ -52,6 +52,18 @@ var _ = Describe("Podman healthcheck run", func() { Expect(hc).Should(Exit(125)) }) + It("podman healthcheck from image's config (not container config)", func() { + // Regression test for #12226: a health check may be defined in + // the container or the container-config of an image. + session := podmanTest.Podman([]string{"create", "--name", "hc", "quay.io/libpod/healthcheck:config-only", "ls"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + hc := podmanTest.Podman([]string{"container", "inspect", "--format", "{{.Config.Healthcheck}}", "hc"}) + hc.WaitWithDefaultTimeout() + Expect(hc).Should(Exit(0)) + Expect(hc.OutputToString()).To(Equal("{[CMD-SHELL curl -f http://localhost/ || exit 1] 0s 5m0s 3s 0}")) + }) + It("podman disable healthcheck with --health-cmd=none on valid container", func() { session := podmanTest.Podman([]string{"run", "-dt", "--health-cmd", "none", "--name", "hc", healthcheck}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/libpod_suite_remote_test.go b/test/e2e/libpod_suite_remote_test.go index 3115c246f..1fa29daa1 100644 --- a/test/e2e/libpod_suite_remote_test.go +++ b/test/e2e/libpod_suite_remote_test.go @@ -16,20 +16,12 @@ import ( "time" "github.com/containers/podman/v3/pkg/rootless" - "github.com/onsi/ginkgo" ) func IsRemote() bool { return true } -func SkipIfRemote(reason string) { - if len(reason) < 5 { - panic("SkipIfRemote must specify a reason to skip") - } - ginkgo.Skip("[remote]: " + reason) -} - // Podman is the exec call to podman on the filesystem func (p *PodmanTestIntegration) Podman(args []string) *PodmanSessionIntegration { var remoteArgs = []string{"--remote", "--url", p.RemoteSocket} @@ -38,11 +30,25 @@ func (p *PodmanTestIntegration) Podman(args []string) *PodmanSessionIntegration return &PodmanSessionIntegration{podmanSession} } +// PodmanSystemdScope runs the podman command in a new systemd scope +func (p *PodmanTestIntegration) PodmanSystemdScope(args []string) *PodmanSessionIntegration { + var remoteArgs = []string{"--remote", "--url", p.RemoteSocket} + remoteArgs = append(remoteArgs, args...) + + wrapper := []string{"systemd-run", "--scope"} + if rootless.IsRootless() { + wrapper = []string{"systemd-run", "--scope", "--user"} + } + + podmanSession := p.PodmanAsUserBase(remoteArgs, 0, 0, "", nil, false, false, wrapper, nil) + return &PodmanSessionIntegration{podmanSession} +} + // PodmanExtraFiles is the exec call to podman on the filesystem and passes down extra files func (p *PodmanTestIntegration) PodmanExtraFiles(args []string, extraFiles []*os.File) *PodmanSessionIntegration { var remoteArgs = []string{"--remote", "--url", p.RemoteSocket} remoteArgs = append(remoteArgs, args...) - podmanSession := p.PodmanAsUserBase(remoteArgs, 0, 0, "", nil, false, false, extraFiles) + podmanSession := p.PodmanAsUserBase(remoteArgs, 0, 0, "", nil, false, false, nil, extraFiles) return &PodmanSessionIntegration{podmanSession} } diff --git a/test/e2e/libpod_suite_test.go b/test/e2e/libpod_suite_test.go index cc03ccc96..001a869b1 100644 --- a/test/e2e/libpod_suite_test.go +++ b/test/e2e/libpod_suite_test.go @@ -8,24 +8,33 @@ import ( "os" "path/filepath" "strings" + + "github.com/containers/podman/v3/pkg/rootless" ) func IsRemote() bool { return false } -func SkipIfRemote(string) { -} - // Podman is the exec call to podman on the filesystem func (p *PodmanTestIntegration) Podman(args []string) *PodmanSessionIntegration { podmanSession := p.PodmanBase(args, false, false) return &PodmanSessionIntegration{podmanSession} } +// PodmanSystemdScope runs the podman command in a new systemd scope +func (p *PodmanTestIntegration) PodmanSystemdScope(args []string) *PodmanSessionIntegration { + wrapper := []string{"systemd-run", "--scope"} + if rootless.IsRootless() { + wrapper = []string{"systemd-run", "--scope", "--user"} + } + podmanSession := p.PodmanAsUserBase(args, 0, 0, "", nil, false, false, wrapper, nil) + return &PodmanSessionIntegration{podmanSession} +} + // PodmanExtraFiles is the exec call to podman on the filesystem and passes down extra files func (p *PodmanTestIntegration) PodmanExtraFiles(args []string, extraFiles []*os.File) *PodmanSessionIntegration { - podmanSession := p.PodmanAsUserBase(args, 0, 0, "", nil, false, false, extraFiles) + podmanSession := p.PodmanAsUserBase(args, 0, 0, "", nil, false, false, nil, extraFiles) return &PodmanSessionIntegration{podmanSession} } diff --git a/test/e2e/network_connect_disconnect_test.go b/test/e2e/network_connect_disconnect_test.go index 6cddf9285..2205a1263 100644 --- a/test/e2e/network_connect_disconnect_test.go +++ b/test/e2e/network_connect_disconnect_test.go @@ -87,6 +87,7 @@ var _ = Describe("Podman network connect and disconnect", func() { dis := podmanTest.Podman([]string{"network", "disconnect", netName, "test"}) dis.WaitWithDefaultTimeout() Expect(dis).Should(Exit(0)) + Expect(dis.ErrorToString()).Should(Equal("")) inspect := podmanTest.Podman([]string{"container", "inspect", "test", "--format", "{{len .NetworkSettings.Networks}}"}) inspect.WaitWithDefaultTimeout() @@ -183,6 +184,7 @@ var _ = Describe("Podman network connect and disconnect", func() { connect := podmanTest.Podman([]string{"network", "connect", newNetName, "test"}) connect.WaitWithDefaultTimeout() Expect(connect).Should(Exit(0)) + Expect(connect.ErrorToString()).Should(Equal("")) inspect := podmanTest.Podman([]string{"container", "inspect", "test", "--format", "{{len .NetworkSettings.Networks}}"}) inspect.WaitWithDefaultTimeout() diff --git a/test/e2e/pod_create_test.go b/test/e2e/pod_create_test.go index 34e879ed4..12aeffd1b 100644 --- a/test/e2e/pod_create_test.go +++ b/test/e2e/pod_create_test.go @@ -957,4 +957,22 @@ ENTRYPOINT ["sleep","99999"] Expect(ctr3.OutputToString()).To(ContainSubstring("hello")) }) + It("podman pod create read network mode from config", func() { + confPath, err := filepath.Abs("config/containers-netns.conf") + Expect(err).ToNot(HaveOccurred()) + os.Setenv("CONTAINERS_CONF", confPath) + defer os.Unsetenv("CONTAINERS_CONF") + if IsRemote() { + podmanTest.RestartRemoteService() + } + + pod := podmanTest.Podman([]string{"pod", "create", "--name", "test", "--infra-name", "test-infra"}) + pod.WaitWithDefaultTimeout() + Expect(pod).Should(Exit(0)) + + inspect := podmanTest.Podman([]string{"inspect", "--format", "{{.HostConfig.NetworkMode}}", "test-infra"}) + inspect.WaitWithDefaultTimeout() + Expect(inspect).Should(Exit(0)) + Expect(inspect.OutputToString()).Should(Equal("host")) + }) }) diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index bdf3ce5d6..c64cfd2d5 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -494,6 +494,23 @@ var _ = Describe("Podman run networking", func() { Expect(containerConfig[0].NetworkSettings.Ports["80/tcp"][0].HostPort).ToNot(Equal(80)) }) + It("podman run forward sctp protocol", func() { + SkipIfRootless("sctp protocol only works as root") + session := podmanTest.Podman([]string{"--log-level=info", "run", "--name=test", "-p", "80/sctp", "-p", "81/sctp", ALPINE}) + session.Wait(90) + Expect(session).Should(Exit(0)) + // we can only check logrus on local podman + if !IsRemote() { + // check that the info message for sctp protocol is only displayed once + Expect(strings.Count(session.ErrorToString(), "Port reservation for SCTP is not supported")).To(Equal(1), "`Port reservation for SCTP is not supported` is not displayed exactly one time in the logrus logs") + } + results := podmanTest.Podman([]string{"inspect", "test"}) + results.Wait(30) + Expect(results).Should(Exit(0)) + Expect(results.OutputToString()).To(ContainSubstring(`"80/sctp":`)) + Expect(results.OutputToString()).To(ContainSubstring(`"81/sctp":`)) + }) + It("podman run hostname test", func() { session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "printenv", "HOSTNAME"}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 95660bfc9..ed2d8938d 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -1381,13 +1381,13 @@ USER mail`, BB) } } - container := podmanTest.Podman([]string{"run", "--rm", "--cgroups=split", ALPINE, "cat", "/proc/self/cgroup"}) + container := podmanTest.PodmanSystemdScope([]string{"run", "--rm", "--cgroups=split", ALPINE, "cat", "/proc/self/cgroup"}) container.WaitWithDefaultTimeout() Expect(container).Should(Exit(0)) checkLines(container.OutputToStringArray()) // check that --cgroups=split is honored also when a container runs in a pod - container = podmanTest.Podman([]string{"run", "--rm", "--pod", "new:split-test-pod", "--cgroups=split", ALPINE, "cat", "/proc/self/cgroup"}) + container = podmanTest.PodmanSystemdScope([]string{"run", "--rm", "--pod", "new:split-test-pod", "--cgroups=split", ALPINE, "cat", "/proc/self/cgroup"}) container.WaitWithDefaultTimeout() Expect(container).Should(Exit(0)) checkLines(container.OutputToStringArray()) diff --git a/test/e2e/system_connection_test.go b/test/e2e/system_connection_test.go index 842ae8df6..76b442ce8 100644 --- a/test/e2e/system_connection_test.go +++ b/test/e2e/system_connection_test.go @@ -3,7 +3,11 @@ package integration import ( "fmt" "io/ioutil" + "net/url" "os" + "os/exec" + "os/user" + "path/filepath" "github.com/containers/common/pkg/config" . "github.com/containers/podman/v3/test/utils" @@ -19,22 +23,16 @@ var _ = Describe("podman system connection", func() { IsSet bool }{} - var ( - podmanTest *PodmanTestIntegration - ) + var podmanTest *PodmanTestIntegration BeforeEach(func() { ConfPath.Value, ConfPath.IsSet = os.LookupEnv("CONTAINERS_CONF") conf, err := ioutil.TempFile("", "containersconf") - if err != nil { - panic(err) - } + Expect(err).ToNot(HaveOccurred()) os.Setenv("CONTAINERS_CONF", conf.Name()) tempdir, err := CreateTempDirInTempDir() - if err != nil { - panic(err) - } + Expect(err).ToNot(HaveOccurred()) podmanTest = PodmanTestCreate(tempdir) podmanTest.Setup() }) @@ -49,196 +47,241 @@ var _ = Describe("podman system connection", func() { } f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + GinkgoWriter.Write( + []byte( + fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()))) }) - It("add ssh://", func() { - cmd := []string{"system", "connection", "add", - "--default", - "--identity", "~/.ssh/id_rsa", - "QA", - "ssh://root@server.fubar.com:2222/run/podman/podman.sock", - } - session := podmanTest.Podman(cmd) - session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(0)) - Expect(session.Out).Should(Say("")) - - cfg, err := config.ReadCustomConfig() - Expect(err).ShouldNot(HaveOccurred()) - Expect(cfg.Engine.ActiveService).To(Equal("QA")) - Expect(cfg.Engine.ServiceDestinations["QA"]).To(Equal( - config.Destination{ - URI: "ssh://root@server.fubar.com:2222/run/podman/podman.sock", - Identity: "~/.ssh/id_rsa", - }, - )) - - cmd = []string{"system", "connection", "rename", - "QA", - "QE", - } - session = podmanTest.Podman(cmd) - session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(0)) - - cfg, err = config.ReadCustomConfig() - Expect(err).ShouldNot(HaveOccurred()) - Expect(cfg.Engine.ActiveService).To(Equal("QE")) - Expect(cfg.Engine.ServiceDestinations["QE"]).To(Equal( - config.Destination{ - URI: "ssh://root@server.fubar.com:2222/run/podman/podman.sock", - Identity: "~/.ssh/id_rsa", - }, - )) - }) + Context("without running API service", func() { + It("add ssh://", func() { + cmd := []string{"system", "connection", "add", + "--default", + "--identity", "~/.ssh/id_rsa", + "QA", + "ssh://root@server.fubar.com:2222/run/podman/podman.sock", + } + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.Out.Contents()).Should(BeEmpty()) - It("add UDS", func() { - cmd := []string{"system", "connection", "add", - "QA-UDS", - "unix:///run/podman/podman.sock", - } - session := podmanTest.Podman(cmd) - session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(0)) - Expect(session.Out).Should(Say("")) - - cfg, err := config.ReadCustomConfig() - Expect(err).ShouldNot(HaveOccurred()) - Expect(cfg.Engine.ActiveService).To(Equal("QA-UDS")) - Expect(cfg.Engine.ServiceDestinations["QA-UDS"]).To(Equal( - config.Destination{ - URI: "unix:///run/podman/podman.sock", - Identity: "", - }, - )) - - cmd = []string{"system", "connection", "add", - "QA-UDS1", - "--socket-path", "/run/user/podman/podman.sock", - "unix:///run/podman/podman.sock", - } - session = podmanTest.Podman(cmd) - session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(0)) - Expect(session.Out).Should(Say("")) - - cfg, err = config.ReadCustomConfig() - Expect(err).ShouldNot(HaveOccurred()) - Expect(cfg.Engine.ActiveService).To(Equal("QA-UDS")) - Expect(cfg.Engine.ServiceDestinations["QA-UDS1"]).To(Equal( - config.Destination{ - URI: "unix:///run/user/podman/podman.sock", - Identity: "", - }, - )) - }) + cfg, err := config.ReadCustomConfig() + Expect(err).ShouldNot(HaveOccurred()) + Expect(cfg).To(HaveActiveService("QA")) + Expect(cfg).Should(VerifyService( + "QA", + "ssh://root@server.fubar.com:2222/run/podman/podman.sock", + "~/.ssh/id_rsa", + )) - It("add tcp", func() { - cmd := []string{"system", "connection", "add", - "QA-TCP", - "tcp://localhost:8888", - } - session := podmanTest.Podman(cmd) - session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(0)) - Expect(session.Out).Should(Say("")) - - cfg, err := config.ReadCustomConfig() - Expect(err).ShouldNot(HaveOccurred()) - Expect(cfg.Engine.ActiveService).To(Equal("QA-TCP")) - Expect(cfg.Engine.ServiceDestinations["QA-TCP"]).To(Equal( - config.Destination{ - URI: "tcp://localhost:8888", - Identity: "", - }, - )) - }) + cmd = []string{"system", "connection", "rename", + "QA", + "QE", + } + session = podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) - It("remove", func() { - cmd := []string{"system", "connection", "add", - "--default", - "--identity", "~/.ssh/id_rsa", - "QA", - "ssh://root@server.fubar.com:2222/run/podman/podman.sock", - } - session := podmanTest.Podman(cmd) - session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(0)) + Expect(config.ReadCustomConfig()).To(HaveActiveService("QE")) + }) - for i := 0; i < 2; i++ { - cmd = []string{"system", "connection", "remove", "QA"} + It("add UDS", func() { + cmd := []string{"system", "connection", "add", + "QA-UDS", + "unix:///run/podman/podman.sock", + } + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.Out.Contents()).Should(BeEmpty()) + + Expect(config.ReadCustomConfig()).Should(VerifyService( + "QA-UDS", + "unix:///run/podman/podman.sock", + "", + )) + + cmd = []string{"system", "connection", "add", + "QA-UDS1", + "--socket-path", "/run/user/podman/podman.sock", + "unix:///run/podman/podman.sock", + } session = podmanTest.Podman(cmd) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - Expect(session.Out).Should(Say("")) + Expect(session.Out.Contents()).Should(BeEmpty()) - cfg, err := config.ReadCustomConfig() - Expect(err).ShouldNot(HaveOccurred()) - Expect(cfg.Engine.ActiveService).To(BeEmpty()) - Expect(cfg.Engine.ServiceDestinations).To(BeEmpty()) - } - }) + Expect(config.ReadCustomConfig()).Should(HaveActiveService("QA-UDS")) + Expect(config.ReadCustomConfig()).Should(VerifyService( + "QA-UDS1", + "unix:///run/user/podman/podman.sock", + "", + )) + }) - It("default", func() { - for _, name := range []string{"devl", "qe"} { + It("add tcp", func() { cmd := []string{"system", "connection", "add", + "QA-TCP", + "tcp://localhost:8888", + } + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.Out.Contents()).Should(BeEmpty()) + + Expect(config.ReadCustomConfig()).Should(VerifyService( + "QA-TCP", + "tcp://localhost:8888", + "", + )) + }) + + It("remove", func() { + session := podmanTest.Podman([]string{"system", "connection", "add", + "--default", + "--identity", "~/.ssh/id_rsa", + "QA", + "ssh://root@server.fubar.com:2222/run/podman/podman.sock", + }) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + // two passes to test that removing non-existent connection is not an error + for i := 0; i < 2; i++ { + session = podmanTest.Podman([]string{"system", "connection", "remove", "QA"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.Out.Contents()).Should(BeEmpty()) + + cfg, err := config.ReadCustomConfig() + Expect(err).ShouldNot(HaveOccurred()) + Expect(cfg.Engine.ActiveService).To(BeEmpty()) + Expect(cfg.Engine.ServiceDestinations).To(BeEmpty()) + } + }) + + It("remove --all", func() { + session := podmanTest.Podman([]string{"system", "connection", "add", "--default", "--identity", "~/.ssh/id_rsa", - name, + "QA", "ssh://root@server.fubar.com:2222/run/podman/podman.sock", + }) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"system", "connection", "remove", "--all"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.Out.Contents()).Should(BeEmpty()) + Expect(session.Err.Contents()).Should(BeEmpty()) + + session = podmanTest.Podman([]string{"system", "connection", "list"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + }) + + It("default", func() { + for _, name := range []string{"devl", "qe"} { + cmd := []string{"system", "connection", "add", + "--default", + "--identity", "~/.ssh/id_rsa", + name, + "ssh://root@server.fubar.com:2222/run/podman/podman.sock", + } + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) } + + cmd := []string{"system", "connection", "default", "devl"} session := podmanTest.Podman(cmd) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - } + Expect(session.Out.Contents()).Should(BeEmpty()) - cmd := []string{"system", "connection", "default", "devl"} - session := podmanTest.Podman(cmd) - session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(0)) - Expect(session.Out).Should(Say("")) - - cfg, err := config.ReadCustomConfig() - Expect(err).ShouldNot(HaveOccurred()) - Expect(cfg.Engine.ActiveService).To(Equal("devl")) - - cmd = []string{"system", "connection", "list"} - session = podmanTest.Podman(cmd) - session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(0)) - Expect(session.Out).Should(Say("Name *URI *Identity *Default")) - - cmd = []string{"system", "connection", "list", "--format", "{{.Name}}"} - session = podmanTest.Podman(cmd) - session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(0)) - Expect(session.OutputToString()).Should(Equal("devl qe")) - }) + Expect(config.ReadCustomConfig()).Should(HaveActiveService("devl")) - It("failed default", func() { - cmd := []string{"system", "connection", "default", "devl"} - session := podmanTest.Podman(cmd) - session.WaitWithDefaultTimeout() - Expect(session).ShouldNot(Exit(0)) - Expect(session.Err).Should(Say("destination is not defined")) - }) + cmd = []string{"system", "connection", "list"} + session = podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.Out).Should(Say("Name *URI *Identity *Default")) + + cmd = []string{"system", "connection", "list", "--format", "{{.Name}}"} + session = podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(Equal("devl qe")) + }) + + It("failed default", func() { + cmd := []string{"system", "connection", "default", "devl"} + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).ShouldNot(Exit(0)) + Expect(session.Err).Should(Say("destination is not defined")) + }) + + It("failed rename", func() { + cmd := []string{"system", "connection", "rename", "devl", "QE"} + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).ShouldNot(Exit(0)) + Expect(session.Err).Should(Say("destination is not defined")) + }) - It("failed rename", func() { - cmd := []string{"system", "connection", "rename", "devl", "QE"} - session := podmanTest.Podman(cmd) - session.WaitWithDefaultTimeout() - Expect(session).ShouldNot(Exit(0)) - Expect(session.Err).Should(Say("destination is not defined")) + It("empty list", func() { + cmd := []string{"system", "connection", "list"} + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(len(session.OutputToStringArray())).Should(Equal(1)) + Expect(session.Err.Contents()).Should(BeEmpty()) + }) }) - It("empty list", func() { - cmd := []string{"system", "connection", "list"} - session := podmanTest.Podman(cmd) - session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(0)) - Expect(session.Out).Should(Say("")) - Expect(session.Err).Should(Say("")) + Context("sshd and API services required", func() { + BeforeEach(func() { + // These tests are unique in as much as they require podman, podman-remote, systemd and sshd. + // podman-remote commands will be executed by ginkgo directly. + SkipIfContainerized("sshd is not available when running in a container") + SkipIfRemote("connection heuristic requires both podman and podman-remote binaries") + SkipIfNotRootless("FIXME: setup ssh keys when root") + SkipIfSystemdNotRunning("cannot test connection heuristic if systemd is not running") + SkipIfNotActive("sshd", "cannot test connection heuristic if sshd is not running") + }) + + It("add ssh:// socket path using connection heuristic", func() { + u, err := user.Current() + Expect(err).ShouldNot(HaveOccurred()) + + cmd := exec.Command(podmanTest.RemotePodmanBinary, + "system", "connection", "add", + "--default", + "--identity", filepath.Join(u.HomeDir, ".ssh", "id_ed25519"), + "QA", + fmt.Sprintf("ssh://%s@localhost", u.Username)) + + session, err := Start(cmd, GinkgoWriter, GinkgoWriter) + Expect(err).ToNot(HaveOccurred(), fmt.Sprintf("%q failed to execute", podmanTest.RemotePodmanBinary)) + Eventually(session, DefaultWaitTimeout).Should(Exit(0)) + Expect(session.Out.Contents()).Should(BeEmpty()) + Expect(session.Err.Contents()).Should(BeEmpty()) + + uri := url.URL{ + Scheme: "ssh", + User: url.User(u.Username), + Host: "localhost:22", + Path: fmt.Sprintf("/run/user/%s/podman/podman.sock", u.Uid), + } + + Expect(config.ReadCustomConfig()).Should(HaveActiveService("QA")) + Expect(config.ReadCustomConfig()).Should(VerifyService( + "QA", + uri.String(), + filepath.Join(u.HomeDir, ".ssh", "id_ed25519"), + )) + }) }) }) diff --git a/test/e2e/trust_test.go b/test/e2e/trust_test.go index 7f97f280a..b591e1c02 100644 --- a/test/e2e/trust_test.go +++ b/test/e2e/trust_test.go @@ -14,7 +14,8 @@ import ( var _ = Describe("Podman trust", func() { var ( - tempdir string + tempdir string + err error podmanTest *PodmanTestIntegration ) @@ -38,21 +39,17 @@ var _ = Describe("Podman trust", func() { }) It("podman image trust show", func() { - path, err := os.Getwd() - if err != nil { - os.Exit(1) - } - session := podmanTest.Podman([]string{"image", "trust", "show", "--registrypath", filepath.Dir(path), "--policypath", filepath.Join(filepath.Dir(path), "policy.json")}) + session := podmanTest.Podman([]string{"image", "trust", "show", "--registrypath", filepath.Join(INTEGRATION_ROOT, "test"), "--policypath", filepath.Join(INTEGRATION_ROOT, "test/policy.json")}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) outArray := session.OutputToStringArray() Expect(len(outArray)).To(Equal(3)) - // image order is not guaranteed. All we can do is check that - // these strings appear in output, we can't cross-check them. - Expect(session.OutputToString()).To(ContainSubstring("accept")) - Expect(session.OutputToString()).To(ContainSubstring("reject")) - Expect(session.OutputToString()).To(ContainSubstring("signed")) + // Repository order is not guaranteed. So, check that + // all expected lines appear in output; we also check total number of lines, so that handles all of them. + Expect(string(session.Out.Contents())).To(MatchRegexp(`(?m)^default\s+accept\s*$`)) + Expect(string(session.Out.Contents())).To(MatchRegexp(`(?m)^docker.io/library/hello-world\s+reject\s*$`)) + Expect(string(session.Out.Contents())).To(MatchRegexp(`(?m)^registry.access.redhat.com\s+signedBy\s+security@redhat.com, security@redhat.com\s+https://access.redhat.com/webassets/docker/content/sigstore\s*$`)) }) It("podman image trust set", func() { @@ -76,24 +73,52 @@ var _ = Describe("Podman trust", func() { }) It("podman image trust show --json", func() { - session := podmanTest.Podman([]string{"image", "trust", "show", "--json"}) + session := podmanTest.Podman([]string{"image", "trust", "show", "--registrypath", filepath.Join(INTEGRATION_ROOT, "test"), "--policypath", filepath.Join(INTEGRATION_ROOT, "test/policy.json"), "--json"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(session.IsJSONOutputValid()).To(BeTrue()) var teststruct []map[string]string json.Unmarshal(session.Out.Contents(), &teststruct) - Expect(teststruct[0]["name"]).To(Equal("* (default)")) - Expect(teststruct[0]["repo_name"]).To(Equal("default")) - Expect(teststruct[0]["type"]).To(Equal("accept")) - Expect(teststruct[1]["type"]).To(Equal("insecureAcceptAnything")) + Expect(len(teststruct)).To(Equal(3)) + // To ease comparison, group the unordered array of repos by repo (and we expect only one entry by repo, so order within groups doesn’t matter) + repoMap := map[string][]map[string]string{} + for _, e := range teststruct { + key := e["name"] + repoMap[key] = append(repoMap[key], e) + } + Expect(repoMap).To(Equal(map[string][]map[string]string{ + "* (default)": {{ + "name": "* (default)", + "repo_name": "default", + "sigstore": "", + "transport": "", + "type": "accept", + }}, + "docker.io/library/hello-world": {{ + "name": "docker.io/library/hello-world", + "repo_name": "docker.io/library/hello-world", + "sigstore": "", + "transport": "", + "type": "reject", + }}, + "registry.access.redhat.com": {{ + "name": "registry.access.redhat.com", + "repo_name": "registry.access.redhat.com", + "sigstore": "https://access.redhat.com/webassets/docker/content/sigstore", + "transport": "", + "type": "signedBy", + "gpg_id": "security@redhat.com, security@redhat.com", + }}, + })) }) It("podman image trust show --raw", func() { - session := podmanTest.Podman([]string{"image", "trust", "show", "--raw"}) + session := podmanTest.Podman([]string{"image", "trust", "show", "--policypath", filepath.Join(INTEGRATION_ROOT, "test/policy.json"), "--raw"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) + contents, err := ioutil.ReadFile(filepath.Join(INTEGRATION_ROOT, "test/policy.json")) + Expect(err).ShouldNot(HaveOccurred()) Expect(session.IsJSONOutputValid()).To(BeTrue()) - Expect(session.OutputToString()).To(ContainSubstring("default")) - Expect(session.OutputToString()).To(ContainSubstring("insecureAcceptAnything")) + Expect(string(session.Out.Contents())).To(Equal(string(contents) + "\n")) }) }) diff --git a/test/e2e/unshare_test.go b/test/e2e/unshare_test.go index 79ce68e89..cf1b8db53 100644 --- a/test/e2e/unshare_test.go +++ b/test/e2e/unshare_test.go @@ -51,7 +51,7 @@ var _ = Describe("Podman unshare", func() { }) It("podman unshare --rootles-cni", func() { - session := podmanTest.Podman([]string{"unshare", "--rootless-cni", "ip", "addr"}) + session := podmanTest.Podman([]string{"unshare", "--rootless-netns", "ip", "addr"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(session.OutputToString()).To(ContainSubstring("tap0")) diff --git a/test/python/docker/compat/test_containers.py b/test/python/docker/compat/test_containers.py index 1ad1e7f15..e6f7d992d 100644 --- a/test/python/docker/compat/test_containers.py +++ b/test/python/docker/compat/test_containers.py @@ -251,3 +251,17 @@ class TestContainers(unittest.TestCase): ctr.start() ret, out = ctr.exec_run(["stat", "-c", "%u:%g", "/workspace"]) self.assertEqual(out.rstrip(), b'1042:1043', "UID/GID set in dockerfile") + + + def test_non_existant_workdir(self): + dockerfile = (B'FROM quay.io/libpod/alpine:latest\n' + B'USER root\n' + B'WORKDIR /workspace/scratch\n' + B'RUN touch test') + img: Image + img, out = self.client.images.build(fileobj=io.BytesIO(dockerfile)) + ctr: Container = self.client.containers.create(image=img.id, detach=True, command="top", + volumes=["test_non_existant_workdir:/workspace"]) + ctr.start() + ret, out = ctr.exec_run(["stat", "/workspace/scratch/test"]) + self.assertEqual(ret, 0, "Working directory created if it doesn't exist") diff --git a/test/system/001-basic.bats b/test/system/001-basic.bats index 78b8ecdfd..03f07d602 100644 --- a/test/system/001-basic.bats +++ b/test/system/001-basic.bats @@ -120,9 +120,7 @@ function setup() { fi run_podman 125 --remote - is "$output" "Error: missing command 'podman COMMAND' -Try 'podman --help' for more information." \ - "podman --remote show usage message without running endpoint" + is "$output" ".*Usage:" "podman --remote show usage message without running endpoint" } # This is for development only; it's intended to make sure our timeout diff --git a/test/system/015-help.bats b/test/system/015-help.bats index b0795b524..a87081687 100644 --- a/test/system/015-help.bats +++ b/test/system/015-help.bats @@ -149,12 +149,12 @@ function check_help() { count=$(expr $count + 1) done - # Any command that takes subcommands, must throw error if called + # Any command that takes subcommands, prints its help and errors if called # without one. dprint "podman $@" run_podman '?' "$@" is "$status" 125 "'podman $*' without any subcommand - exit status" - is "$output" "Error: missing command .*$@ COMMAND" \ + is "$output" ".*Usage:.*Error: missing command '.*$@ COMMAND'" \ "'podman $*' without any subcommand - expected error message" # Assume that 'NoSuchCommand' is not a command diff --git a/test/system/035-logs.bats b/test/system/035-logs.bats index 44b66676e..7fb3e62e4 100644 --- a/test/system/035-logs.bats +++ b/test/system/035-logs.bats @@ -30,6 +30,17 @@ load helpers run_podman rm $cid } +function _additional_events_backend() { + local driver=$1 + # Since PR#10431, 'logs -f' with journald driver is only supported with journald events backend. + if [[ $driver = "journald" ]]; then + run_podman info --format '{{.Host.EventLogger}}' >/dev/null + if [[ $output != "journald" ]]; then + echo "--events-backend journald" + fi + fi +} + function _log_test_multi() { local driver=$1 @@ -42,10 +53,12 @@ function _log_test_multi() { etc='.*' fi + local events_backend=$(_additional_events_backend $driver) + # Simple helper to make the container starts, below, easier to read local -a cid doit() { - run_podman run --log-driver=$driver --rm -d --name "$1" $IMAGE sh -c "$2"; + run_podman ${events_backend} run --log-driver=$driver --rm -d --name "$1" $IMAGE sh -c "$2"; cid+=($(echo "${output:0:12}")) } @@ -57,7 +70,7 @@ function _log_test_multi() { doit c1 "echo a;sleep 10;echo d;sleep 3" doit c2 "sleep 1;echo b;sleep 2;echo c;sleep 3" - run_podman logs -f c1 c2 + run_podman ${events_backend} logs -f c1 c2 is "$output" \ "${cid[0]} a$etc ${cid[1]} b$etc @@ -187,15 +200,20 @@ function _log_test_follow() { contentA=$(random_string) contentB=$(random_string) contentC=$(random_string) + local events_backend=$(_additional_events_backend $driver) + + if [[ -n "${events_backend}" ]]; then + skip_if_remote "remote does not support --events-backend" + fi # Note: it seems we need at least three log lines to hit #11461. - run_podman run --log-driver=$driver --name $cname $IMAGE sh -c "echo $contentA; echo $contentB; echo $contentC" - run_podman logs -f $cname + run_podman ${events_backend} run --log-driver=$driver --name $cname $IMAGE sh -c "echo $contentA; echo $contentB; echo $contentC" + run_podman ${events_backend} logs -f $cname is "$output" "$contentA $contentB $contentC" "logs -f on exitted container works" - run_podman rm -t 0 -f $cname + run_podman ${events_backend} rm -t 0 -f $cname } @test "podman logs - --follow k8s-file" { diff --git a/test/system/070-build.bats b/test/system/070-build.bats index 1d24eef15..3c47b1f5b 100644 --- a/test/system/070-build.bats +++ b/test/system/070-build.bats @@ -39,6 +39,7 @@ EOF cat >$dockerfile <<EOF FROM $IMAGE RUN echo $rand_content > /$rand_filename +VOLUME /a/b/c VOLUME ['/etc/foo', '/etc/bar'] EOF @@ -56,6 +57,25 @@ EOF /\[/etc/foo, /etc/bar]" "weird VOLUME gets converted to directories with brackets and comma" + # Now confirm that each volume got a unique device ID + run_podman run --rm build_test stat -c '%D' / /a /a/b /a/b/c /\[ /\[/etc /\[/etc/foo, /etc /etc/bar\] + # First, the non-volumes should all be the same... + is "${lines[0]}" "${lines[1]}" "devnum( / ) = devnum( /a )" + is "${lines[0]}" "${lines[2]}" "devnum( / ) = devnum( /a/b )" + is "${lines[0]}" "${lines[4]}" "devnum( / ) = devnum( /[ )" + is "${lines[0]}" "${lines[5]}" "devnum( / ) = devnum( /[etc )" + is "${lines[0]}" "${lines[7]}" "devnum( / ) = devnum( /etc )" + is "${lines[6]}" "${lines[8]}" "devnum( /[etc/foo, ) = devnum( /etc/bar] )" + # ...then, each volume should be different + if [[ "${lines[0]}" = "${lines[3]}" ]]; then + die "devnum( / ) (${lines[0]}) = devnum( volume0 ) (${lines[3]}) -- they should differ" + fi + if [[ "${lines[0]}" = "${lines[6]}" ]]; then + die "devnum( / ) (${lines[0]}) = devnum( volume1 ) (${lines[6]}) -- they should differ" + fi + # FIXME: is this expected? I thought /a/b/c and /[etc/foo, would differ + is "${lines[3]}" "${lines[6]}" "devnum( volume0 ) = devnum( volume1 )" + run_podman rmi -f build_test } diff --git a/test/system/120-load.bats b/test/system/120-load.bats index e9959271f..a5508b2f4 100644 --- a/test/system/120-load.bats +++ b/test/system/120-load.bats @@ -126,6 +126,26 @@ verify_iid_and_name() { verify_iid_and_name $img_name } +@test "podman load - from URL" { + get_iid_and_name + run_podman save $img_name -o $archive + run_podman rmi $iid + + HOST_PORT=$(random_free_port) + SERVER=http://127.0.0.1:$HOST_PORT + + # Bind-mount the archive to a container running httpd + run_podman run -d --name myweb -p "$HOST_PORT:80" \ + -v $archive:/var/www/image.tar:Z \ + -w /var/www \ + $IMAGE /bin/busybox-extras httpd -f -p 80 + + run_podman load -i $SERVER/image.tar + verify_iid_and_name $img_name + + run_podman rm -f -t0 myweb +} + @test "podman load - redirect corrupt payload" { run_podman 125 load <<< "Danger, Will Robinson!! This is a corrupt tarball!" is "$output" \ diff --git a/test/system/130-kill.bats b/test/system/130-kill.bats index d85f0a6a9..1ff3a7b61 100644 --- a/test/system/130-kill.bats +++ b/test/system/130-kill.bats @@ -6,9 +6,22 @@ load helpers @test "podman kill - test signal handling in containers" { + + # Prepare for 'logs -f' + run_podman info --format '{{.Host.LogDriver}}' + log_driver=$output + run_podman info --format '{{.Host.EventLogger}}' + event_logger=$output + opt_log_driver= + if [ $log_driver = "journald" ] && [ $event_logger != "journald" ]; then + # Since PR#10431, 'logs -f' with journald driver is only supported with journald events backend. + # Set '--log driver' temporally because remote doesn't support '--events-backend'. + opt_log_driver="--log-driver k8s-file" + fi + # Start a container that will handle all signals by emitting 'got: N' local -a signals=(1 2 3 4 5 6 8 10 12 13 14 15 16 20 21 22 23 24 25 26 64) - run_podman run -d $IMAGE sh -c \ + run_podman run -d ${opt_log_driver} $IMAGE sh -c \ "for i in ${signals[*]}; do trap \"echo got: \$i\" \$i; done; echo READY; while ! test -e /stop; do sleep 0.05; done; diff --git a/test/system/200-pod.bats b/test/system/200-pod.bats index 09a419914..60cfbc9dd 100644 --- a/test/system/200-pod.bats +++ b/test/system/200-pod.bats @@ -60,6 +60,25 @@ function teardown() { run_podman pod rm -f -t 0 $podid } + +@test "podman pod create - custom infra image" { + image="i.do/not/exist:image" + + tmpdir=$PODMAN_TMPDIR/pod-test + run mkdir -p $tmpdir + containersconf=$tmpdir/containers.conf + cat >$containersconf <<EOF +[engine] +infra_image="$image" +EOF + + run_podman 125 pod create --infra-image $image + is "$output" ".*initializing source docker://$image:.*" + + CONTAINERS_CONF=$containersconf run_podman 125 pod create + is "$output" ".*initializing source docker://$image:.*" +} + function rm_podman_pause_image() { run_podman version --format "{{.Server.Version}}-{{.Server.Built}}" run_podman rmi -f "localhost/podman-pause:$output" diff --git a/test/system/272-system-connection.bats b/test/system/272-system-connection.bats index 5a90d9398..4e9ac4dd6 100644 --- a/test/system/272-system-connection.bats +++ b/test/system/272-system-connection.bats @@ -34,10 +34,7 @@ function teardown() { | xargs -l1 --no-run-if-empty umount # Remove all system connections - run_podman system connection ls --format json - while read name; do - run_podman system connection rm "$name" - done < <(jq -r '.[].Name' <<<"$output") + run_podman system connection rm --all basic_teardown } @@ -53,7 +50,8 @@ function _run_podman_remote() { # Very basic test, does not actually connect at any time @test "podman system connection - basic add / ls / remove" { run_podman system connection ls - is "$output" "" "system connection ls: no connections" + is "$output" "Name URI Identity Default" \ + "system connection ls: no connections" c1="c1_$(random_string 15)" c2="c2_$(random_string 15)" diff --git a/test/system/500-networking.bats b/test/system/500-networking.bats index cb73cf24d..21350ed36 100644 --- a/test/system/500-networking.bats +++ b/test/system/500-networking.bats @@ -78,6 +78,9 @@ load helpers if [[ -z $cidr ]]; then # regex to match that we are in 10.X subnet match="10\..*" + # force bridge networking also for rootless + # this ensures that rootless + bridge + userns + ports works + network_arg="--network bridge" else # Issue #9828 make sure a custom slir4netns cidr also works network_arg="--network slirp4netns:cidr=$cidr" @@ -167,6 +170,13 @@ load helpers $IMAGE nc -l -n -v -p $myport cid="$output" + # FIXME: debugging for #11871 + run_podman exec $cid cat /etc/resolv.conf + if is_rootless; then + run_podman unshare --rootless-cni cat /etc/resolv.conf + fi + ps uxww + # check that dns is working inside the container run_podman exec $cid nslookup google.com @@ -420,6 +430,7 @@ load helpers is "$output" "[${cid:0:12}]" "short container id in network aliases" run_podman network disconnect $netname $cid + is "$output" "" "Output should be empty (no errors)" # check that we cannot curl (timeout after 3 sec) run curl --max-time 3 -s $SERVER/index.txt @@ -428,6 +439,7 @@ load helpers fi run_podman network connect $netname $cid + is "$output" "" "Output should be empty (no errors)" # curl should work again run curl --max-time 3 -s $SERVER/index.txt @@ -444,8 +456,23 @@ load helpers die "MAC address did not change after podman network disconnect/connect" fi + # Disconnect/reconnect of a container *with no ports* should succeed quietly + run_podman network disconnect $netname $background_cid + is "$output" "" "disconnect of container with no open ports" + run_podman network connect $netname $background_cid + is "$output" "" "(re)connect of container with no open ports" + + # FIXME FIXME FIXME: #11825: bodhi tests are failing, remote+rootless only, + # with "dnsmasq: failed to create inotify". This error has never occurred + # in CI, and Ed has been unable to reproduce it on 1minutetip. This next + # line is a suggestion from Paul Holzinger for trying to shed light on + # the system context before the failure. This output will be invisible + # if the test passes. + for foo in /proc/\*/fd/*; do readlink -f $foo; done |grep '^/proc/.*inotify' |cut -d/ -f3 | xargs -I '{}' -- ps --no-headers -o '%p %U %a' -p '{}' |uniq -c |sort -n + # connect a second network run_podman network connect $netname2 $cid + is "$output" "" "Output should be empty (no errors)" # check network2 alias for container short id run_podman inspect $cid --format "{{(index .NetworkSettings.Networks \"$netname2\").Aliases}}" diff --git a/test/system/700-play.bats b/test/system/700-play.bats index 8cf279ada..c3e5e9354 100644 --- a/test/system/700-play.bats +++ b/test/system/700-play.bats @@ -76,6 +76,12 @@ RELABEL="system_u:object_r:container_file_t:s0" is "$output" "${RELABEL} $TESTDIR" "selinux relabel should have happened" fi + # Make sure that the K8s pause image isn't pulled but the local podman-pause is built. + run_podman images + run_podman 1 image exists k8s.gcr.io/pause + run_podman version --format "{{.Server.Version}}-{{.Server.Built}}" + run_podman image exists localhost/podman-pause:$output + run_podman stop -a -t 0 run_podman pod rm -t 0 -f test_pod } diff --git a/test/upgrade/test-upgrade.bats b/test/upgrade/test-upgrade.bats index 5cb302a85..c0d601586 100644 --- a/test/upgrade/test-upgrade.bats +++ b/test/upgrade/test-upgrade.bats @@ -97,8 +97,10 @@ podman \$opts run --name myfailedcontainer --label mylabel=$LABEL_FAILED \ podman \$opts run -d --name myrunningcontainer --label mylabel=$LABEL_RUNNING \ --network bridge \ -p $HOST_PORT:80 \ + -p 127.0.0.1:8080-8082:8080-8082 \ -v $pmroot/var/www:/var/www \ -w /var/www \ + --mac-address aa:bb:cc:dd:ee:ff \ $IMAGE /bin/busybox-extras httpd -f -p 80 podman \$opts pod create --name mypod @@ -121,8 +123,15 @@ EOF # Clean up vestiges of previous run $PODMAN rm -f podman_parent || true - # Not entirely a NOP! This is just so we get /run/crun created on a CI VM - $PODMAN run --rm $OLD_PODMAN true + + local netname=testnet-$(random_string 10) + $PODMAN network create $netname + + # Not entirely a NOP! This is just so we get the /run/... mount points created on a CI VM + # --mac-address is needed to create /run/cni, --network is needed to create /run/containers for dnsname + $PODMAN run --rm --mac-address 78:28:a6:8d:24:8a --network $netname $OLD_PODMAN true + $PODMAN network rm -f $netname + # # Use new-podman to run the above script under old-podman. @@ -134,7 +143,8 @@ EOF # # mount /etc/containers/storage.conf to use the same storage settings as on the host # mount /dev/shm because the container locks are stored there - # mount /var/lib/cni and /etc/cni/net.d for cni networking + # mount /var/lib/cni, /run/cni and /etc/cni/net.d for cni networking + # mount /run/containers for the dnsname plugin # $PODMAN run -d --name podman_parent --pid=host \ --privileged \ @@ -145,6 +155,8 @@ EOF -v /dev/fuse:/dev/fuse \ -v /run/crun:/run/crun \ -v /run/netns:/run/netns:rshared \ + -v /run/containers:/run/containers \ + -v /run/cni:/run/cni \ -v /var/lib/cni:/var/lib/cni \ -v /etc/cni/net.d:/etc/cni/net.d \ -v /dev/shm:/dev/shm \ @@ -185,7 +197,7 @@ EOF is "${lines[1]}" "mycreatedcontainer--Created----$LABEL_CREATED" "created" is "${lines[2]}" "mydonecontainer--Exited (0).*----<no value>" "done" is "${lines[3]}" "myfailedcontainer--Exited (17) .*----$LABEL_FAILED" "fail" - is "${lines[4]}" "myrunningcontainer--Up .*--0.0.0.0:$HOST_PORT->80/tcp--$LABEL_RUNNING" "running" + is "${lines[4]}" "myrunningcontainer--Up .*--0\.0\.0\.0:$HOST_PORT->80\/tcp, 127\.0\.0\.1\:8080-8082->8080-8082\/tcp--$LABEL_RUNNING" "running" # For debugging: dump containers and IDs if [[ -n "$PODMAN_UPGRADE_TEST_DEBUG" ]]; then diff --git a/test/utils/matchers.go b/test/utils/matchers.go index 07c1232e7..17ff3ea75 100644 --- a/test/utils/matchers.go +++ b/test/utils/matchers.go @@ -2,57 +2,164 @@ package utils import ( "fmt" + "net/url" + "github.com/containers/common/pkg/config" + . "github.com/onsi/gomega" "github.com/onsi/gomega/format" "github.com/onsi/gomega/gexec" + "github.com/onsi/gomega/matchers" + "github.com/onsi/gomega/types" ) +// HaveActiveService verifies the given service is the active service +func HaveActiveService(name interface{}) OmegaMatcher { + return WithTransform( + func(cfg *config.Config) string { + return cfg.Engine.ActiveService + }, + Equal(name)) +} + +type ServiceMatcher struct { + types.GomegaMatcher + Name interface{} + URI interface{} + Identity interface{} + failureMessage string + negatedFailureMessage string +} + +func VerifyService(name, uri, identity interface{}) OmegaMatcher { + return &ServiceMatcher{ + Name: name, + URI: uri, + Identity: identity, + } +} + +func (matcher *ServiceMatcher) Match(actual interface{}) (success bool, err error) { + cfg, ok := actual.(*config.Config) + if !ok { + return false, fmt.Errorf("ServiceMatcher matcher expects a config.Config") + } + + if _, err = url.Parse(matcher.URI.(string)); err != nil { + return false, err + } + + success, err = HaveKey(matcher.Name).Match(cfg.Engine.ServiceDestinations) + if !success || err != nil { + matcher.failureMessage = HaveKey(matcher.Name).FailureMessage(cfg.Engine.ServiceDestinations) + matcher.negatedFailureMessage = HaveKey(matcher.Name).NegatedFailureMessage(cfg.Engine.ServiceDestinations) + return + } + + sd := cfg.Engine.ServiceDestinations[matcher.Name.(string)] + success, err = Equal(matcher.URI).Match(sd.URI) + if !success || err != nil { + matcher.failureMessage = Equal(matcher.URI).FailureMessage(sd.URI) + matcher.negatedFailureMessage = Equal(matcher.URI).NegatedFailureMessage(sd.URI) + return + } + + success, err = Equal(matcher.Identity).Match(sd.Identity) + if !success || err != nil { + matcher.failureMessage = Equal(matcher.Identity).FailureMessage(sd.Identity) + matcher.negatedFailureMessage = Equal(matcher.Identity).NegatedFailureMessage(sd.Identity) + return + } + + return true, nil +} + +func (matcher *ServiceMatcher) FailureMessage(_ interface{}) string { + return matcher.failureMessage +} + +func (matcher *ServiceMatcher) NegatedFailureMessage(_ interface{}) string { + return matcher.negatedFailureMessage +} + +type URLMatcher struct { + matchers.EqualMatcher +} + +// VerifyURL matches when actual is a valid URL and matches expected +func VerifyURL(uri interface{}) OmegaMatcher { + return &URLMatcher{matchers.EqualMatcher{Expected: uri}} +} + +func (matcher *URLMatcher) Match(actual interface{}) (bool, error) { + e, ok := matcher.Expected.(string) + if !ok { + return false, fmt.Errorf("VerifyURL requires string inputs %T is not supported", matcher.Expected) + } + e_uri, err := url.Parse(e) + if err != nil { + return false, err + } + + a, ok := actual.(string) + if !ok { + return false, fmt.Errorf("VerifyURL requires string inputs %T is not supported", actual) + } + a_uri, err := url.Parse(a) + if err != nil { + return false, err + } + + return (&matchers.EqualMatcher{Expected: e_uri}).Match(a_uri) +} + +type ExitMatcher struct { + types.GomegaMatcher + Expected int + Actual int +} + // ExitWithError matches when assertion is > argument. Default 0 -// Modeled after the gomega Exit() matcher -func ExitWithError(optionalExitCode ...int) *exitMatcher { +// Modeled after the gomega Exit() matcher and also operates on sessions. +func ExitWithError(optionalExitCode ...int) *ExitMatcher { exitCode := 0 if len(optionalExitCode) > 0 { exitCode = optionalExitCode[0] } - return &exitMatcher{exitCode: exitCode} + return &ExitMatcher{Expected: exitCode} } -type exitMatcher struct { - exitCode int - actualExitCode int -} - -func (m *exitMatcher) Match(actual interface{}) (success bool, err error) { +// Match follows gexec.Matcher interface +func (matcher *ExitMatcher) Match(actual interface{}) (success bool, err error) { exiter, ok := actual.(gexec.Exiter) if !ok { return false, fmt.Errorf("ExitWithError must be passed a gexec.Exiter (Missing method ExitCode() int) Got:\n#{format.Object(actual, 1)}") } - m.actualExitCode = exiter.ExitCode() - if m.actualExitCode == -1 { + matcher.Actual = exiter.ExitCode() + if matcher.Actual == -1 { return false, nil } - return m.actualExitCode > m.exitCode, nil + return matcher.Actual > matcher.Expected, nil } -func (m *exitMatcher) FailureMessage(actual interface{}) (message string) { - if m.actualExitCode == -1 { +func (matcher *ExitMatcher) FailureMessage(_ interface{}) (message string) { + if matcher.Actual == -1 { return "Expected process to exit. It did not." } - return format.Message(m.actualExitCode, "to be greater than exit code:", m.exitCode) + return format.Message(matcher.Actual, "to be greater than exit code: ", matcher.Expected) } -func (m *exitMatcher) NegatedFailureMessage(actual interface{}) (message string) { - if m.actualExitCode == -1 { +func (matcher *ExitMatcher) NegatedFailureMessage(_ interface{}) (message string) { + switch { + case matcher.Actual == -1: return "you really shouldn't be able to see this!" - } else { - if m.exitCode == -1 { - return "Expected process not to exit. It did." - } - return format.Message(m.actualExitCode, "is less than or equal to exit code:", m.exitCode) + case matcher.Expected == -1: + return "Expected process not to exit. It did." } + return format.Message(matcher.Actual, "is less than or equal to exit code: ", matcher.Expected) } -func (m *exitMatcher) MatchMayChangeInTheFuture(actual interface{}) bool { + +func (matcher *ExitMatcher) MatchMayChangeInTheFuture(actual interface{}) bool { session, ok := actual.(*gexec.Session) if ok { return session.ExitCode() == -1 diff --git a/test/utils/podmantest_test.go b/test/utils/podmantest_test.go index 9bd1c4a66..1bb9ecb6b 100644 --- a/test/utils/podmantest_test.go +++ b/test/utils/podmantest_test.go @@ -23,7 +23,7 @@ var _ = Describe("PodmanTest test", func() { FakeOutputs["check"] = []string{"check"} os.Setenv("HOOK_OPTION", "hook_option") env := os.Environ() - session := podmanTest.PodmanAsUserBase([]string{"check"}, 1000, 1000, "", env, true, false, nil) + session := podmanTest.PodmanAsUserBase([]string{"check"}, 1000, 1000, "", env, true, false, nil, nil) os.Unsetenv("HOOK_OPTION") session.WaitWithDefaultTimeout() Expect(session.Command.Process).ShouldNot(BeNil()) diff --git a/test/utils/utils.go b/test/utils/utils.go index bfefc58ec..8d1edb23a 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -66,27 +66,31 @@ func (p *PodmanTest) MakeOptions(args []string, noEvents, noCache bool) []string // PodmanAsUserBase exec podman as user. uid and gid is set for credentials usage. env is used // to record the env for debugging -func (p *PodmanTest) PodmanAsUserBase(args []string, uid, gid uint32, cwd string, env []string, noEvents, noCache bool, extraFiles []*os.File) *PodmanSession { +func (p *PodmanTest) PodmanAsUserBase(args []string, uid, gid uint32, cwd string, env []string, noEvents, noCache bool, wrapper []string, extraFiles []*os.File) *PodmanSession { var command *exec.Cmd podmanOptions := p.MakeOptions(args, noEvents, noCache) podmanBinary := p.PodmanBinary if p.RemoteTest { podmanBinary = p.RemotePodmanBinary } + + runCmd := append(wrapper, podmanBinary) if p.RemoteTest { podmanOptions = append([]string{"--remote", "--url", p.RemoteSocket}, podmanOptions...) } if env == nil { - fmt.Printf("Running: %s %s\n", podmanBinary, strings.Join(podmanOptions, " ")) + fmt.Printf("Running: %s %s\n", strings.Join(runCmd, " "), strings.Join(podmanOptions, " ")) } else { - fmt.Printf("Running: (env: %v) %s %s\n", env, podmanBinary, strings.Join(podmanOptions, " ")) + fmt.Printf("Running: (env: %v) %s %s\n", env, strings.Join(runCmd, " "), strings.Join(podmanOptions, " ")) } if uid != 0 || gid != 0 { pythonCmd := fmt.Sprintf("import os; import sys; uid = %d; gid = %d; cwd = '%s'; os.setgid(gid); os.setuid(uid); os.chdir(cwd) if len(cwd)>0 else True; os.execv(sys.argv[1], sys.argv[1:])", gid, uid, cwd) - nsEnterOpts := append([]string{"-c", pythonCmd, podmanBinary}, podmanOptions...) + runCmd = append(runCmd, podmanOptions...) + nsEnterOpts := append([]string{"-c", pythonCmd}, runCmd...) command = exec.Command("python", nsEnterOpts...) } else { - command = exec.Command(podmanBinary, podmanOptions...) + runCmd = append(runCmd, podmanOptions...) + command = exec.Command(runCmd[0], runCmd[1:]...) } if env != nil { command.Env = env @@ -106,7 +110,7 @@ func (p *PodmanTest) PodmanAsUserBase(args []string, uid, gid uint32, cwd string // PodmanBase exec podman with default env. func (p *PodmanTest) PodmanBase(args []string, noEvents, noCache bool) *PodmanSession { - return p.PodmanAsUserBase(args, 0, 0, "", nil, noEvents, noCache, nil) + return p.PodmanAsUserBase(args, 0, 0, "", nil, noEvents, noCache, nil, nil) } // WaitForContainer waits on a started container @@ -393,7 +397,7 @@ func tagOutputToMap(imagesOutput []string) map[string]map[string]bool { return m } -// GetHostDistributionInfo returns a struct with its distribution name and version +// GetHostDistributionInfo returns a struct with its distribution Name and version func GetHostDistributionInfo() HostOS { f, err := os.Open(OSReleasePath) defer f.Close() @@ -487,13 +491,3 @@ func RandomString(n int) string { } return string(b) } - -//SkipIfInContainer skips a test if the test is run inside a container -func SkipIfInContainer(reason string) { - if len(reason) < 5 { - panic("SkipIfInContainer must specify a reason to skip") - } - if os.Getenv("TEST_ENVIRON") == "container" { - Skip("[container]: " + reason) - } -} |