Skip to content

Commit

Permalink
Merge pull request #6420 from jwhonce/wip/json
Browse files Browse the repository at this point in the history
V2 verify JSON output is consistent and doesn't drift
  • Loading branch information
openshift-merge-robot committed May 29, 2020
2 parents e8818ce + 5626c21 commit cd1e25f
Show file tree
Hide file tree
Showing 12 changed files with 253 additions and 12 deletions.
2 changes: 1 addition & 1 deletion pkg/api/handlers/compat/containers_attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions pkg/api/handlers/compat/containers_stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
18 changes: 18 additions & 0 deletions pkg/api/handlers/compat/resize.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pkg/api/handlers/compat/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
4 changes: 4 additions & 0 deletions pkg/api/handlers/libpod/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/api/handlers/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion pkg/domain/entities/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
6 changes: 3 additions & 3 deletions test/apiv2/10-images.at
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
4 changes: 2 additions & 2 deletions test/apiv2/40-pods.at
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Empty file added test/apiv2/rest_api/__init__.py
Empty file.
219 changes: 219 additions & 0 deletions test/apiv2/rest_api/test_rest_v1_0_0.py
Original file line number Diff line number Diff line change
@@ -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()
2 changes: 1 addition & 1 deletion test/system/010-images.bats
Original file line number Diff line number Diff line change
Expand Up @@ -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]\\\+
Expand Down

0 comments on commit cd1e25f

Please sign in to comment.