Commit f013c296 authored by BAIRE Anthony's avatar BAIRE Anthony
Browse files

test commit

parent 6c7a9958
......@@ -3,6 +3,8 @@
import asyncio
from contextlib import contextmanager, suppress
import io
import json
import logging
import re
import socket
......@@ -12,6 +14,7 @@ import unittest
from unittest import mock
import controller
from controller import docker_check_error
import docker
from docker.errors import NotFound
from database import *
......@@ -20,7 +23,7 @@ from database import *
REGISTRY = "localhost:8002/allgo/dev/test"
ENV = "test"
MYSQL_HOST = "dev-mysql"
DOCKER_HOST = "unix:///run/docker.sock"
DOCKER_HOST = "unix:///vol/test-daemon/docker.sock"
PORT = 6789
S = SandboxState
......@@ -31,6 +34,9 @@ log = logging.getLogger("test_controller")
BAD_SANDBOX_NAMES = "", ".", "..", "foo/bar", "../blah", "/root", "foo/"
def update_default(dct, **kw):
for k, v in kw.items():
dct.setdefault(k, v)
def wait_until(func, *, period=.05, timeout=1, msg=None):
limit = time.time() + timeout
......@@ -80,6 +86,41 @@ def lock_loop(loop, *, timeout=1):
with cond:
cond.notify()
class DockerEventsWatcher:
def __init__(self, client):
self.stream = client.events()
self._lock = threading.Lock()
self._queues = {}
self._thread = threading.Thread(target=self._listener)
self._thread.daemon = True
self._thread.start()
def _listener(self):
try:
for event in self.stream:
js = json.loads(event.decode())
log.debug("event %r", js)
with self._lock:
for q in self._queues.values():
q.append(js)
finally:
self._queues = None
@contextmanager
def __call__(self, container):
queue = []
try:
with self._lock:
if self._queues is None:
raise Exception("listener is dead")
self._queues[id(queue)] = queue
yield queue
finally:
with self._lock:
del self._queues[id(queue)]
class DockerError(docker.errors.APIError):
def __init__(self, *k):
......@@ -109,6 +150,7 @@ class ControllerTestCase(unittest.TestCase):
def setUpClass(cls):
cls.dk = docker.Client(DOCKER_HOST)
cls.dk_events_watcher = DockerEventsWatcher(cls.dk)
cls.session = connect_db(MYSQL_HOST)()
# create busybox:latest factory
......@@ -131,7 +173,8 @@ class ControllerTestCase(unittest.TestCase):
for ctr in cls.dk.containers(all=True, filters={"name": "^/%s-" % ENV}):
cls.dk.remove_container(ctr["Id"], force=True)
# remove all images in REGISTRY
# remove all images
# TODO: clean the registry too
assert not REGISTRY.endswith("/")
pfx = REGISTRY + "/"
pfx_factory = pfx + "factory/"
......@@ -265,6 +308,16 @@ class ControllerTestCase(unittest.TestCase):
iid = self.dk.inspect_image("%s/%s" % (REGISTRY, image))
self.assertEqual(status["Image"], iid["Id"], msg="container %r: bad image" % ctr)
@contextmanager
def check_sandbox_events(self, app, txt):
expected = txt.split()
container = self.ctrl.gen_sandbox_name(app)
with self.dk_events_watcher(container) as queue:
yield
seen = [e["Action"] for e in queue if e["Actor"]["Attributes"].get("name") == container]
self.assertListEqual(seen, expected, msg="unexpected events sequence")
@contextmanager
def check_log(self, level, regex):
......@@ -287,6 +340,28 @@ class ControllerTestCase(unittest.TestCase):
finally:
controller.log.removeHandler(hnd)
def add_dummy_version(self, app, number, **kw):
update_default(kw,
state = V.ready,
changelog = "dummy version",
published = False)
ses = self.session
with ses.begin():
ver = WebappVersion(number=number, webapp=app, **kw)
ses.add(ver)
image = self.ctrl.gen_image_name(app)
if ver.state in (V.committed, V.ready):
docker_check_error(self.dk.build, tag="%s:%s" % (image, number), fileobj=io.BytesIO(("""
FROM busybox:latest
LABEL version=%r
""" % ("%s:%s" % (app.docker_name, number))).encode()))
if ver.state == V.ready:
docker_check_error(self.dk.push, image, number)
def start_sandbox(self, app):
with self.session.begin():
......@@ -297,6 +372,12 @@ class ControllerTestCase(unittest.TestCase):
self.check_sandbox_running({app: True})
def kill_sandbox(self, app):
with suppress(docker.errors.NotFound):
self.dk.remove_container(self.ctrl.gen_sandbox_name(app), force=True)
with self.session.begin():
app.sandbox_state = S.idle
def commit_sandbox(self, app, number, *, wait=True, stop=False, published=False, changelog="commit-sandbox", **kw):
with self.session.begin():
ver = WebappVersion(number=number, published=published, state=V.sandbox, changelog=changelog, **kw)
......@@ -308,6 +389,10 @@ class ControllerTestCase(unittest.TestCase):
with self.check_version_transition(ver, V.sandbox, V.ready, ignore_state=V.committed, timeout=30):
self.notify()
def test_docker_check_error(self):
self.assertRaisesRegex(controller.Error, "image does not exist locally",
controller.docker_check_error,
self.dk.push, REGISTRY + "test-unknown-image")
@with_db
def test_common_functions(self, ses, app):
......@@ -371,7 +456,7 @@ class ControllerTestCase(unittest.TestCase):
self.assertGreater(m.call_count, 1)
@with_db
def test_sandbox_start(self, ses, app):
def test_sandbox_start_dockeros(self, ses, app):
with ses.begin():
app.sandbox_state = S.starting
......@@ -381,6 +466,39 @@ class ControllerTestCase(unittest.TestCase):
self.check_sandbox_running({app: "factory/test-busybox:latest"})
@with_db
def test_sandbox_start_latest(self, ses, app):
# one version
with preamble():
ver = self.add_dummy_version(app, "1.0")
self.add_dummy_version(app, "1.0", state=V.error)
self.add_dummy_version(app, "1.1", state=V.error)
self.start_sandbox(app)
self.check_sandbox_running({app: "webapp/test-app:1.0"})
self.kill_sandbox(app)
# multiple versions
with preamble():
self.add_dummy_version(app, "1.1")
ver = self.add_dummy_version(app, "0.9")
self.start_sandbox(app)
self.check_sandbox_running({app: "webapp/test-app:0.9"})
self.kill_sandbox(app)
# freshly committed version
with preamble():
ver = self.add_dummy_version(app, "0.8", state=V.committed)
with mock.patch.object(self.ctrl.sandbox, "pull", mock.Mock()) as m:
self.start_sandbox(app)
self.check_sandbox_running({app: "webapp/test-app:0.8"})
self.assertFalse(m.called)
@with_db
def test_sandbox_start_bad_parameters(self, ses, app):
# bad docker_name
......@@ -461,11 +579,52 @@ class ControllerTestCase(unittest.TestCase):
with preamble():
self.start_sandbox(app)
self.check_sandbox_running({app: "factory/test-busybox:latest"})
self.assertFalse(app.versions)
self.commit_sandbox(app, "1.0", stop=True)
with self.check_sandbox_events(app, """
pause commit unpause
"""):
self.commit_sandbox(app, "1.0", changelog="pouet pouet")
# FIXME: test is good (seems that _commit() is not called if stop is False)
self.check_sandbox_running({app: "factory/test-busybox:latest"})
self.assertEqual(len(app.versions), 1)
ver = app.versions[0]
self.assertEqual(ver.number, "1.0")
self.assertEqual(ver.changelog, "pouet pouet")
self.assertFalse(ver.published)
self.assertIn(ver.state, (V.committed, V.ready))
@with_db
def test_sandbox_commit_dangling(self, ses, app):
with preamble():
self.start_sandbox(app)
with ses.begin():
ses.refresh(app)
app.sandbox_state = S.idle
self.assertFalse(app.versions)
with self.check_sandbox_events(app, """
kill die stop commit destroy
create start die destroy create start
"""):
self.start_sandbox(app)
self.check_sandbox_running({app: "factory/test-busybox:latest"})
with ses.begin():
ses.refresh(app)
self.assertEqual(len(app.versions), 1)
ver = app.versions[0]
self.assertRegex(ver.number, r"\Arecovery-\d{8}-\d{6}\Z")
self.assertEqual(ver.changelog, "pre-commit error: dangling sandbox")
self.assertFalse(ver.published)
self.assertIn(ver.state, (V.committed, V.ready))
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment