Add unit tests for osism/utils semaphore, redlock and task locks#2349
Open
berendt wants to merge 3 commits into
Open
Add unit tests for osism/utils semaphore, redlock and task locks#2349berendt wants to merge 3 commits into
berendt wants to merge 3 commits into
Conversation
Cover the RedisSemaphore distributed-semaphore class in osism/utils/__init__.py: attribute storage and unconditional "semaphore:" key prefixing, acquire() success and timeout paths (deterministic time/uuid mocking), per-iteration expired-holder cleanup, the default and explicit timeout fallbacks, release() idempotency and the context-manager protocol. Part of #2230 Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Christian Berendt <berendt@osism.tech>
Cover create_redlock: the returned pottery Redlock is built with the
given key, masters={redis} and auto_release_time (default 3600 and a
custom value), the "pottery" logger is forced to CRITICAL, and
construction-time stdout/stderr is suppressed. pottery.Redlock is
patched on the module because it is imported lazily inside the
function.
Cover create_netbox_semaphore: max_connections falls back to
settings.NETBOX_MAX_CONNECTIONS or is taken explicitly, the semaphore
is built with a per-URL md5-derived key, timeout=30 and the shared
redis client, and the key is stable per URL.
Part of #2230
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Christian Berendt <berendt@osism.tech>
Cover the global task-lock helpers in osism/utils/__init__.py: - set_task_lock: user falls back to settings.OPERATOR_USER or is taken explicitly, reason=None is serialised as JSON null, the payload carries locked=True plus an ISO-8601 timestamp, and a redis failure returns False and logs the error. - remove_task_lock: deletes osism:task_lock and returns True; a failure returns False and logs. - is_task_locked: returns None when unset, byte-decodes and parses the stored JSON, and returns None while logging on redis errors and JSON-decode errors. - check_task_lock_and_exit: no-ops when unlocked, otherwise logs the user/timestamp/reason and calls exit(1), skips the reason line when reason is None, and defaults missing fields to "unknown". Closes #2230 Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Christian Berendt <berendt@osism.tech>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements #2230.
Adds
tests/unit/utils/test_init_locks.py, the third companion totest_init_connections.pyandtest_init_task_output.py, covering theconcurrency primitives and global task-lock helpers in
osism/utils/__init__.py. No production code is changed — this is apure test addition. 40 test functions (41 cases) following the
established
mocker/loguru_logsconventions of the existing suite.Change set (commit order)
__init__attribute storage andthe unconditional
semaphore:key prefix (including thealready-prefixed input → double prefix);
acquire()first-trysuccess, full-window timeout returning
False, per-iterationzremrangebyscorecleanup, the default-10s and explicit-timeoutfallbacks, and a fresh
uuid.uuid4()per call;release()idempotency; and the
__enter__/__exit__context-manager protocol(returns
self, raisesTimeoutErrorcontaining the key, alwaysreleases, never swallows exceptions).
time.time/time.sleep/uuid.uuid4are patched so the acquisition loop is deterministic.create_redlockreturns thepottery.Redlockbuilt withkey/masters={redis}/auto_release_time(default3600and acustom value), forces the
"pottery"logger toCRITICAL, andsuppresses construction-time stdout/stderr (asserted via
capsys).create_netbox_semaphorefalls back tosettings.NETBOX_MAX_CONNECTIONSor takes an explicit value, buildsa per-URL md5-derived key with
timeout=30and the shared redisclient, and produces a stable key per URL.
set_task_lock,remove_task_lock,is_task_lockedandcheck_task_lock_and_exit,including the operator-user/reason fallbacks, the JSON payload shape
(regex-matched ISO timestamp), the
.decode("utf-8")path, and theerror-logging branches.
builtins.exitandis_task_lockedarepatched for
check_task_lock_and_exit.Notes for the reviewer
pottery.Redlockis patched on thepotterymodule (not importedinto
osism.utils) because production imports it lazily insidecreate_redlock— matching the issue's mocking hint.logging.getLoggeris patched in thecreate_redlocktests so theynever mutate the process-global
potterylogger level (testisolation), addressing the issue's note about that mutable state.
freezegunis not a dev dependency, so theset_task_locktimestampis matched with a regex (
^\d{4}-\d{2}-\d{2}T...) rather than frozen,as the issue allows.
Local verification
Per repository practice the test suite,
flake8,mypyandpython-blackrun only in the pipenv venv / Zuul CI (the dev tools andpotteryare not installed in the base interpreter), so they were notrun locally.
python -m py_compilepasses. The Zuulpython-osism-unit-testsjob will run the suite. The tests exerciseevery branch of the targeted helpers, satisfying the ≥90 % coverage
criterion for them.
Closes #2230
Assisted-by: Claude:claude-opus-4-8