mirror of
https://github.com/containers/podman.git
synced 2025-06-27 05:26:50 +08:00
V2 verify JSON output is consistent and doesn't drift
$ cd test/apiv2 $ python -m unittest -v test_rest_v1_0_0.TestApi Signed-off-by: Jhon Honce <jhonce@redhat.com>
This commit is contained in:
@ -90,7 +90,7 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) {
|
||||
// For Docker compatibility, we need to re-initialize containers in these states.
|
||||
if state == define.ContainerStateConfigured || state == define.ContainerStateExited {
|
||||
if err := ctr.Init(r.Context()); err != nil {
|
||||
utils.InternalServerError(w, errors.Wrapf(err, "error preparing container %s for attach", ctr.ID()))
|
||||
utils.Error(w, "Container in wrong state", http.StatusConflict, errors.Wrapf(err, "error preparing container %s for attach", ctr.ID()))
|
||||
return
|
||||
}
|
||||
} else if !(state == define.ContainerStateCreated || state == define.ContainerStateRunning) {
|
||||
|
@ -45,8 +45,8 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
|
||||
utils.InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
if state != define.ContainerStateRunning && !query.Stream {
|
||||
utils.InternalServerError(w, define.ErrCtrStateInvalid)
|
||||
if state != define.ContainerStateRunning {
|
||||
utils.Error(w, "Container not running and streaming requested", http.StatusConflict, define.ErrCtrStateInvalid)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
package compat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/libpod/libpod"
|
||||
"github.com/containers/libpod/libpod/define"
|
||||
"github.com/containers/libpod/pkg/api/handlers/utils"
|
||||
"github.com/gorilla/schema"
|
||||
"github.com/pkg/errors"
|
||||
@ -43,6 +45,14 @@ func ResizeTTY(w http.ResponseWriter, r *http.Request) {
|
||||
utils.ContainerNotFound(w, name, err)
|
||||
return
|
||||
}
|
||||
if state, err := ctnr.State(); err != nil {
|
||||
utils.InternalServerError(w, errors.Wrapf(err, "cannot obtain container state"))
|
||||
return
|
||||
} else if state != define.ContainerStateRunning {
|
||||
utils.Error(w, "Container not running", http.StatusConflict,
|
||||
fmt.Errorf("container %q in wrong state %q", name, state.String()))
|
||||
return
|
||||
}
|
||||
if err := ctnr.AttachResize(sz); err != nil {
|
||||
utils.InternalServerError(w, errors.Wrapf(err, "cannot resize container"))
|
||||
return
|
||||
@ -56,6 +66,14 @@ func ResizeTTY(w http.ResponseWriter, r *http.Request) {
|
||||
utils.SessionNotFound(w, name, err)
|
||||
return
|
||||
}
|
||||
if state, err := ctnr.State(); err != nil {
|
||||
utils.InternalServerError(w, errors.Wrapf(err, "cannot obtain session container state"))
|
||||
return
|
||||
} else if state != define.ContainerStateRunning {
|
||||
utils.Error(w, "Container not running", http.StatusConflict,
|
||||
fmt.Errorf("container %q in wrong state %q", name, state.String()))
|
||||
return
|
||||
}
|
||||
if err := ctnr.ExecResize(name, sz); err != nil {
|
||||
utils.InternalServerError(w, errors.Wrapf(err, "cannot resize session"))
|
||||
return
|
||||
|
@ -48,7 +48,7 @@ type StatsJSON struct {
|
||||
Stats
|
||||
|
||||
Name string `json:"name,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
ID string `json:"Id,omitempty"`
|
||||
|
||||
// Networks request version >=1.21
|
||||
Networks map[string]docker.NetworkStats `json:"networks,omitempty"`
|
||||
|
@ -66,6 +66,10 @@ func ListContainers(w http.ResponseWriter, r *http.Request) {
|
||||
utils.InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
if len(pss) == 0 {
|
||||
utils.WriteResponse(w, http.StatusOK, "[]")
|
||||
return
|
||||
}
|
||||
utils.WriteResponse(w, http.StatusOK, pss)
|
||||
}
|
||||
|
||||
|
@ -120,7 +120,7 @@ type CreateContainerConfig struct {
|
||||
// swagger:model IDResponse
|
||||
type IDResponse struct {
|
||||
// ID
|
||||
ID string `json:"id"`
|
||||
ID string `json:"Id"`
|
||||
}
|
||||
|
||||
type ContainerTopOKBody struct {
|
||||
|
@ -50,7 +50,7 @@ func (i *Image) Id() string {
|
||||
}
|
||||
|
||||
type ImageSummary struct {
|
||||
ID string
|
||||
ID string `json:"Id"`
|
||||
ParentId string `json:",omitempty"`
|
||||
RepoTags []string `json:",omitempty"`
|
||||
Created time.Time `json:",omitempty"`
|
||||
|
@ -7,15 +7,15 @@
|
||||
podman pull -q $IMAGE
|
||||
|
||||
t GET libpod/images/json 200 \
|
||||
.[0].ID~[0-9a-f]\\{64\\}
|
||||
iid=$(jq -r '.[0].ID' <<<"$output")
|
||||
.[0].Id~[0-9a-f]\\{64\\}
|
||||
iid=$(jq -r '.[0].Id' <<<"$output")
|
||||
|
||||
t GET libpod/images/$iid/exists 204
|
||||
t GET libpod/images/$PODMAN_TEST_IMAGE_NAME/exists 204
|
||||
|
||||
# FIXME: compare to actual podman info
|
||||
t GET libpod/images/json 200 \
|
||||
.[0].ID=${iid}
|
||||
.[0].Id=${iid}
|
||||
|
||||
t GET libpod/images/$iid/json 200 \
|
||||
.Id=$iid \
|
||||
|
@ -5,8 +5,8 @@
|
||||
|
||||
t GET "libpod/pods/json (clean slate at start)" 200 null
|
||||
|
||||
t POST libpod/pods/create name=foo 201 .id~[0-9a-f]\\{64\\}
|
||||
pod_id=$(jq -r .id <<<"$output")
|
||||
t POST libpod/pods/create name=foo 201 .Id~[0-9a-f]\\{64\\}
|
||||
pod_id=$(jq -r .Id <<<"$output")
|
||||
t GET libpod/pods/foo/exists 204
|
||||
t GET libpod/pods/$pod_id/exists 204
|
||||
t GET libpod/pods/notfoo/exists 404
|
||||
|
0
test/apiv2/rest_api/__init__.py
Normal file
0
test/apiv2/rest_api/__init__.py
Normal file
219
test/apiv2/rest_api/test_rest_v1_0_0.py
Normal file
219
test/apiv2/rest_api/test_rest_v1_0_0.py
Normal file
@ -0,0 +1,219 @@
|
||||
import json
|
||||
import os
|
||||
import shlex
|
||||
import signal
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import unittest
|
||||
from collections.abc import Iterable
|
||||
from multiprocessing import Process
|
||||
|
||||
import requests
|
||||
from dateutil.parser import parse
|
||||
|
||||
|
||||
def _url(path):
|
||||
return "http://localhost:8080/v1.0.0/libpod" + path
|
||||
|
||||
|
||||
def podman():
|
||||
binary = os.getenv("PODMAN_BINARY")
|
||||
if binary is None:
|
||||
binary = "bin/podman"
|
||||
return binary
|
||||
|
||||
|
||||
def ctnr(path):
|
||||
r = requests.get(_url("/containers/json?all=true"))
|
||||
try:
|
||||
ctnrs = json.loads(r.text)
|
||||
except Exception as e:
|
||||
sys.stderr.write("Bad container response: {}/{}".format(r.text, e))
|
||||
raise e
|
||||
return path.format(ctnrs[0]["Id"])
|
||||
|
||||
|
||||
class TestApi(unittest.TestCase):
|
||||
podman = None
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
if TestApi.podman.poll() is not None:
|
||||
sys.stderr.write("podman service returned {}",
|
||||
TestApi.podman.returncode)
|
||||
sys.exit(2)
|
||||
requests.get(
|
||||
_url("/images/create?fromSrc=docker.io%2Falpine%3Alatest"))
|
||||
# calling out to podman is easier than the API for running a container
|
||||
subprocess.run([podman(), "run", "alpine", "/bin/ls"],
|
||||
check=True,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL)
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
TestApi.podman = subprocess.Popen(
|
||||
[
|
||||
podman(), "system", "service", "tcp:localhost:8080",
|
||||
"--log-level=debug", "--time=0"
|
||||
],
|
||||
shell=False,
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
time.sleep(2)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
TestApi.podman.terminate()
|
||||
stdout, stderr = TestApi.podman.communicate(timeout=0.5)
|
||||
if stdout:
|
||||
print("\nService Stdout:\n" + stdout.decode('utf-8'))
|
||||
if stderr:
|
||||
print("\nService Stderr:\n" + stderr.decode('utf-8'))
|
||||
|
||||
if TestApi.podman.returncode > 0:
|
||||
sys.stderr.write("podman exited with error code {}\n".format(
|
||||
TestApi.podman.returncode))
|
||||
sys.exit(2)
|
||||
|
||||
return super().tearDownClass()
|
||||
|
||||
def test_info(self):
|
||||
r = requests.get(_url("/info"))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertIsNotNone(r.content)
|
||||
_ = json.loads(r.text)
|
||||
|
||||
def test_events(self):
|
||||
r = requests.get(_url("/events?stream=false"))
|
||||
self.assertEqual(r.status_code, 200, r.text)
|
||||
self.assertIsNotNone(r.content)
|
||||
for line in r.text.splitlines():
|
||||
obj = json.loads(line)
|
||||
# Actor.ID is uppercase for compatibility
|
||||
_ = obj["Actor"]["ID"]
|
||||
|
||||
def test_containers(self):
|
||||
r = requests.get(_url("/containers/json"), timeout=5)
|
||||
self.assertEqual(r.status_code, 200, r.text)
|
||||
obj = json.loads(r.text)
|
||||
self.assertEqual(len(obj), 0)
|
||||
|
||||
def test_containers_all(self):
|
||||
r = requests.get(_url("/containers/json?all=true"))
|
||||
self.assertEqual(r.status_code, 200, r.text)
|
||||
self.validateObjectFields(r.text)
|
||||
|
||||
def test_inspect_container(self):
|
||||
r = requests.get(_url(ctnr("/containers/{}/json")))
|
||||
self.assertEqual(r.status_code, 200, r.text)
|
||||
obj = self.validateObjectFields(r.content)
|
||||
_ = parse(obj["Created"])
|
||||
|
||||
def test_stats(self):
|
||||
r = requests.get(_url(ctnr("/containers/{}/stats?stream=false")))
|
||||
self.assertIn(r.status_code, (200, 409), r.text)
|
||||
if r.status_code == 200:
|
||||
self.validateObjectFields(r.text)
|
||||
|
||||
def test_delete_containers(self):
|
||||
r = requests.delete(_url(ctnr("/containers/{}")))
|
||||
self.assertEqual(r.status_code, 204, r.text)
|
||||
|
||||
def test_stop_containers(self):
|
||||
r = requests.post(_url(ctnr("/containers/{}/start")))
|
||||
self.assertIn(r.status_code, (204, 304), r.text)
|
||||
|
||||
r = requests.post(_url(ctnr("/containers/{}/stop")))
|
||||
self.assertIn(r.status_code, (204, 304), r.text)
|
||||
|
||||
def test_start_containers(self):
|
||||
r = requests.post(_url(ctnr("/containers/{}/stop")))
|
||||
self.assertIn(r.status_code, (204, 304), r.text)
|
||||
|
||||
r = requests.post(_url(ctnr("/containers/{}/start")))
|
||||
self.assertIn(r.status_code, (204, 304), r.text)
|
||||
|
||||
def test_restart_containers(self):
|
||||
r = requests.post(_url(ctnr("/containers/{}/start")))
|
||||
self.assertIn(r.status_code, (204, 304), r.text)
|
||||
|
||||
r = requests.post(_url(ctnr("/containers/{}/restart")), timeout=5)
|
||||
self.assertEqual(r.status_code, 204, r.text)
|
||||
|
||||
def test_resize(self):
|
||||
r = requests.post(_url(ctnr("/containers/{}/resize?h=43&w=80")))
|
||||
self.assertIn(r.status_code, (200, 409), r.text)
|
||||
if r.status_code == 200:
|
||||
self.assertIsNone(r.text)
|
||||
|
||||
def test_attach_containers(self):
|
||||
r = requests.post(_url(ctnr("/containers/{}/attach")))
|
||||
self.assertIn(r.status_code, (101, 409), r.text)
|
||||
|
||||
def test_logs_containers(self):
|
||||
r = requests.get(_url(ctnr("/containers/{}/logs?stdout=true")))
|
||||
self.assertEqual(r.status_code, 200, r.text)
|
||||
|
||||
def test_post_create(self):
|
||||
self.skipTest("TODO: create request body")
|
||||
r = requests.post(_url("/containers/create?args=True"))
|
||||
self.assertEqual(r.status_code, 200, r.text)
|
||||
json.loads(r.text)
|
||||
|
||||
def test_commit(self):
|
||||
r = requests.post(_url(ctnr("/commit?container={}")))
|
||||
self.assertEqual(r.status_code, 200, r.text)
|
||||
self.validateObjectFields(r.text)
|
||||
|
||||
def test_images(self):
|
||||
r = requests.get(_url("/images/json"))
|
||||
self.assertEqual(r.status_code, 200, r.text)
|
||||
self.validateObjectFields(r.content)
|
||||
|
||||
def test_inspect_image(self):
|
||||
r = requests.get(_url("/images/alpine/json"))
|
||||
self.assertEqual(r.status_code, 200, r.text)
|
||||
obj = self.validateObjectFields(r.content)
|
||||
_ = parse(obj["Created"])
|
||||
|
||||
def test_delete_image(self):
|
||||
r = requests.delete(_url("/images/alpine?force=true"))
|
||||
self.assertEqual(r.status_code, 200, r.text)
|
||||
json.loads(r.text)
|
||||
|
||||
def test_pull(self):
|
||||
r = requests.post(_url("/images/pull?reference=alpine"), timeout=5)
|
||||
self.assertEqual(r.status_code, 200, r.text)
|
||||
json.loads(r.text)
|
||||
|
||||
def test_search(self):
|
||||
# Had issues with this test hanging when repositories not happy
|
||||
def do_search():
|
||||
r = requests.get(_url("/images/search?term=alpine"), timeout=5)
|
||||
self.assertEqual(r.status_code, 200, r.text)
|
||||
json.loads(r.text)
|
||||
|
||||
search = Process(target=do_search)
|
||||
search.start()
|
||||
search.join(timeout=10)
|
||||
self.assertFalse(search.is_alive(), "/images/search took too long")
|
||||
|
||||
def validateObjectFields(self, buffer):
|
||||
objs = json.loads(buffer)
|
||||
if not isinstance(objs, dict):
|
||||
for o in objs:
|
||||
_ = o["Id"]
|
||||
else:
|
||||
_ = objs["Id"]
|
||||
return objs
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -28,7 +28,7 @@ load helpers
|
||||
# 'created': podman includes fractional seconds, podman-remote does not
|
||||
tests="
|
||||
Names[0] | $PODMAN_TEST_IMAGE_FQN
|
||||
ID | [0-9a-f]\\\{64\\\}
|
||||
Id | [0-9a-f]\\\{64\\\}
|
||||
Digest | sha256:[0-9a-f]\\\{64\\\}
|
||||
CreatedAt | [0-9-]\\\+T[0-9:.]\\\+Z
|
||||
Size | [0-9]\\\+
|
||||
|
Reference in New Issue
Block a user