github.com/containers/podman/v5@v5.1.0-rc1/test/apiv2/python/rest_api/test_v2_0_0_container.py (about) 1 import io 2 import multiprocessing 3 import queue 4 import random 5 import subprocess 6 import tarfile 7 import threading 8 import unittest 9 10 import requests 11 import os 12 import time 13 from dateutil.parser import parse 14 15 from .fixtures import APITestCase 16 17 18 class ContainerTestCase(APITestCase): 19 def test_list(self): 20 r = requests.get(self.uri("/containers/json"), timeout=5) 21 self.assertEqual(r.status_code, 200, r.text) 22 obj = r.json() 23 self.assertEqual(len(obj), 1) 24 25 def test_list_filters_status(self): 26 r = requests.get( 27 self.podman_url 28 + "/v1.40/containers/json?filters=%7B%22status%22%3A%5B%22running%22%5D%7D" 29 ) 30 self.assertEqual(r.status_code, 200, r.text) 31 payload = r.json() 32 containerAmnt = len(payload) 33 self.assertGreater(containerAmnt, 0) 34 35 def test_list_filters_label(self): 36 r = requests.get( 37 self.podman_url 38 + "/v1.40/containers/json?filters=%7B%22label%22%3A%5B%22nonexistlabel%22%5D%7D" 39 ) 40 self.assertEqual(r.status_code, 200, r.text) 41 payload = r.json() 42 containerAmnt = len(payload) 43 self.assertEqual(containerAmnt, 0) 44 45 def test_list_filters_label_not(self): 46 r = requests.get( 47 self.podman_url 48 + "/v1.40/containers/json?filters=%7B%22label%21%22%3A%5B%22nonexistlabel%22%5D%7D" 49 ) 50 self.assertEqual(r.status_code, 200, r.text) 51 payload = r.json() 52 containerAmnt = len(payload) 53 self.assertGreater(containerAmnt, 0) 54 55 def test_list_all(self): 56 r = requests.get(self.uri("/containers/json?all=true")) 57 self.assertEqual(r.status_code, 200, r.text) 58 self.assertId(r.content) 59 60 def test_inspect(self): 61 r = requests.get(self.uri(self.resolve_container("/containers/{}/json"))) 62 self.assertEqual(r.status_code, 200, r.text) 63 self.assertId(r.content) 64 _ = parse(r.json()["Created"]) 65 66 r = requests.post( 67 self.podman_url + "/v1.40/containers/create?name=topcontainer", 68 json={ 69 "Cmd": ["top"], 70 "Image": "alpine:latest", 71 "Healthcheck": { 72 "Test": ["CMD", "pidof", "top"], 73 "Interval": 5000000000, 74 "Timeout": 2000000000, 75 "Retries": 3, 76 "StartPeriod": 5000000000, 77 }, 78 }, 79 ) 80 self.assertEqual(r.status_code, 201, r.text) 81 payload = r.json() 82 container_id = payload["Id"] 83 self.assertIsNotNone(container_id) 84 85 r = requests.get(self.podman_url + f"/v1.40/containers/{container_id}/json") 86 self.assertEqual(r.status_code, 200, r.text) 87 self.assertId(r.content) 88 out = r.json() 89 self.assertIsNotNone(out["State"].get("Health")) 90 self.assertListEqual(["CMD", "pidof", "top"], out["Config"]["Healthcheck"]["Test"]) 91 self.assertEqual(5000000000, out["Config"]["Healthcheck"]["Interval"]) 92 self.assertEqual(2000000000, out["Config"]["Healthcheck"]["Timeout"]) 93 self.assertEqual(3, out["Config"]["Healthcheck"]["Retries"]) 94 self.assertEqual(5000000000, out["Config"]["Healthcheck"]["StartPeriod"]) 95 96 r = requests.get(self.uri(f"/containers/{container_id}/json")) 97 self.assertEqual(r.status_code, 200, r.text) 98 self.assertId(r.content) 99 out = r.json() 100 hc = out["Config"]["Healthcheck"]["Test"] 101 self.assertListEqual(["CMD", "pidof", "top"], hc) 102 103 r = requests.post(self.podman_url + f"/v1.40/containers/{container_id}/start") 104 self.assertEqual(r.status_code, 204, r.text) 105 106 r = requests.get(self.podman_url + f"/v1.40/containers/{container_id}/json") 107 self.assertEqual(r.status_code, 200, r.text) 108 out = r.json() 109 state = out["State"]["Health"] 110 self.assertIsInstance(state, dict) 111 112 def test_stats(self): 113 r = requests.get(self.uri(self.resolve_container("/containers/{}/stats?stream=false"))) 114 self.assertIn(r.status_code, (200, 409), r.text) 115 if r.status_code == 200: 116 self.assertId(r.content) 117 r = requests.get( 118 self.uri(self.resolve_container("/containers/{}/stats?stream=false&one-shot=true")) 119 ) 120 self.assertIn(r.status_code, (200, 409), r.text) 121 if r.status_code == 200: 122 self.assertId(r.content) 123 124 def test_delete(self): 125 r = requests.delete(self.uri(self.resolve_container("/containers/{}?force=true"))) 126 self.assertEqual(r.status_code, 200, r.text) 127 128 def test_stop(self): 129 r = requests.post(self.uri(self.resolve_container("/containers/{}/start"))) 130 self.assertIn(r.status_code, (204, 304), r.text) 131 132 r = requests.post(self.uri(self.resolve_container("/containers/{}/stop"))) 133 self.assertIn(r.status_code, (204, 304), r.text) 134 135 def test_start(self): 136 r = requests.post(self.uri(self.resolve_container("/containers/{}/stop"))) 137 self.assertIn(r.status_code, (204, 304), r.text) 138 139 r = requests.post(self.uri(self.resolve_container("/containers/{}/start"))) 140 self.assertIn(r.status_code, (204, 304), r.text) 141 142 def test_restart(self): 143 r = requests.post(self.uri(self.resolve_container("/containers/{}/start"))) 144 self.assertIn(r.status_code, (204, 304), r.text) 145 146 r = requests.post(self.uri(self.resolve_container("/containers/{}/restart")), timeout=5) 147 self.assertEqual(r.status_code, 204, r.text) 148 149 def test_resize(self): 150 r = requests.post(self.uri(self.resolve_container("/containers/{}/resize?h=43&w=80"))) 151 self.assertIn(r.status_code, (200, 409), r.text) 152 if r.status_code == 200: 153 self.assertEqual(r.text, "", r.text) 154 155 def test_attach(self): 156 r = requests.post( 157 self.podman_url + "/v1.40/containers/create?name=topcontainer", 158 json={"Cmd": ["sh", "-c", "echo podman; sleep 100"], "Image": "alpine:latest"}, 159 ) 160 self.assertEqual(r.status_code, 201, r.text) 161 payload = r.json() 162 163 r = requests.post( 164 self.podman_url 165 + f"/v1.40/containers/{payload['Id']}/start" 166 ) 167 self.assertEqual(r.status_code, 204, r.text) 168 169 # wait for the log message to appear to avoid flakes on slow systems 170 # with the /attach?logs=true test below 171 for _ in range(5): 172 r = requests.get( 173 self.podman_url 174 + f"/v1.40/containers/{payload['Id']}/logs?stdout=true" 175 ) 176 self.assertIn(r.status_code, (101, 200), r.text) 177 if r.content == b"\x01\x00\x00\x00\x00\x00\x00\x07podman\n": 178 break 179 180 time.sleep(1) 181 182 r = requests.post( 183 self.podman_url 184 + f"/v1.40/containers/{payload['Id']}/attach?logs=true&stream=false" 185 ) 186 self.assertIn(r.status_code, (101, 200), r.text) 187 # see the attach format docs, stdout = 1, length = 7, message = podman\n 188 self.assertEqual(r.content, b"\x01\x00\x00\x00\x00\x00\x00\x07podman\n", r.text) 189 190 r = requests.post( 191 self.podman_url 192 + f"/v1.40/containers/{payload['Id']}/stop?t=0" 193 ) 194 self.assertEqual(r.status_code, 204, r.text) 195 196 requests.delete( 197 self.podman_url + f"/v1.40/containers/{payload['Id']}?force=true" 198 ) 199 200 def test_logs(self): 201 r = requests.get(self.uri(self.resolve_container("/containers/{}/logs?stdout=true"))) 202 self.assertEqual(r.status_code, 200, r.text) 203 r = requests.post( 204 self.podman_url + "/v1.40/containers/create?name=topcontainer", 205 json={"Cmd": ["top", "ls"], "Image": "alpine:latest"}, 206 ) 207 self.assertEqual(r.status_code, 201, r.text) 208 payload = r.json() 209 container_id = payload["Id"] 210 self.assertIsNotNone(container_id) 211 r = requests.get( 212 self.podman_url 213 + f"/v1.40/containers/{payload['Id']}/logs?follow=false&stdout=true&until=0" 214 ) 215 self.assertEqual(r.status_code, 200, r.text) 216 r = requests.get( 217 self.podman_url 218 + f"/v1.40/containers/{payload['Id']}/logs?follow=false&stdout=true&until=1" 219 ) 220 self.assertEqual(r.status_code, 200, r.text) 221 222 def test_commit(self): 223 r = requests.post(self.uri(self.resolve_container("/commit?container={}"))) 224 self.assertEqual(r.status_code, 200, r.text) 225 self.assertId(r.content) 226 227 obj = r.json() 228 self.assertIsInstance(obj, dict) 229 230 def test_prune(self): 231 name = f"Container_{random.getrandbits(160):x}" 232 233 r = requests.post( 234 self.podman_url + f"/v1.40/containers/create?name={name}", 235 json={ 236 "Cmd": ["cp", "/etc/motd", "/motd.size_test"], 237 "Image": "alpine:latest", 238 "NetworkDisabled": True, 239 }, 240 ) 241 self.assertEqual(r.status_code, 201, r.text) 242 create = r.json() 243 244 r = requests.post(self.podman_url + f"/v1.40/containers/{create['Id']}/start") 245 self.assertEqual(r.status_code, 204, r.text) 246 247 r = requests.post(self.podman_url + f"/v1.40/containers/{create['Id']}/wait") 248 self.assertEqual(r.status_code, 200, r.text) 249 wait = r.json() 250 self.assertEqual(wait["StatusCode"], 0, wait["Error"]) 251 252 prune = requests.post(self.podman_url + "/v1.40/containers/prune") 253 self.assertEqual(prune.status_code, 200, prune.status_code) 254 prune_payload = prune.json() 255 self.assertGreater(prune_payload["SpaceReclaimed"], 0) 256 self.assertIn(create["Id"], prune_payload["ContainersDeleted"]) 257 258 # Delete any orphaned containers 259 r = requests.get(self.podman_url + "/v1.40/containers/json?all=true") 260 self.assertEqual(r.status_code, 200, r.text) 261 for self.resolve_container in r.json(): 262 requests.delete( 263 self.podman_url + f"/v1.40/containers/{self.resolve_container['Id']}?force=true" 264 ) 265 266 # Image prune here tied to containers freeing up 267 prune = requests.post(self.podman_url + "/v1.40/images/prune") 268 self.assertEqual(prune.status_code, 200, prune.text) 269 prune_payload = prune.json() 270 self.assertGreater(prune_payload["SpaceReclaimed"], 0) 271 272 # FIXME need method to determine which image is going to be "pruned" to fix test 273 # TODO should handler be recursive when deleting images? 274 # self.assertIn(img["Id"], prune_payload["ImagesDeleted"][1]["Deleted"]) 275 276 # FIXME (@vrothberg): I commented this line out during the `libimage` migration. 277 # It doesn't make sense to report anything to be deleted if the reclaimed space 278 # is zero. I think the test needs some rewrite. 279 # self.assertIsNotNone(prune_payload["ImagesDeleted"][1]["Deleted"]) 280 281 def test_status(self): 282 r = requests.post( 283 self.podman_url + "/v1.40/containers/create?name=topcontainer", 284 json={"Cmd": ["top"], "Image": "alpine:latest"}, 285 ) 286 self.assertEqual(r.status_code, 201, r.text) 287 payload = r.json() 288 container_id = payload["Id"] 289 self.assertIsNotNone(container_id) 290 291 r = requests.get( 292 self.podman_url + "/v1.40/containers/json", 293 params={"all": "true", "filters": f'{{"id":["{container_id}"]}}'}, 294 ) 295 self.assertEqual(r.status_code, 200, r.text) 296 payload = r.json() 297 self.assertEqual(payload[0]["Status"], "Created") 298 299 r = requests.post(self.podman_url + f"/v1.40/containers/{container_id}/start") 300 self.assertEqual(r.status_code, 204, r.text) 301 302 r = requests.get( 303 self.podman_url + "/v1.40/containers/json", 304 params={"all": "true", "filters": f'{{"id":["{container_id}"]}}'}, 305 ) 306 self.assertEqual(r.status_code, 200, r.text) 307 payload = r.json() 308 self.assertTrue(str(payload[0]["Status"]).startswith("Up")) 309 310 r = requests.post(self.podman_url + f"/v1.40/containers/{container_id}/pause") 311 self.assertEqual(r.status_code, 204, r.text) 312 313 r = requests.get( 314 self.podman_url + "/v1.40/containers/json", 315 params={"all": "true", "filters": f'{{"id":["{container_id}"]}}'}, 316 ) 317 self.assertEqual(r.status_code, 200, r.text) 318 payload = r.json() 319 self.assertTrue(str(payload[0]["Status"]).startswith("Up")) 320 self.assertTrue(str(payload[0]["Status"]).endswith("(Paused)")) 321 322 r = requests.post(self.podman_url + f"/v1.40/containers/{container_id}/unpause") 323 self.assertEqual(r.status_code, 204, r.text) 324 r = requests.post(self.podman_url + f"/v1.40/containers/{container_id}/stop") 325 self.assertEqual(r.status_code, 204, r.text) 326 327 r = requests.get( 328 self.podman_url + "/v1.40/containers/json", 329 params={"all": "true", "filters": f'{{"id":["{container_id}"]}}'}, 330 ) 331 self.assertEqual(r.status_code, 200, r.text) 332 payload = r.json() 333 self.assertTrue(str(payload[0]["Status"]).startswith("Exited")) 334 335 r = requests.delete(self.podman_url + f"/v1.40/containers/{container_id}") 336 self.assertEqual(r.status_code, 204, r.text) 337 338 def test_top_no_stream(self): 339 uri = self.uri(self.resolve_container("/containers/{}/top")) 340 q = queue.Queue() 341 342 def _impl(fifo): 343 fifo.put(requests.get(uri, params={"stream": False}, timeout=2)) 344 345 top = threading.Thread(target=_impl, args=(q,)) 346 top.start() 347 time.sleep(2) 348 self.assertFalse(top.is_alive(), f"GET {uri} failed to return in 2s") 349 350 qr = q.get(False) 351 self.assertEqual(qr.status_code, 200, qr.text) 352 353 qr.close() 354 top.join() 355 356 def test_top_stream(self): 357 uri = self.uri(self.resolve_container("/containers/{}/top")) 358 q = queue.Queue() 359 360 stop_thread = False 361 362 def _impl(fifo, stop): 363 try: 364 with requests.get(uri, params={"stream": True, "delay": 1}, stream=True) as r: 365 r.raise_for_status() 366 fifo.put(r) 367 for buf in r.iter_lines(chunk_size=None): 368 if stop(): 369 break 370 fifo.put(buf) 371 except Exception: 372 pass 373 374 top = threading.Thread(target=_impl, args=(q, (lambda: stop_thread))) 375 top.start() 376 time.sleep(4) 377 self.assertTrue(top.is_alive(), f"GET {uri} exited too soon") 378 stop_thread = True 379 380 for _ in range(10): 381 try: 382 qr = q.get_nowait() 383 if qr is not None: 384 self.assertEqual(qr.status_code, 200) 385 qr.close() 386 break 387 except queue.Empty: 388 pass 389 finally: 390 time.sleep(1) 391 else: 392 self.fail("Server failed to respond in 10s") 393 top.join() 394 395 def test_memory(self): 396 r = requests.post( 397 self.podman_url + "/v1.4.0/libpod/containers/create", 398 json={ 399 "Name": "memory", 400 "Cmd": ["top"], 401 "Image": "alpine:latest", 402 "Resource_Limits": { 403 "Memory":{ 404 "Limit": 1000, 405 }, 406 "CPU":{ 407 "Shares": 200, 408 }, 409 }, 410 }, 411 ) 412 self.assertEqual(r.status_code, 201, r.text) 413 payload = r.json() 414 container_id = payload["Id"] 415 self.assertIsNotNone(container_id) 416 417 r = requests.get(self.podman_url + f"/v1.40/containers/{container_id}/json") 418 self.assertEqual(r.status_code, 200, r.text) 419 self.assertId(r.content) 420 out = r.json() 421 self.assertEqual(2000, out["HostConfig"]["MemorySwap"]) 422 self.assertEqual(1000, out["HostConfig"]["Memory"]) 423 424 def test_host_config_port_bindings(self): 425 # create a container with two ports exposed, but only one of the ports bound 426 r = requests.post( 427 self.podman_url + "/v1.40/containers/create", 428 json={ 429 "Name": "memory", 430 "Cmd": ["top"], 431 "Image": "alpine:latest", 432 "HostConfig": { 433 "PortBindings": { 434 "8080": [{"HostPort": "87634"}] 435 } 436 }, 437 "ExposedPorts": { 438 "8080": {}, 439 "8081": {} 440 } 441 }, 442 ) 443 self.assertEqual(r.status_code, 201, r.text) 444 payload = r.json() 445 container_id = payload["Id"] 446 self.assertIsNotNone(container_id) 447 448 r = requests.get(self.podman_url + 449 f"/v1.40/containers/{container_id}/json") 450 self.assertEqual(r.status_code, 200, r.text) 451 inspect_response = r.json() 452 # both ports are in the config 453 self.assertEqual(2, len(inspect_response["Config"]["ExposedPorts"])) 454 self.assertTrue("8080/tcp" in inspect_response["Config"]["ExposedPorts"]) 455 self.assertTrue("8081/tcp" in inspect_response["Config"]["ExposedPorts"]) 456 # only 8080 one port is bound 457 self.assertEqual(1, len(inspect_response["HostConfig"]["PortBindings"])) 458 self.assertTrue("8080/tcp" in inspect_response["HostConfig"]["PortBindings"]) 459 self.assertFalse("8081/tcp" in inspect_response["HostConfig"]["PortBindings"]) 460 461 def execute_process(cmd): 462 return subprocess.run( 463 cmd, 464 shell=True, 465 check=True, 466 stdout=subprocess.PIPE, 467 stderr=subprocess.PIPE, 468 ) 469 470 def create_named_network_ns(network_ns_name): 471 execute_process(f"ip netns add {network_ns_name}") 472 execute_process(f"ip netns exec {network_ns_name} ip link add enp2s0 type veth peer name eth0") 473 execute_process(f"ip netns exec {network_ns_name} ip addr add 10.0.1.0/24 dev eth0") 474 execute_process(f"ip netns exec {network_ns_name} ip link set eth0 up") 475 execute_process(f"ip netns exec {network_ns_name} ip link add enp2s1 type veth peer name eth1") 476 execute_process(f"ip netns exec {network_ns_name} ip addr add 10.0.2.0/24 dev eth1") 477 execute_process(f"ip netns exec {network_ns_name} ip link set eth1 up") 478 479 def delete_named_network_ns(network_ns_name): 480 execute_process(f"ip netns delete {network_ns_name}") 481 482 class ContainerCompatibleAPITestCase(APITestCase): 483 def test_inspect_network(self): 484 if os.getuid() != 0: 485 self.skipTest("test needs to be executed as root!") 486 try: 487 network_ns_name = "test-compat-api" 488 create_named_network_ns(network_ns_name) 489 self.podman.run("rm", "--all", "--force", check=True) 490 self.podman.run("run", "--net", f"ns:/run/netns/{network_ns_name}", "-d", "alpine", "top", check=True) 491 492 r = requests.post(self.uri(self.resolve_container("/containers/{}/start"))) 493 self.assertIn(r.status_code, (204, 304), r.text) 494 495 r = requests.get(self.compat_uri(self.resolve_container("/containers/{}/json"))) 496 self.assertEqual(r.status_code, 200, r.text) 497 self.assertId(r.content) 498 out = r.json() 499 500 self.assertEqual("10.0.2.0", out["NetworkSettings"]["SecondaryIPAddresses"][0]["Addr"]) 501 self.assertEqual(24, out["NetworkSettings"]["SecondaryIPAddresses"][0]["PrefixLen"]) 502 finally: 503 delete_named_network_ns(network_ns_name) 504 505 if __name__ == "__main__": 506 unittest.main()