Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bootstrap: improve tests to test timeout detection and restarting #1882

Merged
merged 5 commits into from
Jul 20, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 51 additions & 5 deletions bootstrap/test_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pathlib import Path
from typing import Any, Dict, Generator, List
from unittest.mock import MagicMock, patch

import time
import pytest
from docker.errors import NotFound
from pyfakefs.fake_filesystem_unittest import TestCase
Expand Down Expand Up @@ -48,25 +48,38 @@
class FakeContainer:
"""Mocks a single Container from Docker-py"""

def __init__(self, name: str) -> None:
def __init__(self, name: str, raise_if_stopped: bool = False) -> None:
self.name: str = name
self.client: Any
self.raise_if_stopped = raise_if_stopped
self.created_time = time.time()
print(f"created container {self.name} at {self.created_time}")

def age(self) -> float:
return time.time() - self.created_time

def set_client(self, client: Any) -> None:
self.client = client

def remove(self) -> None:
if self.raise_if_stopped:
raise RuntimeError("Container cannot be stopped")
print(f"removing container {self.name}")
self.client.containers.remove(self.name)

def stop(self) -> None:
pass

def __repr__(self) -> str:
return self.name


class FakeContainers:
"""Mocks "Containers" class from docker-py"""

def __init__(self, containers: List[FakeContainer]):
def __init__(self, containers: List[FakeContainer], client: "FakeClient"):
self.containers: Dict[str, FakeContainer] = {container.name: container for container in containers}
self.client = client

def get(self, container: str) -> FakeContainer:
result = self.containers.get(container, None)
Expand All @@ -81,6 +94,7 @@ def remove(self, container: str) -> None:
# pylint: disable=unused-argument
def run(self, image: str, name: str = "", **kargs: Dict[str, Any]) -> None:
self.containers[name] = FakeContainer(name)
self.containers[name].set_client(self.client)

def list(self) -> List[FakeContainer]:
return list(self.containers.values())
Expand All @@ -106,13 +120,13 @@ class FakeClient:
"""Mocks a docker-py client for testing purposes"""

def __init__(self) -> None:
self.containers = FakeContainers([])
self.containers = FakeContainers([], self)
self.images = FakeImages()

def set_active_dockers(self, containers: List[FakeContainer]) -> None:
for container in containers:
container.set_client(self)
self.containers = FakeContainers(containers)
self.containers = FakeContainers(containers, self)


class FakeLowLevelAPI:
Expand Down Expand Up @@ -233,3 +247,35 @@ def test_bootstrap_start_invalid_json(self) -> None:
bootstrapper.run()
self.mock_response.json.return_value = {"repository": ["core"]}
assert bootstrapper.is_running("core")

@pytest.mark.timeout(50)
def test_bootstrap_core_timeout(self) -> None:
start_time = time.time()
self.fs.create_file(Bootstrapper.DOCKER_CONFIG_FILE_PATH, contents=SAMPLE_JSON)
self.fs.create_file(Bootstrapper.DEFAULT_FILE_PATH, contents=SAMPLE_JSON)
fake_client = FakeClient()
fake_client.set_active_dockers([FakeContainer(Bootstrapper.CORE_CONTAINER_NAME, raise_if_stopped=True)])
bootstrapper = Bootstrapper(fake_client, FakeLowLevelAPI())
bootstrapper.run()
self.mock_response.json.return_value = {"repository": ["core"]}
assert bootstrapper.is_running("core") is True
# now make it stop responding to requests
# first just remove core from the output
self.mock_response.json.return_value = {"repository": []}

# This should NOT timeout. An exeption will be raise by the container if it stops
bootstrapper.run()

# This SHOULD timeout
# mock time so it passes faster
# this new FakeContainer will not throw when it stops
fake_client.set_active_dockers([FakeContainer(Bootstrapper.CORE_CONTAINER_NAME)])
mock_time = patch("time.time", return_value=start_time + 1000)
mock_time.start()
# this should timeout AND restart core
bootstrapper.run()
patrickelectric marked this conversation as resolved.
Show resolved Hide resolved
# if the timeout worked, the container age will be zero
assert fake_client.containers.get("blueos-core").age() == 0
# check if the age is what we expected, just in case
assert fake_client.containers.get("blueos-core").created_time == start_time + 1000
mock_time.stop()