Motivation
The task processing core of python-osism (Celery broker, queue routing, workers, result backend, Redis streams for task output, distributed locks) is currently not covered by any test in CI. The check pipeline only runs linters, unit tests (tests/unit/), and container image builds. A lightweight integration test job can cover these paths end-to-end with very little infrastructure: a single Redis container and a Celery worker started from the same venv as the tests.
Prerequisite: make the broker URL configurable
broker_url and result_backend are hardcoded to redis://redis in osism/tasks/__init__.py (also duplicated in osism/commands/service.py for beat and flower). In contrast, the direct Redis access in osism.utils._init_redis() already honors REDIS_HOST / REDIS_PORT / REDIS_DB from osism/settings.py.
The Celery Config should derive its URLs the same way, e.g.:
broker_url = os.environ.get(
"CELERY_BROKER_URL",
f"redis://{settings.REDIS_HOST}:{settings.REDIS_PORT}/{settings.REDIS_DB}",
)
result_backend = os.environ.get("CELERY_RESULT_BACKEND", broker_url)
This is backwards compatible (the default of REDIS_HOST is redis) and removes the duplicated literals in service.py.
Side finding to fix along the way: task_track_started = (True,) is a tuple instead of a bool — it only works because a non-empty tuple is truthy.
Proposed setup
New Zuul job python-osism-integration-tests (analogous to the existing jobs, pre-run: playbooks/pre.yml, new playbooks/test-integration.yml), added to the check and periodic-daily pipelines:
- Start a Redis container on the Zuul node (
ensure-docker role from zuul-jobs, then e.g. docker run -d -p 6379:6379 redis:7-alpine).
- Install the package with pipenv exactly like the unit test job does.
- Run
pytest tests/integration with REDIS_HOST=localhost.
The Celery worker is started as a session-scoped pytest fixture from the same venv:
@pytest.fixture(scope="session")
def celery_worker():
proc = subprocess.Popen(
["celery", "-A", "osism.tasks.ansible", "worker",
"-n", "ci-worker", "-Q", "osism-ansible", "-c", "1"],
env={**os.environ, "GATHER_FACTS_SCHEDULE": "0"},
)
# wait until app.control.inspect().ping() succeeds, yield, then terminate()
GATHER_FACTS_SCHEDULE=0 prevents registration of the periodic gather_facts task (which would try to run /run.sh in containers that do not exist in CI). Deliberately only the ansible Celery app is used as worker: it has no import-time dependency on NetBox/OpenStack/ansible-core, unlike conductor and friends, which would require extras and external services.
Test cases (tests/integration/)
- Celery round-trip (the core): dispatch
osism.tasks.ansible.noop.delay() and assert result.get(timeout=60) is True. This validates broker, queue routing (osism-ansible), worker, and result backend in a single test.
- Worker visibility:
app.control.inspect().ping() — the same mechanism as osism get status workers; optionally invoke the CLI directly for end-to-end coverage.
- Redis streams path: write via
push_task_output / finish_task_output with a test task ID, assert that fetch_task_output returns the output and return code. This is the mechanism osism apply uses to stream logs and is testable with Redis alone.
- Locking primitives:
create_redlock acquire/release and set_task_lock / is_task_locked / remove_task_lock.
Notes
- The unit test job currently runs a bare
pytest, which would also collect tests/integration/. The integration tests need an integration marker (skipped when Redis is not reachable) or the unit job must be restricted to pytest tests/unit.
- Expected job runtime: ~2-3 minutes.
Possible follow-ups (out of scope here)
- Compose-based variant using the container image already built in
check (services redis, worker, optionally osism service api), which would additionally cover the image and entrypoint.
- API tests via
uvicorn osism.api:app + httpx, beat scheduling, revoke_task.
Motivation
The task processing core of python-osism (Celery broker, queue routing, workers, result backend, Redis streams for task output, distributed locks) is currently not covered by any test in CI. The
checkpipeline only runs linters, unit tests (tests/unit/), and container image builds. A lightweight integration test job can cover these paths end-to-end with very little infrastructure: a single Redis container and a Celery worker started from the same venv as the tests.Prerequisite: make the broker URL configurable
broker_urlandresult_backendare hardcoded toredis://redisinosism/tasks/__init__.py(also duplicated inosism/commands/service.pyforbeatandflower). In contrast, the direct Redis access inosism.utils._init_redis()already honorsREDIS_HOST/REDIS_PORT/REDIS_DBfromosism/settings.py.The Celery
Configshould derive its URLs the same way, e.g.:This is backwards compatible (the default of
REDIS_HOSTisredis) and removes the duplicated literals inservice.py.Side finding to fix along the way:
task_track_started = (True,)is a tuple instead of a bool — it only works because a non-empty tuple is truthy.Proposed setup
New Zuul job
python-osism-integration-tests(analogous to the existing jobs,pre-run: playbooks/pre.yml, newplaybooks/test-integration.yml), added to thecheckandperiodic-dailypipelines:ensure-dockerrole from zuul-jobs, then e.g.docker run -d -p 6379:6379 redis:7-alpine).pytest tests/integrationwithREDIS_HOST=localhost.The Celery worker is started as a session-scoped pytest fixture from the same venv:
GATHER_FACTS_SCHEDULE=0prevents registration of the periodicgather_factstask (which would try to run/run.shin containers that do not exist in CI). Deliberately only theansibleCelery app is used as worker: it has no import-time dependency on NetBox/OpenStack/ansible-core, unlikeconductorand friends, which would require extras and external services.Test cases (
tests/integration/)osism.tasks.ansible.noop.delay()and assertresult.get(timeout=60) is True. This validates broker, queue routing (osism-ansible), worker, and result backend in a single test.app.control.inspect().ping()— the same mechanism asosism get status workers; optionally invoke the CLI directly for end-to-end coverage.push_task_output/finish_task_outputwith a test task ID, assert thatfetch_task_outputreturns the output and return code. This is the mechanismosism applyuses to stream logs and is testable with Redis alone.create_redlockacquire/release andset_task_lock/is_task_locked/remove_task_lock.Notes
pytest, which would also collecttests/integration/. The integration tests need anintegrationmarker (skipped when Redis is not reachable) or the unit job must be restricted topytest tests/unit.Possible follow-ups (out of scope here)
check(servicesredis, worker, optionallyosism service api), which would additionally cover the image and entrypoint.uvicorn osism.api:app+ httpx, beat scheduling,revoke_task.