github.com/AbhinandanKurakure/podman/v3@v3.4.10/test/python/docker/compat/test_containers.py (about) 1 import io 2 import subprocess 3 import sys 4 import time 5 import unittest 6 from typing import IO, Optional, List 7 8 from docker import DockerClient, errors 9 from docker.models.containers import Container 10 from docker.models.images import Image 11 from docker.models.volumes import Volume 12 from docker.types import Mount 13 14 from test.python.docker import Podman 15 from test.python.docker.compat import common, constant 16 17 import tarfile 18 19 20 class TestContainers(unittest.TestCase): 21 podman = None # initialized podman configuration for tests 22 service = None # podman service instance 23 topContainerId = "" 24 25 def setUp(self): 26 super().setUp() 27 self.client = DockerClient(base_url="tcp://127.0.0.1:8080", timeout=15) 28 TestContainers.podman.restore_image_from_cache(self.client) 29 TestContainers.topContainerId = common.run_top_container(self.client) 30 self.assertIsNotNone(TestContainers.topContainerId) 31 32 def tearDown(self): 33 common.remove_all_containers(self.client) 34 common.remove_all_images(self.client) 35 self.client.close() 36 return super().tearDown() 37 38 @classmethod 39 def setUpClass(cls): 40 super().setUpClass() 41 TestContainers.podman = Podman() 42 TestContainers.service = TestContainers.podman.open( 43 "system", "service", "tcp:127.0.0.1:8080", "--time=0" 44 ) 45 # give the service some time to be ready... 46 time.sleep(2) 47 48 rc = TestContainers.service.poll() 49 if rc is not None: 50 raise subprocess.CalledProcessError(rc, "podman system service") 51 52 @classmethod 53 def tearDownClass(cls): 54 TestContainers.service.terminate() 55 stdout, stderr = TestContainers.service.communicate(timeout=0.5) 56 if stdout: 57 sys.stdout.write("\nContainers Service Stdout:\n" + stdout.decode("utf-8")) 58 if stderr: 59 sys.stderr.write("\nContainers Service Stderr:\n" + stderr.decode("utf-8")) 60 61 TestContainers.podman.tear_down() 62 return super().tearDownClass() 63 64 def test_create_container(self): 65 # Run a container with detach mode 66 self.client.containers.create(image="alpine", detach=True) 67 self.assertEqual(len(self.client.containers.list(all=True)), 2) 68 69 def test_create_network(self): 70 net = self.client.networks.create("testNetwork", driver="bridge") 71 ctnr = self.client.containers.create(image="alpine", detach=True) 72 73 # TODO fix when ready 74 # This test will not work until all connect|disconnect 75 # code is fixed. 76 # net.connect(ctnr) 77 78 # nets = self.client.networks.list(greedy=True) 79 # self.assertGreaterEqual(len(nets), 1) 80 81 # TODO fix endpoint to include containers 82 # for n in nets: 83 # if n.id == "testNetwork": 84 # self.assertEqual(ctnr.id, n.containers) 85 # self.assertTrue(False, "testNetwork not found") 86 87 def test_start_container(self): 88 # Podman docs says it should give a 304 but returns with no response 89 # # Start a already started container should return 304 90 # response = self.client.api.start(container=TestContainers.topContainerId) 91 # self.assertEqual(error.exception.response.status_code, 304) 92 93 # Create a new container and validate the count 94 self.client.containers.create(image=constant.ALPINE, name="container2") 95 containers = self.client.containers.list(all=True) 96 self.assertEqual(len(containers), 2) 97 98 def test_start_container_with_random_port_bind(self): 99 container = self.client.containers.create( 100 image=constant.ALPINE, 101 name="containerWithRandomBind", 102 ports={"1234/tcp": None}, 103 ) 104 containers = self.client.containers.list(all=True) 105 self.assertTrue(container in containers) 106 107 def test_stop_container(self): 108 top = self.client.containers.get(TestContainers.topContainerId) 109 self.assertEqual(top.status, "running") 110 111 # Stop a running container and validate the state 112 top.stop() 113 top.reload() 114 self.assertIn(top.status, ("stopped", "exited")) 115 116 def test_kill_container(self): 117 top = self.client.containers.get(TestContainers.topContainerId) 118 self.assertEqual(top.status, "running") 119 120 # Kill a running container and validate the state 121 top.kill() 122 top.reload() 123 self.assertIn(top.status, ("stopped", "exited")) 124 125 def test_restart_container(self): 126 # Validate the container state 127 top = self.client.containers.get(TestContainers.topContainerId) 128 top.stop() 129 top.reload() 130 self.assertIn(top.status, ("stopped", "exited")) 131 132 # restart a running container and validate the state 133 top.restart() 134 top.reload() 135 self.assertEqual(top.status, "running") 136 137 def test_remove_container(self): 138 # Remove container by ID with force 139 top = self.client.containers.get(TestContainers.topContainerId) 140 top.remove(force=True) 141 self.assertEqual(len(self.client.containers.list()), 0) 142 143 def test_remove_container_without_force(self): 144 # Validate current container count 145 self.assertEqual(len(self.client.containers.list()), 1) 146 147 # Remove running container should throw error 148 top = self.client.containers.get(TestContainers.topContainerId) 149 with self.assertRaises(errors.APIError) as error: 150 top.remove() 151 self.assertEqual(error.exception.response.status_code, 500) 152 153 # Remove container by ID without force 154 top.stop() 155 top.remove() 156 self.assertEqual(len(self.client.containers.list()), 0) 157 158 def test_pause_container(self): 159 # Validate the container state 160 top = self.client.containers.get(TestContainers.topContainerId) 161 self.assertEqual(top.status, "running") 162 163 # Pause a running container and validate the state 164 top.pause() 165 top.reload() 166 self.assertEqual(top.status, "paused") 167 168 def test_pause_stopped_container(self): 169 # Stop the container 170 top = self.client.containers.get(TestContainers.topContainerId) 171 top.stop() 172 173 # Pause exited container should throw error 174 with self.assertRaises(errors.APIError) as error: 175 top.pause() 176 self.assertEqual(error.exception.response.status_code, 500) 177 178 def test_unpause_container(self): 179 top = self.client.containers.get(TestContainers.topContainerId) 180 181 # Validate the container state 182 top.pause() 183 top.reload() 184 self.assertEqual(top.status, "paused") 185 186 # Pause a running container and validate the state 187 top.unpause() 188 top.reload() 189 self.assertEqual(top.status, "running") 190 191 def test_list_container(self): 192 # Add container and validate the count 193 self.client.containers.create(image="alpine", detach=True) 194 containers = self.client.containers.list(all=True) 195 self.assertEqual(len(containers), 2) 196 197 def test_filters(self): 198 self.skipTest("TODO Endpoint does not yet support filters") 199 200 # List container with filter by id 201 filters = {"id": TestContainers.topContainerId} 202 ctnrs = self.client.containers.list(all=True, filters=filters) 203 self.assertEqual(len(ctnrs), 1) 204 205 # List container with filter by name 206 filters = {"name": "top"} 207 ctnrs = self.client.containers.list(all=True, filters=filters) 208 self.assertEqual(len(ctnrs), 1) 209 210 def test_copy_to_container(self): 211 ctr: Optional[Container] = None 212 vol: Optional[Volume] = None 213 try: 214 test_file_content = b"Hello World!" 215 vol = self.client.volumes.create("test-volume") 216 ctr = self.client.containers.create(image="alpine", 217 detach=True, 218 command="top", 219 volumes=["test-volume:/test-volume-read-only:ro"]) 220 ctr.start() 221 222 buff: IO[bytes] = io.BytesIO() 223 with tarfile.open(fileobj=buff, mode="w:") as tf: 224 ti: tarfile.TarInfo = tarfile.TarInfo() 225 ti.uid = 1042 226 ti.gid = 1043 227 ti.name = "a.txt" 228 ti.path = "a.txt" 229 ti.mode = 0o644 230 ti.type = tarfile.REGTYPE 231 ti.size = len(test_file_content) 232 tf.addfile(ti, fileobj=io.BytesIO(test_file_content)) 233 234 buff.seek(0) 235 ctr.put_archive("/tmp/", buff) 236 ret, out = ctr.exec_run(["stat", "-c", "%u:%g", "/tmp/a.txt"]) 237 238 self.assertEqual(ret, 0) 239 self.assertEqual(out.rstrip(), b'1042:1043', "UID/GID of copied file") 240 241 ret, out = ctr.exec_run(["cat", "/tmp/a.txt"]) 242 self.assertEqual(ret, 0) 243 self.assertEqual(out.rstrip(), test_file_content, "Content of copied file") 244 245 buff.seek(0) 246 with self.assertRaises(errors.APIError): 247 ctr.put_archive("/test-volume-read-only/", buff) 248 finally: 249 if ctr is not None: 250 ctr.stop() 251 ctr.remove() 252 if vol is not None: 253 vol.remove(force=True) 254 255 def test_mount_preexisting_dir(self): 256 dockerfile = (B'FROM quay.io/libpod/alpine:latest\n' 257 B'USER root\n' 258 B'RUN mkdir -p /workspace\n' 259 B'RUN chown 1042:1043 /workspace') 260 img: Image 261 img, out = self.client.images.build(fileobj=io.BytesIO(dockerfile)) 262 ctr: Container = self.client.containers.create(image=img.id, detach=True, command="top", 263 volumes=["test_mount_preexisting_dir_vol:/workspace"]) 264 ctr.start() 265 ret, out = ctr.exec_run(["stat", "-c", "%u:%g", "/workspace"]) 266 self.assertEqual(out.rstrip(), b'1042:1043', "UID/GID set in dockerfile") 267 268 269 def test_non_existant_workdir(self): 270 dockerfile = (B'FROM quay.io/libpod/alpine:latest\n' 271 B'USER root\n' 272 B'WORKDIR /workspace/scratch\n' 273 B'RUN touch test') 274 img: Image 275 img, out = self.client.images.build(fileobj=io.BytesIO(dockerfile)) 276 ctr: Container = self.client.containers.create(image=img.id, detach=True, command="top", 277 volumes=["test_non_existant_workdir:/workspace"]) 278 ctr.start() 279 ret, out = ctr.exec_run(["stat", "/workspace/scratch/test"]) 280 self.assertEqual(ret, 0, "Working directory created if it doesn't exist") 281 282 def test_mount_rw_by_default(self): 283 ctr: Optional[Container] = None 284 vol: Optional[Volume] = None 285 try: 286 vol = self.client.volumes.create("test-volume") 287 ctr = self.client.containers.create(image="alpine", 288 detach=True, 289 command="top", 290 mounts=[Mount(target="/vol-mnt", 291 source="test-volume", 292 type='volume', 293 read_only=False)]) 294 ctr_inspect = self.client.api.inspect_container(ctr.id) 295 binds: List[str] = ctr_inspect["HostConfig"]["Binds"] 296 self.assertEqual(len(binds), 1) 297 self.assertEqual(binds[0], 'test-volume:/vol-mnt:rw,rprivate,nosuid,nodev,rbind') 298 finally: 299 if ctr is not None: 300 ctr.remove() 301 if vol is not None: 302 vol.remove(force=True)