mirror of
https://github.com/containers/podman.git
synced 2025-06-26 21:07:02 +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.
|
// For Docker compatibility, we need to re-initialize containers in these states.
|
||||||
if state == define.ContainerStateConfigured || state == define.ContainerStateExited {
|
if state == define.ContainerStateConfigured || state == define.ContainerStateExited {
|
||||||
if err := ctr.Init(r.Context()); err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
} else if !(state == define.ContainerStateCreated || state == define.ContainerStateRunning) {
|
} else if !(state == define.ContainerStateCreated || state == define.ContainerStateRunning) {
|
||||||
|
@ -45,8 +45,8 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.InternalServerError(w, err)
|
utils.InternalServerError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if state != define.ContainerStateRunning && !query.Stream {
|
if state != define.ContainerStateRunning {
|
||||||
utils.InternalServerError(w, define.ErrCtrStateInvalid)
|
utils.Error(w, "Container not running and streaming requested", http.StatusConflict, define.ErrCtrStateInvalid)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
package compat
|
package compat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containers/libpod/libpod"
|
"github.com/containers/libpod/libpod"
|
||||||
|
"github.com/containers/libpod/libpod/define"
|
||||||
"github.com/containers/libpod/pkg/api/handlers/utils"
|
"github.com/containers/libpod/pkg/api/handlers/utils"
|
||||||
"github.com/gorilla/schema"
|
"github.com/gorilla/schema"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -43,6 +45,14 @@ func ResizeTTY(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.ContainerNotFound(w, name, err)
|
utils.ContainerNotFound(w, name, err)
|
||||||
return
|
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 {
|
if err := ctnr.AttachResize(sz); err != nil {
|
||||||
utils.InternalServerError(w, errors.Wrapf(err, "cannot resize container"))
|
utils.InternalServerError(w, errors.Wrapf(err, "cannot resize container"))
|
||||||
return
|
return
|
||||||
@ -56,6 +66,14 @@ func ResizeTTY(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.SessionNotFound(w, name, err)
|
utils.SessionNotFound(w, name, err)
|
||||||
return
|
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 {
|
if err := ctnr.ExecResize(name, sz); err != nil {
|
||||||
utils.InternalServerError(w, errors.Wrapf(err, "cannot resize session"))
|
utils.InternalServerError(w, errors.Wrapf(err, "cannot resize session"))
|
||||||
return
|
return
|
||||||
|
@ -48,7 +48,7 @@ type StatsJSON struct {
|
|||||||
Stats
|
Stats
|
||||||
|
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
ID string `json:"id,omitempty"`
|
ID string `json:"Id,omitempty"`
|
||||||
|
|
||||||
// Networks request version >=1.21
|
// Networks request version >=1.21
|
||||||
Networks map[string]docker.NetworkStats `json:"networks,omitempty"`
|
Networks map[string]docker.NetworkStats `json:"networks,omitempty"`
|
||||||
|
@ -66,6 +66,10 @@ func ListContainers(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.InternalServerError(w, err)
|
utils.InternalServerError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if len(pss) == 0 {
|
||||||
|
utils.WriteResponse(w, http.StatusOK, "[]")
|
||||||
|
return
|
||||||
|
}
|
||||||
utils.WriteResponse(w, http.StatusOK, pss)
|
utils.WriteResponse(w, http.StatusOK, pss)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +120,7 @@ type CreateContainerConfig struct {
|
|||||||
// swagger:model IDResponse
|
// swagger:model IDResponse
|
||||||
type IDResponse struct {
|
type IDResponse struct {
|
||||||
// ID
|
// ID
|
||||||
ID string `json:"id"`
|
ID string `json:"Id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContainerTopOKBody struct {
|
type ContainerTopOKBody struct {
|
||||||
|
@ -50,7 +50,7 @@ func (i *Image) Id() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ImageSummary struct {
|
type ImageSummary struct {
|
||||||
ID string
|
ID string `json:"Id"`
|
||||||
ParentId string `json:",omitempty"`
|
ParentId string `json:",omitempty"`
|
||||||
RepoTags []string `json:",omitempty"`
|
RepoTags []string `json:",omitempty"`
|
||||||
Created time.Time `json:",omitempty"`
|
Created time.Time `json:",omitempty"`
|
||||||
|
@ -7,15 +7,15 @@
|
|||||||
podman pull -q $IMAGE
|
podman pull -q $IMAGE
|
||||||
|
|
||||||
t GET libpod/images/json 200 \
|
t GET libpod/images/json 200 \
|
||||||
.[0].ID~[0-9a-f]\\{64\\}
|
.[0].Id~[0-9a-f]\\{64\\}
|
||||||
iid=$(jq -r '.[0].ID' <<<"$output")
|
iid=$(jq -r '.[0].Id' <<<"$output")
|
||||||
|
|
||||||
t GET libpod/images/$iid/exists 204
|
t GET libpod/images/$iid/exists 204
|
||||||
t GET libpod/images/$PODMAN_TEST_IMAGE_NAME/exists 204
|
t GET libpod/images/$PODMAN_TEST_IMAGE_NAME/exists 204
|
||||||
|
|
||||||
# FIXME: compare to actual podman info
|
# FIXME: compare to actual podman info
|
||||||
t GET libpod/images/json 200 \
|
t GET libpod/images/json 200 \
|
||||||
.[0].ID=${iid}
|
.[0].Id=${iid}
|
||||||
|
|
||||||
t GET libpod/images/$iid/json 200 \
|
t GET libpod/images/$iid/json 200 \
|
||||||
.Id=$iid \
|
.Id=$iid \
|
||||||
|
@ -5,8 +5,8 @@
|
|||||||
|
|
||||||
t GET "libpod/pods/json (clean slate at start)" 200 null
|
t GET "libpod/pods/json (clean slate at start)" 200 null
|
||||||
|
|
||||||
t POST libpod/pods/create name=foo 201 .id~[0-9a-f]\\{64\\}
|
t POST libpod/pods/create name=foo 201 .Id~[0-9a-f]\\{64\\}
|
||||||
pod_id=$(jq -r .id <<<"$output")
|
pod_id=$(jq -r .Id <<<"$output")
|
||||||
t GET libpod/pods/foo/exists 204
|
t GET libpod/pods/foo/exists 204
|
||||||
t GET libpod/pods/$pod_id/exists 204
|
t GET libpod/pods/$pod_id/exists 204
|
||||||
t GET libpod/pods/notfoo/exists 404
|
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
|
# 'created': podman includes fractional seconds, podman-remote does not
|
||||||
tests="
|
tests="
|
||||||
Names[0] | $PODMAN_TEST_IMAGE_FQN
|
Names[0] | $PODMAN_TEST_IMAGE_FQN
|
||||||
ID | [0-9a-f]\\\{64\\\}
|
Id | [0-9a-f]\\\{64\\\}
|
||||||
Digest | sha256:[0-9a-f]\\\{64\\\}
|
Digest | sha256:[0-9a-f]\\\{64\\\}
|
||||||
CreatedAt | [0-9-]\\\+T[0-9:.]\\\+Z
|
CreatedAt | [0-9-]\\\+T[0-9:.]\\\+Z
|
||||||
Size | [0-9]\\\+
|
Size | [0-9]\\\+
|
||||||
|
Reference in New Issue
Block a user