diff options
Diffstat (limited to 'test')
24 files changed, 557 insertions, 312 deletions
diff --git a/test/apiv2/12-imagesMore.at b/test/apiv2/12-imagesMore.at index 96eba1db5..67b4f1c79 100644 --- a/test/apiv2/12-imagesMore.at +++ b/test/apiv2/12-imagesMore.at @@ -26,7 +26,8 @@ t GET libpod/images/$IMAGE/json 200 \ .RepoTags[1]=localhost:5000/myrepo:mytag # Run registry container -podman run -d --name registry -p 5000:5000 quay.io/libpod/registry:2.6 /entrypoint.sh /etc/docker/registry/config.yml +# FIXME this fails if python tests have been run first... +podman run -d --name registry -p 5000:5000 quay.io/libpod/registry:2.7 /entrypoint.sh /etc/docker/registry/config.yml wait_for_port localhost 5000 # Push to local registry and check output @@ -58,7 +59,7 @@ t DELETE libpod/containers/registry?force=true 200 # Remove images t DELETE libpod/images/$IMAGE 200 \ .ExitCode=0 -t DELETE libpod/images/quay.io/libpod/registry:2.6 200 \ +t DELETE libpod/images/quay.io/libpod/registry:2.7 200 \ .ExitCode=0 if [ -z "${GOT_DIGEST}" ] ; then diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at index cc5eda88e..49f8fb3fc 100644 --- a/test/apiv2/20-containers.at +++ b/test/apiv2/20-containers.at @@ -394,7 +394,8 @@ t GET containers/$cid/json 200 \ .Config.Healthcheck.Retries=3 # compat api: Test for mount options support -payload='{"Mounts":[{"Type":"tmpfs","Target":"/mnt/scratch","TmpfsOptions":{"SizeBytes":1024,"Mode":755}}]}' +# Sigh, JSON can't handle octal. 0755(octal) = 493(decimal) +payload='{"Mounts":[{"Type":"tmpfs","Target":"/mnt/scratch","TmpfsOptions":{"SizeBytes":1024,"Mode":493}}]}' t POST containers/create Image=$IMAGE HostConfig="$payload" 201 .Id~[0-9a-f]\\{64\\} cid=$(jq -r '.Id' <<<"$output") t GET containers/$cid/json 200 \ diff --git a/test/apiv2/python/conftest.py b/test/apiv2/python/conftest.py new file mode 100644 index 000000000..54a267049 --- /dev/null +++ b/test/apiv2/python/conftest.py @@ -0,0 +1,8 @@ +""" +Configure pytest +""" + + +def pytest_report_header(config): + """Add header to report.""" + return "python client -- requests library" diff --git a/test/apiv2/python/requirements.txt b/test/apiv2/python/requirements.txt new file mode 100644 index 000000000..d9cfc687a --- /dev/null +++ b/test/apiv2/python/requirements.txt @@ -0,0 +1,5 @@ +requests-mock~=1.9.3 +requests~=2.20.0 +setuptools~=50.3.2 +python-dateutil~=2.8.1 +PyYAML~=5.4.1 diff --git a/test/apiv2/test-apiv2 b/test/apiv2/test-apiv2 index bd728e130..ff328cfc8 100755 --- a/test/apiv2/test-apiv2 +++ b/test/apiv2/test-apiv2 @@ -368,7 +368,7 @@ function start_service() { die "Cannot start service on non-localhost ($HOST)" fi - echo $WORKDIR + echo "rootdir: "$WORKDIR # Some tests use shortnames; force registry override to work around # docker.io throttling. # FIXME esm revisit pulling expected images re: shortnames caused tests to fail @@ -376,7 +376,7 @@ function start_service() { $PODMAN_BIN \ --root $WORKDIR/server_root --syslog=true \ system service \ - --time 15 \ + --time 0 \ tcp:127.0.0.1:$PORT \ &> $WORKDIR/server.log & service_pid=$! @@ -443,7 +443,7 @@ function start_registry() { -e REGISTRY_HTTP_TLS_KEY=/auth/domain.key \ ${REGISTRY_IMAGE} - wait_for_port localhost $REGISTRY_PORT + wait_for_port localhost $REGISTRY_PORT 10 } function stop_registry() { @@ -492,13 +492,16 @@ function wait_for_port() { local port=$2 # Numeric port local _timeout=${3:-5} # Optional; default to 5 seconds + local path=/dev/tcp/$host/$port + # Wait - while [ $_timeout -gt 0 ]; do + local i=$_timeout + while [ $i -gt 0 ]; do { exec 3<> /dev/tcp/$host/$port; } &>/dev/null && return sleep 1 - _timeout=$(( $_timeout - 1 )) + i=$(( $i - 1 )) done - die "Timed out waiting for service" + die "Timed out (${_timeout}s) waiting for service ($path)" } ############ @@ -543,6 +546,9 @@ done ############################################################################### # BEGIN entry handler (subtest invoker) +echo '============================= test session starts ==============================' +echo "podman client -- $(curl --version)" + # Identify the tests to run. If called with args, use those as globs. tests_to_run=() if [ -n "$*" ]; then @@ -558,6 +564,7 @@ if [ -n "$*" ]; then else tests_to_run=($TESTS_DIR/*.at) fi +echo -e "collected ${#tests_to_run[@]} items\n" start_service diff --git a/test/e2e/build/Dockerfile.with-multiple-secret b/test/e2e/build/Containerfile.with-multiple-secret index f3478914f..f3478914f 100644 --- a/test/e2e/build/Dockerfile.with-multiple-secret +++ b/test/e2e/build/Containerfile.with-multiple-secret diff --git a/test/e2e/build/Dockerfile.with-secret b/test/e2e/build/Containerfile.with-secret index 920663a92..920663a92 100644 --- a/test/e2e/build/Dockerfile.with-secret +++ b/test/e2e/build/Containerfile.with-secret diff --git a/test/e2e/build/Dockerfile.test-cp-root-dir b/test/e2e/build/Dockerfile.test-cp-root-dir deleted file mode 100644 index 9f7de7c32..000000000 --- a/test/e2e/build/Dockerfile.test-cp-root-dir +++ /dev/null @@ -1,2 +0,0 @@ -FROM scratch -COPY Dockerfile.test-cp-root-dir / diff --git a/test/e2e/build/Dockerfile.with-secret-verify-leak b/test/e2e/build/secret-verify-leak/Containerfile.with-secret-verify-leak index 0957ac6a6..0957ac6a6 100644 --- a/test/e2e/build/Dockerfile.with-secret-verify-leak +++ b/test/e2e/build/secret-verify-leak/Containerfile.with-secret-verify-leak diff --git a/test/e2e/build/workdir-symlink/Dockerfile b/test/e2e/build/workdir-symlink/Dockerfile new file mode 100644 index 000000000..abc9b47ee --- /dev/null +++ b/test/e2e/build/workdir-symlink/Dockerfile @@ -0,0 +1,5 @@ +FROM alpine +RUN mkdir /tmp/destination +RUN ln -s /tmp/destination /tmp/link +WORKDIR /tmp/link +CMD ["echo", "hello"] diff --git a/test/e2e/build_test.go b/test/e2e/build_test.go index a1c2f5e54..c5903f037 100644 --- a/test/e2e/build_test.go +++ b/test/e2e/build_test.go @@ -60,7 +60,7 @@ var _ = Describe("Podman build", func() { }) It("podman build with a secret from file", func() { - session := podmanTest.Podman([]string{"build", "-f", "build/Dockerfile.with-secret", "-t", "secret-test", "--secret", "id=mysecret,src=build/secret.txt", "build/"}) + session := podmanTest.Podman([]string{"build", "-f", "build/Containerfile.with-secret", "-t", "secret-test", "--secret", "id=mysecret,src=build/secret.txt", "build/"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(session.OutputToString()).To(ContainSubstring("somesecret")) @@ -71,7 +71,7 @@ var _ = Describe("Podman build", func() { }) It("podman build with multiple secrets from files", func() { - session := podmanTest.Podman([]string{"build", "-f", "build/Dockerfile.with-multiple-secret", "-t", "multiple-secret-test", "--secret", "id=mysecret,src=build/secret.txt", "--secret", "id=mysecret2,src=build/anothersecret.txt", "build/"}) + session := podmanTest.Podman([]string{"build", "-f", "build/Containerfile.with-multiple-secret", "-t", "multiple-secret-test", "--secret", "id=mysecret,src=build/secret.txt", "--secret", "id=mysecret2,src=build/anothersecret.txt", "build/"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(session.OutputToString()).To(ContainSubstring("somesecret")) @@ -83,7 +83,7 @@ var _ = Describe("Podman build", func() { }) It("podman build with a secret from file and verify if secret file is not leaked into image", func() { - session := podmanTest.Podman([]string{"build", "-f", "build/Dockerfile.with-secret-verify-leak", "-t", "secret-test-leak", "--secret", "id=mysecret,src=build/secret.txt", "build/"}) + session := podmanTest.Podman([]string{"build", "-f", "build/secret-verify-leak/Containerfile.with-secret-verify-leak", "-t", "secret-test-leak", "--secret", "id=mysecret,src=build/secret.txt", "build/"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(session.OutputToString()).To(ContainSubstring("somesecret")) @@ -259,6 +259,19 @@ var _ = Describe("Podman build", func() { Expect(session.OutputToString()).NotTo(ContainSubstring("io.podman.annotations.seccomp")) }) + It("podman build where workdir is a symlink and run without creating new workdir", func() { + session := podmanTest.Podman([]string{ + "build", "-f", "build/workdir-symlink/Dockerfile", "-t", "test-symlink", + }) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"run", "--workdir", "/tmp/link", "test-symlink"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring("hello")) + }) + It("podman build --http_proxy flag", func() { os.Setenv("http_proxy", "1.2.3.4") if IsRemote() { diff --git a/test/e2e/containers_conf_test.go b/test/e2e/containers_conf_test.go index bfed01854..09cd68042 100644 --- a/test/e2e/containers_conf_test.go +++ b/test/e2e/containers_conf_test.go @@ -562,6 +562,11 @@ var _ = Describe("Verify podman containers.conf usage", func() { inspect = podmanTest.Podman([]string{"inspect", "--format", "{{ .HostConfig.Cgroups }}", result.OutputToString()}) inspect.WaitWithDefaultTimeout() Expect(inspect.OutputToString()).To(Equal("disabled")) + + // Check we can also create a pod when cgroups=disabled + result = podmanTest.Podman([]string{"pod", "create"}) + result.WaitWithDefaultTimeout() + Expect(result).Should(Exit(0)) }) It("podman containers.conf runtime", func() { diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go index c0c71652e..6a4083565 100644 --- a/test/e2e/play_kube_test.go +++ b/test/e2e/play_kube_test.go @@ -38,6 +38,21 @@ spec: hostname: unknown ` +var workdirSymlinkPodYaml = ` +apiVersion: v1 +kind: Pod +metadata: + labels: + app: test-symlink + name: test-symlink +spec: + containers: + - image: test-symlink + name: test-symlink + resources: {} + restartPolicy: Never +` + var podnameEqualsContainerNameYaml = ` apiVersion: v1 kind: Pod @@ -1332,6 +1347,26 @@ var _ = Describe("Podman play kube", func() { Expect(sharednamespaces).To(ContainSubstring("pid")) }) + It("podman play kube should be able to run image where workdir is a symlink", func() { + session := podmanTest.Podman([]string{ + "build", "-f", "build/workdir-symlink/Dockerfile", "-t", "test-symlink", + }) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + err := writeYaml(workdirSymlinkPodYaml, kubeYaml) + Expect(err).To(BeNil()) + + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) + kube.WaitWithDefaultTimeout() + Expect(kube).Should(Exit(0)) + + logs := podmanTest.Podman([]string{"pod", "logs", "-c", "test-symlink-test-symlink", "test-symlink"}) + logs.WaitWithDefaultTimeout() + Expect(logs).Should(Exit(0)) + Expect(logs.OutputToString()).To(ContainSubstring("hello")) + }) + It("podman play kube should not rename pod if container in pod has same name", func() { err := writeYaml(podnameEqualsContainerNameYaml, kubeYaml) Expect(err).To(BeNil()) @@ -1853,6 +1888,26 @@ var _ = Describe("Podman play kube", func() { Expect(kube).Should(Exit(0)) }) + It("podman play kube test duplicate container name", func() { + p := getPod(withCtr(getCtr(withName("testctr"), withCmd([]string{"echo", "hello"}))), withCtr(getCtr(withName("testctr"), withCmd([]string{"echo", "world"})))) + + err := generateKubeYaml("pod", p, kubeYaml) + Expect(err).To(BeNil()) + + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) + kube.WaitWithDefaultTimeout() + Expect(kube).To(ExitWithError()) + + p = getPod(withPodInitCtr(getCtr(withImage(ALPINE), withCmd([]string{"echo", "hello"}), withInitCtr(), withName("initctr"))), withCtr(getCtr(withImage(ALPINE), withName("initctr"), withCmd([]string{"top"})))) + + err = generateKubeYaml("pod", p, kubeYaml) + Expect(err).To(BeNil()) + + kube = podmanTest.Podman([]string{"play", "kube", kubeYaml}) + kube.WaitWithDefaultTimeout() + Expect(kube).To(ExitWithError()) + }) + It("podman play kube test hostname", func() { pod := getPod() err := generateKubeYaml("pod", pod, kubeYaml) diff --git a/test/e2e/run_device_test.go b/test/e2e/run_device_test.go index b8bdc84f8..479837dda 100644 --- a/test/e2e/run_device_test.go +++ b/test/e2e/run_device_test.go @@ -44,6 +44,11 @@ var _ = Describe("Podman run device", func() { session := podmanTest.Podman([]string{"run", "-q", "--security-opt", "label=disable", "--device", "/dev/kmsg", ALPINE, "test", "-c", "/dev/kmsg"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) + if !isRootless() { + session = podmanTest.Podman([]string{"run", "-q", "--security-opt", "label=disable", "--device", "/dev/kmsg", "--cap-add", "SYS_ADMIN", ALPINE, "head", "-n", "1", "/dev/kmsg"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + } }) It("podman run device rename test", func() { diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index aa1887f84..2202cadd8 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -2,15 +2,19 @@ package integration import ( "fmt" + "net" "os" "strings" + "syscall" + "github.com/containernetworking/plugins/pkg/ns" . "github.com/containers/podman/v4/test/utils" "github.com/containers/storage/pkg/stringid" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gexec" "github.com/uber/jaeger-client-go/utils" + "github.com/vishvananda/netlink" ) var _ = Describe("Podman run networking", func() { @@ -694,6 +698,157 @@ EXPOSE 2004-2005/tcp`, ALPINE) Expect(session.OutputToString()).To(ContainSubstring("11.11.11.11")) }) + addAddr := func(cidr string, containerInterface netlink.Link) error { + _, ipnet, err := net.ParseCIDR(cidr) + Expect(err).To(BeNil()) + addr := &netlink.Addr{IPNet: ipnet, Label: ""} + if err := netlink.AddrAdd(containerInterface, addr); err != nil && err != syscall.EEXIST { + return err + } + return nil + } + + loopbackup := func() { + lo, err := netlink.LinkByName("lo") + Expect(err).To(BeNil()) + err = netlink.LinkSetUp(lo) + Expect(err).To(BeNil()) + } + + linkup := func(name string, mac string, addresses []string) { + linkAttr := netlink.NewLinkAttrs() + linkAttr.Name = name + m, err := net.ParseMAC(mac) + Expect(err).To(BeNil()) + linkAttr.HardwareAddr = net.HardwareAddr(m) + eth := &netlink.Dummy{LinkAttrs: linkAttr} + err = netlink.LinkAdd(eth) + Expect(err).To(BeNil()) + err = netlink.LinkSetUp(eth) + Expect(err).To(BeNil()) + for _, address := range addresses { + err := addAddr(address, eth) + Expect(err).To(BeNil()) + } + } + + routeAdd := func(gateway string) { + gw := net.ParseIP(gateway) + route := &netlink.Route{Dst: nil, Gw: gw} + netlink.RouteAdd(route) + } + + setupNetworkNs := func(networkNSName string) { + ns.WithNetNSPath("/run/netns/"+networkNSName, func(_ ns.NetNS) error { + loopbackup() + linkup("eth0", "46:7f:45:6e:4f:c8", []string{"10.25.40.0/24", "fd04:3e42:4a4e:3381::/64"}) + linkup("eth1", "56:6e:35:5d:3e:a8", []string{"10.88.0.0/16"}) + + routeAdd("10.25.40.0") + return nil + }) + } + + checkNetworkNsInspect := func(name string) { + inspectOut := podmanTest.InspectContainer(name) + Expect(inspectOut[0].NetworkSettings.IPAddress).To(Equal("10.25.40.0")) + Expect(inspectOut[0].NetworkSettings.IPPrefixLen).To(Equal(24)) + Expect(len(inspectOut[0].NetworkSettings.SecondaryIPAddresses)).To(Equal(1)) + Expect(inspectOut[0].NetworkSettings.SecondaryIPAddresses[0].Addr).To(Equal("10.88.0.0")) + Expect(inspectOut[0].NetworkSettings.SecondaryIPAddresses[0].PrefixLength).To(Equal(16)) + Expect(inspectOut[0].NetworkSettings.GlobalIPv6Address).To(Equal("fd04:3e42:4a4e:3381::")) + Expect(inspectOut[0].NetworkSettings.GlobalIPv6PrefixLen).To(Equal(64)) + Expect(len(inspectOut[0].NetworkSettings.SecondaryIPv6Addresses)).To(Equal(0)) + Expect(inspectOut[0].NetworkSettings.MacAddress).To(Equal("46:7f:45:6e:4f:c8")) + Expect(len(inspectOut[0].NetworkSettings.AdditionalMacAddresses)).To(Equal(1)) + Expect(inspectOut[0].NetworkSettings.AdditionalMacAddresses[0]).To(Equal("56:6e:35:5d:3e:a8")) + Expect(inspectOut[0].NetworkSettings.Gateway).To(Equal("10.25.40.0")) + + } + + It("podman run newtork inspect fails gracefully on non-reachable network ns", func() { + SkipIfRootless("ip netns is not supported for rootless users") + + networkNSName := RandomString(12) + addNamedNetwork := SystemExec("ip", []string{"netns", "add", networkNSName}) + Expect(addNamedNetwork).Should(Exit(0)) + + setupNetworkNs(networkNSName) + + name := RandomString(12) + session := podmanTest.Podman([]string{"run", "-d", "--name", name, "--net", "ns:/run/netns/" + networkNSName, ALPINE, "top"}) + session.WaitWithDefaultTimeout() + + // delete the named network ns before inspect + delNetworkNamespace := SystemExec("ip", []string{"netns", "delete", networkNSName}) + Expect(delNetworkNamespace).Should(Exit(0)) + + inspectOut := podmanTest.InspectContainer(name) + Expect(inspectOut[0].NetworkSettings.IPAddress).To(Equal("")) + Expect(len(inspectOut[0].NetworkSettings.Networks)).To(Equal(0)) + }) + + It("podman inspect can handle joined network ns with multiple interfaces", func() { + SkipIfRootless("ip netns is not supported for rootless users") + + networkNSName := RandomString(12) + addNamedNetwork := SystemExec("ip", []string{"netns", "add", networkNSName}) + Expect(addNamedNetwork).Should(Exit(0)) + defer func() { + delNetworkNamespace := SystemExec("ip", []string{"netns", "delete", networkNSName}) + Expect(delNetworkNamespace).Should(Exit(0)) + }() + setupNetworkNs(networkNSName) + + name := RandomString(12) + session := podmanTest.Podman([]string{"run", "--name", name, "--net", "ns:/run/netns/" + networkNSName, ALPINE}) + session.WaitWithDefaultTimeout() + + session = podmanTest.Podman([]string{"container", "rm", name}) + session.WaitWithDefaultTimeout() + + // no network teardown should touch joined network ns interfaces + session = podmanTest.Podman([]string{"run", "-d", "--replace", "--name", name, "--net", "ns:/run/netns/" + networkNSName, ALPINE, "top"}) + session.WaitWithDefaultTimeout() + + checkNetworkNsInspect(name) + }) + + It("podman do not tamper with joined network ns interfaces", func() { + SkipIfRootless("ip netns is not supported for rootless users") + + networkNSName := RandomString(12) + addNamedNetwork := SystemExec("ip", []string{"netns", "add", networkNSName}) + Expect(addNamedNetwork).Should(Exit(0)) + defer func() { + delNetworkNamespace := SystemExec("ip", []string{"netns", "delete", networkNSName}) + Expect(delNetworkNamespace).Should(Exit(0)) + }() + + setupNetworkNs(networkNSName) + + name := RandomString(12) + session := podmanTest.Podman([]string{"run", "--name", name, "--net", "ns:/run/netns/" + networkNSName, ALPINE}) + session.WaitWithDefaultTimeout() + + checkNetworkNsInspect(name) + + name = RandomString(12) + session = podmanTest.Podman([]string{"run", "--name", name, "--net", "ns:/run/netns/" + networkNSName, ALPINE}) + session.WaitWithDefaultTimeout() + + checkNetworkNsInspect(name) + + // delete container, the network inspect should not change + session = podmanTest.Podman([]string{"container", "rm", name}) + session.WaitWithDefaultTimeout() + + session = podmanTest.Podman([]string{"run", "-d", "--replace", "--name", name, "--net", "ns:/run/netns/" + networkNSName, ALPINE, "top"}) + session.WaitWithDefaultTimeout() + + checkNetworkNsInspect(name) + }) + It("podman run network in bogus user created network namespace", func() { session := podmanTest.Podman([]string{"run", "-dt", "--net", "ns:/run/netns/xxy", ALPINE, "wget", "www.podman.io"}) session.Wait(90) diff --git a/test/e2e/system_service_test.go b/test/e2e/system_service_test.go index dcf5e03b2..2bc7756d6 100644 --- a/test/e2e/system_service_test.go +++ b/test/e2e/system_service_test.go @@ -58,6 +58,7 @@ var _ = Describe("podman system service", func() { const magicComment = "pprof service listening on" It("are available", func() { + Skip("FIXME: Test is too flaky (#12624)") SkipIfRemote("service subcommand not supported remotely") address := url.URL{ @@ -97,6 +98,7 @@ var _ = Describe("podman system service", func() { }) It("are not available", func() { + Skip("FIXME: Test is too flaky (#12624)") SkipIfRemote("service subcommand not supported remotely") address := url.URL{ diff --git a/test/python/docker/__init__.py b/test/python/docker/__init__.py index 816667b82..d46f465f7 100644 --- a/test/python/docker/__init__.py +++ b/test/python/docker/__init__.py @@ -1,4 +1,6 @@ -import configparser +""" +Helpers for integration tests using DockerClient +""" import json import os import pathlib @@ -11,7 +13,7 @@ from docker import DockerClient from .compat import constant -class Podman(object): +class PodmanAPI: """ Instances hold the configuration and setup for running podman commands """ @@ -53,17 +55,13 @@ location = "mirror.localhost:5000" """ - with open(os.environ["CONTAINERS_REGISTRIES_CONF"], "w") as w: - w.write(conf) + with open(os.environ["CONTAINERS_REGISTRIES_CONF"], "w") as file: + file.write(conf) - os.environ["CNI_CONFIG_PATH"] = os.path.join( - self.anchor_directory, "cni", "net.d" - ) + os.environ["CNI_CONFIG_PATH"] = os.path.join(self.anchor_directory, "cni", "net.d") os.makedirs(os.environ["CNI_CONFIG_PATH"], exist_ok=True) self.cmd.append("--network-config-dir=" + os.environ["CNI_CONFIG_PATH"]) - cni_cfg = os.path.join( - os.environ["CNI_CONFIG_PATH"], "87-podman-bridge.conflist" - ) + cni_cfg = os.path.join(os.environ["CNI_CONFIG_PATH"], "87-podman-bridge.conflist") # json decoded and encoded to ensure legal json buf = json.loads( """ @@ -93,8 +91,8 @@ location = "mirror.localhost:5000" } """ ) - with open(cni_cfg, "w") as w: - json.dump(buf, w) + with open(cni_cfg, "w") as file: + json.dump(buf, file) def open(self, command, *args, **kwargs): """Podman initialized instance to run a given command @@ -111,6 +109,7 @@ location = "mirror.localhost:5000" shell = kwargs.get("shell", False) + # pylint: disable=consider-using-with return subprocess.Popen( cmd, shell=shell, @@ -144,9 +143,11 @@ location = "mirror.localhost:5000" ) def tear_down(self): + """Delete test environment.""" shutil.rmtree(self.anchor_directory, ignore_errors=True) def restore_image_from_cache(self, client: DockerClient): + """Populate images from cache.""" path = os.path.join(self.image_cache, constant.ALPINE_TARBALL) if not os.path.exists(path): img = client.images.pull(constant.ALPINE) @@ -157,5 +158,6 @@ location = "mirror.localhost:5000" self.run("load", "-i", path, check=True) def flush_image_cache(self): - for f in pathlib.Path(self.image_cache).glob("*.tar"): - f.unlink(f) + """Delete image cache.""" + for file in pathlib.Path(self.image_cache).glob("*.tar"): + file.unlink(missing_ok=True) diff --git a/test/python/docker/compat/common.py b/test/python/docker/compat/common.py index bdc67c287..218ac9872 100644 --- a/test/python/docker/compat/common.py +++ b/test/python/docker/compat/common.py @@ -1,23 +1,92 @@ +""" +Fixtures and Helpers for unittests. +""" +import subprocess +import sys +import time +import unittest + +# pylint: disable=no-name-in-module,import-error,wrong-import-order from docker import DockerClient +from test.python.docker import PodmanAPI from test.python.docker.compat import constant def run_top_container(client: DockerClient): - c = client.containers.create( - constant.ALPINE, command="top", detach=True, tty=True, name="top" + """Run top command in a alpine container.""" + ctnr = client.containers.create( + constant.ALPINE, + command="top", + detach=True, + tty=True, + name="top", ) - c.start() - return c.id + ctnr.start() + return ctnr.id def remove_all_containers(client: DockerClient): + """Delete all containers from the Podman service.""" for ctnr in client.containers.list(all=True): ctnr.remove(force=True) def remove_all_images(client: DockerClient): + """Delete all images from the Podman service.""" for img in client.images.list(): # FIXME should DELETE /images accept the sha256: prefix? id_ = img.id.removeprefix("sha256:") client.images.remove(id_, force=True) + + +class DockerTestCase(unittest.TestCase): + """Specialized TestCase class for testing against Podman service.""" + + podman: PodmanAPI = None # initialized podman configuration for tests + service: subprocess.Popen = None # podman service instance + + top_container_id: str = None + docker: DockerClient = None + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + + cls.podman = PodmanAPI() + super().addClassCleanup(cls.podman.tear_down) + + cls.service = cls.podman.open("system", "service", "tcp:127.0.0.1:8080", "--time=0") + # give the service some time to be ready... + time.sleep(2) + + return_code = cls.service.poll() + if return_code is not None: + raise subprocess.CalledProcessError(return_code, "podman system service") + + @classmethod + def tearDownClass(cls) -> None: + cls.service.terminate() + stdout, stderr = cls.service.communicate(timeout=0.5) + if stdout: + sys.stdout.write("\ndocker-py -- Service Stdout:\n" + stdout.decode("utf-8")) + if stderr: + sys.stderr.write("\ndocker-py -- Service Stderr:\n" + stderr.decode("utf-8")) + + return super().tearDownClass() + + def setUp(self) -> None: + super().setUp() + + self.docker = DockerClient(base_url="tcp://127.0.0.1:8080", timeout=15) + self.addCleanup(self.docker.close) + + self.podman.restore_image_from_cache(self.docker) + self.top_container_id = run_top_container(self.docker) + self.assertIsNotNone(self.top_container_id, "Failed to create 'top' container") + + def tearDown(self) -> None: + remove_all_containers(self.docker) + remove_all_images(self.docker) + + super().tearDown() diff --git a/test/python/docker/compat/constant.py b/test/python/docker/compat/constant.py index 892293c97..2a0046daf 100644 --- a/test/python/docker/compat/constant.py +++ b/test/python/docker/compat/constant.py @@ -1,6 +1,8 @@ +""" +Constants to use in writing unittests. +""" ALPINE = "quay.io/libpod/alpine:latest" ALPINE_SHORTNAME = "alpine" ALPINE_TARBALL = "alpine.tar" BB = "quay.io/libpod/busybox:latest" NGINX = "quay.io/libpod/alpine_nginx:latest" -infra = "k8s.gcr.io/pause:3.2" diff --git a/test/python/docker/compat/test_containers.py b/test/python/docker/compat/test_containers.py index d14c09fc1..c0b6c9abe 100644 --- a/test/python/docker/compat/test_containers.py +++ b/test/python/docker/compat/test_containers.py @@ -1,111 +1,56 @@ +""" +Integration tests for exercising docker-py against Podman Service. +""" import io -import subprocess -import sys -import time -import unittest -from typing import IO, Optional, List +import tarfile +from typing import IO, List, Optional -from docker import DockerClient, errors +from docker import errors from docker.models.containers import Container from docker.models.images import Image from docker.models.volumes import Volume from docker.types import Mount -from test.python.docker import Podman +# pylint: disable=no-name-in-module,import-error,wrong-import-order from test.python.docker.compat import common, constant -import tarfile - - -class TestContainers(unittest.TestCase): - podman = None # initialized podman configuration for tests - service = None # podman service instance - topContainerId = "" - - def setUp(self): - super().setUp() - self.client = DockerClient(base_url="tcp://127.0.0.1:8080", timeout=15) - TestContainers.podman.restore_image_from_cache(self.client) - TestContainers.topContainerId = common.run_top_container(self.client) - self.assertIsNotNone(TestContainers.topContainerId) - - def tearDown(self): - common.remove_all_containers(self.client) - common.remove_all_images(self.client) - self.client.close() - return super().tearDown() - - @classmethod - def setUpClass(cls): - super().setUpClass() - TestContainers.podman = Podman() - TestContainers.service = TestContainers.podman.open( - "system", "service", "tcp:127.0.0.1:8080", "--time=0" - ) - # give the service some time to be ready... - time.sleep(2) - - rc = TestContainers.service.poll() - if rc is not None: - raise subprocess.CalledProcessError(rc, "podman system service") - @classmethod - def tearDownClass(cls): - TestContainers.service.terminate() - stdout, stderr = TestContainers.service.communicate(timeout=0.5) - if stdout: - sys.stdout.write("\nContainers Service Stdout:\n" + stdout.decode("utf-8")) - if stderr: - sys.stderr.write("\nContainers Service Stderr:\n" + stderr.decode("utf-8")) - - TestContainers.podman.tear_down() - return super().tearDownClass() +# pylint: disable=missing-function-docstring +class TestContainers(common.DockerTestCase): + """TestCase for exercising containers.""" def test_create_container(self): - # Run a container with detach mode - self.client.containers.create(image="alpine", detach=True) - self.assertEqual(len(self.client.containers.list(all=True)), 2) + """Run a container with detach mode.""" + self.docker.containers.create(image="alpine", detach=True) + self.assertEqual(len(self.docker.containers.list(all=True)), 2) def test_create_network(self): - net = self.client.networks.create("testNetwork", driver="bridge") - ctnr = self.client.containers.create(image="alpine", detach=True) - - # TODO fix when ready - # This test will not work until all connect|disconnect - # code is fixed. - # net.connect(ctnr) - - # nets = self.client.networks.list(greedy=True) - # self.assertGreaterEqual(len(nets), 1) - - # TODO fix endpoint to include containers - # for n in nets: - # if n.id == "testNetwork": - # self.assertEqual(ctnr.id, n.containers) - # self.assertTrue(False, "testNetwork not found") + """Add network to a container.""" + self.docker.networks.create("testNetwork", driver="bridge") + self.docker.containers.create(image="alpine", detach=True) def test_start_container(self): # Podman docs says it should give a 304 but returns with no response # # Start a already started container should return 304 - # response = self.client.api.start(container=TestContainers.topContainerId) + # response = self.docker.api.start(container=self.top_container_id) # self.assertEqual(error.exception.response.status_code, 304) # Create a new container and validate the count - self.client.containers.create(image=constant.ALPINE, name="container2") - containers = self.client.containers.list(all=True) + self.docker.containers.create(image=constant.ALPINE, name="container2") + containers = self.docker.containers.list(all=True) self.assertEqual(len(containers), 2) def test_start_container_with_random_port_bind(self): - container = self.client.containers.create( + container = self.docker.containers.create( image=constant.ALPINE, name="containerWithRandomBind", ports={"1234/tcp": None}, ) - containers = self.client.containers.list(all=True) + containers = self.docker.containers.list(all=True) self.assertTrue(container in containers) def test_stop_container(self): - top = self.client.containers.get(TestContainers.topContainerId) + top = self.docker.containers.get(self.top_container_id) self.assertEqual(top.status, "running") # Stop a running container and validate the state @@ -114,7 +59,7 @@ class TestContainers(unittest.TestCase): self.assertIn(top.status, ("stopped", "exited")) def test_kill_container(self): - top = self.client.containers.get(TestContainers.topContainerId) + top = self.docker.containers.get(self.top_container_id) self.assertEqual(top.status, "running") # Kill a running container and validate the state @@ -124,7 +69,7 @@ class TestContainers(unittest.TestCase): def test_restart_container(self): # Validate the container state - top = self.client.containers.get(TestContainers.topContainerId) + top = self.docker.containers.get(self.top_container_id) top.stop() top.reload() self.assertIn(top.status, ("stopped", "exited")) @@ -136,16 +81,16 @@ class TestContainers(unittest.TestCase): def test_remove_container(self): # Remove container by ID with force - top = self.client.containers.get(TestContainers.topContainerId) + top = self.docker.containers.get(self.top_container_id) top.remove(force=True) - self.assertEqual(len(self.client.containers.list()), 0) + self.assertEqual(len(self.docker.containers.list()), 0) def test_remove_container_without_force(self): # Validate current container count - self.assertEqual(len(self.client.containers.list()), 1) + self.assertEqual(len(self.docker.containers.list()), 1) # Remove running container should throw error - top = self.client.containers.get(TestContainers.topContainerId) + top = self.docker.containers.get(self.top_container_id) with self.assertRaises(errors.APIError) as error: top.remove() self.assertEqual(error.exception.response.status_code, 500) @@ -153,11 +98,11 @@ class TestContainers(unittest.TestCase): # Remove container by ID without force top.stop() top.remove() - self.assertEqual(len(self.client.containers.list()), 0) + self.assertEqual(len(self.docker.containers.list()), 0) def test_pause_container(self): # Validate the container state - top = self.client.containers.get(TestContainers.topContainerId) + top = self.docker.containers.get(self.top_container_id) self.assertEqual(top.status, "running") # Pause a running container and validate the state @@ -167,7 +112,7 @@ class TestContainers(unittest.TestCase): def test_pause_stopped_container(self): # Stop the container - top = self.client.containers.get(TestContainers.topContainerId) + top = self.docker.containers.get(self.top_container_id) top.stop() # Pause exited container should throw error @@ -176,7 +121,7 @@ class TestContainers(unittest.TestCase): self.assertEqual(error.exception.response.status_code, 500) def test_unpause_container(self): - top = self.client.containers.get(TestContainers.topContainerId) + top = self.docker.containers.get(self.top_container_id) # Validate the container state top.pause() @@ -190,21 +135,21 @@ class TestContainers(unittest.TestCase): def test_list_container(self): # Add container and validate the count - self.client.containers.create(image="alpine", detach=True) - containers = self.client.containers.list(all=True) + self.docker.containers.create(image="alpine", detach=True) + containers = self.docker.containers.list(all=True) self.assertEqual(len(containers), 2) def test_filters(self): self.skipTest("TODO Endpoint does not yet support filters") # List container with filter by id - filters = {"id": TestContainers.topContainerId} - ctnrs = self.client.containers.list(all=True, filters=filters) + filters = {"id": self.top_container_id} + ctnrs = self.docker.containers.list(all=True, filters=filters) self.assertEqual(len(ctnrs), 1) # List container with filter by name filters = {"name": "top"} - ctnrs = self.client.containers.list(all=True, filters=filters) + ctnrs = self.docker.containers.list(all=True, filters=filters) self.assertEqual(len(ctnrs), 1) def test_copy_to_container(self): @@ -212,31 +157,33 @@ class TestContainers(unittest.TestCase): vol: Optional[Volume] = None try: test_file_content = b"Hello World!" - vol = self.client.volumes.create("test-volume") - ctr = self.client.containers.create(image="alpine", - detach=True, - command="top", - volumes=["test-volume:/test-volume-read-only:ro"]) + vol = self.docker.volumes.create("test-volume") + ctr = self.docker.containers.create( + image="alpine", + detach=True, + command="top", + volumes=["test-volume:/test-volume-read-only:ro"], + ) ctr.start() buff: IO[bytes] = io.BytesIO() - with tarfile.open(fileobj=buff, mode="w:") as tf: - ti: tarfile.TarInfo = tarfile.TarInfo() - ti.uid = 1042 - ti.gid = 1043 - ti.name = "a.txt" - ti.path = "a.txt" - ti.mode = 0o644 - ti.type = tarfile.REGTYPE - ti.size = len(test_file_content) - tf.addfile(ti, fileobj=io.BytesIO(test_file_content)) + with tarfile.open(fileobj=buff, mode="w:") as file: + info: tarfile.TarInfo = tarfile.TarInfo() + info.uid = 1042 + info.gid = 1043 + info.name = "a.txt" + info.path = "a.txt" + info.mode = 0o644 + info.type = tarfile.REGTYPE + info.size = len(test_file_content) + file.addfile(info, fileobj=io.BytesIO(test_file_content)) buff.seek(0) ctr.put_archive("/tmp/", buff) ret, out = ctr.exec_run(["stat", "-c", "%u:%g", "/tmp/a.txt"]) self.assertEqual(ret, 0) - self.assertEqual(out.rstrip(), b'1042:1043', "UID/GID of copied file") + self.assertEqual(out.rstrip(), b"1042:1043", "UID/GID of copied file") ret, out = ctr.exec_run(["cat", "/tmp/a.txt"]) self.assertEqual(ret, 0) @@ -253,48 +200,61 @@ class TestContainers(unittest.TestCase): vol.remove(force=True) def test_mount_preexisting_dir(self): - dockerfile = (B'FROM quay.io/libpod/alpine:latest\n' - B'USER root\n' - B'RUN mkdir -p /workspace\n' - B'RUN chown 1042:1043 /workspace') + dockerfile = ( + b"FROM quay.io/libpod/alpine:latest\n" + b"USER root\n" + b"RUN mkdir -p /workspace\n" + b"RUN chown 1042:1043 /workspace" + ) 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_mount_preexisting_dir_vol:/workspace"]) + img, out = self.docker.images.build(fileobj=io.BytesIO(dockerfile)) + ctr: Container = self.docker.containers.create( + image=img.id, + detach=True, + command="top", + volumes=["test_mount_preexisting_dir_vol:/workspace"], + ) ctr.start() - ret, out = ctr.exec_run(["stat", "-c", "%u:%g", "/workspace"]) - self.assertEqual(out.rstrip(), b'1042:1043', "UID/GID set in dockerfile") - + _, 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') + 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"]) + img, _ = self.docker.images.build(fileobj=io.BytesIO(dockerfile)) + ctr: Container = self.docker.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"]) + ret, _ = ctr.exec_run(["stat", "/workspace/scratch/test"]) self.assertEqual(ret, 0, "Working directory created if it doesn't exist") def test_mount_rw_by_default(self): ctr: Optional[Container] = None vol: Optional[Volume] = None + try: - vol = self.client.volumes.create("test-volume") - ctr = self.client.containers.create(image="alpine", - detach=True, - command="top", - mounts=[Mount(target="/vol-mnt", - source="test-volume", - type='volume', - read_only=False)]) - ctr_inspect = self.client.api.inspect_container(ctr.id) + vol = self.docker.volumes.create("test-volume") + ctr = self.docker.containers.create( + image="alpine", + detach=True, + command="top", + mounts=[ + Mount(target="/vol-mnt", source="test-volume", type="volume", read_only=False) + ], + ) + ctr_inspect = self.docker.api.inspect_container(ctr.id) binds: List[str] = ctr_inspect["HostConfig"]["Binds"] self.assertEqual(len(binds), 1) - self.assertEqual(binds[0], 'test-volume:/vol-mnt:rw,rprivate,nosuid,nodev,rbind') + self.assertEqual(binds[0], "test-volume:/vol-mnt:rw,rprivate,nosuid,nodev,rbind") finally: if ctr is not None: ctr.remove() diff --git a/test/python/docker/compat/test_images.py b/test/python/docker/compat/test_images.py index 05d0e3e12..ac9a8d671 100644 --- a/test/python/docker/compat/test_images.py +++ b/test/python/docker/compat/test_images.py @@ -1,116 +1,73 @@ -import collections +""" +Integration tests for exercising docker-py against Podman Service. +""" import io import os -import subprocess -import sys -import time import unittest -from docker import DockerClient, errors -from docker.errors import APIError +from docker import errors -from test.python.docker import Podman +# pylint: disable=no-name-in-module,import-error,wrong-import-order from test.python.docker.compat import common, constant -class TestImages(unittest.TestCase): - podman = None # initialized podman configuration for tests - service = None # podman service instance - - def setUp(self): - super().setUp() - self.client = DockerClient(base_url="tcp://127.0.0.1:8080", timeout=15) - - TestImages.podman.restore_image_from_cache(self.client) - - def tearDown(self): - common.remove_all_images(self.client) - self.client.close() - return super().tearDown() - - @classmethod - def setUpClass(cls): - super().setUpClass() - TestImages.podman = Podman() - TestImages.service = TestImages.podman.open( - "system", "service", "tcp:127.0.0.1:8080", "--time=0" - ) - # give the service some time to be ready... - time.sleep(2) - - returncode = TestImages.service.poll() - if returncode is not None: - raise subprocess.CalledProcessError(returncode, "podman system service") - - @classmethod - def tearDownClass(cls): - TestImages.service.terminate() - stdout, stderr = TestImages.service.communicate(timeout=0.5) - if stdout: - sys.stdout.write("\nImages Service Stdout:\n" + stdout.decode("utf-8")) - if stderr: - sys.stderr.write("\nImAges Service Stderr:\n" + stderr.decode("utf-8")) - - TestImages.podman.tear_down() - return super().tearDownClass() +class TestImages(common.DockerTestCase): + """TestCase for exercising images.""" def test_tag_valid_image(self): """Validates if the image is tagged successfully""" - alpine = self.client.images.get(constant.ALPINE) + alpine = self.docker.images.get(constant.ALPINE) self.assertTrue(alpine.tag("demo", constant.ALPINE_SHORTNAME)) - alpine = self.client.images.get(constant.ALPINE) - for t in alpine.tags: - self.assertIn("alpine", t) + alpine = self.docker.images.get(constant.ALPINE) + for tag in alpine.tags: + self.assertIn("alpine", tag) - # @unittest.skip("doesn't work now") def test_retag_valid_image(self): - """Validates if name updates when the image is retagged""" - alpine = self.client.images.get(constant.ALPINE) + """Validates if name updates when the image is re-tagged.""" + alpine = self.docker.images.get(constant.ALPINE) self.assertTrue(alpine.tag("demo", "rename")) - alpine = self.client.images.get(constant.ALPINE) + alpine = self.docker.images.get(constant.ALPINE) self.assertNotIn("demo:test", alpine.tags) def test_list_images(self): """List images""" - self.assertEqual(len(self.client.images.list()), 1) + self.assertEqual(len(self.docker.images.list()), 1) # Add more images - self.client.images.pull(constant.BB) - self.assertEqual(len(self.client.images.list()), 2) - self.assertEqual(len(self.client.images.list(all=True)), 2) + self.docker.images.pull(constant.BB) + self.assertEqual(len(self.docker.images.list()), 2) + self.assertEqual(len(self.docker.images.list(all=True)), 2) # List images with filter - self.assertEqual(len(self.client.images.list(filters={"reference": "alpine"})), 1) + self.assertEqual(len(self.docker.images.list(filters={"reference": "alpine"})), 1) def test_search_image(self): """Search for image""" - for r in self.client.images.search("alpine"): + for registry in self.docker.images.search("alpine"): # registry matches if string is in either one - self.assertIn("alpine", r["Name"]+" "+r["Description"].lower()) + self.assertIn("alpine", registry["Name"] + " " + registry["Description"].lower()) def test_search_bogus_image(self): """Search for bogus image should throw exception""" - try: - r = self.client.images.search("bogus/bogus") - except: - return - self.assertTrue(len(r) == 0) + with self.assertRaises(errors.APIError): + self.docker.images.search("bogus/bogus") def test_remove_image(self): """Remove image""" # Check for error with wrong image name with self.assertRaises(errors.NotFound): - self.client.images.remove("dummy") - self.assertEqual(len(self.client.images.list()), 1) + self.docker.images.remove("dummy") - self.client.images.remove(constant.ALPINE) - self.assertEqual(len(self.client.images.list()), 0) + common.remove_all_containers(self.docker) + self.assertEqual(len(self.docker.images.list()), 1) + self.docker.images.remove(constant.ALPINE) + self.assertEqual(len(self.docker.images.list()), 0) def test_image_history(self): """Image history""" - img = self.client.images.get(constant.ALPINE) + img = self.docker.images.get(constant.ALPINE) history = img.history() image_id = img.id[7:] if img.id.startswith("sha256:") else img.id @@ -122,47 +79,49 @@ class TestImages(unittest.TestCase): def test_get_image_exists_not(self): """Negative test for get image""" with self.assertRaises(errors.NotFound): - response = self.client.images.get("image_does_not_exists") - collections.deque(response) + self.docker.images.get("image_does_not_exists") def test_save_image(self): """Export Image""" - image = self.client.images.pull(constant.BB) + image = self.docker.images.pull(constant.BB) file = os.path.join(TestImages.podman.image_cache, "busybox.tar") with open(file, mode="wb") as tarball: for frame in image.save(named=True): tarball.write(frame) - sz = os.path.getsize(file) - self.assertGreater(sz, 0) + self.assertGreater(os.path.getsize(file), 0) def test_load_image(self): """Import|Load Image""" - self.assertEqual(len(self.client.images.list()), 1) + self.assertEqual(len(self.docker.images.list()), 1) - image = self.client.images.pull(constant.BB) + image = self.docker.images.pull(constant.BB) file = os.path.join(TestImages.podman.image_cache, "busybox.tar") with open(file, mode="wb") as tarball: for frame in image.save(): tarball.write(frame) with open(file, mode="rb") as saved: - _ = self.client.images.load(saved) + self.docker.images.load(saved) - self.assertEqual(len(self.client.images.list()), 2) + self.assertEqual(len(self.docker.images.list()), 2) def test_load_corrupt_image(self): """Import|Load Image failure""" tarball = io.BytesIO("This is a corrupt tarball".encode("utf-8")) - with self.assertRaises(APIError): - self.client.images.load(tarball) + with self.assertRaises(errors.APIError): + self.docker.images.load(tarball) def test_build_image(self): + """Build Image with custom labels.""" labels = {"apple": "red", "grape": "green"} - _ = self.client.images.build( - path="test/python/docker/build_labels", labels=labels, tag="labels", isolation="default" + self.docker.images.build( + path="test/python/docker/build_labels", + labels=labels, + tag="labels", + isolation="default", ) - image = self.client.images.get("labels") + image = self.docker.images.get("labels") self.assertEqual(image.labels["apple"], labels["apple"]) self.assertEqual(image.labels["grape"], labels["grape"]) diff --git a/test/python/docker/compat/test_system.py b/test/python/docker/compat/test_system.py index a928de0ee..f6092996b 100644 --- a/test/python/docker/compat/test_system.py +++ b/test/python/docker/compat/test_system.py @@ -1,71 +1,32 @@ -import subprocess -import sys -import time -import unittest +""" +Integration tests for exercising docker-py against Podman Service. +""" -from docker import DockerClient - -from test.python.docker import Podman, constant +# pylint: disable=no-name-in-module,import-error,wrong-import-order +from test.python.docker import constant from test.python.docker.compat import common -class TestSystem(unittest.TestCase): - podman = None # initialized podman configuration for tests - service = None # podman service instance - topContainerId = "" - - def setUp(self): - super().setUp() - self.client = DockerClient(base_url="tcp://127.0.0.1:8080", timeout=15) - - TestSystem.podman.restore_image_from_cache(self.client) - TestSystem.topContainerId = common.run_top_container(self.client) - - def tearDown(self): - common.remove_all_containers(self.client) - common.remove_all_images(self.client) - self.client.close() - return super().tearDown() - - @classmethod - def setUpClass(cls): - super().setUpClass() - TestSystem.podman = Podman() - TestSystem.service = TestSystem.podman.open( - "system", "service", "tcp:127.0.0.1:8080", "--time=0" - ) - # give the service some time to be ready... - time.sleep(2) - - returncode = TestSystem.service.poll() - if returncode is not None: - raise subprocess.CalledProcessError(returncode, "podman system service") - - @classmethod - def tearDownClass(cls): - TestSystem.service.terminate() - stdout, stderr = TestSystem.service.communicate(timeout=0.5) - if stdout: - sys.stdout.write("\nImages Service Stdout:\n" + stdout.decode("utf-8")) - if stderr: - sys.stderr.write("\nImAges Service Stderr:\n" + stderr.decode("utf-8")) +# pylint: disable=missing-function-docstring +class TestSystem(common.DockerTestCase): + """TestCase for exercising Podman system services.""" - TestSystem.podman.tear_down() - return super().tearDownClass() - - def test_Info(self): - info = self.client.info() + def test_info(self): + info = self.docker.info() self.assertIsNotNone(info) self.assertEqual(info["RegistryConfig"]["IndexConfigs"]["localhost:5000"]["Secure"], False) - self.assertEqual(info["RegistryConfig"]["IndexConfigs"]["localhost:5000"]["Mirrors"], ["mirror.localhost:5000"]) + self.assertEqual( + info["RegistryConfig"]["IndexConfigs"]["localhost:5000"]["Mirrors"], + ["mirror.localhost:5000"], + ) def test_info_container_details(self): - info = self.client.info() + info = self.docker.info() self.assertEqual(info["Containers"], 1) - self.client.containers.create(image=constant.ALPINE) - info = self.client.info() + self.docker.containers.create(image=constant.ALPINE) + info = self.docker.info() self.assertEqual(info["Containers"], 2) def test_version(self): - version = self.client.version() + version = self.docker.version() self.assertIsNotNone(version["Platform"]["Name"]) diff --git a/test/python/docker/conftest.py b/test/python/docker/conftest.py new file mode 100644 index 000000000..c63780fa8 --- /dev/null +++ b/test/python/docker/conftest.py @@ -0,0 +1,8 @@ +""" +Configure pytest +""" + + +def pytest_report_header(config): + """Add header to report.""" + return "python client -- DockerClient" diff --git a/test/system/070-build.bats b/test/system/070-build.bats index c963d8325..e47d66542 100644 --- a/test/system/070-build.bats +++ b/test/system/070-build.bats @@ -210,6 +210,30 @@ EOF run_podman rmi -f build_test } +@test "podman parallel build should not race" { + skip_if_remote "following test is not supported for remote clients" + + # Run thirty parallel builds using the same Containerfile + cat >$PODMAN_TMPDIR/Containerfile <<EOF +FROM $IMAGE +RUN echo hi +EOF + + local count=30 + for i in $(seq --format '%02g' 1 $count); do + timeout --foreground -v --kill=10 60 \ + $PODMAN build -t i$i $PODMAN_TMPDIR &>/dev/null & + done + + # Wait for all background builds to complete. Note that this succeeds + # even if some of the individual builds fail! Our actual test is below. + wait + + # Now delete all built images. If any image wasn't built, rmi will fail + # and test will fail. + run_podman rmi $(seq --format 'i%02g' 1 $count) +} + @test "podman build - URLs" { tmpdir=$PODMAN_TMPDIR/build-test mkdir -p $tmpdir |