Skip to content

Add lightweight integration tests for Celery/Redis basics in CI#2369

Open
berendt wants to merge 3 commits into
mainfrom
implement/issue-2368-celery-redis-integration-tests
Open

Add lightweight integration tests for Celery/Redis basics in CI#2369
berendt wants to merge 3 commits into
mainfrom
implement/issue-2368-celery-redis-integration-tests

Conversation

@berendt

@berendt berendt commented Jun 12, 2026

Copy link
Copy Markdown
Member

Implements #2368.

Adds a lightweight integration-test job that covers the Celery/Redis
task-processing core (broker, queue routing, worker, result backend, Redis
streams for task output, distributed locks) end-to-end in CI, plus the
prerequisite that makes the broker URL configurable.

Change set (in commit order)

1. Derive Celery broker URL from settings and environment

osism/tasks/__init__.py, osism/commands/service.py

  • Config.broker_url / Config.result_backend were hardcoded to
    redis://redis, with the same literals duplicated in the beat and
    flower service commands. They now derive from REDIS_HOST /
    REDIS_PORT / REDIS_DB (the same settings osism.utils._init_redis
    already honors), overridable via CELERY_BROKER_URL /
    CELERY_RESULT_BACKEND. The default of REDIS_HOST is redis, so this
    is backwards compatible.
  • service.py now references Config.broker_url (single source of truth)
    for beat/flower, removing the duplicated literals.
  • Fixed task_track_started, which was the tuple (True,) instead of the
    intended bool (it only worked because a non-empty tuple is truthy).

This is what lets the tests point Celery at a local Redis via
REDIS_HOST=localhost.

2. Add Celery/Redis integration tests

tests/integration/, setup.cfg, README.md

  • test_celery.pyround-trip: ansible.noop.delay()
    result.get(timeout=60) is True (broker + osism-ansible routing +
    worker + result backend); worker visibility: a fresh Celery
    client configured from Config sees the worker via
    inspect().ping() (the mechanism behind osism get status workers).
  • test_redis_streams.pypush_task_output / finish_task_output /
    fetch_task_output round-trip (the path osism apply uses to stream
    logs).
  • test_locking.pycreate_redlock acquire/release and
    set_task_lock / is_task_locked / remove_task_lock.
  • conftest.py starts the worker as a session-scoped fixture from the
    same venv, running only the ansible Celery app (no import-time
    dependency on NetBox/OpenStack/ansible-core) with
    GATHER_FACTS_SCHEDULE=0 so the periodic gather_facts task is not
    registered. It polls inspect().ping() until the worker is ready and
    terminates it on teardown.
  • Tests carry an integration marker (registered in setup.cfg for the
    --strict-markers run) and are skipped when Redis is not reachable,
    so a local pytest is unaffected.

3. Add Zuul integration-test job

.zuul.yaml, playbooks/test-integration.yml

  • New python-osism-integration-tests job added to the check and
    periodic-daily pipelines. It reuses playbooks/pre.yml (which already
    sets up Docker via ensure-docker), starts a redis:7-alpine
    container, installs the package with pipenv exactly like the unit-test
    job, and runs pytest tests/integration with REDIS_HOST=localhost.

Notes / deviations

  • The unit-test job is already restricted to tests/unit via
    testpaths = tests/unit in setup.cfg, so a bare pytest never
    collects tests/integration — the issue's first concern is already
    satisfied. The integration marker + Redis-reachability skip are still
    added (the marker is required by --strict-markers, and the skip keeps
    pytest tests/integration green locally without Redis).
  • No CHANGELOG.md entry: this repo maintains the changelog in batched,
    post-release commits that map merged PR numbers to release versions, so
    per-PR entries are out of convention here.
  • Follow-ups listed in the issue (compose-based variant, API/beat/revoke
    coverage) are intentionally out of scope.

Local verification

  • black --check — clean (6 files)
  • flake8 — clean
  • pytest tests/integration — not run locally (no Redis); exercised by
    the new CI job.

🤖 Generated with Claude Code

@berendt berendt force-pushed the implement/issue-2368-celery-redis-integration-tests branch 3 times, most recently from 1b3d448 to cb9d08b Compare June 12, 2026 13:45
@berendt berendt marked this pull request as ready for review June 12, 2026 13:48
@berendt berendt requested a review from ideaship June 12, 2026 13:49

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 security issues, and 1 other issue

Security issues:

  • Detected subprocess function 'Popen' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
  • Detected subprocess function 'Popen' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="osism/commands/service.py" line_range="63" />
<code_context>
         elif service == "flower":
             p = subprocess.Popen(
-                "celery --broker=redis://redis flower",
+                f"celery --broker={Config.broker_url} flower",
                 shell=True,
             )
</code_context>
<issue_to_address>
**issue (bug_risk):** Handle special characters in broker URL for the flower invocation.

Because this uses `shell=True`, any `&`, `?`, or other shell metacharacters in `Config.broker_url` can be split or reinterpreted by the shell. Please use a list-based `Popen` without `shell=True`, e.g. `Popen(["celery", "--broker", Config.broker_url, "flower"])`, or otherwise ensure the URL is safely quoted/escaped.
</issue_to_address>

### Comment 2
<location path="osism/commands/service.py" line_range="51-54" />
<code_context>
                subprocess.Popen(
                    f"celery -A {t} --broker={Config.broker_url} beat -s /tmp/celerybeat-schedule-{t}.db",
                    shell=True,
                )
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'Popen' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

### Comment 3
<location path="osism/commands/service.py" line_range="62-65" />
<code_context>
            p = subprocess.Popen(
                f"celery --broker={Config.broker_url} flower",
                shell=True,
            )
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'Popen' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread osism/commands/service.py Outdated
Comment thread osism/commands/service.py
Comment thread osism/commands/service.py Outdated
berendt added 3 commits June 12, 2026 15:56
The Celery Config hardcoded broker_url and result_backend to
redis://redis, with the same literals duplicated in the beat and
flower service commands. Derive the URLs from REDIS_HOST / REDIS_PORT /
REDIS_DB -- the same settings osism.utils._init_redis already honors --
and allow an override via CELERY_BROKER_URL / CELERY_RESULT_BACKEND.
The default of REDIS_HOST is redis, so this stays backwards
compatible while letting the integration tests point Celery at a
local Redis with REDIS_HOST=localhost.

Also fix task_track_started, which was the tuple (True,) instead of
the intended bool; it only worked because a non-empty tuple is truthy.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Christian Berendt <berendt@osism.tech>
Add tests/integration/ covering the task-processing core against a
live Redis and a Celery worker started from the same virtualenv:

- Celery round-trip: ansible.noop.delay() returns True, exercising
  broker, osism-ansible queue routing, worker and result backend.
- Worker visibility: app.control.inspect().ping() sees the worker,
  the mechanism behind `osism get status workers`.
- Redis streams: push/finish/fetch_task_output round-trip, the path
  `osism apply` uses to stream task logs.
- Locking: create_redlock acquire/release and set/is/remove_task_lock.

The worker runs only the ansible Celery app, which has no import-time
dependency on NetBox/OpenStack/ansible-core. GATHER_FACTS_SCHEDULE=0
keeps the periodic gather_facts task from registering.

The tests carry an `integration` marker (registered for the
--strict-markers run) and are skipped when Redis is not reachable, so
the default `pytest tests/unit` run is unaffected.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Christian Berendt <berendt@osism.tech>
Add the python-osism-integration-tests job to the check and
periodic-daily pipelines. It reuses playbooks/pre.yml (which already
sets up Docker via ensure-docker), starts a redis:7-alpine container,
installs the package with pipenv like the unit-test job, and runs
`pytest tests/integration` with REDIS_HOST=localhost.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Christian Berendt <berendt@osism.tech>
@berendt berendt force-pushed the implement/issue-2368-celery-redis-integration-tests branch from cb9d08b to 81ec7d2 Compare June 12, 2026 13:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Ready

Development

Successfully merging this pull request may close these issues.

2 participants