fix experiment synchronizer, run container as root, add Makefile

This commit is contained in:
gmega 2024-12-06 10:20:58 -03:00
parent 7fe9dafe50
commit 8605cce688
No known key found for this signature in database
GPG Key ID: 6290D34EAD824B18
7 changed files with 97 additions and 31 deletions

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
*.pyc
.idea
/volume/deluge*
/volume/deluge*
*.timestamp

62
Makefile Normal file
View File

@ -0,0 +1,62 @@
SHELL := bash
.SHELLFLAGS := -eu -o pipefail -c
.PHONY: test-unit-local \
start-local-integration-harness \
stop-local-integration-harness \
test-integration-local \
test-all-local \
test-unit-docker \
test-integration-docker \
clean
# Runs the unit tests locally.
unit:
poetry run pytest -m "not integration"
# Starts the local integration harness. This is required for running pytest with the "integration" marker.
harness-start:
rm -rf ${PWD}/volume/deluge-{1,2,3}
docker compose -f docker-compose.local.yaml up
# Stops the local integration harness.
harness-stop:
docker compose -f docker-compose.local.yaml down --volumes --remove-orphans
# Runs the integration tests locally. This requires the integration harness to be running.
integration:
echo "NOTE: Make sure to have started the integration harness or this will not work"
poetry run pytest -m "integration"
tests: unit integration
docker/.lastbuilt-test.timestamp: docker/bittorrent-benchmarks.Dockerfile
docker build -t bittorrent-benchmarks:test -f ./docker/bittorrent-benchmarks.Dockerfile .
touch docker/.lastbuilt-test.timestamp
docker/.lastbuilt-release.timestamp: docker/bittorrent-benchmarks.Dockerfile
docker build -t bittorrent-benchmarks:test --build-arg BUILD_TYPE="release" \
-f ./docker/bittorrent-benchmarks.Dockerfile .
touch docker/.lastbuilt-release.timestamp
# Builds the test image required for local dockerized integration tests.
image-test: docker/.lastbuilt-test.timestamp
image-release: docker/.lastbuilt-release.timestamp
# Runs the unit tests in a docker container.
unit-docker: image-test
docker run --entrypoint poetry --rm bittorrent-benchmarks:test run pytest -m "not integration"
# Runs the integration tests in a docker container.
integration-docker: image-test
docker compose -f docker-compose.local.yaml -f docker-compose.ci.yaml down --volumes --remove-orphans
docker compose -f docker-compose.local.yaml -f docker-compose.ci.yaml up \
--abort-on-container-exit --exit-code-from test-runner
tests-docker: unit-docker integration-docker
clean:
rm -rf docker/.lastbuilt*
rm -rf volume/deluge-{1,2,3}
docker compose -f docker-compose.local.yaml -f docker-compose.ci.yaml down --volumes --rmi all --remove-orphans

View File

@ -3,7 +3,8 @@
import logging
from abc import ABC, abstractmethod
from collections.abc import Iterable
from typing import Optional
from time import time, sleep
from typing import Optional, List
from typing_extensions import Generic, TypeVar
@ -44,17 +45,30 @@ class ExperimentEnvironment:
def await_ready(self, timeout: float = 0) -> bool:
"""Awaits for all components to be ready, or until a timeout is reached."""
# TODO we should probably have per-component timeouts, or at least provide feedback
# as to what was the completion state of each component.
if not await_predicate(
lambda: all(component.is_ready() for component in self.components),
timeout=timeout,
polling_interval=self.polling_interval,
):
return False
start_time = time()
not_ready = [component for component in self.components]
logging.info(f'Awaiting for components to be ready: {self._component_names(not_ready)}')
while len(not_ready) != 0:
for component in not_ready:
if component.is_ready():
logger.info(f'Component {str(component)} is ready.')
not_ready.remove(component)
sleep(self.polling_interval)
if (timeout != 0) and (time() - start_time > timeout):
print("timeout")
logger.info(f'Some components timed out: {self._component_names(not_ready)}')
return False
return True
@staticmethod
def _component_names(components: List[ExperimentComponent]) -> str:
return ', '.join(str(component) for component in components)
def run(self, experiment: Experiment):
"""Runs the :class:`Experiment` within this :class:`ExperimentEnvironment`."""
if not self.await_ready():

View File

@ -44,10 +44,12 @@ def test_should_timeout_if_component_takes_too_long():
]
environment = ExperimentEnvironment(components, polling_interval=0)
assert not environment.await_ready(0.1)
assert not environment.await_ready(0.09)
assert components[0].iteration == 5
assert components[1].iteration < 3
# Because ExperimentEnvironment sweeps through the components, it will
# iterate exactly once before timing out.
assert components[0].iteration == 1
assert components[1].iteration == 1
class ExperimentThatReliesOnComponents(Experiment):

View File

@ -1,7 +0,0 @@
#!/usr/bin/env bash
set -e
# These have to be wiped out before we boot the containers. Note that this will only work
# if you've set up rootless Docker.
rm -rf ./volume/{deluge-1,deluge-2,deluge-3}
docker compose up

View File

@ -14,4 +14,3 @@ touch /opt/bittorrent-benchmarks/volume/.initialized
echo "Launching tests."
cd /opt/bittorrent-benchmarks
poetry run pytest --exitfirst

View File

@ -1,27 +1,22 @@
FROM python:3.12-slim
ARG UID=1000
ARG GID=1000
ARG BUILD_TYPE="test"
RUN groupadd -g ${GID} runner \
&& useradd -u ${UID} -g ${GID} -s /bin/bash -m runner
RUN mkdir /opt/bittorrent-benchmarks && chown -R runner:runner /opt/bittorrent-benchmarks
RUN pip install poetry
USER runner
RUN mkdir /opt/bittorrent-benchmarks
WORKDIR /opt/bittorrent-benchmarks
COPY --chown=runner:runner pyproject.toml poetry.lock ./
RUN if [ "$BUILD_TYPE" = "production" ]; then \
echo "Image is a production build"; \
COPY pyproject.toml poetry.lock ./
RUN if [ "$BUILD_TYPE" = "release" ]; then \
echo "Image is a release build"; \
poetry install --only main --no-root; \
else \
echo "Image is a test build"; \
poetry install --no-root; \
fi
COPY --chown=runner:runner . .
COPY . .
RUN poetry install --only main
ENTRYPOINT ["poetry", "run", "bittorrent-benchmarks", "/opt/bittorrent-benchmarks/experiments.yaml"]