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()